15 Commits

Author SHA1 Message Date
1a24136c35 优化AI界面,添加分页加载功能,支持动态加载更多智能体数据;重构UI布局 2025-09-23 11:57:11 +08:00
742410223c Merge pull request #29 from Zhong202501/main
手动创建AI界面调整
2025-09-23 10:37:15 +08:00
bd5aff7564 手动创建AI界面调整 2025-09-22 17:57:39 +08:00
b43c1585c4 Merge pull request #28 from Zhong202501/main
创建AI界面UI兼容;动态页面调整
2025-09-22 10:48:57 +08:00
cb582393f1 手动创造AI界面;调整输入框点击区域;创建AI时的三点彩色动画 2025-09-19 18:45:10 +08:00
a200d00587 UI调整 2025-09-18 18:19:19 +08:00
6d2133545f 动态详情页面评论调整 2025-09-18 18:16:54 +08:00
2aad126010 创建AI界面UI兼容;动态页面调整 2025-09-17 18:41:15 +08:00
b7b777d2d0 Merge pull request #26 from Zhong202501/new
首页底部导航栏图标;创建AI界面
2025-09-17 11:03:17 +08:00
e804c8be0c Merge pull request #20 from Kevinlinpr/new-bottom-create-button
Feat: Add Create Bottom Sheet and icons
2025-09-17 10:44:34 +08:00
228a74695e Merge pull request #22 from Zhong202501/main
添加Category接口
2025-09-17 10:39:22 +08:00
41a51b85da 首页底部导航栏图标;创建AI界面 2025-09-16 18:18:36 +08:00
e74e8615a5 Agent卡片组件UI;Agent聊天界面输入框显示问题 2025-09-15 14:06:05 +08:00
349d39daf2 修复BUG:我的界面右上角图标会跟随背景图一起向上滑走 2025-09-12 18:24:49 +08:00
8154a0ddc4 Category接口;Agent卡片组件背景颜色 2025-09-11 18:14:54 +08:00
64 changed files with 1364 additions and 773 deletions

View File

@@ -4,10 +4,10 @@
<selectionStates> <selectionStates>
<SelectionState runConfigName="app"> <SelectionState runConfigName="app">
<option name="selectionMode" value="DROPDOWN" /> <option name="selectionMode" value="DROPDOWN" />
<DropdownSelection timestamp="2025-09-09T09:51:06.656104400Z"> <DropdownSelection timestamp="2025-09-17T06:25:35.585100400Z">
<Target type="DEFAULT_BOOT"> <Target type="DEFAULT_BOOT">
<handle> <handle>
<DeviceId pluginId="Default" identifier="serial=192.168.0.227:45035;connection=094cb92e" /> <DeviceId pluginId="Default" identifier="serial=192.168.0.216:5555;connection=698a7727" />
</handle> </handle>
</Target> </Target>
</DropdownSelection> </DropdownSelection>

View File

@@ -271,6 +271,44 @@ data class RemoveAccountRequestBody(
val password: String, val password: String,
) )
data class CategoryTemplate(
@SerializedName("id")
val id: Int,
@SerializedName("name")
val name: String,
@SerializedName("description")
val description: String,
@SerializedName("avatar")
val avatar: String,
@SerializedName("parentId")
val parentId: Int?,
@SerializedName("parent")
val parent: CategoryTemplate?,
@SerializedName("children")
val children: List<CategoryTemplate>?,
@SerializedName("sort")
val sort: Int,
@SerializedName("isActive")
val isActive: Boolean,
@SerializedName("promptCount")
val promptCount: Int?,
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("updatedAt")
val updatedAt: String
)
data class CategoryListResponse(
@SerializedName("page")
val page: Int,
@SerializedName("pageSize")
val pageSize: Int,
@SerializedName("total")
val total: Int,
@SerializedName("list")
val list: List<CategoryTemplate>
)
interface RaveNowAPI { interface RaveNowAPI {
@GET("membership/config") @GET("membership/config")
@retrofit2.http.Headers("X-Requires-Auth: true") @retrofit2.http.Headers("X-Requires-Auth: true")
@@ -552,6 +590,7 @@ interface RaveNowAPI {
@Query("pageSize") pageSize: Int = 20, @Query("pageSize") pageSize: Int = 20,
@Query("withWorkflow") withWorkflow: Int = 1, @Query("withWorkflow") withWorkflow: Int = 1,
@Query("authorId") authorId: Int? = null, @Query("authorId") authorId: Int? = null,
@Query("categoryIds") categoryIds: List<Int>? = null,
): Response<DataContainer<ListContainer<Agent>>> ): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts") @GET("outside/my/prompts")
@@ -605,7 +644,38 @@ interface RaveNowAPI {
suspend fun joinRoom(@Body body: JoinGroupChatRequestBody, suspend fun joinRoom(@Body body: JoinGroupChatRequestBody,
): Response<DataContainer<Room>> ): Response<DataContainer<Room>>
@GET("outside/categories")
suspend fun getCategories(
@Query("page") page: Int? = null,
@Query("pageSize") pageSize: Int? = null,
@Query("parentId") parentId: Int? = null,
@Query("isActive") isActive: Boolean? = null,
@Query("name") name: String? = null,
@Query("withChildren") withChildren: Boolean? = null,
@Query("withParent") withParent: Boolean? = null,
@Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<CategoryListResponse>>
@GET("outside/categories/tree")
suspend fun getCategoryTree(
@Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<List<CategoryTemplate>>>
@GET("outside/categories/{id}")
suspend fun getCategoryById(
@Path("id") id: Int
): Response<DataContainer<CategoryTemplate>>
@GET("outside/prompts")
suspend fun getPromptsByCategory(
@Query("categoryIds") categoryIds: List<Int>? = null,
@Query("categoryName") categoryName: String? = null,
@Query("uncategorized") uncategorized: String? = null,
@Query("page") page: Int? = null,
@Query("pageSize") pageSize: Int? = null
): Response<ListContainer<Agent>>
} }

View File

@@ -29,10 +29,12 @@ import androidx.activity.compose.BackHandler
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
@@ -61,7 +63,17 @@ import com.aiosman.ravenow.ui.composables.form.FormTextInput2
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.foundation.border
import androidx.compose.ui.draw.shadow
import com.aiosman.ravenow.AppState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.RepeatMode
import androidx.compose.animation.core.StartOffset
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.offset
/** /**
* 添加智能体界面 * 添加智能体界面
*/ */
@@ -73,7 +85,8 @@ fun AddAgentScreen() {
var agnetNameError by remember { mutableStateOf<String?>(null) } var agnetNameError by remember { mutableStateOf<String?>(null) }
var agnetDescError by remember { mutableStateOf<String?>(null) } var agnetDescError by remember { mutableStateOf<String?>(null) }
var errorMessage by remember { mutableStateOf<String?>(null) } var errorMessage by remember { mutableStateOf<String?>(null) }
var isProcessing by remember { mutableStateOf(false) }
var showWaveAnimation by remember { mutableStateOf(false) }
fun onNameChange(value: String) { fun onNameChange(value: String) {
@@ -118,9 +131,10 @@ fun AddAgentScreen() {
.background(color = appColors.decentBackground), .background(color = appColors.decentBackground),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
var showManualCreation by remember { mutableStateOf(false) }
StatusBarSpacer() StatusBarSpacer()
Box( Box(
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp) modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp)
.background(color = appColors.decentBackground) .background(color = appColors.decentBackground)
) { ) {
// 自定义header控制返回按钮行为 // 自定义header控制返回按钮行为
@@ -148,47 +162,355 @@ fun AddAgentScreen() {
stringResource(R.string.agent_add), stringResource(R.string.agent_add),
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f),
textAlign = TextAlign.Center,
fontSize = 17.sp, fontSize = 17.sp,
color = appColors.text color = appColors.text
) )
Spacer(modifier = Modifier.size(12.dp)) }
Icon( }
Spacer(modifier = Modifier.height(1.dp))
Column(
modifier = Modifier modifier = Modifier
.size(24.dp) .fillMaxWidth()
.noRippleClickable { .height(50.dp)
// 提交创建智能体的逻辑可以在这里实现 .padding(horizontal = 20.dp),
}, ) {
imageVector = Icons.Default.Check, Image(
contentDescription = "Add", painter = painterResource(id = R.mipmap.group_copy),
tint = appColors.text contentDescription = "",
modifier = Modifier
.size(48.dp)
.clip(
RoundedCornerShape(48.dp)
),
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.height(16.dp))
Column(
modifier = Modifier.fillMaxWidth()
.padding(start = 20.dp)
) {
Text(
text = "${AppState.profile?.nickName ?: "User"} 你好呀!今天想创造什么?",
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
}
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier.fillMaxWidth()
.padding(start = 20.dp)
) {
if (!showManualCreation) {
Text(
text = "只需要一句话你的专属AI将在这里诞生。",
fontSize = 14.sp,
color = LocalAppTheme.current.text.copy(alpha = 0.6f),
) )
} }
} }
Spacer(modifier = Modifier.height(44.dp)) Spacer(modifier = Modifier.height(24.dp))
Box(
modifier = Modifier.size(88.dp),
contentAlignment = Alignment.Center if (!showManualCreation) {
) { Column(
CustomAsyncImage(
context,
model.croppedBitmap,
modifier = Modifier modifier = Modifier
.size(88.dp) .padding(horizontal = 20.dp)
.clip( ) {
RoundedCornerShape(88.dp)
Box(
modifier = Modifier
.fillMaxWidth()
.height(95.dp)
.shadow(
elevation = 10.dp,
shape = RoundedCornerShape(10.dp),
spotColor = Color(0x33F563FF),
ambientColor = Color(0x99F563FF),
clip = false
)
.background(
brush = Brush.linearGradient(
listOf(
Color(0xFF6246FF),
Color(0xFF7C45ED)
)
), ),
contentDescription = "", shape = RoundedCornerShape(10.dp)
contentScale = ContentScale.Crop, )
placeholderRes = R.mipmap.rider_pro_agent_avatar .padding(0.5.dp)
.background(
color = appColors.inputBackground2,
shape = RoundedCornerShape(10.dp)
)
) {
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Box(
modifier = Modifier
.fillMaxSize()
.noRippleClickable {
model.viewModelScope.launch {
focusRequester.requestFocus()
keyboardController?.show()
}
}
)
FormTextInput2(
value = model.desc,
hint = "一个会写诗的AI一个懂你笑点的AI...",
background = appColors.inputBackground2,
focusRequester = focusRequester,
modifier = Modifier
.fillMaxWidth()
.height(95.dp)
) { value ->
onDescChange(value)
}
Row(
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(end = 12.dp, bottom = 12.dp)
.noRippleClickable {
if (!isProcessing && model.desc.isNotEmpty()) {
isProcessing = true
model.viewModelScope.launch {
try {
//AI美化功能待实现
} catch (e: Exception) {
e.printStackTrace()
} finally {
isProcessing = false
}
}
}
},
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.mipmap.icons_info_magic),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = Color.Unspecified
)
Spacer(modifier = Modifier.width(5.dp))
Text(
text = "AI美化",
color = Color(0xFF6246FF),
fontSize = 14.sp
)
}
}
}
Spacer(modifier = Modifier.height(24.dp))
if (showWaveAnimation) {
Row(
modifier = Modifier
.align(Alignment.Start)
.padding(start = 20.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier.size(18.dp)
) {
val infiniteTransition = rememberInfiniteTransition()
val dot1Translation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(0)
)
)
val dot2Translation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(333)
)
)
val dot3Translation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(666)
)
)
// 三个彩色圆点
Box(
modifier = Modifier
.size(6.dp)
.align(Alignment.BottomStart)
.offset(y = dot1Translation.dp)
.background(Color(0xFFFFD400), CircleShape)
) )
Box( Box(
modifier = Modifier modifier = Modifier
.size(32.dp) .size(6.dp)
.clip(CircleShape) .align(Alignment.BottomCenter)
.background(appColors.main) .offset(y = dot2Translation.dp)
.background(Color(0xFF2F80FF), CircleShape)
)
Box(
modifier = Modifier
.size(6.dp)
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.offset(y = dot3Translation.dp)
.background(Color(0xFF27C84D), CircleShape)
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "正在为你构思",
color = Color.Black.copy(alpha = 0.6f),
fontSize = 14.sp
)
}
} else {
Box(
modifier = Modifier
.align(Alignment.Start)
.padding(start = 20.dp)
.width(136.dp)
.height(40.dp)
.border(
width = 1.dp,
color = Color(0x33858B98),
shape = RoundedCornerShape(12.dp)
)
.background(
color = appColors.background,
shape = RoundedCornerShape(12.dp),
)
.noRippleClickable {
showManualCreation = true
}
) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 18.dp, vertical = 12.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = null,
modifier = Modifier.size(18.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "手动创造Ai",
color = Color.Black,
fontWeight = FontWeight.W600,
fontSize = 14.sp
)
}
}
}
}else {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.align(Alignment.Start)
) {
// 添加新的一句话创造AI按钮
Box(
modifier = Modifier
.align(Alignment.Start)
.width(140.dp)
.height(40.dp)
.shadow(
elevation = 10.dp,
shape = RoundedCornerShape(10.dp),
spotColor = Color(0x33F563FF),
ambientColor = Color(0x99F563FF),
clip = false
)
.background(
brush = Brush.linearGradient(
listOf(
Color(0xFF6246FF),
Color(0xFF7C45ED)
)
),
shape = RoundedCornerShape(10.dp)
)
.padding(0.5.dp)
.background(
color = appColors.background,
shape = RoundedCornerShape(10.dp),
)
.noRippleClickable {
showManualCreation = false
}
) {
Row(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 12.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.mipmap.icons_info_magic),
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(18.dp),
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = "一句话创造AI",
color = Color.Black,
fontWeight = FontWeight.W600,
fontSize = 14.sp
)
}
}
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "头像",
fontSize = 12.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
Box(
modifier = Modifier
.size(72.dp)
.clip(CircleShape)
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(0x777c45ed),
Color(0x777c68ef),
Color(0x557bd8f8)
)
)
)
.align(Alignment.Start)
.noRippleClickable { .noRippleClickable {
// 设置正在选择头像的标志 // 设置正在选择头像的标志
model.isSelectingAvatar = true model.isSelectingAvatar = true
@@ -197,31 +519,41 @@ fun AddAgentScreen() {
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Icon( Icon(
Icons.Default.Add, painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = "Add", contentDescription = "Edit",
tint = Color.White, tint = Color.White,
modifier = Modifier.size(20.dp),
) )
} }
} }
Spacer(modifier = Modifier.height(58.dp)) Spacer(modifier = Modifier.height(18.dp))
// 原版两个输入框
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
Text(
text = stringResource(R.string.agent_name),
fontSize = 12.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
FormTextInput( FormTextInput(
value = model.name, value = model.name,
label = stringResource(R.string.agent_name), hint = "给它取个名字,让它成为独一无二的你",
hint = stringResource(R.string.agent_name_hint),
background = appColors.inputBackground2, background = appColors.inputBackground2,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { value -> ) { value ->
onNameChange(value) onNameChange(value)
} }
// Spacer(modifier = Modifier.height(16.dp)) Text(
text = stringResource(R.string.agent_desc),
fontSize = 12.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
FormTextInput2( FormTextInput2(
value = model.desc, value = model.desc,
label = stringResource(R.string.agent_desc),
hint = stringResource(R.string.agent_desc_hint), hint = stringResource(R.string.agent_desc_hint),
background = appColors.inputBackground2, background = appColors.inputBackground2,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
@@ -229,38 +561,41 @@ fun AddAgentScreen() {
onDescChange(value) onDescChange(value)
} }
} }
Spacer(modifier = Modifier.height(58.dp)) }
// 错误信息显示 // 错误信息显示
Spacer(modifier = Modifier.weight(1f))
Box(modifier = Modifier.fillMaxWidth()) {
errorMessage?.let { error -> errorMessage?.let { error ->
Text( Text(
text = error, text = error,
color = Color.Red, color = Color.Red,
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier
.padding(bottom = 20.dp)
.align(Alignment.Center),
fontSize = 14.sp fontSize = 14.sp
) )
Spacer(modifier = Modifier.height(16.dp)) }
} }
ActionButton( ActionButton(
modifier = Modifier modifier = Modifier
.width(345.dp) .width(345.dp)
.padding(horizontal = 16.dp) .padding(bottom = 40.dp)
.background( .background(
brush = Brush.linearGradient( brush = Brush.linearGradient(
colors = listOf( colors = listOf(
Color(0xFFEE2A33), Color(0x777c45ed),
Color(0xFFD80264), Color(0x777c68ef),
Color(0xFF8468BC) Color(0x557bd8f8)
) )
), ),
shape = RoundedCornerShape(24.dp) shape = RoundedCornerShape(24.dp)
), ),
color = Color.White, color = Color.White,
backgroundColor = Color.Transparent, backgroundColor = Color.Transparent,
text = stringResource(R.string.agent_create), text = stringResource(R.string.create_confirm),
isLoading = model.isUpdating, isLoading = model.isUpdating,
loadingText = stringResource(R.string.agent_createing),
enabled = !model.isUpdating && validate() enabled = !model.isUpdating && validate()
) { ) {
// 验证输入 // 验证输入
@@ -268,12 +603,19 @@ fun AddAgentScreen() {
if (validationError != null) { if (validationError != null) {
// 显示验证错误 // 显示验证错误
errorMessage = validationError errorMessage = validationError
model.viewModelScope.launch {
kotlinx.coroutines.delay(3000)
errorMessage = null
}
return@ActionButton return@ActionButton
} }
// 清除之前的错误信息 // 清除之前的错误信息
errorMessage = null errorMessage = null
// 显示波动动画
showWaveAnimation = true
// 调用创建智能体API // 调用创建智能体API
model.viewModelScope.launch { model.viewModelScope.launch {
try { try {
@@ -284,6 +626,9 @@ fun AddAgentScreen() {
navController.popBackStack() navController.popBackStack()
} }
} catch (e: Exception) { } catch (e: Exception) {
// 隐藏波动动画
showWaveAnimation = false
// 显示错误信息 // 显示错误信息
errorMessage = "创建智能体失败: ${e.message}" errorMessage = "创建智能体失败: ${e.message}"
e.printStackTrace() e.printStackTrace()

View File

@@ -66,9 +66,9 @@ object AddAgentViewModel : ViewModel() {
fun validate(): String? { fun validate(): String? {
return when { return when {
name.isEmpty() -> "智能体名称不能为空" // name.isEmpty() -> "智能体名称不能为空"
name.length < 2 -> "智能体名称长度不能少于2个字符" // name.length < 2 -> "智能体名称长度不能少于2个字符"
name.length > 20 -> "智能体名称长度不能超过20个字符" // name.length > 20 -> "智能体名称长度不能超过20个字符"
desc.isEmpty() -> "智能体描述不能为空" desc.isEmpty() -> "智能体描述不能为空"
desc.length > 100 -> "智能体描述长度不能超过100个字符" desc.length > 100 -> "智能体描述长度不能超过100个字符"
else -> null else -> null

View File

@@ -283,7 +283,7 @@ fun ChatAiScreen(userId: String) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(AppColors.decentBackground) .background(Color.White)
.padding(paddingValues) .padding(paddingValues)
) { ) {
LazyColumn( LazyColumn(

View File

@@ -69,72 +69,22 @@ fun EditCommentBottomModal(
.background(AppColors.background) .background(AppColors.background)
.padding(horizontal = 16.dp, vertical = 16.dp) .padding(horizontal = 16.dp, vertical = 16.dp)
) { ) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.fillMaxWidth()
) {
Text(
if (replyComment == null) "Comment" else "Reply",
fontWeight = FontWeight.W600,
modifier = Modifier.weight(1f),
fontSize = 20.sp,
fontStyle = FontStyle.Italic,
color = AppColors.text
)
}
Spacer(modifier = Modifier.height(16.dp))
if (replyComment != null) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(24.dp)
.clip(CircleShape)
) {
CustomAsyncImage(
context,
replyComment.avatar,
modifier = Modifier.fillMaxSize(),
contentDescription = "Avatar",
)
}
Spacer(modifier = Modifier.width(8.dp))
Text(
replyComment.name,
fontWeight = FontWeight.Bold,
fontSize = 16.sp,
color = AppColors.text
)
}
Spacer(modifier = Modifier.height(4.dp))
Text(
replyComment.comment,
maxLines = 1,
modifier = Modifier
.fillMaxWidth()
.padding(start = 32.dp),
overflow = TextOverflow.Ellipsis,
color = AppColors.text
)
Spacer(modifier = Modifier.height(16.dp))
}
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth(), .fillMaxWidth(),
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { ) {
Box( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
.clip(RoundedCornerShape(20.dp)) .clip(RoundedCornerShape(20.dp))
.background(Color.White) .background(Color.Gray.copy(alpha = 0.1f))
.border(1.dp, Color.Black, RoundedCornerShape(20.dp))
.padding(horizontal = 16.dp, vertical = 16.dp) .padding(horizontal = 16.dp, vertical = 16.dp)
) { ) {
Row( Row(
verticalAlignment = Alignment.Top modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) { ) {
BasicTextField( BasicTextField(
value = text, value = text,
@@ -149,30 +99,39 @@ fun EditCommentBottomModal(
color = Color.Black, color = Color.Black,
fontWeight = FontWeight.Normal fontWeight = FontWeight.Normal
), ),
minLines = 1 decorationBox = { innerTextField ->
Box(
modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.CenterStart
) {
innerTextField()
if (text.isEmpty()) {
Text(
text = if (replyComment == null) "快来互动吧..." else "回复@${replyComment.name}",
color = AppColors.text.copy(alpha = 0.3f), // 30%透明度
) )
Spacer(modifier = Modifier.width(8.dp)) }
Crossfade( }
targetState = text.isNotEmpty(), animationSpec = tween(500), }
label = "" )
) { isNotEmpty -> }
}
Spacer(modifier = Modifier.width(12.dp))
Icon( Icon(
painter = painterResource(id = R.mipmap.rider_pro_moment_post), painter = painterResource(id = R.mipmap.btn),
contentDescription = "Send", contentDescription = "Send",
modifier = Modifier modifier = Modifier
.size(20.dp) .size(40.dp)
.align(Alignment.Top) .padding(top = 13.dp)
.noRippleClickable { .noRippleClickable {
if (text.isNotEmpty()) { if (text.isNotEmpty()) {
onSend(text) onSend(text)
text = "" text = ""
} }
}, },
tint = if (isNotEmpty) Color.Unspecified else AppColors.nonActive tint = Color.Unspecified
) )
}
}
}
} }
Spacer(modifier = Modifier.height(navBarHeight)) Spacer(modifier = Modifier.height(navBarHeight))
} }

View File

@@ -92,16 +92,22 @@ fun MomentCard(
showFollowButton = showFollowButton showFollowButton = showFollowButton
) )
} }
val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.noRippleClickable { .noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
navController.navigateToPost( navController.navigateToPost(
momentEntity.id, momentEntity.id,
highlightCommentId = 0, highlightCommentId = 0,
initImagePagerIndex = imageIndex initImagePagerIndex = imageIndex
) )
} }
}
) { ) {
MomentContentGroup( MomentContentGroup(
momentEntity = momentEntity, momentEntity = momentEntity,
@@ -213,7 +219,6 @@ fun MomentPostLocation(location: String) {
text = location, text = location,
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 12.sp, fontSize = 12.sp,
) )
} }
@@ -238,6 +243,8 @@ fun MomentTopRowGroup(
Row( Row(
modifier = Modifier modifier = Modifier
) { ) {
val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L
CustomAsyncImage( CustomAsyncImage(
context, context,
momentEntity.avatar, momentEntity.avatar,
@@ -246,12 +253,16 @@ fun MomentTopRowGroup(
.size(40.dp) .size(40.dp)
.clip(RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(40.dp))
.noRippleClickable { .noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
navController.navigate( navController.navigate(
NavigationRoute.AccountProfile.route.replace( NavigationRoute.AccountProfile.route.replace(
"{id}", "{id}",
momentEntity.authorId.toString() momentEntity.authorId.toString()
) )
) )
}
}, },
contentScale = ContentScale.Crop contentScale = ContentScale.Crop
) )
@@ -267,7 +278,19 @@ fun MomentTopRowGroup(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
MomentName( MomentName(
modifier = Modifier.weight(1f), modifier = Modifier.weight(1f)
.noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
momentEntity.authorId.toString()
)
)
}
},
name = momentEntity.nickname name = momentEntity.nickname
) )
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
@@ -416,6 +439,8 @@ fun MomentBottomOperateRowGroup(
momentEntity: MomentEntity, momentEntity: MomentEntity,
imageIndex: Int = 0 imageIndex: Int = 0
) { ) {
val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L
var showCommentModal by remember { mutableStateOf(false) } var showCommentModal by remember { mutableStateOf(false) }
if (showCommentModal) { if (showCommentModal) {
ModalBottomSheet( ModalBottomSheet(
@@ -451,58 +476,14 @@ fun MomentBottomOperateRowGroup(
.height(56.dp) .height(56.dp)
.padding(start = 16.dp, end = 0.dp) .padding(start = 16.dp, end = 0.dp)
) { ) {
Row( Column(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Box(
modifier = Modifier.fillMaxHeight(),
contentAlignment = Alignment.Center
) {
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
}
Spacer(modifier = Modifier.width(4.dp))
Box(
modifier = Modifier
.fillMaxHeight()
.noRippleClickable {
onCommentClick()
},
contentAlignment = Alignment.Center
) {
MomentOperateBtn(
icon = R.drawable.rider_pro_comment,
count = momentEntity.commentCount.toString()
)
}
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
,
contentAlignment = Alignment.CenterEnd
) {
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
onFavoriteClick()
}
}
}
}
if (momentEntity.images.size > 1) { if (momentEntity.images.size > 1) {
Row( Row(
modifier = Modifier.fillMaxSize(), modifier = Modifier
.fillMaxWidth()
.weight(1f),
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -523,8 +504,68 @@ fun MomentBottomOperateRowGroup(
} }
} }
Row(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight(),
contentAlignment = Alignment.CenterStart
) {
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
AnimatedFavouriteIcon(
modifier = Modifier.size(24.dp),
isFavourite = momentEntity.isFavorite
) {
onFavoriteClick()
} }
}
}
Box(
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight()
.noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
onCommentClick()
}
},
contentAlignment = Alignment.CenterEnd
) {
MomentOperateBtn(
icon = R.drawable.rider_pro_comment,
count = momentEntity.commentCount.toString()
)
}
Spacer(modifier = Modifier.width(24.dp))
Box(
modifier = Modifier
.wrapContentWidth()
.fillMaxHeight(),
contentAlignment = Alignment.CenterEnd
) {
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
AnimatedLikeIcon(
modifier = Modifier.size(24.dp),
liked = momentEntity.liked
) {
onLikeClick()
}
}
}
}
}
}
} }

View File

@@ -15,9 +15,11 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -52,7 +54,7 @@ fun FormTextInput(
) { ) {
Row( Row(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(25.dp))
.background(background ?: AppColors.inputBackground) .background(background ?: AppColors.inputBackground)
.let { .let {
if (error != null) { if (error != null) {
@@ -79,13 +81,27 @@ fun FormTextInput(
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(start = 16.dp) ) {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = null,
modifier = Modifier
.size(16.dp)
.align(Alignment.TopStart),
tint = Color.Unspecified.copy(alpha = 0.4f)
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.width(24.dp))
Box(
modifier = Modifier.weight(1f)
) { ) {
if (value.isEmpty()) { if (value.isEmpty()) {
Text( Text(
text = hint ?: "", text = hint ?: "",
style = TextStyle( style = TextStyle(
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = AppColors.inputHint color = AppColors.inputHint
) )
@@ -100,13 +116,14 @@ fun FormTextInput(
}, },
singleLine = true, singleLine = true,
textStyle = TextStyle( textStyle = TextStyle(
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = AppColors.text color = AppColors.text
), ),
cursorBrush = SolidColor(AppColors.text), cursorBrush = SolidColor(AppColors.text),
) )
}
}
} }

View File

@@ -15,14 +15,18 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
@@ -44,6 +48,7 @@ fun FormTextInput2(
error: String? = null, error: String? = null,
hint: String? = null, hint: String? = null,
background: Color? = null, background: Color? = null,
focusRequester: FocusRequester? = null,
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -52,7 +57,7 @@ fun FormTextInput2(
) { ) {
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(25.dp))
.background(background ?: AppColors.inputBackground) .background(background ?: AppColors.inputBackground)
.let { .let {
if (error != null) { if (error != null) {
@@ -79,13 +84,27 @@ fun FormTextInput2(
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.padding(top = 8.dp) ) {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = null,
modifier = Modifier
.size(16.dp)
.align(Alignment.TopStart),
tint = Color.Unspecified.copy(alpha = 0.4f)
)
Row(
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.width(24.dp))
Box(
modifier = Modifier.weight(1f)
) { ) {
if (value.isEmpty()) { if (value.isEmpty()) {
Text( Text(
text = hint ?: "", text = hint ?: "",
style = TextStyle( style = TextStyle(
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = AppColors.inputHint color = AppColors.inputHint
) )
@@ -98,15 +117,24 @@ fun FormTextInput2(
onValueChange = { onValueChange = {
onValueChange(it) onValueChange(it)
}, },
modifier = Modifier
.let {
if (focusRequester != null) {
it.focusRequester(focusRequester)
} else {
it
}
},
textStyle = TextStyle( textStyle = TextStyle(
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = AppColors.text, color = AppColors.text,
lineHeight = 20.sp lineHeight = 20.sp
), ),
cursorBrush = SolidColor(AppColors.text), cursorBrush = SolidColor(AppColors.text),
) )
}
}
} }

View File

@@ -362,7 +362,7 @@ fun IndexScreen() {
Text( Text(
text = it.label(), text = it.label(),
fontSize = 10.sp, fontSize = 10.sp,
color = if (isSelected) AppColors.brandColorsColor else AppColors.text, color = if (isSelected) Color.Blue else AppColors.text,
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
) )
} }

View File

@@ -17,13 +17,13 @@ sealed class NavigationItem(
data object Home : NavigationItem("Home", data object Home : NavigationItem("Home",
icon = { R.drawable.rider_pro_nav_home }, icon = { R.drawable.rider_pro_nav_home },
selectedIcon = { R.mipmap.rider_pro_nav_home_hl }, selectedIcon = { R.mipmap.bars_x_buttons_home_s },
label = { stringResource(R.string.main_home) } label = { stringResource(R.string.main_home) }
) )
data object Ai : NavigationItem("Ai", data object Ai : NavigationItem("Ai",
icon = { R.mipmap.bars_x_buttons_discover_bold}, icon = { R.mipmap.bars_x_buttons_discover_bold},
selectedIcon = { R.mipmap.dynamic_hl }, selectedIcon = { R.mipmap.bars_x_buttons_discover_fill },
label = { stringResource(R.string.index_dynamic) } label = { stringResource(R.string.index_dynamic) }
) )
// data object Ai : NavigationItem("Ai", // data object Ai : NavigationItem("Ai",
@@ -40,13 +40,13 @@ sealed class NavigationItem(
data object Notification : NavigationItem("Notification", data object Notification : NavigationItem("Notification",
icon = { R.drawable.rider_pro_nav_notification }, icon = { R.drawable.rider_pro_nav_notification },
selectedIcon = { R.mipmap.rider_pro_nav_message_hl }, selectedIcon = { R.mipmap.bars_x_buttons_chat_s },
label = { stringResource(R.string.main_message) } label = { stringResource(R.string.main_message) }
) )
data object Profile : NavigationItem("Profile", data object Profile : NavigationItem("Profile",
icon = { R.drawable.rider_pro_nav_profile }, icon = { R.drawable.rider_pro_nav_profile },
selectedIcon = { R.mipmap.rider_pro_nav_profile_hl }, selectedIcon = { R.mipmap.bars_x_buttons_user_s },
label = { stringResource(R.string.main_profile) } label = { stringResource(R.string.main_profile) }
) )

View File

@@ -28,9 +28,14 @@ import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Scaffold
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -69,13 +74,24 @@ import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.ResourceCleanupManager import com.aiosman.ravenow.utils.ResourceCleanupManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.lazy.grid.items as gridItems
import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.grid.items
@OptIn( ExperimentalFoundationApi::class) // 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
val layoutInfo = this.layoutInfo
val totalItemsCount = layoutInfo.totalItemsCount
val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
return lastVisibleItemIndex >= (totalItemsCount - buffer)
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun Agent() { fun Agent() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -89,7 +105,6 @@ fun Agent() {
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = viewModel()
val scrollState = rememberScrollState()
// 确保推荐Agent数据已加载 // 确保推荐Agent数据已加载
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
@@ -107,37 +122,38 @@ fun Agent() {
} }
} }
Column( val agentItems = viewModel.agentItems
modifier = Modifier var selectedTabIndex by remember { mutableStateOf(0) }
.fillMaxSize()
.verticalScroll(scrollState) // 无限滚动状态
.padding( val listState = rememberLazyListState()
top = statusBarPaddingValues.calculateTopPadding(),
bottom = navigationBarPaddings, // 创建一个可观察的滚动到底部状态
start = 16.dp, val isScrolledToEnd by remember {
end = 16.dp derivedStateOf {
), listState.isScrolledToEnd()
horizontalAlignment = Alignment.CenterHorizontally, }
) { }
Row(
modifier = Modifier // 检测滚动到底部并加载更多数据
.wrapContentHeight() LaunchedEffect(isScrolledToEnd) {
.height(44.dp) if (isScrolledToEnd && !viewModel.isLoadingMore && agentItems.isNotEmpty() && viewModel.hasMoreData) {
.fillMaxWidth(), viewModel.loadMoreAgents()
horizontalArrangement = Arrangement.Start, }
verticalAlignment = Alignment.CenterVertically }
) {
Scaffold(
topBar = {
TopAppBar(
title = {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = "Rave AI", text = "Rave AI",
fontSize = 20.sp, fontSize = 20.sp,
fontWeight = FontWeight.W900, fontWeight = FontWeight.W900,
color = AppColors.text, color = AppColors.text
modifier = Modifier
.align(Alignment.CenterVertically)
) )
},
Spacer(modifier = Modifier.weight(1f)) actions = {
Image( Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search), painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search", contentDescription = "search",
@@ -148,95 +164,40 @@ fun Agent() {
}, },
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
) )
} },
Spacer(modifier = Modifier.height(15.dp)) colors = TopAppBarDefaults.topAppBarColors(
// // 搜索框 containerColor = AppColors.background
// Row( )
// modifier = Modifier )
// .height(36.dp) },
// .weight(1f) containerColor = AppColors.background,
// .clip(shape = RoundedCornerShape(8.dp)) contentWindowInsets = WindowInsets(0, 0, 0, 0)
// .background(AppColors.inputBackground) ) { paddingValues ->
// .padding(horizontal = 8.dp, vertical = 0.dp) LazyColumn(
// .noRippleClickable { state = listState,
// // 搜索框点击事件 modifier = Modifier
// }, .fillMaxSize()
// verticalAlignment = Alignment.CenterVertically .padding(paddingValues)
// ) { .padding(
// Icon( bottom = navigationBarPaddings,
// painter = painterResource(id = R.drawable.rider_pro_nav_search), start = 16.dp,
// contentDescription = null, end = 16.dp
// tint = AppColors.inputHint )
// ) ) {
// Box {
// Text(
// text = stringResource(R.string.search),
// modifier = Modifier.padding(start = 8.dp),
// color = AppColors.inputHint,
// fontSize = 17.sp
// )
// }
// }
// Spacer(modifier = Modifier.width(16.dp))
// // 创建智能体
// Icon(
// modifier = Modifier
// .size(36.dp)
// .noRippleClickable {
// if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
// // 检查游客模式,如果是游客则跳转登录
// if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
// navController.navigate(NavigationRoute.Login.route)
// } else {
// // 导航到添加智能体页面
// navController.navigate(
// NavigationRoute.AddAgent.route
// )
// }
// }) {
// lastClickTime = System.currentTimeMillis()
// }
// },
// painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
// contentDescription = null,
// tint = AppColors.text
// )
// 类别标签页 - 吸顶
stickyHeader(key = "category_tabs") {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(vertical = 8.dp) .background(AppColors.background)
.padding(top = 4.dp, bottom = 8.dp)
) { ) {
// // 标题
// Row(
// verticalAlignment = Alignment.CenterVertically,
// modifier = Modifier.padding(bottom = 12.dp)
// ) {
// Image(
// painter = painterResource(R.mipmap.rider_pro_agent2),
// contentDescription = "agent",
// modifier = Modifier.size(28.dp),
//
// )
// Spacer(modifier = Modifier.width(4.dp))
// androidx.compose.material3.Text(
// text = stringResource(R.string.agent_recommend_agent),
// fontSize = 16.sp,
// fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
// color = AppColors.text
// )
// }
var selectedTabIndex by remember { mutableStateOf(0) }
// 标签页
LazyRow( LazyRow(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight() .wrapContentHeight()
.padding(bottom = 16.dp), .padding(bottom = 8.dp),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
@@ -246,6 +207,7 @@ fun Agent() {
isSelected = selectedTabIndex == 0, isSelected = selectedTabIndex == 0,
onClick = { onClick = {
selectedTabIndex = 0 selectedTabIndex = 0
viewModel.loadAllAgents()
} }
) )
} }
@@ -254,6 +216,24 @@ fun Agent() {
TabSpacer() TabSpacer()
} }
// 动态添加分类标签
viewModel.categories.take(4).forEachIndexed { index, category ->
item {
CustomTabItem(
text = category.name,
isSelected = selectedTabIndex == index + 1,
onClick = {
selectedTabIndex = index + 1
viewModel.loadAgentsByCategory(category.id)
}
)
}
item {
TabSpacer()
}
}
item { item {
CustomTabItem( CustomTabItem(
text = "scenes", text = "scenes",
@@ -271,9 +251,9 @@ fun Agent() {
item { item {
CustomTabItem( CustomTabItem(
text = "voices", text = "voices",
isSelected = selectedTabIndex == 2, isSelected = selectedTabIndex == 6,
onClick = { onClick = {
selectedTabIndex = 2 selectedTabIndex = 6
} }
) )
} }
@@ -285,9 +265,9 @@ fun Agent() {
item { item {
CustomTabItem( CustomTabItem(
text = "anime", text = "anime",
isSelected = selectedTabIndex == 3, isSelected = selectedTabIndex == 7,
onClick = { onClick = {
selectedTabIndex = 3 selectedTabIndex = 7
} }
) )
} }
@@ -299,16 +279,27 @@ fun Agent() {
item { item {
CustomTabItem( CustomTabItem(
text = "assist", text = "assist",
isSelected = selectedTabIndex == 4, isSelected = selectedTabIndex == 8,
onClick = { onClick = {
selectedTabIndex = 4 selectedTabIndex = 8
} }
) )
} }
} }
}
when (selectedTabIndex) { }
0 -> { // 推荐内容区域
item {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
when {
selectedTabIndex == 0 -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
}
selectedTabIndex in 1..viewModel.categories.size -> {
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel) AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
} }
else -> { else -> {
@@ -317,12 +308,16 @@ fun Agent() {
} }
} }
} }
}
// "发现更多" 标题 - 吸顶
stickyHeader(key = "discover_more") {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.wrapContentHeight(), .background(AppColors.background)
// center the tabs .padding(top = 8.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Start, horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom verticalAlignment = Alignment.Bottom
) { ) {
@@ -330,7 +325,6 @@ fun Agent() {
painter = painterResource(R.mipmap.rider_pro_agent2), painter = painterResource(R.mipmap.rider_pro_agent2),
contentDescription = "agent", contentDescription = "agent",
modifier = Modifier.size(28.dp), modifier = Modifier.size(28.dp),
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
@@ -339,22 +333,24 @@ fun Agent() {
fontWeight = androidx.compose.ui.text.font.FontWeight.W600, fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text color = AppColors.text
) )
} }
Spacer(modifier = Modifier.height(50.dp)) }
Column(
// Agent网格 - 使用行式布局
items(
items = agentItems.chunked(2),
key = { row -> row.firstOrNull()?.openId ?: "" }
) { rowItems ->
Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) { ) {
val agentItems = viewModel.agentItems.take(15) rowItems.forEach { agentItem ->
LazyVerticalGrid( Box(
columns = GridCells.Fixed(2), modifier = Modifier.weight(1f)
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(50.dp)
) { ) {
items(agentItems) { agentItem ->
AgentCardSquare( AgentCardSquare(
agentItem = agentItem, agentItem = agentItem,
viewModel = viewModel, viewModel = viewModel,
@@ -362,6 +358,36 @@ fun Agent() {
) )
} }
} }
// 如果这一行只有一个item添加一个空的占位符
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
}
// 加载更多指示器
if (viewModel.isLoadingMore) {
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
horizontalArrangement = Arrangement.Center
) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.text,
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
androidx.compose.material3.Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
)
}
}
}
} }
} }
} }
@@ -369,7 +395,7 @@ fun Agent() {
@Composable @Composable
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) { fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val cardHeight = 180.dp val cardHeight = 200.dp
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一 val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
// 防抖状态 // 防抖状态
@@ -378,8 +404,9 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = avatarSize / 2)
.height(cardHeight) .height(cardHeight)
.background(Color(0xFFE0E0E0), RoundedCornerShape(12.dp)) // 灰色背景 .background(AppColors.nonActive, RoundedCornerShape(12.dp)) // 修改背景颜色
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController) viewModel.goToProfile(agentItem.openId, navController)
@@ -426,7 +453,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = agentItem.title, text = agentItem.title,
fontSize = 16.sp, fontSize = 14.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600, fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text, color = AppColors.text,
maxLines = 1, maxLines = 1,
@@ -435,21 +462,26 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Box(
modifier = Modifier
.height(85.dp)
.fillMaxWidth()
) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = agentItem.desc, text = agentItem.desc,
fontSize = 14.sp, fontSize = 12.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 2, maxLines = 5,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
modifier = Modifier.weight(1f, fill = false)
) )
}
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
// 聊天按钮,位于底部居中 // 聊天按钮,位于底部居中
Box( Box(
modifier = Modifier modifier = Modifier
.width(80.dp) .width(60.dp)
.height(32.dp) .height(32.dp)
.background( .background(
color = Color(0X147c7480), color = Color(0X147c7480),

View File

@@ -6,7 +6,10 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.api.ApiClient import com.aiosman.ravenow.data.api.ApiClient
import com.aiosman.ravenow.data.api.CategoryTemplate
import com.aiosman.ravenow.data.api.RaveNowAPI import com.aiosman.ravenow.data.api.RaveNowAPI
import com.aiosman.ravenow.data.api.SingleChatRequestBody import com.aiosman.ravenow.data.api.SingleChatRequestBody
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
@@ -22,6 +25,8 @@ object AgentViewModel: ViewModel() {
var agentItems by mutableStateOf<List<AgentItem>>(emptyList()) var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
private set private set
var categories by mutableStateOf<List<CategoryItem>>(emptyList())
private set
var errorMessage by mutableStateOf<String?>(null) var errorMessage by mutableStateOf<String?>(null)
private set private set
@@ -33,31 +38,140 @@ object AgentViewModel: ViewModel() {
var isLoading by mutableStateOf(false) var isLoading by mutableStateOf(false)
private set private set
// 分页相关状态
var isLoadingMore by mutableStateOf(false)
private set
var currentPage by mutableStateOf(1)
private set
var hasMoreData by mutableStateOf(true)
private set
private val pageSize = 20
private var currentCategoryId: Int? = null
init { init {
loadAgentData() loadAgentData()
loadCategories()
} }
private fun loadAgentData() { private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) {
viewModelScope.launch { viewModelScope.launch {
if (isLoadMore) {
isLoadingMore = true
} else {
isLoading = true isLoading = true
// 重置分页状态
currentPage = 1
hasMoreData = true
currentCategoryId = categoryId
}
errorMessage = null errorMessage = null
try { try {
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1) val response = if (categoryId != null) {
// 根据分类ID获取智能体
apiClient.getAgent(
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = listOf(categoryId)
)
} else {
// 获取所有智能体
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1)
}
if (response.isSuccessful) { if (response.isSuccessful) {
val agents = response.body()?.data?.list ?: emptyList() val responseData = response.body()?.data
agentItems = agents.map { agent -> val agents = responseData?.list ?: emptyList<Agent>()
val newAgentItems = agents.map { agent ->
AgentItem.fromAgent(agent) AgentItem.fromAgent(agent)
} }
if (isLoadMore) {
// 加载更多:追加到现有列表
agentItems = agentItems + newAgentItems
currentPage = page
} else {
// 首次加载或刷新:替换整个列表
agentItems = newAgentItems
currentPage = 1
}
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
} else { } else {
errorMessage = "获取Agent数据失败: ${response.code()}" errorMessage = "获取Agent数据失败: ${response.code()}"
} }
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}" errorMessage = "网络请求失败: ${e.message}"
} finally { } finally {
if (isLoadMore) {
isLoadingMore = false
} else {
isLoading = false isLoading = false
} }
} }
} }
}
private fun loadCategories() {
viewModelScope.launch {
try {
val response = apiClient.getCategories(
pageSize = 20,
withChildren = false,
withParent = false,
withCount = true,
hideEmpty = true
)
println("分类数据请求完成,响应成功: ${response.isSuccessful}")
if (response.isSuccessful) {
val categoryList = response.body()?.data?.list ?: emptyList()
println("获取到 ${categoryList.size} 个分类")
categories = categoryList.map { category ->
CategoryItem.fromCategoryTemplate(category)
}
println("成功处理并映射了 ${categories.size} 个分类")
} else {
errorMessage = "获取分类数据失败: ${response.code()}"
println("获取分类数据失败: ${response.code()}")
}
} catch (e: Exception) {
errorMessage = "获取分类数据失败: ${e.message}"
println("获取分类数据异常: ${e.message}")
e.printStackTrace()
}
}
}
fun loadAgentsByCategory(categoryId: Int) {
loadAgentData(categoryId)
}
fun loadAllAgents() {
loadAgentData()
}
/**
* 加载更多Agent数据
*/
fun loadMoreAgents() {
// 检查是否正在加载或没有更多数据
if (isLoadingMore || !hasMoreData) {
return
}
val nextPage = currentPage + 1
loadAgentData(
categoryId = currentCategoryId,
page = nextPage,
isLoadMore = true
)
}
fun createSingleChat( fun createSingleChat(
openId: String, openId: String,
) { ) {
@@ -119,6 +233,29 @@ object AgentViewModel: ViewModel() {
errorMessage = null errorMessage = null
isRefreshing = false isRefreshing = false
isLoading = false isLoading = false
isLoadingMore = false
currentPage = 1
hasMoreData = true
currentCategoryId = null
} }
} }
data class CategoryItem(
val id: Int,
val name: String,
val description: String,
val avatar: String,
val promptCount: Int?
) {
companion object {
fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem {
return CategoryItem(
id = template.id,
name = template.name,
description = template.description,
avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
promptCount = template.promptCount
)
}
}
}

View File

@@ -49,6 +49,8 @@ import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import com.aiosman.ravenow.ui.composables.TabItem import com.aiosman.ravenow.ui.composables.TabItem
@@ -120,6 +122,8 @@ fun MomentsList() {
// //
// } // }
// Spacer(modifier = Modifier.width(16.dp)) // Spacer(modifier = Modifier.width(16.dp))
val lastClickTime = remember { mutableStateOf(0L) }
val clickDelay = 500L
Text( Text(
text = stringResource(R.string.moment), text = stringResource(R.string.moment),
fontSize = 20.sp, fontSize = 20.sp,
@@ -137,7 +141,11 @@ fun MomentsList() {
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)
.noRippleClickable { .noRippleClickable {
val currentTime = System.currentTimeMillis()
if (currentTime - lastClickTime.value > clickDelay) {
lastClickTime.value = currentTime
navController.navigate(NavigationRoute.Search.route) navController.navigate(NavigationRoute.Search.route)
}
}, },
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
) )

View File

@@ -50,10 +50,12 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.nativeCanvas
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -369,39 +371,6 @@ fun ProfileV3(
) )
} }
} }
if (isSelf&&isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = statusBarPaddingValues.calculateTopPadding(),
start = 8.dp,
end = 8.dp
)
.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier
.padding(16.dp)
.clip(RoundedCornerShape(8.dp))
.background(
AppColors.background.copy(
alpha = 0.7f
)
)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = AppColors.text
)
}
}
}
} }
Box( Box(
@@ -509,6 +478,7 @@ fun ProfileV3(
} }
Box(modifier = Modifier.fillMaxWidth()) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -566,7 +536,24 @@ fun ProfileV3(
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
if (isSelf&&isMain) { if (isSelf&&isMain) {
Box( Box(
modifier = Modifier.noRippleClickable { modifier = Modifier
.size(24.dp)
.padding(16.dp)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
if (isSelf&&isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = 32.dp ,
end = 16.dp
)
.noRippleClickable {
IndexViewModel.openDrawer = true IndexViewModel.openDrawer = true
} }
) { ) {
@@ -584,9 +571,6 @@ fun ProfileV3(
} }
} }
}
Spacer(modifier = Modifier.height(8.dp))
} }
} }
) { ) {

View File

@@ -279,6 +279,7 @@ fun LoginPage() {
contentDescription = "Rave Now", contentDescription = "Rave Now",
modifier = Modifier modifier = Modifier
.size(52.dp) .size(52.dp)
.clip(RoundedCornerShape(10.dp))
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(

View File

@@ -917,7 +917,20 @@ fun Header(
Text( Text(
text = nickname ?: "", text = nickname ?: "",
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
modifier = Modifier.weight(1f), modifier = Modifier
.weight(1f)
.debouncedClickable(debounceTime = 1000L) {
userId?.let {
debouncedNavigation {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
userId.toString()
)
)
}
}
},
color = AppColors.text, color = AppColors.text,
fontSize = 17.sp fontSize = 17.sp
) )
@@ -1196,7 +1209,7 @@ fun PostImageView(
) )
} }
// Navigation and Indicator container // 图片导航控件
if (images.size > 1) { if (images.size > 1) {
Row( Row(
modifier = Modifier modifier = Modifier
@@ -1347,91 +1360,19 @@ fun CommentItem(
} }
) {} ) {}
) { ) {
Row { Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
) {
Text( Text(
text = commentEntity.name, text = commentEntity.name,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
fontSize = 11.sp, fontSize = 11.sp,
color = AppColors.text color = AppColors.text
) )
Spacer(modifier = Modifier.width(8.dp)) Column(
Text( horizontalAlignment = Alignment.End
text = commentEntity.date.timeAgo(context),
fontSize = 11.sp,
color = Color.Gray
)
}
Row (modifier = Modifier.padding(top = 4.dp)){
if (isChild) {
val annotatedText = buildAnnotatedString {
if (commentEntity.replyUserId != null) {
pushStringAnnotation(
tag = "replyUser",
annotation = commentEntity.replyUserId.toString()
)
withStyle(
style = SpanStyle(
fontWeight = FontWeight.W600,
color = Color(0xFF6F94AE)
)
) { ) {
append("@${commentEntity.replyUserNickname}")
}
pop()
}
append(" ${commentEntity.comment}")
}
Box {
CustomClickableText(
text = annotatedText,
onClick = { offset ->
annotatedText.getStringAnnotations(
tag = "replyUser",
start = offset,
end = offset
).firstOrNull()?.let {
debouncedNavigation {
navController.navigate(
NavigationRoute.AccountProfile.route.replace(
"{id}",
it.item
)
)
}
}
},
style = TextStyle(fontSize = 14.sp, color = AppColors.text),
onLongPress = {
onLongClick(commentEntity)
},
)
}
} else {
Text(
text = commentEntity.comment,
fontSize = 13.sp,
maxLines = Int.MAX_VALUE,
softWrap = true,
lineHeight = 20.sp,
color = AppColors.text,
modifier = Modifier.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onLongClick = {
onLongClick(
commentEntity
)
},
) {
}
)
}
}
Row (modifier = Modifier.padding(top = 12.dp),
verticalAlignment = Alignment.CenterVertically,){
AnimatedLikeIcon( AnimatedLikeIcon(
liked = commentEntity.liked, liked = commentEntity.liked,
onClick = { onClick = {
@@ -1446,29 +1387,54 @@ fun CommentItem(
}, },
modifier = Modifier.size(16.dp) modifier = Modifier.size(16.dp)
) )
Spacer(modifier = Modifier.width(4.dp))
Text( Text(
text = commentEntity.likes.toString(), text = commentEntity.likes.toString(),
fontSize = 12.sp, fontSize = 12.sp,
fontWeight = FontWeight.Bold, fontWeight = FontWeight.Bold,
color = AppColors.text color = AppColors.text,
modifier = Modifier.padding(top = 4.dp,end = 4.dp)
) )
Text( }
text = stringResource(R.string.like), }
fontSize = 12.sp,
fontWeight = FontWeight.Bold,
color = AppColors.nonActiveText,
)
Spacer(modifier = Modifier.width(27.dp))
Icon(
painter = painterResource(id = R.drawable.rider_pro_comment), Text(
contentDescription = "", text = commentEntity.comment,
modifier = Modifier.size(16.dp), fontSize = 13.sp,
tint = AppColors.nonActiveText maxLines = Int.MAX_VALUE,
softWrap = true,
lineHeight = 20.sp,
color = AppColors.text,
modifier = Modifier
.fillMaxWidth()
.padding(end = 50.dp)
.padding(top = 0.dp)
.combinedClickable(
interactionSource = remember { MutableInteractionSource() },
indication = null,
onLongClick = {
onLongClick(
commentEntity
) )
Spacer(modifier = Modifier.width(4.dp)) },
) {
}
)
Row (
modifier = Modifier.padding(top = 12.dp),
verticalAlignment = Alignment.CenterVertically,
){
Text(
text = commentEntity.date.timeAgo(context),
fontSize = 12.sp,
color = Color.Gray
)
Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringResource(R.string.reply), text = stringResource(R.string.reply),
fontSize = 12.sp, fontSize = 12.sp,
@@ -1541,6 +1507,7 @@ fun CommentItem(
} }
} }
@Composable @Composable
fun PostBottomBar( fun PostBottomBar(
onCreateCommentClick: () -> Unit = {}, onCreateCommentClick: () -> Unit = {},
@@ -1607,6 +1574,24 @@ fun PostBottomBar(
} }
} }
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
AnimatedFavouriteIcon(
isFavourite = momentEntity?.isFavorite == true,
onClick = {
// 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
onFavoriteClick()
}
},
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(text = momentEntity?.favoriteCount.toString(), color = AppColors.text)
Spacer(modifier = Modifier.width(16.dp))
AnimatedLikeIcon( AnimatedLikeIcon(
liked = momentEntity?.liked == true, liked = momentEntity?.liked == true,
onClick = { onClick = {
@@ -1623,24 +1608,6 @@ fun PostBottomBar(
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
Text(text = momentEntity?.likeCount.toString(), color = AppColors.text) Text(text = momentEntity?.likeCount.toString(), color = AppColors.text)
Spacer(modifier = Modifier.width(16.dp))
AnimatedFavouriteIcon(
isFavourite = momentEntity?.isFavorite == true,
onClick = {
// 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
debouncedNavigation {
navController.navigate(NavigationRoute.Login.route)
}
} else {
onFavoriteClick()
}
},
modifier = Modifier.size(24.dp)
)
Spacer(modifier = Modifier.width(4.dp))
Text(text = momentEntity?.favoriteCount.toString(), color = AppColors.text)
} }
BottomNavigationPlaceholder( BottomNavigationPlaceholder(
color = AppColors.background color = AppColors.background

Binary file not shown.

After

Width:  |  Height:  |  Size: 773 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 927 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 581 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 634 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 602 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 660 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 912 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 623 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 967 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 330 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 816 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 966 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 470 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 372 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1022 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 569 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -17,7 +17,7 @@
<string name="following_upper">关注</string> <string name="following_upper">关注</string>
<string name="unfollow_upper">取消关注</string> <string name="unfollow_upper">取消关注</string>
<string name="comment_count">%d条评论</string> <string name="comment_count">%d条评论</string>
<string name="post_comment_hint">说点什么</string> <string name="post_comment_hint">快来互动吧...</string>
<string name="follow_upper">关注</string> <string name="follow_upper">关注</string>
<string name="login_upper">登录</string> <string name="login_upper">登录</string>
<string name="lets_ride_upper">确认</string> <string name="lets_ride_upper">确认</string>
@@ -133,12 +133,13 @@
<string name="agent_hot">热门</string> <string name="agent_hot">热门</string>
<string name="agent_recommend">推荐</string> <string name="agent_recommend">推荐</string>
<string name="agent_other">其他</string> <string name="agent_other">其他</string>
<string name="agent_add">创建智能体</string> <string name="agent_add">创建AI</string>
<string name="agent_name">名称</string> <string name="agent_name">名称</string>
<string name="agent_name_hint">请输入名称</string> <string name="agent_name_hint">请输入名称</string>
<string name="agent_desc">设定描述</string> <string name="agent_desc">设定描述</string>
<string name="agent_desc_hint">示例: 一位经验丰富的销售员,擅长通过幽默风趣的语言和生动的案例,将复杂的产品转化为客户易于理解并感兴趣的话题</string> <string name="agent_desc_hint">示例: 一位经验丰富的销售员,擅长通过幽默风趣的语言和生动的案例,将复杂的产品转化为客户易于理解并感兴趣的话题</string>
<string name="agent_create">创建智能体</string> <string name="agent_create">创建智能体</string>
<string name="create_confirm">好的,就它了</string>
<string name="moment_content_hint">需要一些灵感来写文章吗?让人工智能来帮你!</string> <string name="moment_content_hint">需要一些灵感来写文章吗?让人工智能来帮你!</string>
<string name="moment_ai_co">AI文案优化</string> <string name="moment_ai_co">AI文案优化</string>
<string name="moment_ai_delete">删除</string> <string name="moment_ai_delete">删除</string>

View File

@@ -178,6 +178,7 @@
<string name="agent_recommend_agent">推荐给你的智能体</string> <string name="agent_recommend_agent">推荐给你的智能体</string>
<string name="hot_chat_room">正在高能对话中</string> <string name="hot_chat_room">正在高能对话中</string>
<string name="create_agent">创建智能体</string> <string name="create_agent">创建智能体</string>
<string name="create_confirm">confirm</string>
<string name="publish_dynamic">发布动态</string> <string name="publish_dynamic">发布动态</string>
<string name="hot_agent">热门智能体</string> <string name="hot_agent">热门智能体</string>
<string name="group_room_enter">进入</string> <string name="group_room_enter">进入</string>