@@ -327,7 +327,7 @@ data class MomentEntity(
|
||||
// 是否关注
|
||||
val followStatus: Boolean,
|
||||
// 动态内容
|
||||
val momentTextContent: String,
|
||||
val momentTextContent: String?,
|
||||
// 动态图片
|
||||
@DrawableRes val momentPicture: Int,
|
||||
// 点赞数
|
||||
|
||||
@@ -73,7 +73,8 @@ fun AgentCard(
|
||||
agentEntity.avatar,
|
||||
contentDescription = agentEntity.openId,
|
||||
modifier = Modifier.size(40.dp),
|
||||
contentScale = ContentScale.Crop
|
||||
contentScale = ContentScale.Crop,
|
||||
defaultRes = com.aiosman.ravenow.R.mipmap.group_copy
|
||||
)
|
||||
}
|
||||
Column(
|
||||
|
||||
@@ -445,9 +445,9 @@ fun MomentContentGroup(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (momentEntity.momentTextContent.isNotEmpty()) {
|
||||
if (!momentEntity.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = momentEntity.momentTextContent,
|
||||
text = momentEntity.momentTextContent ?: "",
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||
|
||||
@@ -467,8 +467,7 @@ fun AgentCardSquare(
|
||||
navController: NavHostController
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val cardHeight = 180.dp
|
||||
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
|
||||
val cardHeight = 210.dp
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
@@ -477,96 +476,76 @@ fun AgentCardSquare(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(cardHeight)
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
.clip(RoundedCornerShape(12.dp))
|
||||
.noRippleClickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
viewModel.goToProfile(agentItem.openId, navController)
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
},
|
||||
contentAlignment = Alignment.TopCenter
|
||||
}
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.offset(y = 4.dp)
|
||||
.size(avatarSize)
|
||||
.background(AppColors.background, RoundedCornerShape(avatarSize / 2))
|
||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.group_copy),
|
||||
contentDescription = "默认头像",
|
||||
modifier = Modifier.size(avatarSize),
|
||||
)
|
||||
if (agentItem.avatar.isNotEmpty()) {
|
||||
// 背景大图
|
||||
CustomAsyncImage(
|
||||
imageUrl = agentItem.avatar,
|
||||
contentDescription = "Agent头像",
|
||||
modifier = Modifier
|
||||
.size(avatarSize)
|
||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
contentDescription = agentItem.title,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
defaultRes = R.mipmap.rider_pro_agent
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 内容区域(名称和描述)
|
||||
// 底部渐变与文字
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
0f to Color.Transparent,
|
||||
1f to Color(0xB2000000)
|
||||
)
|
||||
)
|
||||
.padding(12.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp + avatarSize + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
.padding(bottom = 40.dp) // 为底部聊天按钮预留空间
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = agentItem.title,
|
||||
color = Color.White,
|
||||
fontSize = 14.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||
color = AppColors.text,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W700,
|
||||
maxLines = 1,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = agentItem.desc,
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.secondaryText,
|
||||
color = Color.White.copy(alpha = 0.92f),
|
||||
fontSize = 11.sp,
|
||||
maxLines = 2,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false)
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 聊天按钮
|
||||
// 底部居中 Chat 按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 12.dp)
|
||||
.width(60.dp)
|
||||
.width(70.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = AppColors.text,
|
||||
shape = RoundedCornerShape(
|
||||
topStart = 14.dp,
|
||||
topEnd = 14.dp,
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 14.dp
|
||||
)
|
||||
)
|
||||
.clickable {
|
||||
.background(AppColors.text, RoundedCornerShape(16.dp))
|
||||
.noRippleClickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
viewModel.createSingleChat(agentItem.openId)
|
||||
viewModel.goToChatAi(
|
||||
agentItem.openId,
|
||||
navController = navController
|
||||
)
|
||||
viewModel.goToChatAi(agentItem.openId, navController)
|
||||
}
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
@@ -576,9 +555,9 @@ fun AgentCardSquare(
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.chat),
|
||||
fontSize = 15.sp,
|
||||
color = AppColors.background,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||
fontSize = 13.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -658,7 +637,8 @@ fun AgentLargeCard(
|
||||
imageUrl = agentItem.avatar,
|
||||
contentDescription = agentItem.title,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
defaultRes = R.mipmap.rider_pro_agent
|
||||
)
|
||||
|
||||
// 底部渐变与文字
|
||||
@@ -776,23 +756,16 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.group_copy),
|
||||
contentDescription = "默认头像",
|
||||
modifier = Modifier.size(48.dp),
|
||||
)
|
||||
|
||||
if (agentItem.avatar.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = agentItem.avatar,
|
||||
contentDescription = "Agent头像",
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.clip(RoundedCornerShape(24.dp)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
defaultRes = R.mipmap.group_copy
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
|
||||
@@ -998,7 +971,6 @@ fun ChatRoomCard(
|
||||
// 优先显示banner,如果没有banner则显示头像
|
||||
val imageUrl = if (chatRoom.banner.isNotEmpty()) chatRoom.banner else chatRoom.avatar
|
||||
|
||||
if (imageUrl.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = imageUrl,
|
||||
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
||||
@@ -1010,17 +982,9 @@ fun ChatRoomCard(
|
||||
topEnd = 12.dp,
|
||||
bottomStart = 0.dp,
|
||||
bottomEnd = 0.dp)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||
defaultRes = R.mipmap.rider_pro_agent
|
||||
)
|
||||
} else {
|
||||
// 默认房间图标
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||
contentDescription = "默认房间图标",
|
||||
modifier = Modifier.size(cardSize * 0.4f),
|
||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||
)
|
||||
}
|
||||
|
||||
// 房间名称,重叠在底部
|
||||
Box(
|
||||
|
||||
@@ -136,22 +136,27 @@ fun DiscoverView() {
|
||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 4.dp)
|
||||
) {
|
||||
items(moments) { momentItem ->
|
||||
// 如果图片列表为空,跳过这个 item
|
||||
if (momentItem.images.isEmpty()) {
|
||||
return@items
|
||||
}
|
||||
|
||||
val debouncer = rememberDebouncer()
|
||||
|
||||
val textContent = momentItem.momentTextContent
|
||||
// 对于英文和日文,每行字符数会更少,使用更保守的估算
|
||||
val estimatedCharsPerLine = if (textContent.isNotEmpty()) {
|
||||
val estimatedCharsPerLine = if (!textContent.isNullOrEmpty()) {
|
||||
// 检测是否包含非中文字符(英文、日文等)
|
||||
val hasNonChinese = textContent.any {
|
||||
val hasNonChinese = textContent?.any {
|
||||
val code = it.code
|
||||
!(code >= 0x4E00 && code <= 0x9FFF) // 不在中文字符范围内
|
||||
}
|
||||
} ?: false
|
||||
if (hasNonChinese) 15 else 20 // 英文/日文每行更少字符
|
||||
} else {
|
||||
20
|
||||
}
|
||||
val textLines = if (textContent.isNotEmpty()) {
|
||||
val estimatedLines = (textContent.length / estimatedCharsPerLine) + 1
|
||||
val textLines = if (!textContent.isNullOrEmpty()) {
|
||||
val estimatedLines = ((textContent?.length ?: 0) / estimatedCharsPerLine) + 1
|
||||
minOf(estimatedLines, 2) // 最多2行
|
||||
} else {
|
||||
0
|
||||
@@ -209,9 +214,9 @@ fun DiscoverView() {
|
||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
||||
) {
|
||||
// 文本内容区域,限制最大高度
|
||||
if (momentItem.momentTextContent.isNotEmpty()) {
|
||||
if (!momentItem.momentTextContent.isNullOrEmpty()) {
|
||||
androidx.compose.material3.Text(
|
||||
text = momentItem.momentTextContent,
|
||||
text = momentItem.momentTextContent ?: "",
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
fontSize = 12.sp,
|
||||
color = AppColors.text,
|
||||
|
||||
@@ -150,7 +150,7 @@ fun FullArticleModal(
|
||||
|
||||
// 帖子内容
|
||||
NewsContent(
|
||||
content = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
|
||||
content = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""),
|
||||
images = moment.images,
|
||||
context = context
|
||||
)
|
||||
|
||||
@@ -288,7 +288,7 @@ fun NewsItem(
|
||||
|
||||
// 新闻内容(超出使用省略号)
|
||||
Text(
|
||||
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
|
||||
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
||||
@@ -132,12 +132,12 @@ fun PostRecommendationItem(
|
||||
}
|
||||
|
||||
// 文字内容
|
||||
if (moment.momentTextContent.isNotEmpty()) {
|
||||
if (!moment.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.8f)
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent,
|
||||
text = moment.momentTextContent ?: "",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.recommend
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
@@ -12,6 +13,7 @@ import androidx.compose.material3.ButtonDefaults
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -25,8 +27,12 @@ import androidx.compose.ui.unit.sp
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.data.UserService
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* Prompt推荐Item组件
|
||||
@@ -43,11 +49,47 @@ fun PromptRecommendationItem(
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
val userService: UserService = UserServiceImpl()
|
||||
|
||||
// 导航到个人资料
|
||||
fun navigateToProfile() {
|
||||
scope.launch {
|
||||
try {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
// Prompt推荐的一定是AI账号,直接传递isAiAccount = true
|
||||
navController.navigate(
|
||||
NavigationRoute.AccountProfile.route
|
||||
.replace("{id}", profile.id.toString())
|
||||
.replace("{isAiAccount}", "true")
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// 处理错误,避免崩溃
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 导航到AI聊天
|
||||
fun navigateToChatAi() {
|
||||
scope.launch {
|
||||
try {
|
||||
val profile = userService.getUserProfileByOpenId(openId)
|
||||
navController.navigateToChatAi(profile.id.toString())
|
||||
} catch (e: Exception) {
|
||||
// 处理错误,避免崩溃
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.background(Color.Black)
|
||||
.clickable(
|
||||
onClick = { navigateToProfile() }
|
||||
)
|
||||
) {
|
||||
// 背景大图
|
||||
CustomAsyncImage(
|
||||
@@ -122,8 +164,8 @@ fun PromptRecommendationItem(
|
||||
// Start chatting 按钮
|
||||
Button(
|
||||
onClick = {
|
||||
// 导航到聊天页面
|
||||
navController.navigate("${NavigationRoute.Chat.route}/$openId")
|
||||
// 导航到AI聊天页面(区分AI聊天和普通聊天)
|
||||
navigateToChatAi()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
||||
@@ -278,12 +278,12 @@ fun VideoRecommendationItem(
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
if (moment.momentTextContent.isNotEmpty()) {
|
||||
if (!moment.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(0.8f)
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent,
|
||||
text = moment.momentTextContent ?: "",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
|
||||
@@ -178,7 +178,7 @@ fun TimeGroup(time: String = "2024.06.08 12:23") {
|
||||
|
||||
@Composable
|
||||
fun ProfileMomentCard(
|
||||
content: String,
|
||||
content: String?,
|
||||
imageUrl: String,
|
||||
like: String,
|
||||
comment: String,
|
||||
@@ -220,7 +220,7 @@ fun ProfileMomentCard(
|
||||
columnHeight = coordinates.size.height
|
||||
}
|
||||
) {
|
||||
if (content.isNotEmpty()) {
|
||||
if (!content.isNullOrEmpty()) {
|
||||
MomentCardTopContent(content)
|
||||
}
|
||||
MomentCardPicture(imageUrl, momentEntity = momentEntity)
|
||||
@@ -237,7 +237,7 @@ fun ProfileMomentCard(
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MomentCardTopContent(content: String) {
|
||||
fun MomentCardTopContent(content: String?) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -247,7 +247,7 @@ fun MomentCardTopContent(content: String) {
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(top = 16.dp, bottom = 0.dp, start = 16.dp, end = 16.dp),
|
||||
text = content, fontSize = 16.sp, color = AppColors.text
|
||||
text = content ?: "", fontSize = 16.sp, color = AppColors.text
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -447,12 +447,12 @@ fun VideoPlayer(
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||
)
|
||||
if (moment.momentTextContent.isNotEmpty()) {
|
||||
if (!moment.momentTextContent.isNullOrEmpty()) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 4.dp),
|
||||
text = moment.momentTextContent,
|
||||
text = moment.momentTextContent ?: "",
|
||||
fontSize = 16.sp,
|
||||
color = Color.White,
|
||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||
|
||||
Reference in New Issue
Block a user