Merge remote-tracking branch 'origin/main' into feat/pr-20251104-154907

This commit is contained in:
2025-11-10 14:13:05 +08:00
24 changed files with 178 additions and 144 deletions

View File

@@ -19,6 +19,7 @@
android:supportsRtl="true"
android:theme="@style/Theme.RaveNow"
android:usesCleartextTraffic="true"
android:networkSecurityConfig="@xml/network_security_config"
tools:targetApi="31">
<meta-data
android:name="com.google.android.geo.API_KEY"

View File

@@ -97,7 +97,8 @@ class UserServiceImpl : UserService {
pageSize = pageSize,
search = nickname,
followerId = followerId,
followingId = followingId
followingId = followingId,
includeAI = true
)
val body = resp.body() ?: throw ServiceException("Failed to get account")
return ListContainer<AccountProfileEntity>(

View File

@@ -954,7 +954,7 @@ interface RaveNowAPI {
@Query("nickname") search: String? = null,
@Query("followerId") followerId: Int? = null,
@Query("followingId") followingId: Int? = null,
@Query("includeAI") includeAI: Boolean? = false,
@Query("includeAI") includeAI: Boolean? = true,
@Query("chatSessionIdNotNull") chatSessionIdNotNull: Boolean? = true,
): Response<ListContainer<AccountProfile>>

View File

@@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
@@ -32,7 +33,7 @@ fun FollowButton(
.wrapContentWidth()
.clip(RoundedCornerShape(8.dp))
.background(
color = if (isFollowing) AppColors.main else AppColors.nonActive
color = if (isFollowing) AppColors.nonActive else AppColors.nonActive
)
.padding(horizontal = 16.dp, vertical = 8.dp)
.noRippleClickable {
@@ -41,11 +42,9 @@ fun FollowButton(
contentAlignment = Alignment.Center
) {
Text(
text = if (isFollowing) stringResource(R.string.following_upper) else stringResource(
R.string.follow_upper
),
text = if (isFollowing) stringResource(R.string.follow_upper_had) else stringResource(R.string.follow_upper),
fontSize = fontSize,
color = if (isFollowing) AppColors.mainText else AppColors.text,
color = if (isFollowing) AppColors.text else AppColors.text,
style = TextStyle(fontWeight = FontWeight.Bold)
)
}

View File

@@ -9,6 +9,10 @@ import androidx.lifecycle.viewModelScope
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.ChatState
import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.AgentRuleQuota
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
import com.aiosman.ravenow.data.api.AgentRule
import com.aiosman.ravenow.data.api.AgentRuleQuota
@@ -292,7 +296,7 @@ class GroupChatInfoViewModel(
val openId = targetOpenId ?: promptOpenId
?: throw Exception("无法获取智能体ID")
val requestBody = com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody(
val requestBody = UpdateAgentRuleRequestBody(
id = ruleId,
rule = newRuleText,
openId = openId

View File

@@ -17,6 +17,7 @@ import com.aiosman.ravenow.event.MomentAddEvent
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
import com.aiosman.ravenow.event.MomentLikeChangeEvent
import com.aiosman.ravenow.event.MomentRemoveEvent
import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@@ -122,10 +123,11 @@ open class BaseMomentModel :ViewModel(){
userService.unFollowUser(moment.authorId.toString())
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, false))
} else {
userService.followUser(moment.authorId.toString())
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, true))
// 调用 FollowerNoticeViewModel.followUser() 实现与 FollowerNotice.kt 相同的效果
// 该方法内部会调用 userService.followUser() 并发布 FollowChangeEvent 事件
FollowerNoticeViewModel.followUser(moment.authorId)
}
} catch (e: Exception) {
} catch (e: Exception) {
e.printStackTrace()
}
}

View File

@@ -98,7 +98,7 @@ fun OtherProfileAction(
}
) {
Text(
text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper),
text = if (profile.isFollowing) stringResource(R.string.follow_upper_had) else stringResource(R.string.follow_upper),
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = if (profile.isFollowing) {

View File

@@ -77,7 +77,7 @@ fun EmailSignupScreen() {
email.isEmpty() -> context.getString(R.string.text_error_email_required)
// 邮箱格式
!android.util.Patterns.EMAIL_ADDRESS.matcher(email)
.matches() -> context.getString(R.string.text_error_email_format)
.matches() -> context.getString(R.string.text_error_email_format_1)
else -> null
}

View File

@@ -72,8 +72,17 @@ fun UserAuthScreen() {
var passwordError by remember { mutableStateOf<String?>(null) }
var captchaInfo by remember { mutableStateOf<CaptchaInfo?>(null) }
fun validateForm(): Boolean {
emailError =
if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
// 如果密码为空,先检查邮箱格式
if (password.isEmpty()) {
emailError = when {
email.isEmpty() -> context.getString(R.string.text_error_email_required)
!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() ->
context.getString(R.string.text_error_email_format)
else -> null
}
} else {
emailError = if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
}
// 使用通用密码校验器
val passwordValidation = PasswordValidator.validateCurrentPassword(password, context)

View File

@@ -52,6 +52,8 @@ import androidx.compose.ui.draw.rotate
import androidx.compose.ui.draw.scale
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.PathEffect
@@ -151,71 +153,42 @@ fun NewPostScreen() {
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.padding(horizontal = 16.dp)
.background(AppColors.divider)
)
Spacer(modifier = Modifier.height(24.dp))
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
.padding(start = 16.dp)
.width(100.dp)
.height(40.dp)
.clip(RoundedCornerShape(20.dp))
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(0xFF8CDDFF),
Color(0xFF9887FF),
Color(0xFFFF8D28)
),
)
)
.padding(horizontal = 14.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center
) {
Image(
painter = painterResource(id = R.mipmap.rider_pro_moment_ai),
painter = painterResource(id = R.mipmap.icon_ai),
contentDescription = null,
modifier = Modifier
.size(24.dp)
.size(16.dp)
)
Text(
text = stringResource(R.string.moment_ai_co),
fontWeight = FontWeight.Bold,
fontSize = 15.sp,
fontWeight = FontWeight.Normal,
fontSize = 13.sp,
modifier = Modifier
.padding(start = 8.dp)
.weight(1f),
color = AppColors.text,
)
Switch(
checked = isAiEnabled,
onCheckedChange = {
isChecked ->
isAiEnabled = isChecked
if (isChecked) {
// 收起键盘
keyboardController?.hide()
isRequesting = true
isRotating = true
model.viewModelScope.launch {
try {
model.agentMoment(model.textContent)
} catch (e: Exception) {
e.printStackTrace()
}finally {
isRequesting = false
isRotating = false
isAiEnabled = false
}
}
} else {
}
},
enabled = !isRequesting && model.textContent.isNotEmpty(),
colors = SwitchDefaults.colors(
checkedThumbColor = Color.White,
checkedTrackColor = AppColors.brandColorsColor,
uncheckedThumbColor = Color.White,
uncheckedTrackColor = AppColors.nonActive,
uncheckedBorderColor = Color.White,
disabledCheckedTrackColor = AppColors.brandColorsColor.copy(alpha = 0.8f),
disabledCheckedThumbColor= Color.White,
disabledUncheckedTrackColor = AppColors.nonActive,
disabledUncheckedThumbColor= Color.White
),
modifier = Modifier.scale(0.8f)
.padding(start = 2.dp),
color = Color.White,
)
}
@@ -352,7 +325,7 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
modifier = Modifier.align(Alignment.CenterStart),
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_close),
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = "Back",
modifier = Modifier
.size(24.dp)
@@ -366,9 +339,31 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
},
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
Text(
modifier = Modifier.align(Alignment.CenterVertically),
text = stringResource(R.string.publish_dynamic),
fontSize = 17.sp,
color = AppColors.text,
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.mipmap.rider_pro_moment_post),
painter = painterResource(id = R.mipmap.icon_draft_box_light),
contentDescription = "",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
// 添加防抖逻辑
val currentTime = System.currentTimeMillis()
if (currentTime - lastSendClickTime > debounceTime) {
lastSendClickTime = currentTime
}
},
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(20.dp))
Image(
painter = painterResource(id = R.mipmap.icon_released_light),
contentDescription = "Send",
modifier = Modifier
.size(24.dp)
@@ -391,11 +386,8 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
}finally {
uploading = false
}
}
}
},
)
}
@@ -488,72 +480,24 @@ fun AddImageGrid() {
}
val addImageDebouncer = rememberDebouncer()
val takePhotoDebouncer = rememberDebouncer()
val stroke = Stroke(
width = 2f,
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
)
DraggableGrid(
items = NewPostViewModel.imageList,
onMove = { from, to ->
NewPostViewModel.imageList = NewPostViewModel.imageList.toMutableList().apply {
add(to, removeAt(from))
}
},
lockedIndices = listOf(
),
onDragModeEnd = {},
onDragModeStart = {},
additionalItems = listOf(
),
getItemId = { it.id }
) { item, isDrag ->
Box(
modifier = Modifier
) {
CustomAsyncImage(
LocalContext.current,
item.bitmap,
contentDescription = "Image",
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.noRippleClickable {
navController.navigate(NavigationRoute.NewPostImageGrid.route)
},
contentScale = ContentScale.Crop
)
if (isDrag) {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Black.copy(alpha = 0.4f))
)
}
}
}
val canAddMoreImages = model.imageList.size < 9
LazyVerticalGrid(
columns = GridCells.Fixed(5),
contentPadding = PaddingValues(8.dp),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
contentPadding = PaddingValues(horizontal = 19.dp, vertical = 4.dp),
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
// 添加按钮
if (canAddMoreImages) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(16.dp)) // 设置圆角
.background(AppColors.basicMain) // 设置背景色
.clip(RoundedCornerShape(24.dp))
.background(Color(0xFFFAF9FB))
.noRippleClickable {
addImageDebouncer {
if (model.imageList.size < 9) {
@@ -572,20 +516,21 @@ fun AddImageGrid() {
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "Add Image",
modifier = Modifier
.size(24.dp)
.size(23.3.dp)
.align(Alignment.Center),
tint = AppColors.nonActiveText
)
}
}
// 相机按钮
item {
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(16.dp)) // 设置圆角
.background(AppColors.basicMain) // 设置背景色
.clip(RoundedCornerShape(24.dp))
.background(Color(0xFFFAF9FB))
.noRippleClickable {
if (model.imageList.size < 9) {
val photoFile = File(context.cacheDir, "photo.jpg")
@@ -605,13 +550,60 @@ fun AddImageGrid() {
painter = painterResource(id = R.drawable.rider_pro_camera),
contentDescription = "Take Photo",
modifier = Modifier
.size(24.dp)
.size(23.3.dp)
.align(Alignment.Center),
tint = AppColors.nonActiveText
)
}
}
}
// 已添加的图片,显示在相机按钮后面
items(model.imageList.size) { index ->
val item = model.imageList[index]
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1f)
.clip(RoundedCornerShape(24.dp))
.background(Color(0xFFFAF9FB))
) {
CustomAsyncImage(
context,
item.bitmap,
contentDescription = "Image",
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
navController.navigate(NavigationRoute.NewPostImageGrid.route)
},
contentScale = ContentScale.Crop
)
// // 删除按钮 - 右上角
// Box(
// modifier = Modifier
// .align(Alignment.TopEnd)
// .padding(4.dp)
// .size(20.dp)
// .clip(RoundedCornerShape(10.dp))
// .background(Color.Black.copy(alpha = 0.6f))
// .noRippleClickable {
// model.imageList = model.imageList.toMutableList().apply {
// removeAt(index)
// }
// },
// contentAlignment = Alignment.Center
// ) {
// Icon(
// painter = painterResource(id = R.drawable.rider_pro_close),
// contentDescription = "Delete",
// modifier = Modifier.size(12.dp),
// tint = Color.White
// )
// }
}
}
}
}

View File

@@ -13,6 +13,7 @@ import com.aiosman.ravenow.data.UserServiceImpl
import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentServiceImpl
import com.aiosman.ravenow.event.FollowChangeEvent
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
import com.aiosman.ravenow.event.MomentLikeChangeEvent
import com.aiosman.ravenow.event.MomentRemoveEvent
@@ -166,7 +167,8 @@ class PostViewModel(
moment?.let {
userService.followUser(it.authorId.toString())
moment = moment?.copy(followStatus = true)
// 更新我的关注页面的关注数
// 发送关注事件,通知动态列表更新关注状态
EventBus.getDefault().post(FollowChangeEvent(it.authorId, true))
}
}
@@ -174,7 +176,8 @@ class PostViewModel(
moment?.let {
userService.unFollowUser(it.authorId.toString())
moment = moment?.copy(followStatus = false)
// 更新我的关注页面的关注数
// 发送取消关注事件,通知动态列表更新关注状态
EventBus.getDefault().post(FollowChangeEvent(it.authorId, false))
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 492 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 553 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 671 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 533 B

View File

@@ -20,6 +20,7 @@
<string name="comment_count">%d コメント</string>
<string name="post_comment_hint">何か言ってください</string>
<string name="follow_upper">フォロー</string>
<string name="follow_upper_had">フォローしました</string>
<string name="login_upper">ログイン</string>
<string name="lets_ride_upper">レッツ・レヴ・ナウ</string>
<string name="or_login_with">または</string>
@@ -40,6 +41,7 @@
<string name="login_confirm_password_label">パスワードの確認</string>
<string name="agree_terms_of_service">はい、RaveNowのプライバシーポリシーを読み、同意します。</string>
<string name="agree_promotion">はい、Rave Nowのメーリングリストに追加されたいです。</string>
<string name="text_error_email_format_1">メールボックスフォーマットエラー</string>
<string name="text_error_email_format">無効なメールアドレス</string>
<string name="text_error_password_format">6文字以上で、文字と数字を含めてください。</string>
<string name="text_error_confirm_password_mismatch">入力されたパスワードが一致していることを確認してください。</string>

View File

@@ -19,6 +19,7 @@
<string name="comment_count">%d条评论</string>
<string name="post_comment_hint">快来互动吧...</string>
<string name="follow_upper">关注</string>
<string name="follow_upper_had">已关注</string>
<string name="login_upper">登录</string>
<string name="lets_ride_upper">确认</string>
<string name="or_login_with">其他账号登录</string>
@@ -39,7 +40,8 @@
<string name="login_confirm_password_label">再次输入密码</string>
<string name="agree_terms_of_service">我已阅读用户协议</string>
<string name="agree_promotion">我同意 Rave Now 推送消息</string>
<string name="text_error_email_format">邮箱格式错误</string>
<string name="text_error_email_format_1">邮箱格式错误</string>
<string name="text_error_email_format">无效的邮箱</string>
<string name="text_error_password_format">至少6位包含字母、数字</string>
<string name="text_error_confirm_password_mismatch">密码和确认密码必须相同</string>
<string name="text_error_confirm_password_required">请输入确认密码</string>
@@ -143,19 +145,19 @@
<string name="agent_desc_hint">示例: 一位经验丰富的销售员,擅长通过幽默风趣的语言和生动的案例,将复杂的产品转化为客户易于理解并感兴趣的话题</string>
<string name="agent_create">创建智能体</string>
<string name="create_confirm">好的,就它了</string>
<string name="moment_content_hint">需要一些灵感来写文章吗?让人工智能来帮你!</string>
<string name="moment_ai_co">AI文案优化</string>
<string name="moment_content_hint">你的帖子需要一些灵感让AI帮助你!</string>
<string name="moment_ai_co">文案优化</string>
<string name="moment_ai_delete">删除</string>
<string name="moment_ai_apply">应用</string>
<string name="chat_ai">智能体</string>
<string name="chat_group">群聊</string>
<string name="chat_friend">朋友</string>
<string name="chat_all">全部</string>
<string name="favourites_null">暂无数据</string>
<string name="favourites_null">咦,什么都没有...</string>
<string name="agent_chat_list_title">智能体聊天</string>
<string name="agent_chat_empty_title">AI在等你开启第一句对话</string>
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起一场对话!</string>
<string name="agent_chat_empty_title">AI 在等你的开场白</string>
<string name="agent_chat_empty_subtitle">去首页探索一下,主动发起对话!</string>
<string name="agent_chat_me_prefix">我: </string>
<string name="agent_chat_image">[图片]</string>
<string name="agent_chat_voice">[语音]</string>
@@ -165,12 +167,12 @@
<string name="agent_chat_load_failed">加载失败</string>
<string name="agent_chat_load_more_failed">加载更多失败</string>
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
<string name="group_chat_empty">没有群聊消息的宇宙安静</string>
<string name="group_chat_empty">没有群聊宇宙安静</string>
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
<string name="group_chat_empty_join">去首页探索感兴趣的主题房间</string>
<string name="friend_chat_empty_title">和朋友,还没说第一句话呢</string>
<string name="friend_chat_empty_subtitle">一段崭新的友谊 等待被唤醒</string>
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</string>
<string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string>
<string name="friend_chat_me_prefix">我: </string>
<string name="friend_chat_load_failed">加载失败</string>
<string name="create_group_chat">创建群聊</string>

View File

@@ -19,6 +19,7 @@
<string name="comment_count">%d Comments</string>
<string name="post_comment_hint">Say something…</string>
<string name="follow_upper">FOLLOW</string>
<string name="follow_upper_had">Followed</string>
<string name="login_upper">Log in</string>
<string name="lets_ride_upper">Let\'s Rave Now</string>
<string name="or_login_with">or</string>
@@ -39,6 +40,7 @@
<string name="login_confirm_password_label">Confirm password</string>
<string name="agree_terms_of_service">Yes, I have read and agree to RaveNows Privacy Policy.</string>
<string name="agree_promotion">Yes, I want to be added to the Rave Now mailing list.</string>
<string name="text_error_email_format_1">Email format error</string>
<string name="text_error_email_format">Invalid email</string>
<string name="text_error_password_format">At least 6 characters and contain letters, and numbers.</string>
<string name="text_error_confirm_password_mismatch">Please ensure that the passwords entered twice are consistent.</string>

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<debug-overrides>
<trust-anchors>
<!-- Trust user added CAs while debuggable only -->
<certificates src="user" />
<certificates src="system" />
</trust-anchors>
</debug-overrides>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
<certificates src="user" />
</trust-anchors>
</base-config>
</network-security-config>