feat: 增加Google支付及优化UI与性能
- **支付功能**: - 在应用启动时初始化Google Play Billing Client,为应用内购买做准备。 - 添加了`billing-ktx`依赖。 - **动态和个人主页**: - 动态推荐页:用户头像和昵称区域支持点击跳转到对应的个人资料页。 - 个人资料页:优化了用户资料加载逻辑,使其同时支持通过用户ID和OpenID加载。 - 评论功能:优化了评论交互,评论成功后才更新评论数。 - 数据模型:调整动态图片,优先使用`smallDirectUrl`以优化加载速度。 - **AI智能体页面**: - 移除API请求中的`random`参数,以改善数据缓存和一致性。 - 优化了导航到AI智能体主页的逻辑,直接传递`openId`,简化了数据请求。 - 清理了部分未使用的代码和布局。 - **群聊列表UI**: - 调整了群聊列表项的布局、字体大小和颜色,优化了视觉样式。 - 移除了列表项之间的分割线。
This commit is contained in:
@@ -17,8 +17,8 @@ android {
|
|||||||
applicationId = "com.aiosman.ravenow"
|
applicationId = "com.aiosman.ravenow"
|
||||||
minSdk = 24
|
minSdk = 24
|
||||||
targetSdk = 35
|
targetSdk = 35
|
||||||
versionCode = 1000019
|
versionCode = 1000021
|
||||||
versionName = "1.0.000.19"
|
versionName = "1.0.000.21"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
vectorDrawables {
|
vectorDrawables {
|
||||||
@@ -140,5 +140,8 @@ dependencies {
|
|||||||
implementation(libs.androidx.room.ktx)
|
implementation(libs.androidx.room.ktx)
|
||||||
ksp(libs.androidx.room.compiler)
|
ksp(libs.androidx.room.compiler)
|
||||||
|
|
||||||
|
// Google Play Billing
|
||||||
|
implementation(libs.billing.ktx)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ import android.app.Application
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import com.android.billingclient.api.BillingClient
|
||||||
|
import com.android.billingclient.api.BillingClientStateListener
|
||||||
|
import com.android.billingclient.api.BillingResult
|
||||||
|
import com.android.billingclient.api.PurchasesUpdatedListener
|
||||||
import com.google.firebase.FirebaseApp
|
import com.google.firebase.FirebaseApp
|
||||||
import com.google.firebase.perf.FirebasePerformance
|
import com.google.firebase.perf.FirebasePerformance
|
||||||
|
|
||||||
@@ -12,6 +16,8 @@ import com.google.firebase.perf.FirebasePerformance
|
|||||||
*/
|
*/
|
||||||
class RaveNowApplication : Application() {
|
class RaveNowApplication : Application() {
|
||||||
|
|
||||||
|
private var billingClient: BillingClient? = null
|
||||||
|
|
||||||
override fun attachBaseContext(base: Context) {
|
override fun attachBaseContext(base: Context) {
|
||||||
// 禁用字体缩放,固定字体大小为系统默认大小
|
// 禁用字体缩放,固定字体大小为系统默认大小
|
||||||
val configuration = Configuration(base.resources.configuration)
|
val configuration = Configuration(base.resources.configuration)
|
||||||
@@ -48,6 +54,53 @@ class RaveNowApplication : Application() {
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("RaveNowApplication", "Firebase初始化失败在进程 $processName", e)
|
Log.e("RaveNowApplication", "Firebase初始化失败在进程 $processName", e)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化 Google Play Billing
|
||||||
|
initBillingClient()
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 Google Play Billing Client
|
||||||
|
*/
|
||||||
|
private fun initBillingClient() {
|
||||||
|
val purchasesUpdatedListener = PurchasesUpdatedListener { billingResult, purchases ->
|
||||||
|
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK && purchases != null) {
|
||||||
|
// 处理购买成功
|
||||||
|
Log.d("RaveNowApplication", "购买成功: ${purchases.size} 个商品")
|
||||||
|
} else if (billingResult.responseCode == BillingClient.BillingResponseCode.USER_CANCELED) {
|
||||||
|
// 用户取消购买
|
||||||
|
Log.d("RaveNowApplication", "用户取消购买")
|
||||||
|
} else {
|
||||||
|
// 处理其他错误
|
||||||
|
Log.e("RaveNowApplication", "购买失败: ${billingResult.debugMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
billingClient = BillingClient.newBuilder(this)
|
||||||
|
.setListener(purchasesUpdatedListener)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
billingClient?.startConnection(object : BillingClientStateListener {
|
||||||
|
override fun onBillingSetupFinished(billingResult: BillingResult) {
|
||||||
|
if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
|
||||||
|
Log.d("RaveNowApplication", "BillingClient 初始化成功")
|
||||||
|
} else {
|
||||||
|
Log.e("RaveNowApplication", "BillingClient 初始化失败: ${billingResult.debugMessage}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBillingServiceDisconnected() {
|
||||||
|
Log.w("RaveNowApplication", "BillingClient 连接断开,尝试重新连接")
|
||||||
|
// 可以在这里实现重连逻辑
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取 BillingClient 实例
|
||||||
|
*/
|
||||||
|
fun getBillingClient(): BillingClient? {
|
||||||
|
return billingClient
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ data class Moment(
|
|||||||
val commentCount: Long,
|
val commentCount: Long,
|
||||||
@SerializedName("time")
|
@SerializedName("time")
|
||||||
val time: String?,
|
val time: String?,
|
||||||
|
@SerializedName("createdAt")
|
||||||
|
val createdAt: String? = null,
|
||||||
|
@SerializedName("location")
|
||||||
|
val location: String? = null,
|
||||||
@SerializedName("isFollowed")
|
@SerializedName("isFollowed")
|
||||||
val isFollowed: Boolean,
|
val isFollowed: Boolean,
|
||||||
// 新闻相关字段
|
// 新闻相关字段
|
||||||
@@ -70,11 +74,11 @@ data class Moment(
|
|||||||
"" // 如果头像为空,使用空字符串
|
"" // 如果头像为空,使用空字符串
|
||||||
},
|
},
|
||||||
nickname = user.nickName ?: "未知用户", // 如果昵称为空,使用默认值
|
nickname = user.nickName ?: "未知用户", // 如果昵称为空,使用默认值
|
||||||
location = "Worldwide",
|
location = location ?: "Worldwide",
|
||||||
time = if (time != null && time.isNotEmpty()) {
|
time = when {
|
||||||
ApiClient.dateFromApiString(time)
|
createdAt != null && createdAt.isNotEmpty() -> ApiClient.dateFromApiString(createdAt)
|
||||||
} else {
|
time != null && time.isNotEmpty() -> ApiClient.dateFromApiString(time)
|
||||||
java.util.Date() // 如果时间为空,使用当前时间作为默认值
|
else -> java.util.Date() // 如果时间为空,使用当前时间作为默认值
|
||||||
},
|
},
|
||||||
followStatus = isFollowed,
|
followStatus = isFollowed,
|
||||||
momentTextContent = textContent,
|
momentTextContent = textContent,
|
||||||
@@ -204,7 +208,7 @@ data class Video(
|
|||||||
data class User(
|
data class User(
|
||||||
@SerializedName("id")
|
@SerializedName("id")
|
||||||
val id: Long,
|
val id: Long,
|
||||||
@SerializedName("nickName")
|
@SerializedName("nickname")
|
||||||
val nickName: String?,
|
val nickName: String?,
|
||||||
@SerializedName("avatar")
|
@SerializedName("avatar")
|
||||||
val avatar: String?,
|
val avatar: String?,
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
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.systemBars
|
import androidx.compose.foundation.layout.systemBars
|
||||||
@@ -24,22 +23,18 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.layout.wrapContentSize
|
import androidx.compose.foundation.layout.wrapContentSize
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Icon
|
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
@@ -63,33 +58,24 @@ import com.aiosman.ravenow.LocalNavController
|
|||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
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
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgent
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.hot.HotAgent
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import com.aiosman.ravenow.ui.composables.TabItem
|
|
||||||
import com.aiosman.ravenow.ui.composables.TabSpacer
|
import com.aiosman.ravenow.ui.composables.TabSpacer
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.CustomTabItem
|
import com.aiosman.ravenow.ui.index.tabs.moment.CustomTabItem
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.ExploreViewModel
|
|
||||||
import com.aiosman.ravenow.utils.DebounceUtils
|
import com.aiosman.ravenow.utils.DebounceUtils
|
||||||
import com.aiosman.ravenow.utils.ResourceCleanupManager
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.window.Dialog
|
import androidx.compose.ui.window.Dialog
|
||||||
import androidx.compose.ui.window.DialogProperties
|
import androidx.compose.ui.window.DialogProperties
|
||||||
import androidx.compose.foundation.lazy.grid.items as gridItems
|
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.foundation.layout.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import com.airbnb.lottie.compose.LottieAnimation
|
import com.airbnb.lottie.compose.LottieAnimation
|
||||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||||
@@ -113,10 +99,6 @@ fun Agent() {
|
|||||||
val navigationBarPaddings =
|
val navigationBarPaddings =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
// 游客模式下只显示热门Agent,正常用户显示我的Agent和热门Agent
|
|
||||||
val tabCount = if (AppStore.isGuest) 1 else 2
|
|
||||||
var pagerState = rememberPagerState { tabCount }
|
|
||||||
var scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
val viewModel: AgentViewModel = AgentViewModel
|
val viewModel: AgentViewModel = AgentViewModel
|
||||||
|
|
||||||
@@ -125,16 +107,6 @@ fun Agent() {
|
|||||||
viewModel.ensureDataLoaded()
|
viewModel.ensureDataLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 防抖状态
|
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
|
||||||
|
|
||||||
// 页面退出时只清理必要的资源,不清理推荐Agent数据
|
|
||||||
DisposableEffect(Unit) {
|
|
||||||
onDispose {
|
|
||||||
// 只清理子页面的资源,保留推荐Agent数据
|
|
||||||
// ResourceCleanupManager.cleanupPageResources("ai")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val agentItems = viewModel.agentItems
|
val agentItems = viewModel.agentItems
|
||||||
var selectedTabIndex by remember { mutableStateOf(0) }
|
var selectedTabIndex by remember { mutableStateOf(0) }
|
||||||
@@ -165,7 +137,7 @@ fun Agent() {
|
|||||||
contentDescription = "Rave AI Logo",
|
contentDescription = "Rave AI Logo",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(44.dp)
|
.height(44.dp)
|
||||||
.padding(top =9.dp,bottom=9.dp)
|
.padding(top = 9.dp, bottom = 9.dp)
|
||||||
.wrapContentSize(),
|
.wrapContentSize(),
|
||||||
// colorFilter = ColorFilter.tint(AppColors.text)
|
// colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
@@ -176,7 +148,7 @@ fun Agent() {
|
|||||||
contentDescription = "search",
|
contentDescription = "search",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(44.dp)
|
.size(44.dp)
|
||||||
.padding(top = 9.dp,bottom=9.dp)
|
.padding(top = 9.dp, bottom = 9.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
navController.navigate(NavigationRoute.Search.route)
|
navController.navigate(NavigationRoute.Search.route)
|
||||||
},
|
},
|
||||||
@@ -267,11 +239,19 @@ fun Agent() {
|
|||||||
) {
|
) {
|
||||||
when {
|
when {
|
||||||
selectedTabIndex == 0 -> {
|
selectedTabIndex == 0 -> {
|
||||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
AgentViewPagerSection(
|
||||||
|
agentItems = viewModel.agentItems.take(15),
|
||||||
|
viewModel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedTabIndex in 1..viewModel.categories.size -> {
|
selectedTabIndex in 1..viewModel.categories.size -> {
|
||||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
AgentViewPagerSection(
|
||||||
|
agentItems = viewModel.agentItems.take(15),
|
||||||
|
viewModel
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
|
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
|
||||||
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
|
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
|
||||||
@@ -329,7 +309,6 @@ fun Agent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 只有当热门聊天室有数据时,才展示“发现更多”区域
|
// 只有当热门聊天室有数据时,才展示“发现更多”区域
|
||||||
if (viewModel.chatRooms.isNotEmpty()) {
|
|
||||||
item { Spacer(modifier = Modifier.height(20.dp)) }
|
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||||
|
|
||||||
// "发现更多" 标题 - 吸顶
|
// "发现更多" 标题 - 吸顶
|
||||||
@@ -404,58 +383,7 @@ fun Agent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AgentGridLayout(
|
|
||||||
agentItems: List<AgentItem>,
|
|
||||||
viewModel: AgentViewModel,
|
|
||||||
navController: NavHostController
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
// 将agentItems按两列分组
|
|
||||||
agentItems.chunked(2).forEachIndexed { rowIndex, rowItems ->
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(
|
|
||||||
top = if (rowIndex == 0) 30.dp else 20.dp, // 第一行添加更多顶部间距
|
|
||||||
bottom = 20.dp
|
|
||||||
),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
// 第一列
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
AgentCardSquare(
|
|
||||||
agentItem = rowItems[0],
|
|
||||||
viewModel = viewModel,
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二列(如果存在)
|
|
||||||
if (rowItems.size > 1) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
AgentCardSquare(
|
|
||||||
agentItem = rowItems[1],
|
|
||||||
viewModel = viewModel,
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 如果只有一列,添加空白占位
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,9 +490,10 @@ fun AgentCardSquare(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel) {
|
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
if (agentItems.isEmpty()) return
|
if (agentItems.isEmpty()) return
|
||||||
|
|
||||||
@@ -711,186 +640,6 @@ fun AgentLargeCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier,navController: NavHostController) {
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 0.dp)
|
|
||||||
) {
|
|
||||||
// 显示3个agent
|
|
||||||
agentItems.forEachIndexed { index, agentItem ->
|
|
||||||
AgentCard2(agentItem = agentItem, viewModel = viewModel, navController = LocalNavController.current)
|
|
||||||
if (index < agentItems.size - 1) {
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("SuspiciousIndentation")
|
|
||||||
@Composable
|
|
||||||
fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: NavHostController) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
// 防抖状态
|
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 3.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 左侧头像
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(48.dp)
|
|
||||||
.background(Color(0x00F5F5F5), RoundedCornerShape(24.dp))
|
|
||||||
.clickable {
|
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
|
||||||
viewModel.goToProfile(agentItem.openId, navController)
|
|
||||||
}) {
|
|
||||||
lastClickTime = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = agentItem.avatar,
|
|
||||||
contentDescription = "Agent头像",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(48.dp)
|
|
||||||
.clip(RoundedCornerShape(24.dp)),
|
|
||||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
|
||||||
defaultRes = R.mipmap.group_copy
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
|
|
||||||
// 中间文字内容
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = 8.dp)
|
|
||||||
) {
|
|
||||||
// 标题
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = agentItem.title,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
|
||||||
color = AppColors.text,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 描述
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = agentItem.desc,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.secondaryText,
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右侧聊天按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(width = 60.dp, height = 32.dp)
|
|
||||||
.background(
|
|
||||||
color = Color(0X147c7480),
|
|
||||||
shape = RoundedCornerShape(
|
|
||||||
topStart = 14.dp,
|
|
||||||
topEnd = 14.dp,
|
|
||||||
bottomStart = 0.dp,
|
|
||||||
bottomEnd = 14.dp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.clickable {
|
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
} else {
|
|
||||||
viewModel.createSingleChat(agentItem.openId)
|
|
||||||
viewModel.goToChatAi(
|
|
||||||
agentItem.openId,
|
|
||||||
navController = navController
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
lastClickTime = System.currentTimeMillis()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = stringResource(R.string.chat),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Composable
|
|
||||||
fun ChatRoomsSection(
|
|
||||||
chatRooms: List<ChatRoom>,
|
|
||||||
navController: NavHostController
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
// 标题
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.mipmap.rider_pro_hot_room),
|
|
||||||
contentDescription = "chat room",
|
|
||||||
modifier = Modifier.size(28.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = stringResource(R.string.hot_rooms),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
chatRooms.chunked(2).forEach { rowRooms ->
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 12.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
rowRooms.forEach { chatRoom ->
|
|
||||||
ChatRoomCard(
|
|
||||||
chatRoom = chatRoom,
|
|
||||||
navController = navController,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatRoomCard(
|
fun ChatRoomCard(
|
||||||
chatRoom: ChatRoom,
|
chatRoom: ChatRoom,
|
||||||
@@ -938,7 +687,10 @@ fun ChatRoomCard(
|
|||||||
.size(cardSize)
|
.size(cardSize)
|
||||||
.background(AppColors.tabUnselectedBackground, RoundedCornerShape(12.dp))
|
.background(AppColors.tabUnselectedBackground, RoundedCornerShape(12.dp))
|
||||||
.clickable(enabled = !viewModel.isJoiningRoom) {
|
.clickable(enabled = !viewModel.isJoiningRoom) {
|
||||||
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(
|
||||||
|
lastClickTime,
|
||||||
|
500L
|
||||||
|
) {
|
||||||
// 加入群聊房间
|
// 加入群聊房间
|
||||||
viewModel.joinRoom(
|
viewModel.joinRoom(
|
||||||
id = chatRoom.id,
|
id = chatRoom.id,
|
||||||
@@ -953,7 +705,8 @@ fun ChatRoomCard(
|
|||||||
// 处理错误,可以显示Toast或其他提示
|
// 处理错误,可以显示Toast或其他提示
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}) {
|
}
|
||||||
|
) {
|
||||||
lastClickTime = System.currentTimeMillis()
|
lastClickTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -967,11 +720,14 @@ fun ChatRoomCard(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(cardSize)
|
.width(cardSize)
|
||||||
.height(120.dp)
|
.height(120.dp)
|
||||||
.clip(RoundedCornerShape(
|
.clip(
|
||||||
|
RoundedCornerShape(
|
||||||
topStart = 12.dp,
|
topStart = 12.dp,
|
||||||
topEnd = 12.dp,
|
topEnd = 12.dp,
|
||||||
bottomStart = 0.dp,
|
bottomStart = 0.dp,
|
||||||
bottomEnd = 0.dp)),
|
bottomEnd = 0.dp
|
||||||
|
)
|
||||||
|
),
|
||||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
contentScale = androidx.compose.ui.layout.ContentScale.Crop,
|
||||||
defaultRes = R.mipmap.rider_pro_agent
|
defaultRes = R.mipmap.rider_pro_agent
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ object AgentViewModel: ViewModel() {
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
withWorkflow = 1,
|
withWorkflow = 1,
|
||||||
categoryIds = listOf(categoryId),
|
categoryIds = listOf(categoryId),
|
||||||
random = 1
|
// random = 1
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// 获取推荐智能体,使用random=1
|
// 获取推荐智能体,使用random=1
|
||||||
@@ -143,7 +143,7 @@ object AgentViewModel: ViewModel() {
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
withWorkflow = 1,
|
withWorkflow = 1,
|
||||||
categoryIds = null,
|
categoryIds = null,
|
||||||
random = 1
|
// random = 1
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -197,7 +197,7 @@ object AgentViewModel: ViewModel() {
|
|||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 20,
|
||||||
isRecommended = 1,
|
isRecommended = 1,
|
||||||
random = "1"
|
// random = "1"
|
||||||
)
|
)
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val allRooms = response.body()?.list ?: emptyList()
|
val allRooms = response.body()?.list ?: emptyList()
|
||||||
@@ -332,18 +332,17 @@ object AgentViewModel: ViewModel() {
|
|||||||
openId: String,
|
openId: String,
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
// 直接使用openId导航,页面内的AiProfileViewModel会处理数据加载
|
||||||
|
// 避免重复请求,因为AiProfileViewModel.loadProfile已经支持通过openId加载
|
||||||
try {
|
try {
|
||||||
val profile = userService.getUserProfileByOpenId(openId)
|
|
||||||
// 从Agent列表点击进去的一定是智能体,直接传递isAiAccount = true
|
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
NavigationRoute.AccountProfile.route
|
NavigationRoute.AccountProfile.route
|
||||||
.replace("{id}", profile.id.toString())
|
.replace("{id}", openId)
|
||||||
.replace("{isAiAccount}", "true")
|
.replace("{isAiAccount}", "true")
|
||||||
)
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// swallow error to avoid crash on navigation attempt failures
|
Log.e("AgentViewModel", "Navigation failed", e)
|
||||||
}
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
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
|
||||||
@@ -27,6 +26,7 @@ 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 androidx.compose.ui.graphics.Color
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
@@ -153,13 +153,6 @@ fun GroupChatListScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
if (index < GroupChatListViewModel.groupChatList.size - 1) {
|
|
||||||
HorizontalDivider(
|
|
||||||
modifier = Modifier.padding(horizontal = 24.dp),
|
|
||||||
color = AppColors.divider
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) {
|
if (GroupChatListViewModel.isLoading && GroupChatListViewModel.groupChatList.isNotEmpty()) {
|
||||||
@@ -213,16 +206,16 @@ fun GroupChatItem(
|
|||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val chatDebouncer = rememberDebouncer()
|
val chatDebouncer = rememberDebouncer()
|
||||||
val avatarDebouncer = rememberDebouncer()
|
val avatarDebouncer = rememberDebouncer()
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 24.dp, vertical = 12.dp)
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
chatDebouncer {
|
chatDebouncer {
|
||||||
onChatClick(conversation)
|
onChatClick(conversation)
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Box {
|
Box {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
@@ -242,9 +235,9 @@ fun GroupChatItem(
|
|||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 12.dp)
|
.padding(start = 12.dp, top = 2.dp),
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -252,22 +245,22 @@ fun GroupChatItem(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = conversation.groupName,
|
text = conversation.groupName,
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(6.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = conversation.lastMessageTime,
|
text = conversation.lastMessageTime,
|
||||||
fontSize = 12.sp,
|
fontSize = 11.sp,
|
||||||
color = AppColors.secondaryText
|
color = AppColors.secondaryText
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
Spacer(modifier = Modifier.height(6.dp))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -275,29 +268,29 @@ fun GroupChatItem(
|
|||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
|
text = "${if (conversation.isSelf) stringResource(R.string.friend_chat_me_prefix) else ""}${conversation.displayText}",
|
||||||
fontSize = 14.sp,
|
fontSize = 12.sp,
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(10.dp))
|
||||||
|
|
||||||
if (conversation.unreadCount > 0) {
|
if (conversation.unreadCount > 0) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
|
.size(if (conversation.unreadCount > 99) 24.dp else 20.dp)
|
||||||
.background(
|
.background(
|
||||||
color = AppColors.main,
|
color = Color(0xFFFF3B30),
|
||||||
shape = CircleShape
|
shape = CircleShape
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
|
text = if (conversation.unreadCount > 99) "99+" else conversation.unreadCount.toString(),
|
||||||
color = AppColors.mainText,
|
color = Color.White,
|
||||||
fontSize = if (conversation.unreadCount > 99) 9.sp else 10.sp,
|
fontSize = if (conversation.unreadCount > 99) 11.sp else 12.sp,
|
||||||
fontWeight = FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,10 +35,16 @@ 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.R
|
import com.aiosman.ravenow.R
|
||||||
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
import com.aiosman.ravenow.entity.MomentEntity
|
||||||
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import com.aiosman.ravenow.data.UserService
|
||||||
|
import com.aiosman.ravenow.data.UserServiceImpl
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态推荐Item组件(post_normal)
|
* 动态推荐Item组件(post_normal)
|
||||||
@@ -50,16 +56,37 @@ fun PostRecommendationItem(
|
|||||||
moment: MomentEntity,
|
moment: MomentEntity,
|
||||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
||||||
|
onCommentAdded: ((MomentEntity) -> Unit)? = null,
|
||||||
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onShareClick: ((MomentEntity) -> Unit)? = null,
|
onShareClick: ((MomentEntity) -> Unit)? = null,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val userService: UserService = UserServiceImpl()
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
var showCommentModal by remember { mutableStateOf(false) }
|
||||||
var sheetState = rememberModalBottomSheetState(
|
var sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 导航到个人资料
|
||||||
|
fun navigateToProfile() {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
val profile = userService.getUserProfile(moment.authorId.toString())
|
||||||
|
navController.navigate(
|
||||||
|
NavigationRoute.AccountProfile.route
|
||||||
|
.replace("{id}", profile.id.toString())
|
||||||
|
.replace("{isAiAccount}", if (profile.aiAccount) "true" else "false")
|
||||||
|
)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 处理错误,避免崩溃
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 图片列表
|
// 图片列表
|
||||||
val images = moment.images
|
val images = moment.images
|
||||||
val imageCount = images.size
|
val imageCount = images.size
|
||||||
@@ -71,8 +98,9 @@ fun PostRecommendationItem(
|
|||||||
) {
|
) {
|
||||||
// 图片显示区域(替代视频播放器)
|
// 图片显示区域(替代视频播放器)
|
||||||
if (imageCount > 0) {
|
if (imageCount > 0) {
|
||||||
// 只显示第一张图片,优先使用 thumbnailDirectUrl
|
// 只显示第一张图片,优先使用 smallDirectUrl
|
||||||
val imageUrl = images[0].thumbnailDirectUrl
|
val imageUrl = images[0].smallDirectUrl
|
||||||
|
?: images[0].thumbnailDirectUrl
|
||||||
?: images[0].directUrl
|
?: images[0].directUrl
|
||||||
?: images[0].url
|
?: images[0].url
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
@@ -104,32 +132,16 @@ fun PostRecommendationItem(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, bottom = 16.dp)
|
.padding(start = 16.dp, bottom = 16.dp)
|
||||||
) {
|
) {
|
||||||
// 用户头像和昵称
|
// 用户昵称
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.padding(bottom = 8.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(40.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(Color.White.copy(alpha = 0.2f))
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = moment.avatar,
|
|
||||||
contentDescription = "用户头像",
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
defaultRes = R.drawable.default_avatar
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
Text(
|
||||||
text = "@${moment.nickname}",
|
text = "@${moment.nickname}",
|
||||||
modifier = Modifier.padding(start = 8.dp),
|
modifier = Modifier
|
||||||
|
.padding(bottom = 8.dp)
|
||||||
|
.noRippleClickable { navigateToProfile() },
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
// 文字内容
|
// 文字内容
|
||||||
if (!moment.momentTextContent.isNullOrEmpty()) {
|
if (!moment.momentTextContent.isNullOrEmpty()) {
|
||||||
@@ -160,7 +172,10 @@ fun PostRecommendationItem(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
// 用户头像
|
// 用户头像
|
||||||
UserAvatar(avatarUrl = moment.avatar)
|
UserAvatar(
|
||||||
|
avatarUrl = moment.avatar,
|
||||||
|
onClick = { navigateToProfile() }
|
||||||
|
)
|
||||||
|
|
||||||
// 点赞
|
// 点赞
|
||||||
VideoBtn(
|
VideoBtn(
|
||||||
@@ -205,21 +220,35 @@ fun PostRecommendationItem(
|
|||||||
containerColor = Color.White,
|
containerColor = Color.White,
|
||||||
sheetState = sheetState
|
sheetState = sheetState
|
||||||
) {
|
) {
|
||||||
CommentModalContent(postId = moment.id) {
|
CommentModalContent(
|
||||||
// 评论添加后的回调
|
postId = moment.id,
|
||||||
|
commentCount = moment.commentCount,
|
||||||
|
onCommentAdded = {
|
||||||
|
onCommentAdded?.invoke(moment)
|
||||||
}
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun UserAvatar(avatarUrl: String? = null) {
|
private fun UserAvatar(
|
||||||
|
avatarUrl: String? = null,
|
||||||
|
onClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp)
|
.padding(bottom = 16.dp)
|
||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(Color.White.copy(alpha = 0.2f))
|
.background(Color.White.copy(alpha = 0.2f))
|
||||||
|
.then(
|
||||||
|
if (onClick != null) {
|
||||||
|
Modifier.noRippleClickable { onClick() }
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
|
|||||||
@@ -239,11 +239,13 @@ fun RecommendScreen() {
|
|||||||
onCommentClick = { m ->
|
onCommentClick = { m ->
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
} else {
|
}
|
||||||
|
// 注意:不在这里增加评论数,应该在评论真正提交成功后再增加
|
||||||
|
},
|
||||||
|
onCommentAdded = { m ->
|
||||||
scope.launch {
|
scope.launch {
|
||||||
RecommendViewModel.onAddComment(m.id)
|
RecommendViewModel.onAddComment(m.id)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onFavoriteClick = { m ->
|
onFavoriteClick = { m ->
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
|
|||||||
@@ -44,12 +44,21 @@ class AiProfileViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 检查id是否是纯数字(用户ID),如果不是则当作openId处理
|
||||||
|
val isUserId = id.toIntOrNull() != null
|
||||||
|
|
||||||
|
if (isUserId) {
|
||||||
// 先通过用户ID获取基本信息,获取chatAIId
|
// 先通过用户ID获取基本信息,获取chatAIId
|
||||||
val basicProfile = userService.getUserProfile(id)
|
val basicProfile = userService.getUserProfile(id)
|
||||||
profileId = id.toInt()
|
profileId = id.toInt()
|
||||||
|
|
||||||
// 使用chatAIId通过getUserProfileByOpenId获取完整信息(包含creatorProfile)
|
// 使用chatAIId通过getUserProfileByOpenId获取完整信息(包含creatorProfile)
|
||||||
profile = userService.getUserProfileByOpenId(basicProfile.chatAIId)
|
profile = userService.getUserProfileByOpenId(basicProfile.chatAIId)
|
||||||
|
} else {
|
||||||
|
// 直接通过openId获取完整信息
|
||||||
|
profile = userService.getUserProfileByOpenId(id)
|
||||||
|
profileId = profile?.id ?: 0
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e("AiProfileViewModel", "Error loading profile", e)
|
Log.e("AiProfileViewModel", "Error loading profile", e)
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ zoomable = "1.6.1"
|
|||||||
camerax = "1.4.0"
|
camerax = "1.4.0"
|
||||||
mlkitBarcode = "17.3.0"
|
mlkitBarcode = "17.3.0"
|
||||||
room = "2.8.3"
|
room = "2.8.3"
|
||||||
|
billing = "8.0.0"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
|
accompanist-systemuicontroller = { module = "com.google.accompanist:accompanist-systemuicontroller", version.ref = "accompanistSystemuicontroller" }
|
||||||
@@ -114,6 +115,7 @@ mlkit-barcode-scanning = { module = "com.google.mlkit:barcode-scanning", version
|
|||||||
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||||
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||||
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||||
|
billing-ktx = { module = "com.android.billingclient:billing-ktx", version.ref = "billing" }
|
||||||
[plugins]
|
[plugins]
|
||||||
android-application = { id = "com.android.application", version.ref = "agp" }
|
android-application = { id = "com.android.application", version.ref = "agp" }
|
||||||
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
|
||||||
|
|||||||
Reference in New Issue
Block a user