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 tabSelectedText: Color,
var tabUnselectedText: Color, var tabUnselectedText: Color,
var bubbleBackground: Color, var bubbleBackground: Color,
var profileBackground:Color,
) )
class LightThemeColors : AppThemeData( class LightThemeColors : AppThemeData(
@@ -64,7 +65,8 @@ class LightThemeColors : AppThemeData(
tabUnselectedBackground = Color(0x147C7480), tabUnselectedBackground = Color(0x147C7480),
tabSelectedText = Color(0xffffffff), tabSelectedText = Color(0xffffffff),
tabUnselectedText = Color(0xff000000), tabUnselectedText = Color(0xff000000),
bubbleBackground = Color(0xfff5f5f5) bubbleBackground = Color(0xfff5f5f5),
profileBackground = Color(0xffffffff)
) )
class DarkThemeColors : AppThemeData( class DarkThemeColors : AppThemeData(
@@ -95,5 +97,7 @@ class DarkThemeColors : AppThemeData(
tabUnselectedBackground = Color(0xff7C7480), tabUnselectedBackground = Color(0xff7C7480),
tabSelectedText = Color(0xff000000), tabSelectedText = Color(0xff000000),
tabUnselectedText = Color(0xffffffff), 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.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import com.aiosman.ravenow.data.Agent
import com.aiosman.ravenow.data.ListContainer import com.aiosman.ravenow.data.ListContainer
import com.aiosman.ravenow.data.AgentService import com.aiosman.ravenow.data.AgentService
import com.aiosman.ravenow.data.DataContainer
import com.aiosman.ravenow.data.MomentService import com.aiosman.ravenow.data.MomentService
import com.aiosman.ravenow.data.ServiceException import com.aiosman.ravenow.data.ServiceException
import com.aiosman.ravenow.data.UploadImage import com.aiosman.ravenow.data.UploadImage
@@ -196,7 +198,7 @@ class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
return if (extra.authorId != null) { return if (extra.authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>> // 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 val listContainer = dataContainer.data
ListContainer( ListContainer(
list = listContainer.list.map { it.toAgentEntity()}, 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.MomentService
import com.aiosman.ravenow.data.UploadImage import com.aiosman.ravenow.data.UploadImage
import com.aiosman.ravenow.entity.AccountProfileEntity 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.MomentEntity
import com.aiosman.ravenow.entity.MomentLoader import com.aiosman.ravenow.entity.MomentLoader
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
@@ -35,11 +38,17 @@ object MyProfileViewModel : ViewModel() {
val momentService: MomentService = MomentServiceImpl() val momentService: MomentService = MomentServiceImpl()
var profile by mutableStateOf<AccountProfileEntity?>(null) var profile by mutableStateOf<AccountProfileEntity?>(null)
var moments by mutableStateOf<List<MomentEntity>>(emptyList()) var moments by mutableStateOf<List<MomentEntity>>(emptyList())
var agents by mutableStateOf<List<AgentEntity>>(emptyList())
val momentLoader: MomentLoader = MomentLoader().apply { val momentLoader: MomentLoader = MomentLoader().apply {
onListChanged = { onListChanged = {
moments = it moments = it
} }
} }
val agentLoader: AgentLoader = AgentLoader().apply {
onListChanged = {
agents = it
}
}
var refreshing by mutableStateOf(false) var refreshing by mutableStateOf(false)
var firstLoad = true var firstLoad = true
@@ -64,6 +73,7 @@ object MyProfileViewModel : ViewModel() {
profile?.let { profile?.let {
try { try {
momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id)) momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id))
agentLoader.loadData(extra = AgentLoaderExtraArgs(authorId = it.id))
} catch (e: Exception) { } catch (e: Exception) {
Log.e("MyProfileViewModel", "loadProfile: ", e) Log.e("MyProfileViewModel", "loadProfile: ", e)
} }
@@ -152,6 +162,7 @@ object MyProfileViewModel : ViewModel() {
fun ResetModel() { fun ResetModel() {
profile = null profile = null
momentLoader.clear() momentLoader.clear()
agentLoader.clear()
firstLoad = true firstLoad = true
} }

View File

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

View File

@@ -25,6 +25,7 @@ fun ProfileWrap(
}, },
profile = MyProfileViewModel.profile, profile = MyProfileViewModel.profile,
moments = MyProfileViewModel.moments, moments = MyProfileViewModel.moments,
agents = MyProfileViewModel.agents,
onLoadMore = { onLoadMore = {
MyProfileViewModel.loadMoreMoment() MyProfileViewModel.loadMoreMoment()
}, },
@@ -33,6 +34,9 @@ fun ProfileWrap(
}, },
onComment = { onComment = {
navController.navigateToPost(it.id) 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.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
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.width
@@ -18,6 +20,8 @@ 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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@@ -37,78 +41,88 @@ fun OtherProfileAction(
onChat: (() -> Unit)? = null onChat: (() -> Unit)? = null
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
// 定义渐变色
val followGradient = Brush.horizontalGradient(
colors = listOf(
Color(0xFFE53E3E), // 红色
Color(0xFF9F7AEA) // 紫色
)
)
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth()
) { ) {
// 关注按钮 - 渐变样式
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(32.dp)) .weight(1f)
.background(if (profile.isFollowing) AppColors.main else AppColors.basicMain) .clip(RoundedCornerShape(8.dp))
.padding(horizontal = 16.dp, vertical = 4.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 { .noRippleClickable {
onFollow?.invoke() 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(
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, fontSize = 14.sp,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
color = if (profile.isFollowing) AppColors.mainText else AppColors.text, color = if (profile.isFollowing) {
modifier = Modifier.padding(8.dp), // 已关注状态 - 灰色文字
AppColors.text.copy(alpha = 0.6f)
} else {
// 未关注状态 - 白色文字
Color.White
},
) )
} }
// 私信按钮 - 灰色背景样式
if (AppState.enableChat) { if (AppState.enableChat) {
Spacer(modifier = Modifier.width(8.dp))
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center, horizontalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(32.dp)) .weight(1f)
.background(AppColors.basicMain) .clip(RoundedCornerShape(8.dp))
.padding(horizontal = 16.dp, vertical = 4.dp) .background(AppColors.nonActive) // 使用主题灰色背景
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChat?.invoke() 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(
text = stringResource(R.string.chat_upper), text = stringResource(R.string.chat_upper),
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
color = AppColors.text, color = AppColors.text, // 使用主题文字颜色
modifier = Modifier.padding(8.dp),
) )
} }
} }
} }
// 按钮
} }

View File

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

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 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.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column 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.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
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.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@@ -26,70 +28,142 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
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.res.painterResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable @Composable
fun UserAgentsRow( fun UserAgentsRow(
userId: Int?, userId: Int?,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
onMoreClick: () -> Unit = {},
onAgentClick: (AgentEntity) -> Unit = {}
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val viewModel: UserAgentsViewModel = viewModel() val viewModel: UserAgentsViewModel = viewModel()
val isSelf = userId == null
// 加载用户的智能体数据 // 加载用户的智能体数据
LaunchedEffect(userId) { LaunchedEffect(userId) {
if (userId != null) { // 无论userId是否为null都加载数据
// null表示加载当前用户自己的智能体
println("UserAgentsRow: LaunchedEffect 触发, userId = $userId, isSelf = $isSelf")
println("UserAgentsRow: 准备调用 viewModel.loadUserAgents")
viewModel.loadUserAgents(userId) viewModel.loadUserAgents(userId)
} else { println("UserAgentsRow: 已调用 viewModel.loadUserAgents")
viewModel.clearAgents()
}
} }
if (viewModel.agents.isNotEmpty()) { // 总是显示智能体区域,即使没有数据也显示标题和状态
Column( Column(
modifier = modifier modifier = modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
Text( Text(
text = "的智能体", text = if (isSelf) "我的智能体" else "TA的智能体",
fontSize = 16.sp, fontSize = 16.sp,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
color = AppColors.text, color = AppColors.text,
modifier = Modifier.padding(bottom = 12.dp) 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( LazyRow(
horizontalArrangement = Arrangement.spacedBy(12.dp), horizontalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
// 显示智能体项目
items(viewModel.agents) { agent -> items(viewModel.agents) { agent ->
AgentItem(agent = agent) AgentItem(
agent = agent,
onClick = { onAgentClick(agent) }
)
}
// 添加"更多"按钮
item {
MoreAgentItem(
onClick = onMoreClick
)
}
}
} }
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
} }
}
} }
@Composable @Composable
private fun AgentItem( private fun AgentItem(
agent: AgentEntity, agent: AgentEntity,
modifier: Modifier = Modifier modifier: Modifier = Modifier,
onClick: () -> Unit = {}
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = modifier modifier = modifier.noRippleClickable { onClick() }
) { ) {
// 头像 // 头像
Box( 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) var error by mutableStateOf<String?>(null)
private set private set
fun loadUserAgents(userId: Int) { fun loadUserAgents(userId: Int?) {
viewModelScope.launch { viewModelScope.launch {
isLoading = true isLoading = true
error = null error = null
try { try {
println("UserAgentsViewModel: 开始加载智能体数据, userId = $userId")
agentLoader.loadData(AgentLoaderExtraArgs(authorId = userId)) agentLoader.loadData(AgentLoaderExtraArgs(authorId = userId))
println("UserAgentsViewModel: 加载完成, agents.size = ${agents.size}")
} catch (e: Exception) { } catch (e: Exception) {
error = e.message ?: "加载失败" error = e.message ?: "加载失败"
println("UserAgentsViewModel: 加载失败, error = $error")
e.printStackTrace()
} finally { } finally {
isLoading = false 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.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement 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.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
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.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
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.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@@ -19,6 +24,7 @@ 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@@ -35,55 +41,78 @@ fun UserContentPageIndicator(
){ ){
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
Row(
modifier = Modifier Column(
.verticalScroll(rememberScrollState()) modifier = Modifier.fillMaxWidth()
.fillMaxWidth()
.padding(horizontal = 16.dp),
) { ) {
Row( Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(32.dp)) .fillMaxWidth()
.background(if (pagerState.currentPage == 0) AppColors.background else Color.Transparent) .padding(horizontal = 16.dp),
.padding(horizontal = 16.dp, vertical = 4.dp) horizontalArrangement = Arrangement.SpaceEvenly
) {
// 图片/相册 Tab
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.weight(1f)
.noRippleClickable { .noRippleClickable {
// switch to gallery
scope.launch { scope.launch {
pagerState.scrollToPage(0) pagerState.scrollToPage(0)
} }
} }
.padding(vertical = 12.dp)
) { ) {
Text( Icon(
text = stringResource(R.string.gallery), painter = painterResource(id = R.drawable.rider_pro_images),
fontSize = 14.sp, contentDescription = "Gallery",
fontWeight = FontWeight.W600, tint = if (pagerState.currentPage == 0) AppColors.text else AppColors.text.copy(alpha = 0.6f),
color = AppColors.text, modifier = Modifier.size(24.dp)
modifier = Modifier.padding(8.dp),
) )
} }
Spacer(modifier = Modifier.width(4.dp))
Row( // Agent Tab
verticalAlignment = Alignment.CenterVertically, Column(
horizontalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.clip(RoundedCornerShape(32.dp)) .weight(1f)
.background(if (pagerState.currentPage == 1) AppColors.background else Color.Transparent)
.padding(horizontal = 16.dp, vertical = 4.dp)
.noRippleClickable { .noRippleClickable {
// switch to moments
scope.launch { scope.launch {
pagerState.scrollToPage(1) pagerState.scrollToPage(1)
} }
} }
.padding(vertical = 12.dp)
) { ) {
Text( Icon(
text = stringResource(R.string.moment), painter = painterResource(id = R.drawable.rider_pro_nav_ai),
fontSize = 14.sp, contentDescription = "Agents",
fontWeight = FontWeight.W600, tint = if (pagerState.currentPage == 1) AppColors.text else AppColors.text.copy(alpha = 0.6f),
color = AppColors.text, modifier = Modifier.size(24.dp)
modifier = Modifier.padding(8.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 import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable @Composable
fun UserItem(accountProfileEntity: AccountProfileEntity) { fun UserItem(
accountProfileEntity: AccountProfileEntity,
postCount: Int = 0
) {
val navController = LocalNavController.current val navController = LocalNavController.current
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -56,8 +59,26 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.weight(1f) 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( Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@@ -78,12 +99,14 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.followers_upper), text = "粉丝",
color = AppColors.text color = AppColors.text
) )
} }
// 关注数
Column( Column(
verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
.noRippleClickable { .noRippleClickable {
@@ -93,8 +116,7 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
accountProfileEntity.id.toString() accountProfileEntity.id.toString()
) )
) )
}, }
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
Text( Text(
text = accountProfileEntity.followingCount.toString(), text = accountProfileEntity.followingCount.toString(),
@@ -104,17 +126,10 @@ fun UserItem(accountProfileEntity: AccountProfileEntity) {
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = stringResource(R.string.following_upper), text = "关注",
color = AppColors.text color = AppColors.text
) )
} }
Column(
verticalArrangement = Arrangement.Center,
modifier = Modifier.weight(1f),
horizontalAlignment = Alignment.CenterHorizontally,
) {
}
} }
} }
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))