feat: 新增AI智能体主页
- 新增全新设计的AI智能体主页界面(`AiProfileV3`),包括个人信息卡片、操作按钮和动态列表。 - 添加相应的 `AiProfileViewModel` 来处理数据加载、关注/取关以及动态列表分页逻辑。 - 创建 `AiProfileWrap` 作为页面入口,并根据 `isAiAccount` 参数在导航中分发至新的AI主页。 - 在 `AccountProfileEntity` 和 `Account` 数据模型中增加了AI角色背景图字段(`aiRoleAvatar`, `aiRoleAvatarMedium`, `aiRoleAvatarLarge`)。
This commit is contained in:
@@ -64,6 +64,11 @@ data class AccountProfile(
|
||||
val aiAccount: Boolean,
|
||||
|
||||
val chatAIId: String,
|
||||
|
||||
// AI角色背景图
|
||||
val aiRoleAvatar: String? = null,
|
||||
val aiRoleAvatarMedium: String? = null,
|
||||
val aiRoleAvatarLarge: String? = null,
|
||||
) {
|
||||
/**
|
||||
* 转换为Entity
|
||||
@@ -89,7 +94,16 @@ data class AccountProfile(
|
||||
chatToken = openImToken,
|
||||
aiAccount = aiAccount,
|
||||
rawAvatar = avatar,
|
||||
chatAIId = chatAIId
|
||||
chatAIId = chatAIId,
|
||||
aiRoleAvatar = aiRoleAvatar?.let {
|
||||
if (it.isNotEmpty()) "${ApiClient.BASE_SERVER}$it" else null
|
||||
},
|
||||
aiRoleAvatarMedium = aiRoleAvatarMedium?.let {
|
||||
if (it.isNotEmpty()) "${ApiClient.BASE_SERVER}$it" else null
|
||||
},
|
||||
aiRoleAvatarLarge = aiRoleAvatarLarge?.let {
|
||||
if (it.isNotEmpty()) "${ApiClient.BASE_SERVER}$it" else null
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,6 +67,11 @@ data class AccountProfileEntity(
|
||||
val rawAvatar: String,
|
||||
|
||||
val chatAIId: String,
|
||||
|
||||
// AI角色背景图
|
||||
val aiRoleAvatar: String? = null,
|
||||
val aiRoleAvatarMedium: String? = null,
|
||||
val aiRoleAvatarLarge: String? = null,
|
||||
)
|
||||
|
||||
/**
|
||||
|
||||
@@ -76,6 +76,7 @@ import com.aiosman.ravenow.ui.post.NewPostImageGridScreen
|
||||
import com.aiosman.ravenow.ui.post.NewPostScreen
|
||||
import com.aiosman.ravenow.ui.post.PostScreen
|
||||
import com.aiosman.ravenow.ui.profile.AccountProfileV2
|
||||
import com.aiosman.ravenow.ui.profile.AiProfileWrap
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.vip.VipSelPage
|
||||
import com.aiosman.ravenow.ui.notification.NotificationScreen
|
||||
import com.aiosman.ravenow.ui.scan.ScanQrScreen
|
||||
@@ -345,7 +346,13 @@ fun NavigationController(
|
||||
) {
|
||||
val id = it.arguments?.getString("id")!!
|
||||
val isAiAccount = it.arguments?.getBoolean("isAiAccount") ?: false
|
||||
AccountProfileV2(id, isAiAccount)
|
||||
|
||||
// 根据isAiAccount参数分发到不同的Profile页面
|
||||
if (isAiAccount) {
|
||||
AiProfileWrap(id)
|
||||
} else {
|
||||
AccountProfileV2(id, isAiAccount)
|
||||
}
|
||||
}
|
||||
}
|
||||
composable(
|
||||
|
||||
456
app/src/main/java/com/aiosman/ravenow/ui/profile/AiProfileV3.kt
Normal file
456
app/src/main/java/com/aiosman/ravenow/ui/profile/AiProfileV3.kt
Normal file
@@ -0,0 +1,456 @@
|
||||
package com.aiosman.ravenow.ui.profile
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.systemBars
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.*
|
||||
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.layout.ContentScale
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.AppState
|
||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||
import com.aiosman.ravenow.LocalAppTheme
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.R
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
import com.aiosman.ravenow.ui.navigateToPost
|
||||
import java.text.NumberFormat
|
||||
import java.util.Locale
|
||||
|
||||
@Composable
|
||||
fun AiProfileV3(
|
||||
profile: AccountProfileEntity?,
|
||||
moments: List<MomentEntity>,
|
||||
postCount: Long? = null,
|
||||
isSelf: Boolean = false,
|
||||
onFollowClick: () -> Unit = {},
|
||||
onChatClick: () -> Unit = {},
|
||||
onShareClick: () -> Unit = {},
|
||||
onLoadMore: () -> Unit = {},
|
||||
onComment: (MomentEntity) -> Unit = {},
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(appColors.background)
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
// 顶部状态栏间距
|
||||
item {
|
||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
|
||||
Spacer(modifier = Modifier.height(statusBarPadding.calculateTopPadding()))
|
||||
}
|
||||
|
||||
// AI大卡片
|
||||
item {
|
||||
AiProfileCard(
|
||||
profile = profile,
|
||||
postCount = postCount,
|
||||
numberFormat = numberFormat
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
|
||||
// 三按钮交互区
|
||||
if (!isSelf) {
|
||||
item {
|
||||
AiProfileActions(
|
||||
profile = profile,
|
||||
onFollowClick = onFollowClick,
|
||||
onChatClick = onChatClick,
|
||||
onShareClick = onShareClick
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
}
|
||||
}
|
||||
|
||||
// 动态Grid - 使用items来渲染每个动态
|
||||
item {
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(3),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(((moments.size / 3 + 1) * 130).dp) // 动态计算高度
|
||||
.padding(horizontal = 16.dp),
|
||||
userScrollEnabled = false // 禁用内部滚动
|
||||
) {
|
||||
itemsIndexed(moments) { idx, moment ->
|
||||
if (moment.images.isNotEmpty()) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(1f)
|
||||
.padding(2.dp)
|
||||
.noRippleClickable {
|
||||
onComment(moment)
|
||||
}
|
||||
) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = moment.images[0].thumbnail,
|
||||
contentDescription = "",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
context = LocalContext.current
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 底部间距
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(100.dp))
|
||||
}
|
||||
}
|
||||
|
||||
// 顶部返回按钮
|
||||
TopBar()
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TopBar() {
|
||||
val navController = LocalNavController.current
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(Color.Transparent)
|
||||
) {
|
||||
StatusBarSpacer()
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.statusBarsPadding(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
// 返回按钮
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(40.dp)
|
||||
.clip(CircleShape)
|
||||
.background(Color.Black.copy(alpha = 0.3f))
|
||||
.noRippleClickable {
|
||||
navController.popBackStack()
|
||||
},
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Text(
|
||||
text = "←",
|
||||
fontSize = 24.sp,
|
||||
color = Color.White,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AiProfileCard(
|
||||
profile: AccountProfileEntity?,
|
||||
postCount: Long?,
|
||||
numberFormat: NumberFormat
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp) // 添加四边边距
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(400.dp)
|
||||
.clip(RoundedCornerShape(16.dp)) // 添加圆角
|
||||
) {
|
||||
// 背景图 - 使用aiRoleAvatar
|
||||
val backgroundUrl = profile?.aiRoleAvatarLarge
|
||||
?: profile?.aiRoleAvatarMedium
|
||||
?: profile?.aiRoleAvatar
|
||||
?: profile?.avatar
|
||||
|
||||
CustomAsyncImage(
|
||||
context = LocalContext.current,
|
||||
imageUrl = backgroundUrl ?: "",
|
||||
contentDescription = "AI Background",
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentScale = ContentScale.Crop
|
||||
)
|
||||
|
||||
// 底部渐变遮罩
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
colors = listOf(
|
||||
Color.Transparent,
|
||||
Color.Black.copy(alpha = 0.7f)
|
||||
),
|
||||
startY = 200f,
|
||||
endY = 1200f
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// 左下角内容
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomStart)
|
||||
.padding(20.dp),
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
// AI昵称 - 左对齐
|
||||
Text(
|
||||
text = profile?.nickName ?: "",
|
||||
fontSize = 22.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(6.dp))
|
||||
|
||||
// AI简介 - 左对齐
|
||||
if (!profile?.bio.isNullOrEmpty()) {
|
||||
Text(
|
||||
text = profile?.bio ?: "",
|
||||
fontSize = 13.sp,
|
||||
color = Color.White.copy(alpha = 0.85f),
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
lineHeight = 18.sp
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
}
|
||||
|
||||
// 统计数据行 - 左对齐
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(24.dp)
|
||||
) {
|
||||
StatItem(
|
||||
count = postCount ?: 0L,
|
||||
label = stringResource(R.string.posts),
|
||||
numberFormat = numberFormat
|
||||
)
|
||||
StatItem(
|
||||
count = profile?.followerCount?.toLong() ?: 0L,
|
||||
label = stringResource(R.string.followers_upper),
|
||||
numberFormat = numberFormat
|
||||
)
|
||||
StatItem(
|
||||
count = profile?.followingCount?.toLong() ?: 0L,
|
||||
label = stringResource(R.string.following_upper),
|
||||
numberFormat = numberFormat
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StatItem(
|
||||
count: Long,
|
||||
label: String,
|
||||
numberFormat: NumberFormat
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.Start
|
||||
) {
|
||||
Text(
|
||||
text = formatNumber(count, numberFormat),
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
color = Color.White
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = label,
|
||||
fontSize = 12.sp,
|
||||
color = Color.White.copy(alpha = 0.8f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatNumber(count: Long, numberFormat: NumberFormat): String {
|
||||
return when {
|
||||
count >= 1_000_000 -> String.format("%.1fM", count / 1_000_000.0)
|
||||
count >= 1_000 -> String.format("%.1fK", count / 1_000.0)
|
||||
else -> numberFormat.format(count)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AiProfileActions(
|
||||
profile: AccountProfileEntity?,
|
||||
onFollowClick: () -> Unit,
|
||||
onChatClick: () -> Unit,
|
||||
onShareClick: () -> Unit
|
||||
) {
|
||||
val appColors = LocalAppTheme.current
|
||||
val navController = LocalNavController.current
|
||||
|
||||
// 定义渐变色
|
||||
val followGradient = Brush.horizontalGradient(
|
||||
colors = listOf(
|
||||
Color(0xFF7c45ed),
|
||||
Color(0x777c68ef)
|
||||
)
|
||||
)
|
||||
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp)
|
||||
) {
|
||||
// Follow按钮
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.let { modifier ->
|
||||
if (profile?.isFollowing == true) {
|
||||
// 已关注状态 - 透明背景
|
||||
modifier.background(Color.Transparent)
|
||||
} else {
|
||||
// 未关注状态 - 渐变背景
|
||||
modifier.background(brush = followGradient)
|
||||
}
|
||||
}
|
||||
.let { modifier ->
|
||||
if (profile?.isFollowing == true) {
|
||||
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 {
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
onFollowClick()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = if (profile?.isFollowing == true)
|
||||
stringResource(R.string.follow_upper_had)
|
||||
else
|
||||
stringResource(R.string.follow_upper),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W900,
|
||||
color = if (profile?.isFollowing == true) {
|
||||
appColors.text.copy(alpha = 0.6f)
|
||||
} else {
|
||||
Color.White
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// Chat按钮
|
||||
if (AppState.enableChat) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(appColors.nonActive)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
onChatClick()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.chat_upper),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W900,
|
||||
color = appColors.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Share按钮
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(appColors.nonActive)
|
||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||
.noRippleClickable {
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
onShareClick()
|
||||
}
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(R.string.share),
|
||||
fontSize = 14.sp,
|
||||
fontWeight = FontWeight.W900,
|
||||
color = appColors.text,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.aiosman.ravenow.ui.profile
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.aiosman.ravenow.data.AccountService
|
||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||
import com.aiosman.ravenow.entity.MomentEntity
|
||||
import com.aiosman.ravenow.entity.MomentLoader
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.event.FollowChangeEvent
|
||||
import kotlinx.coroutines.launch
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
|
||||
class AiProfileViewModel : ViewModel() {
|
||||
var profileId by mutableStateOf(0)
|
||||
val accountService: AccountService = AccountServiceImpl()
|
||||
val userService = UserServiceImpl()
|
||||
var profile by mutableStateOf<AccountProfileEntity?>(null)
|
||||
var refreshing by mutableStateOf(false)
|
||||
|
||||
var momentLoader = MomentLoader().apply {
|
||||
pageSize = 20
|
||||
onListChanged = {
|
||||
moments = it
|
||||
}
|
||||
}
|
||||
var moments by mutableStateOf<List<MomentEntity>>(listOf())
|
||||
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
fun loadProfile(id: String, pullRefresh: Boolean = false) {
|
||||
viewModelScope.launch {
|
||||
if (pullRefresh) {
|
||||
refreshing = true
|
||||
}
|
||||
|
||||
try {
|
||||
profile = userService.getUserProfile(id)
|
||||
profileId = id.toInt()
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileViewModel", "Error loading profile", e)
|
||||
e.printStackTrace()
|
||||
}
|
||||
|
||||
refreshing = false
|
||||
|
||||
profile?.let {
|
||||
try {
|
||||
momentLoader.loadData(MomentLoaderExtraArgs(authorId = it.id))
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileViewModel", "Error loading moments", e)
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun loadMoreMoment() {
|
||||
viewModelScope.launch {
|
||||
profile?.let { profileData ->
|
||||
try {
|
||||
Log.d("AiProfileViewModel", "loadMoreMoment: 开始加载更多, 当前moments数量: ${moments.size}, hasNext: ${momentLoader.hasNext}")
|
||||
momentLoader.loadMore(extra = MomentLoaderExtraArgs(authorId = profileData.id))
|
||||
Log.d("AiProfileViewModel", "loadMoreMoment: 加载完成, 新的moments数量: ${moments.size}")
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileViewModel", "loadMoreMoment: ", e)
|
||||
}
|
||||
} ?: Log.w("AiProfileViewModel", "loadMoreMoment: profile为null,无法加载更多")
|
||||
}
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
fun onFollowChangeEvent(event: FollowChangeEvent) {
|
||||
if (event.userId == profile?.id) {
|
||||
profile = profile?.copy(
|
||||
followerCount = profile!!.followerCount + if (event.isFollow) 1 else -1,
|
||||
isFollowing = event.isFollow
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun followUser(userId: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
userService.followUser(userId)
|
||||
EventBus.getDefault().post(FollowChangeEvent(userId.toInt(), true))
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileViewModel", "Error following user", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun unFollowUser(userId: String) {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
userService.unFollowUser(userId)
|
||||
EventBus.getDefault().post(FollowChangeEvent(userId.toInt(), false))
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileViewModel", "Error unfollowing user", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val bio get() = profile?.bio ?: ""
|
||||
val nickName get() = profile?.nickName ?: ""
|
||||
val avatar get() = profile?.avatar
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
EventBus.getDefault().unregister(this)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package com.aiosman.ravenow.ui.profile
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||
import com.aiosman.ravenow.LocalNavController
|
||||
import com.aiosman.ravenow.data.UserServiceImpl
|
||||
import com.aiosman.ravenow.exp.viewModelFactory
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel
|
||||
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
|
||||
import com.aiosman.ravenow.ui.navigateToChatAi
|
||||
import com.aiosman.ravenow.ui.navigateToPost
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Composable
|
||||
fun AiProfileWrap(id: String) {
|
||||
val model: AiProfileViewModel = viewModel(factory = viewModelFactory {
|
||||
AiProfileViewModel()
|
||||
}, key = "aiViewModel_${id}")
|
||||
|
||||
val navController = LocalNavController.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
model.loadProfile(id)
|
||||
MyProfileViewModel.loadProfile()
|
||||
}
|
||||
|
||||
val isSelf = id == MyProfileViewModel.profile?.id.toString()
|
||||
|
||||
AiProfileV3(
|
||||
profile = model.profile,
|
||||
moments = model.moments,
|
||||
postCount = model.momentLoader.total,
|
||||
isSelf = isSelf,
|
||||
onFollowClick = {
|
||||
model.profile?.let {
|
||||
if (it.isFollowing) {
|
||||
model.unFollowUser(id)
|
||||
} else {
|
||||
model.followUser(id)
|
||||
}
|
||||
}
|
||||
},
|
||||
onChatClick = {
|
||||
// 检查游客模式
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
model.profile?.let { profile ->
|
||||
scope.launch {
|
||||
try {
|
||||
// 参考主页逻辑:使用chatAIId作为openId创建单聊并导航
|
||||
// 创建单聊
|
||||
MineAgentViewModel.createSingleChat(profile.chatAIId)
|
||||
|
||||
// 通过chatAIId获取完整的AI profile(类似主页的goToChatAi逻辑)
|
||||
val userService = UserServiceImpl()
|
||||
val aiProfile = userService.getUserProfileByOpenId(profile.chatAIId)
|
||||
|
||||
// 导航到AI聊天页面
|
||||
navController.navigateToChatAi(aiProfile.id.toString())
|
||||
} catch (e: Exception) {
|
||||
Log.e("AiProfileWrap", "Error navigating to AI chat", e)
|
||||
// 如果获取失败,直接使用当前profile的id
|
||||
navController.navigateToChatAi(profile.id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onShareClick = {
|
||||
// TODO: 实现分享逻辑
|
||||
Log.d("AiProfileWrap", "分享功能待实现")
|
||||
},
|
||||
onLoadMore = {
|
||||
Log.d("AiProfileWrap", "onLoadMore被调用")
|
||||
model.loadMoreMoment()
|
||||
},
|
||||
onComment = { moment ->
|
||||
navController.navigateToPost(moment.id)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user