Refactor: 个人主页UI及智能体列表功能调整

- **个人主页UI调整:**
    - "编辑个人资料"按钮样式调整为与"私信"按钮一致。
    - "关注"按钮样式调整为渐变色,"已关注"状态下显示灰色边框。
    - "私信"按钮样式调整为灰色背景。
    - "帖子"、"粉丝"、"关注"文案调整。
    - 个人主页背景色适配深色模式。
    - 个人主页内容切换Tab图标化,并添加下划线指示器。
- **智能体列表功能:**
    - 新增`UserAgentsList.kt`用于展示用户的智能体列表。
    - 在个人主页中集成智能体列表展示(新的Tab页)。
    - 调整`UserAgentsRow`组件,使其能够加载并展示当前用户或其他用户的智能体,并添加"更多"按钮。
    - `MyProfileViewModel`中增加对智能体数据的加载和管理。
    - `UserAgentsViewModel`调整为可以加载指定用户或当前用户的智能体数据。
- **其他:**
    - `Colors.kt`中新增`profileBackground`颜色定义。
    - `Agent.kt`中调整了`getAgent`方法返回类型。
This commit is contained in:
2025-08-31 20:23:28 +08:00
parent a79628026c
commit 21200910c1
12 changed files with 578 additions and 190 deletions

View File

@@ -34,6 +34,7 @@ open class AppThemeData(
var tabSelectedText: Color,
var tabUnselectedText: Color,
var bubbleBackground: Color,
var profileBackground:Color,
)
class LightThemeColors : AppThemeData(
@@ -64,7 +65,8 @@ class LightThemeColors : AppThemeData(
tabUnselectedBackground = Color(0x147C7480),
tabSelectedText = Color(0xffffffff),
tabUnselectedText = Color(0xff000000),
bubbleBackground = Color(0xfff5f5f5)
bubbleBackground = Color(0xfff5f5f5),
profileBackground = Color(0xffffffff)
)
class DarkThemeColors : AppThemeData(
@@ -95,5 +97,7 @@ class DarkThemeColors : AppThemeData(
tabUnselectedBackground = Color(0xff7C7480),
tabSelectedText = Color(0xff000000),
tabUnselectedText = Color(0xffffffff),
bubbleBackground = Color(0xfff2d2c2e)
bubbleBackground = Color(0xfff2d2c2e),
profileBackground = Color(0xff100c12)
)

View File

@@ -2,8 +2,10 @@ package com.aiosman.ravenow.entity
import androidx.paging.PagingSource
import androidx.paging.PagingState
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.AgentService
import com.aiosman.ravenow.data.DataContainer
import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.UploadImage
@@ -196,7 +198,7 @@ class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
return if (extra.authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>>
val dataContainer = body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>>
val dataContainer = body as DataContainer<ListContainer<Agent>>
val listContainer = dataContainer.data
ListContainer(
list = listContainer.list.map { it.toAgentEntity()},

View File

@@ -16,6 +16,9 @@ import com.aiosman.ravenow.data.AccountServiceImpl
import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.UploadImage
import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.entity.AgentLoader
import com.aiosman.ravenow.entity.AgentLoaderExtraArgs
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
@@ -35,11 +38,17 @@ object MyProfileViewModel : ViewModel() {
val momentService: MomentService = MomentServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null)
var moments by mutableStateOf<List<MomentEntity>>(emptyList())
var agents by mutableStateOf<List<AgentEntity>>(emptyList())
val momentLoader: MomentLoader = MomentLoader().apply {
onListChanged = {
moments = it
}
}
val agentLoader: AgentLoader = AgentLoader().apply {
onListChanged = {
agents = it
}
}
var refreshing by mutableStateOf(false)
var firstLoad = true
@@ -64,6 +73,7 @@ object MyProfileViewModel : ViewModel() {
profile?.let {
try {
momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id))
agentLoader.loadData(extra = AgentLoaderExtraArgs(authorId = it.id))
} catch (e: Exception) {
Log.e("MyProfileViewModel", "loadProfile: ", e)
}
@@ -152,6 +162,7 @@ object MyProfileViewModel : ViewModel() {
fun ResetModel() {
profile = null
momentLoader.clear()
agentLoader.clear()
firstLoad = true
}

View File

@@ -72,6 +72,7 @@ import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.MainActivity
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.entity.MomentEntity
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
@@ -88,6 +89,7 @@ import com.aiosman.ravenow.ui.index.tabs.profile.composable.MomentPostUnit
import com.aiosman.ravenow.ui.index.tabs.profile.composable.OtherProfileAction
import com.aiosman.ravenow.ui.index.tabs.profile.composable.SelfProfileAction
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsRow
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserAgentsList
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserContentPageIndicator
import com.aiosman.ravenow.ui.index.tabs.profile.composable.UserItem
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@@ -107,11 +109,13 @@ fun ProfileV3(
onFollowClick: () -> Unit = {},
onChatClick: () -> Unit = {},
moments: List<MomentEntity>,
agents: List<AgentEntity> = emptyList(),
isSelf: Boolean = true,
isMain:Boolean = false,
onLoadMore: () -> Unit = {},
onLike: (MomentEntity) -> Unit = {},
onComment: (MomentEntity) -> Unit = {},
onAgentClick: (AgentEntity) -> Unit = {},
) {
val model = MyProfileViewModel
val state = rememberCollapsingToolbarScaffoldState()
@@ -195,7 +199,7 @@ fun ProfileV3(
CollapsingToolbarScaffold(
modifier = Modifier
.fillMaxSize()
.background(AppColors.decentBackground),
.background(AppColors.profileBackground),
state = state,
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
toolbarScrollable = true,
@@ -206,7 +210,7 @@ fun ProfileV3(
.fillMaxWidth()
.height(miniToolbarHeight.dp)
// 保持在最低高度和当前高度之间
.background(AppColors.decentBackground)
.background(AppColors.profileBackground)
) {
}
// header
@@ -214,7 +218,8 @@ fun ProfileV3(
modifier = Modifier
.parallax(0.5f)
.fillMaxWidth()
.height(600.dp)
.height(700.dp)
.background(AppColors.profileBackground)
.verticalScroll(toolbarScrollState)
) {
Box(
@@ -232,7 +237,7 @@ fun ProfileV3(
modifier = Modifier
.fillMaxWidth()
.height(bannerHeight.dp)
.background(AppColors.decentBackground)
.background(AppColors.profileBackground)
) {
Box(
modifier = Modifier
@@ -319,12 +324,13 @@ fun ProfileV3(
Box(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.decentBackground)
.background(AppColors.profileBackground)
) {
// user info
Column(
modifier = Modifier
.fillMaxWidth()
) {
// Spacer(modifier = Modifier.height(16.dp))
// 个人信息
@@ -332,7 +338,10 @@ fun ProfileV3(
modifier = Modifier.padding(horizontal = 16.dp)
) {
profile?.let {
UserItem(it)
UserItem(
accountProfileEntity = it,
postCount = moments.size
)
}
}
Spacer(modifier = Modifier.height(20.dp))
@@ -366,8 +375,16 @@ fun ProfileV3(
// 添加用户智能体行
UserAgentsRow(
userId = profile?.id,
modifier = Modifier.padding(top = 16.dp)
userId = if (isSelf) null else profile?.id,
modifier = Modifier.padding(top = 16.dp),
onMoreClick = {
// 导航到智能体列表页面
// TODO: 实现导航逻辑
},
onAgentClick = { agent ->
// 导航到智能体详情页面
// TODO: 实现导航逻辑
}
)
}
@@ -384,7 +401,7 @@ fun ProfileV3(
.graphicsLayer {
alpha = 1 - state.toolbarState.progress
}
.background(AppColors.decentBackground)
.background(AppColors.profileBackground)
.onGloballyPositioned {
miniToolbarHeight = with(density) {
it.size.height.toDp().value.toInt()
@@ -462,7 +479,7 @@ fun ProfileV3(
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.decentBackground)
.background(AppColors.profileBackground)
) {
UserContentPageIndicator(
pagerState = pagerState,
@@ -521,38 +538,14 @@ fun ProfileV3(
}
1 ->
LazyColumn(
modifier = Modifier
.fillMaxSize(),
state = listState
) {
if (moments.isEmpty() && isSelf) {
item {
EmptyMomentPostUnit()
}
}
item {
for (element in moments) {
element.let {
MomentPostUnit(
it,
onLikeClick = {
onLike(it)
},
onCommentClick = {
onComment(it)
}
UserAgentsList(
agents = agents,
onAgentClick = onAgentClick,
modifier = Modifier.fillMaxSize()
)
}
}
}
item {
Spacer(modifier = Modifier.height(120.dp))
}
}
}
}
}
}
PullRefreshIndicator(

View File

@@ -25,6 +25,7 @@ fun ProfileWrap(
},
profile = MyProfileViewModel.profile,
moments = MyProfileViewModel.moments,
agents = MyProfileViewModel.agents,
onLoadMore = {
MyProfileViewModel.loadMoreMoment()
},
@@ -33,6 +34,9 @@ fun ProfileWrap(
},
onComment = {
navController.navigateToPost(it.id)
},
onAgentClick = { agent ->
// TODO: 处理Agent点击事件导航到聊天页面
}
)
}

View File

@@ -2,9 +2,11 @@ package com.aiosman.ravenow.ui.index.tabs.profile.composable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
@@ -18,6 +20,8 @@ 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.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -37,78 +41,88 @@ fun OtherProfileAction(
onChat: (() -> Unit)? = null
) {
val AppColors = LocalAppTheme.current
// 定义渐变色
val followGradient = Brush.horizontalGradient(
colors = listOf(
Color(0xFFE53E3E), // 红色
Color(0xFF9F7AEA) // 紫色
)
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()
) {
// 关注按钮 - 渐变样式
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(if (profile.isFollowing) AppColors.main else AppColors.basicMain)
.padding(horizontal = 16.dp, vertical = 4.dp)
.weight(1f)
.clip(RoundedCornerShape(8.dp))
.let { modifier ->
if (profile.isFollowing) {
// 已关注状态 - 透明背景
modifier.background(Color.Transparent)
} else {
// 未关注状态 - 渐变背景
modifier.background(brush = followGradient)
}
}
.let { modifier ->
if (profile.isFollowing) {
modifier.border(
width = 1.dp,
color = AppColors.text.copy(alpha = 0.3f),
shape = RoundedCornerShape(8.dp)
)
} else {
modifier
}
}
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
onFollow?.invoke()
}
) {
if (profile.isFollowing) {
Icon(
Icons.Default.Clear,
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = AppColors.mainText
)
} else {
Icon(
Icons.Default.Add,
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = AppColors.mainText
)
}
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(if (profile.isFollowing) R.string.unfollow_upper else R.string.follow_upper),
text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = if (profile.isFollowing) AppColors.mainText else AppColors.text,
modifier = Modifier.padding(8.dp),
color = if (profile.isFollowing) {
// 已关注状态 - 灰色文字
AppColors.text.copy(alpha = 0.6f)
} else {
// 未关注状态 - 白色文字
Color.White
},
)
}
// 私信按钮 - 灰色背景样式
if (AppState.enableChat) {
Spacer(modifier = Modifier.width(8.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(AppColors.basicMain)
.padding(horizontal = 16.dp, vertical = 4.dp)
.weight(1f)
.clip(RoundedCornerShape(8.dp))
.background(AppColors.nonActive) // 使用主题灰色背景
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
onChat?.invoke()
}
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_comment),
contentDescription = "",
modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(R.string.chat_upper),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
modifier = Modifier.padding(8.dp),
color = AppColors.text, // 使用主题文字颜色
)
}
}
}
// 按钮
}

View File

@@ -3,14 +3,9 @@ package com.aiosman.ravenow.ui.index.tabs.profile.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Edit
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@@ -29,34 +24,25 @@ fun SelfProfileAction(
onEditProfile: () -> Unit
) {
val AppColors = LocalAppTheme.current
// 按钮
// 编辑个人资料按钮 - 参考私信按钮样式
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(
AppColors.basicMain
)
.padding(horizontal = 16.dp, vertical = 4.dp)
.fillMaxWidth()
.clip(RoundedCornerShape(8.dp))
.background(AppColors.nonActive) // 使用主题灰色背景
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
onEditProfile()
}
) {
Icon(
Icons.Default.Edit,
contentDescription = "",
modifier = Modifier.size(24.dp),
tint = AppColors.text
)
Spacer(modifier = Modifier.width(4.dp))
Text(
text = stringResource(R.string.edit_profile),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
modifier = Modifier.padding(8.dp)
color = AppColors.text, // 使用主题文字颜色
)
}
}

View File

@@ -0,0 +1,209 @@
package com.aiosman.ravenow.ui.index.tabs.profile.composable
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
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.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.utils.DebounceUtils
@Composable
fun UserAgentsList(
agents: List<AgentEntity>,
onAgentClick: (AgentEntity) -> Unit = {},
modifier: Modifier = Modifier
) {
val AppColors = LocalAppTheme.current
LazyColumn(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
if (agents.isEmpty()) {
item {
EmptyAgentsView()
}
} else {
items(agents) { agent ->
UserAgentCard(
agent = agent,
onAgentClick = onAgentClick
)
}
}
// 底部间距
item {
Spacer(modifier = Modifier.height(120.dp))
}
}
}
@Composable
fun UserAgentCard(
agent: AgentEntity,
onAgentClick: (AgentEntity) -> Unit
) {
val AppColors = LocalAppTheme.current
// 防抖状态
var lastClickTime by remember { mutableStateOf(0L) }
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 左侧头像
Box(
modifier = Modifier
.size(48.dp)
.background(AppColors.nonActive, RoundedCornerShape(24.dp)),
contentAlignment = Alignment.Center
) {
if (agent.avatar.isNotEmpty()) {
CustomAsyncImage(
imageUrl = agent.avatar,
contentDescription = "Agent头像",
modifier = Modifier
.size(48.dp)
.clip(RoundedCornerShape(24.dp)),
contentScale = ContentScale.Crop
)
} else {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "默认头像",
modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
}
}
Spacer(modifier = Modifier.width(12.dp))
// 中间文字内容
Column(
modifier = Modifier
.weight(1f)
.padding(end = 8.dp)
) {
// 标题
Text(
text = agent.title,
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Spacer(modifier = Modifier.height(8.dp))
// 描述
Text(
text = agent.desc,
fontSize = 12.sp,
color = AppColors.secondaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
// 右侧聊天按钮
Box(
modifier = Modifier
.size(width = 60.dp, height = 32.dp)
.background(
color = AppColors.nonActive,
shape = RoundedCornerShape(8.dp)
)
.clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
onAgentClick(agent)
}) {
lastClickTime = System.currentTimeMillis()
}
},
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.chat),
fontSize = 12.sp,
color = AppColors.text,
fontWeight = FontWeight.W500
)
}
}
}
@Composable
fun EmptyAgentsView() {
val AppColors = LocalAppTheme.current
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(R.mipmap.rider_pro_agent),
contentDescription = "暂无Agent",
modifier = Modifier.size(48.dp),
colorFilter = ColorFilter.tint(AppColors.secondaryText.copy(alpha = 0.5f))
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = "暂无创建的智能体",
fontSize = 14.sp,
color = AppColors.secondaryText,
fontWeight = FontWeight.W500
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "去创建你的第一个智能体吧",
fontSize = 12.sp,
color = AppColors.secondaryText.copy(alpha = 0.7f)
)
}
}

View File

@@ -1,5 +1,6 @@
package com.aiosman.ravenow.ui.index.tabs.profile.composable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -15,6 +16,7 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
@@ -26,70 +28,142 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable
fun UserAgentsRow(
userId: Int?,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onMoreClick: () -> Unit = {},
onAgentClick: (AgentEntity) -> Unit = {}
) {
val AppColors = LocalAppTheme.current
val viewModel: UserAgentsViewModel = viewModel()
val isSelf = userId == null
// 加载用户的智能体数据
LaunchedEffect(userId) {
if (userId != null) {
// 无论userId是否为null都加载数据
// null表示加载当前用户自己的智能体
println("UserAgentsRow: LaunchedEffect 触发, userId = $userId, isSelf = $isSelf")
println("UserAgentsRow: 准备调用 viewModel.loadUserAgents")
viewModel.loadUserAgents(userId)
} else {
viewModel.clearAgents()
}
println("UserAgentsRow: 已调用 viewModel.loadUserAgents")
}
if (viewModel.agents.isNotEmpty()) {
// 总是显示智能体区域,即使没有数据也显示标题和状态
Column(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Text(
text = "的智能体",
text = if (isSelf) "我的智能体" else "TA的智能体",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
modifier = Modifier.padding(bottom = 12.dp)
)
when {
viewModel.isLoading -> {
// 显示加载状态
println("UserAgentsRow: 显示加载状态")
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载中...",
fontSize = 14.sp,
color = AppColors.text.copy(alpha = 0.6f)
)
}
}
viewModel.error != null -> {
// 显示错误状态
println("UserAgentsRow: 显示错误状态, error = ${viewModel.error}")
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载失败: ${viewModel.error}",
fontSize = 14.sp,
color = AppColors.text.copy(alpha = 0.6f)
)
}
}
viewModel.agents.isEmpty() -> {
// 显示空状态
println("UserAgentsRow: 显示空状态, agents.size = ${viewModel.agents.size}")
Box(
modifier = Modifier
.fillMaxWidth()
.height(60.dp),
contentAlignment = Alignment.Center
) {
Text(
text = if (isSelf) "您还没有创建智能体" else "TA还没有创建智能体",
fontSize = 14.sp,
color = AppColors.text.copy(alpha = 0.6f)
)
}
}
else -> {
// 显示智能体列表
println("UserAgentsRow: 显示智能体列表, agents.size = ${viewModel.agents.size}")
LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()
) {
// 显示智能体项目
items(viewModel.agents) { agent ->
AgentItem(agent = agent)
AgentItem(
agent = agent,
onClick = { onAgentClick(agent) }
)
}
// 添加"更多"按钮
item {
MoreAgentItem(
onClick = onMoreClick
)
}
}
}
}
Spacer(modifier = Modifier.height(16.dp))
}
}
}
@Composable
private fun AgentItem(
agent: AgentEntity,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
val AppColors = LocalAppTheme.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier
modifier = modifier.noRippleClickable { onClick() }
) {
// 头像
Box(
@@ -122,3 +196,46 @@ private fun AgentItem(
)
}
}
@Composable
private fun MoreAgentItem(
modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) {
val AppColors = LocalAppTheme.current
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier.noRippleClickable { onClick() }
) {
// 圆形右箭头按钮
Box(
modifier = Modifier
.size(48.dp)
.clip(CircleShape)
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Icon(
painter = painterResource(id = R.drawable.rave_now_nav_right),
contentDescription = "更多",
tint = AppColors.text,
modifier = Modifier.size(20.dp)
)
}
Spacer(modifier = Modifier.height(8.dp))
// "更多"文字
Text(
text = "更多",
fontSize = 12.sp,
fontWeight = FontWeight.W500,
color = AppColors.text,
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.width(48.dp)
)
}
}

View File

@@ -26,15 +26,19 @@ class UserAgentsViewModel : ViewModel() {
var error by mutableStateOf<String?>(null)
private set
fun loadUserAgents(userId: Int) {
fun loadUserAgents(userId: Int?) {
viewModelScope.launch {
isLoading = true
error = null
try {
println("UserAgentsViewModel: 开始加载智能体数据, userId = $userId")
agentLoader.loadData(AgentLoaderExtraArgs(authorId = userId))
println("UserAgentsViewModel: 加载完成, agents.size = ${agents.size}")
} catch (e: Exception) {
error = e.message ?: "加载失败"
println("UserAgentsViewModel: 加载失败, error = $error")
e.printStackTrace()
} finally {
isLoading = false
}

View File

@@ -3,15 +3,20 @@ package com.aiosman.ravenow.ui.index.tabs.profile.composable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
@@ -19,6 +24,7 @@ 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.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@@ -35,55 +41,78 @@ fun UserContentPageIndicator(
){
val scope = rememberCoroutineScope()
val AppColors = LocalAppTheme.current
Row(
modifier = Modifier
.verticalScroll(rememberScrollState())
.fillMaxWidth()
.padding(horizontal = 16.dp),
Column(
modifier = Modifier.fillMaxWidth()
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(if (pagerState.currentPage == 0) AppColors.background else Color.Transparent)
.padding(horizontal = 16.dp, vertical = 4.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly
) {
// 图片/相册 Tab
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.weight(1f)
.noRippleClickable {
// switch to gallery
scope.launch {
pagerState.scrollToPage(0)
}
}
.padding(vertical = 12.dp)
) {
Text(
text = stringResource(R.string.gallery),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
modifier = Modifier.padding(8.dp),
Icon(
painter = painterResource(id = R.drawable.rider_pro_images),
contentDescription = "Gallery",
tint = if (pagerState.currentPage == 0) AppColors.text else AppColors.text.copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
}
Spacer(modifier = Modifier.width(4.dp))
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
// Agent Tab
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.clip(RoundedCornerShape(32.dp))
.background(if (pagerState.currentPage == 1) AppColors.background else Color.Transparent)
.padding(horizontal = 16.dp, vertical = 4.dp)
.weight(1f)
.noRippleClickable {
// switch to moments
scope.launch {
pagerState.scrollToPage(1)
}
}
.padding(vertical = 12.dp)
) {
Text(
text = stringResource(R.string.moment),
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = AppColors.text,
modifier = Modifier.padding(8.dp)
Icon(
painter = painterResource(id = R.drawable.rider_pro_nav_ai),
contentDescription = "Agents",
tint = if (pagerState.currentPage == 1) AppColors.text else AppColors.text.copy(alpha = 0.6f),
modifier = Modifier.size(24.dp)
)
}
}
// 下划线指示器
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
) {
Box(
modifier = Modifier
.weight(1f)
.height(2.dp)
.background(
if (pagerState.currentPage == 0) AppColors.text else Color.Transparent
)
)
Box(
modifier = Modifier
.weight(1f)
.height(2.dp)
.background(
if (pagerState.currentPage == 1) AppColors.text else Color.Transparent
)
)
}
}

View File

@@ -29,7 +29,10 @@ import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable
fun UserItem(accountProfileEntity: AccountProfileEntity) {
fun UserItem(
accountProfileEntity: AccountProfileEntity,
postCount: Int = 0
) {
val navController = LocalNavController.current
val AppColors = LocalAppTheme.current
@@ -56,8 +59,26 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f)
) {
// 帖子数
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.weight(1f)
) {
Text(
text = postCount.toString(),
fontWeight = FontWeight.W600,
fontSize = 16.sp,
color = AppColors.text
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = "帖子",
color = AppColors.text
)
}
// 粉丝数
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.weight(1f)
@@ -78,12 +99,14 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.followers_upper),
text = "粉丝",
color = AppColors.text
)
}
// 关注数
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.weight(1f)
.noRippleClickable {
@@ -93,8 +116,7 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
accountProfileEntity.id.toString()
)
)
},
horizontalAlignment = Alignment.CenterHorizontally,
}
) {
Text(
text = accountProfileEntity.followingCount.toString(),
@@ -104,17 +126,10 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.following_upper),
text = "关注",
color = AppColors.text
)
}
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
}
}
}
Spacer(modifier = Modifier.height(12.dp))