Enhance AI Agent Profile Interaction

This commit introduces several enhancements to how AI agent profiles are displayed and interacted with:

**Profile Display:**
- **AI Account Distinction:** Profile pages now differentiate between regular user accounts and AI agent accounts.
    - AI agent profiles will not display the "Agents" tab in their profile.
    - The profile header height is adjusted for AI accounts.
- **Navigation Parameter:** An `isAiAccount` boolean parameter is added to the `AccountProfile` navigation route to indicate if the profile being viewed belongs to an AI.

**Interaction & Navigation:**
- **Avatar Click Navigation:**
    - Clicking an AI agent's avatar in various lists (Hot Agents, My Agents, User Agents Row, User Agents List) now navigates to the agent's dedicated profile page.
    - When navigating to an agent's profile from an agent list, `isAiAccount` is set to `true`.
- **Chat Initiation:** Clicking the chat button on AI agent cards in the "Agent" tab (both Hot and My Agents) now correctly initiates a chat with the respective AI.
- **ViewModel Updates:**
    - `AgentViewModel`, `MineAgentViewModel`, and `HotAgentViewModel` now include a `goToProfile` function to handle navigation to agent profiles, correctly passing the `isAiAccount` flag.

**Code Refinements:**
- Click handlers for agent avatars and chat buttons are now wrapped with `DebounceUtils.simpleDebounceClick` to prevent multiple rapid clicks.
- The `UserContentPageIndicator` now conditionally hides the "Agent" tab based on the `isAiAccount` status.
- `UserAgentsRow` and `UserAgentsList` now accept an `onAvatarClick` callback for navigating to agent profiles.
- `AgentItem` (used in `UserAgentsRow`) and `UserAgentCard` (used in `UserAgentsList`) now handle avatar clicks.
- The general `Agent` composable (used in `AiPostComposable`) now also supports an `onAvatarClick` callback.
This commit is contained in:
2025-09-01 14:17:44 +08:00
parent 484d641554
commit 83cff9d56c
23 changed files with 780 additions and 254 deletions

View File

@@ -45,6 +45,12 @@ object AppState {
var enableGoogleLogin: Boolean = false var enableGoogleLogin: Boolean = false
var enableChat = false var enableChat = false
suspend fun initWithAccount(scope: CoroutineScope, context: Context) { suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
// 如果是游客模式,使用简化的初始化流程
if (AppStore.isGuest) {
initWithGuestAccount()
return
}
val accountService: AccountService = AccountServiceImpl() val accountService: AccountService = AccountServiceImpl()
// 获取用户认证信息 // 获取用户认证信息
val resp = accountService.getMyAccount() val resp = accountService.getMyAccount()
@@ -69,6 +75,18 @@ object AppState {
initChat(context) initChat(context)
} }
/**
* 游客模式的简化初始化
*/
private fun initWithGuestAccount() {
// 游客模式下不初始化推送和TRTC
// 设置默认的用户信息
UserId = 0
profile = null
enableChat = false
Log.d("AppState", "Guest mode initialized without push notifications and TRTC")
}
private suspend fun initChat(context: Context){ private suspend fun initChat(context: Context){
val dictService :DictService = DictServiceImpl() val dictService :DictService = DictServiceImpl()
val enableItem = dictService.getDictByKey(ConstVars.DICT_KEY_ENABLE_TRTC) val enableItem = dictService.getDictByKey(ConstVars.DICT_KEY_ENABLE_TRTC)
@@ -149,6 +167,27 @@ object AppState {
AppStore.saveDarkMode(darkMode) AppStore.saveDarkMode(darkMode)
} }
/**
* 检查是否是游客模式,并且是否需要登录
* @return true 如果是游客模式
*/
fun isGuestMode(): Boolean {
return AppStore.isGuest
}
/**
* 检查游客模式并提示登录
* @param onGuestMode 当是游客模式时的回调
* @return true 如果是游客模式
*/
fun checkGuestModeAndPromptLogin(onGuestMode: (() -> Unit)? = null): Boolean {
if (AppStore.isGuest) {
onGuestMode?.invoke()
return true
}
return false
}
fun ReloadAppState(context: Context) { fun ReloadAppState(context: Context) {
// 重置动态列表页面 // 重置动态列表页面
TimelineMomentViewModel.ResetModel() TimelineMomentViewModel.ResetModel()
@@ -175,6 +214,9 @@ object AppState {
IndexViewModel.ResetModel() IndexViewModel.ResetModel()
UserId = null UserId = null
// 清除游客状态
AppStore.isGuest = false
// 关闭 TrtcService // 关闭 TrtcService
val trtcService = Intent( val trtcService = Intent(
context, context,

View File

@@ -41,3 +41,33 @@ object ConstVars {
const val DICT_KEY_REPORT_OPTIONS = "report_reasons" const val DICT_KEY_REPORT_OPTIONS = "report_reasons"
} }
enum class GuestLoginCheckOutScene {
CREATE_POST,
CREATE_AGENT,
VIEW_MESSAGES,
VIEW_PROFILE,
JOIN_GROUP_CHAT,
CHAT_WITH_AGENT,
LIKE_MOMENT,
COMMENT_MOMENT,
FOLLOW_USER,
REPORT_CONTENT
}
object GuestLoginCheckOut {
var NeedLoginScene = listOf<GuestLoginCheckOutScene>(
GuestLoginCheckOutScene.CREATE_POST,
GuestLoginCheckOutScene.CREATE_AGENT,
GuestLoginCheckOutScene.VIEW_MESSAGES,
GuestLoginCheckOutScene.VIEW_PROFILE,
GuestLoginCheckOutScene.JOIN_GROUP_CHAT,
GuestLoginCheckOutScene.CHAT_WITH_AGENT,
GuestLoginCheckOutScene.LIKE_MOMENT,
GuestLoginCheckOutScene.COMMENT_MOMENT,
GuestLoginCheckOutScene.FOLLOW_USER,
GuestLoginCheckOutScene.REPORT_CONTENT
)
fun needLogin(scene: GuestLoginCheckOutScene): Boolean {
return AppStore.isGuest && NeedLoginScene.contains(scene)
}
}

View File

@@ -114,8 +114,9 @@ class MainActivity : ComponentActivity() {
// 检查是否有登录态 // 检查是否有登录态
val isAccountValidate = getAccount() val isAccountValidate = getAccount()
var startDestination = NavigationRoute.Login.route var startDestination = NavigationRoute.Login.route
// 如果有登录态,且记住登录状态,且账号有效,则初始化 FCM,下一步进入首页 // 如果有登录态,且记住登录状态,且账号有效,则初始化应用状态,下一步进入首页
if (AppStore.token != null && AppStore.rememberMe && isAccountValidate) { if (AppStore.token != null && AppStore.rememberMe && (isAccountValidate || AppStore.isGuest)) {
// 根据用户类型进行相应的初始化游客模式会跳过推送和TRTC初始化
AppState.initWithAccount(scope, this@MainActivity) AppState.initWithAccount(scope, this@MainActivity)
startDestination = NavigationRoute.Index.route startDestination = NavigationRoute.Index.route
} }

View File

@@ -7,6 +7,7 @@ import com.aiosman.ravenow.data.api.AppConfig
import com.aiosman.ravenow.data.api.CaptchaInfo import com.aiosman.ravenow.data.api.CaptchaInfo
import com.aiosman.ravenow.data.api.ChangePasswordRequestBody import com.aiosman.ravenow.data.api.ChangePasswordRequestBody
import com.aiosman.ravenow.data.api.GoogleRegisterRequestBody import com.aiosman.ravenow.data.api.GoogleRegisterRequestBody
import com.aiosman.ravenow.data.api.GuestLoginRequestBody
import com.aiosman.ravenow.data.api.LoginUserRequestBody import com.aiosman.ravenow.data.api.LoginUserRequestBody
import com.aiosman.ravenow.data.api.RegisterMessageChannelRequestBody import com.aiosman.ravenow.data.api.RegisterMessageChannelRequestBody
import com.aiosman.ravenow.data.api.RegisterRequestBody import com.aiosman.ravenow.data.api.RegisterRequestBody
@@ -300,6 +301,13 @@ interface AccountService {
*/ */
suspend fun loginUserWithGoogle(googleId: String): UserAuth suspend fun loginUserWithGoogle(googleId: String): UserAuth
/**
* 游客登录
* @param deviceId 设备ID
* @param deviceInfo 设备信息
*/
suspend fun guestLogin(deviceId: String, deviceInfo: String? = null): UserAuth
/** /**
* 退出登录 * 退出登录
*/ */
@@ -456,6 +464,21 @@ class AccountServiceImpl : AccountService {
return UserAuth(0, body.token) return UserAuth(0, body.token)
} }
override suspend fun guestLogin(deviceId: String, deviceInfo: String?): UserAuth {
val resp = ApiClient.api.guestLogin(GuestLoginRequestBody(
deviceId = deviceId,
deviceInfo = deviceInfo
))
if (!resp.isSuccessful) {
parseErrorResponse(resp.errorBody())?.let {
throw it.toServiceException()
}
throw ServiceException("Failed to guest login")
}
val body = resp.body() ?: throw ServiceException("Failed to guest login")
return UserAuth(0, body.token, isGuest = true)
}
override suspend fun regiterUserWithGoogleAccount(idToken: String) { override suspend fun regiterUserWithGoogleAccount(idToken: String) {
val resp = ApiClient.api.registerWithGoogle(GoogleRegisterRequestBody(idToken)) val resp = ApiClient.api.registerWithGoogle(GoogleRegisterRequestBody(idToken))
if (!resp.isSuccessful) { if (!resp.isSuccessful) {

View File

@@ -5,7 +5,8 @@ import com.aiosman.ravenow.entity.AccountProfileEntity
data class UserAuth( data class UserAuth(
val id: Int, val id: Int,
val token: String? = null val token: String? = null,
val isGuest: Boolean = false
) )
/** /**

View File

@@ -23,7 +23,29 @@ fun getUnsafeOkHttpClient(
): OkHttpClient { ): OkHttpClient {
return try { return try {
// Create a trust manager that does not validate certificate chains // Create a trust manager that does not validate certificate chains
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
@Throws(CertificateException::class)
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
@Throws(CertificateException::class)
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
}
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
return arrayOf()
}
})
// Install the all-trusting trust manager
val sslContext = SSLContext.getInstance("SSL")
sslContext.init(null, trustAllCerts, java.security.SecureRandom())
// Create an ssl socket factory with our all-trusting manager
val sslSocketFactory = sslContext.socketFactory
OkHttpClient.Builder() OkHttpClient.Builder()
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
.hostnameVerifier { _, _ -> true } .hostnameVerifier { _, _ -> true }
.apply { .apply {
authInterceptor?.let { authInterceptor?.let {

View File

@@ -95,6 +95,19 @@ data class LoginUserRequestBody(
val captcha: CaptchaInfo? = null, val captcha: CaptchaInfo? = null,
) )
data class GuestLoginRequestBody(
@SerializedName("deviceID")
val deviceId: String,
@SerializedName("platform")
val platform: String = "android",
@SerializedName("deviceInfo")
val deviceInfo: String? = null,
@SerializedName("userAgent")
val userAgent: String? = null,
@SerializedName("ipAddress")
val ipAddress: String? = null
)
data class GoogleRegisterRequestBody( data class GoogleRegisterRequestBody(
@SerializedName("idToken") @SerializedName("idToken")
val idToken: String val idToken: String
@@ -274,6 +287,9 @@ interface RaveNowAPI {
@POST("login") @POST("login")
suspend fun login(@Body body: LoginUserRequestBody): Response<AuthResult> suspend fun login(@Body body: LoginUserRequestBody): Response<AuthResult>
@POST("guest/login")
suspend fun guestLogin(@Body body: GuestLoginRequestBody): Response<AuthResult>
@GET("auth/token") @GET("auth/token")
suspend fun checkToken(): Response<ValidateTokenResult> suspend fun checkToken(): Response<ValidateTokenResult>

View File

@@ -2,6 +2,7 @@ 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.AppStore
import com.aiosman.ravenow.data.Agent 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
@@ -28,24 +29,37 @@ suspend fun createAgent(
title: String, title: String,
desc: String, desc: String,
avatar: UploadImage? = null, avatar: UploadImage? = null,
workflowId:Int = 1, workflowId: Int = 1,
isPublic:Boolean = true, isPublic: Boolean = true,
breakMode:Boolean = false, breakMode: Boolean = false,
useWorkflow:Boolean = true, useWorkflow: Boolean = true,
): AgentEntity { ): AgentEntity {
val textTitle = title.toRequestBody("text/plain".toMediaTypeOrNull()) val textTitle = title.toRequestBody("text/plain".toMediaTypeOrNull())
val textDesc = desc.toRequestBody("text/plain".toMediaTypeOrNull()) val textDesc = desc.toRequestBody("text/plain".toMediaTypeOrNull())
val workflowIdRequestBody = workflowId.toString().toRequestBody("text/plain".toMediaTypeOrNull()) val workflowIdRequestBody =
workflowId.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val isPublicRequestBody = isPublic.toString().toRequestBody("text/plain".toMediaTypeOrNull()) val isPublicRequestBody = isPublic.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val breakModeRequestBody = breakMode.toString().toRequestBody("text/plain".toMediaTypeOrNull()) val breakModeRequestBody = breakMode.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val useWorkflowRequestBody = useWorkflow.toString().toRequestBody("text/plain".toMediaTypeOrNull()) val useWorkflowRequestBody =
useWorkflow.toString().toRequestBody("text/plain".toMediaTypeOrNull())
val workflowInputsValue = "{\"si\":\"$desc\"}" val workflowInputsValue = "{\"si\":\"$desc\"}"
val workflowInputsRequestBody = workflowInputsValue.toRequestBody("text/plain".toMediaTypeOrNull()) val workflowInputsRequestBody =
workflowInputsValue.toRequestBody("text/plain".toMediaTypeOrNull())
val avatarField: MultipartBody.Part? = avatar?.let { val avatarField: MultipartBody.Part? = avatar?.let {
createMultipartBody(it.file, it.filename, "avatar") createMultipartBody(it.file, it.filename, "avatar")
} }
val response = ApiClient.api.createAgent(avatarField, textTitle ,textDesc,textDesc,workflowIdRequestBody,isPublicRequestBody,breakModeRequestBody,useWorkflowRequestBody,workflowInputsRequestBody) val response = ApiClient.api.createAgent(
avatarField,
textTitle,
textDesc,
textDesc,
workflowIdRequestBody,
isPublicRequestBody,
breakModeRequestBody,
useWorkflowRequestBody,
workflowInputsRequestBody
)
val body = response.body()?.data ?: throw ServiceException("Failed to create agent") val body = response.body()?.data ?: throw ServiceException("Failed to create agent")
return body.toAgentEntity() return body.toAgentEntity()
@@ -99,7 +113,11 @@ class AgentRemoteDataSource(
class AgentServiceImpl() : AgentService { class AgentServiceImpl() : AgentService {
val agentBackend = AgentBackend() val agentBackend = AgentBackend()
override suspend fun getAgent(pageNumber: Int, pageSize: Int, authorId: Int?): ListContainer<AgentEntity> { override suspend fun getAgent(
pageNumber: Int,
pageSize: Int,
authorId: Int?
): ListContainer<AgentEntity> {
return agentBackend.getAgent( return agentBackend.getAgent(
pageNumber = pageNumber, pageNumber = pageNumber,
authorId = authorId authorId = authorId
@@ -107,50 +125,62 @@ class AgentServiceImpl() : AgentService {
} }
} }
class AgentBackend { class AgentBackend {
val DataBatchSize = 20 val DataBatchSize = 20
suspend fun getAgent( suspend fun getAgent(
pageNumber: Int, pageNumber: Int,
authorId: Int? = null authorId: Int? = null
): ListContainer<AgentEntity> { ): ListContainer<AgentEntity> {
val resp = if (authorId != null) { // 如果是游客模式且获取我的AgentauthorId为null返回空列表
ApiClient.api.getAgent( if (authorId == null && AppStore.isGuest) {
page = pageNumber, return ListContainer(
pageSize = DataBatchSize, total = 0,
authorId = authorId page = pageNumber,
) pageSize = DataBatchSize,
} else { list = emptyList()
ApiClient.api.getMyAgent( )
page = pageNumber, }
pageSize = DataBatchSize
)
}
val body = resp.body() ?: throw ServiceException("Failed to get agents") val resp = if (authorId != null) {
ApiClient.api.getAgent(
page = pageNumber,
pageSize = DataBatchSize,
authorId = authorId
)
} else {
ApiClient.api.getMyAgent(
page = pageNumber,
pageSize = DataBatchSize
)
}
// 处理不同的返回类型 val body = resp.body() ?: throw ServiceException("Failed to get agents")
return if (authorId != null) {
// getAgent 返回 DataContainer<ListContainer<Agent>> // 处理不同的返回类型
val dataContainer = body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>> return if (authorId != null) {
val listContainer = dataContainer.data // getAgent 返回 DataContainer<ListContainer<Agent>>
ListContainer( val dataContainer =
total = listContainer.total, body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>>
page = pageNumber, val listContainer = dataContainer.data
pageSize = DataBatchSize, ListContainer(
list = listContainer.list.map { it.toAgentEntity() } total = listContainer.total,
) page = pageNumber,
} else { pageSize = DataBatchSize,
// getMyAgent 返回 ListContainer<Agent> list = listContainer.list.map { it.toAgentEntity() }
val listContainer = body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent> )
ListContainer( } else {
total = listContainer.total, // getMyAgent 返回 ListContainer<Agent>
page = pageNumber, val listContainer =
pageSize = DataBatchSize, body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
list = listContainer.list.map { it.toAgentEntity() } ListContainer(
) total = listContainer.total,
} page = pageNumber,
pageSize = DataBatchSize,
list = listContainer.list.map { it.toAgentEntity() }
)
} }
} }
}
data class AgentEntity( data class AgentEntity(
//val author: String, //val author: String,
@@ -172,15 +202,27 @@ fun createMultipartBody(file: File, filename: String, name: String): MultipartBo
val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull()) val requestFile = file.asRequestBody("image/*".toMediaTypeOrNull())
return MultipartBody.Part.createFormData(name, filename, requestFile) return MultipartBody.Part.createFormData(name, filename, requestFile)
} }
class AgentLoaderExtraArgs( class AgentLoaderExtraArgs(
val authorId: Int? = null val authorId: Int? = null
) )
class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
class AgentLoader : DataLoader<AgentEntity, AgentLoaderExtraArgs>() {
override suspend fun fetchData( override suspend fun fetchData(
page: Int, page: Int,
pageSize: Int, pageSize: Int,
extra: AgentLoaderExtraArgs extra: AgentLoaderExtraArgs
): ListContainer<AgentEntity> { ): ListContainer<AgentEntity> {
// 如果是游客模式且获取我的AgentauthorId为null返回空列表
if (extra.authorId == null && AppStore.isGuest) {
return ListContainer(
total = 0,
page = page,
pageSize = pageSize,
list = emptyList()
)
}
val result = if (extra.authorId != null) { val result = if (extra.authorId != null) {
ApiClient.api.getAgent( ApiClient.api.getAgent(
page = page, page = page,
@@ -201,16 +243,17 @@ class AgentLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
val dataContainer = body as DataContainer<ListContainer<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() },
total = listContainer.total, total = listContainer.total,
page = page, page = page,
pageSize = pageSize pageSize = pageSize
) )
} else { } else {
// getMyAgent 返回 ListContainer<Agent> // getMyAgent 返回 ListContainer<Agent>
val listContainer = body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent> val listContainer =
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
ListContainer( ListContainer(
list = listContainer.list.map { it.toAgentEntity()}, list = listContainer.list.map { it.toAgentEntity() },
total = listContainer.total, total = listContainer.total,
page = page, page = page,
pageSize = pageSize pageSize = pageSize

View File

@@ -12,6 +12,7 @@ object AppStore {
private const val PREFS_NAME = "app_prefs_$STORE_VERSION" private const val PREFS_NAME = "app_prefs_$STORE_VERSION"
var token: String? = null var token: String? = null
var rememberMe: Boolean = false var rememberMe: Boolean = false
var isGuest: Boolean = false
private lateinit var sharedPreferences: SharedPreferences private lateinit var sharedPreferences: SharedPreferences
lateinit var googleSignInOptions: GoogleSignInOptions lateinit var googleSignInOptions: GoogleSignInOptions
fun init(context: Context) { fun init(context: Context) {
@@ -36,6 +37,7 @@ object AppStore {
sharedPreferences.edit().apply { sharedPreferences.edit().apply {
putString("token", token) putString("token", token)
putBoolean("rememberMe", rememberMe) putBoolean("rememberMe", rememberMe)
putBoolean("isGuest", isGuest)
}.apply() }.apply()
} }
@@ -43,6 +45,7 @@ object AppStore {
// shared preferences // shared preferences
token = sharedPreferences.getString("token", null) token = sharedPreferences.getString("token", null)
rememberMe = sharedPreferences.getBoolean("rememberMe", false) rememberMe = sharedPreferences.getBoolean("rememberMe", false)
isGuest = sharedPreferences.getBoolean("isGuest", false)
} }
fun saveDarkMode(darkMode: Boolean) { fun saveDarkMode(darkMode: Boolean) {

View File

@@ -59,6 +59,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.Messaging import com.aiosman.ravenow.Messaging
@@ -238,10 +240,14 @@ fun IndexScreen() {
modifier = Modifier.noRippleClickable { modifier = Modifier.noRippleClickable {
coroutineScope.launch { coroutineScope.launch {
drawerState.close() drawerState.close()
Messaging.unregisterDevice(context) // 只有非游客用户才需要取消注册推送设备
if (!AppStore.isGuest) {
Messaging.unregisterDevice(context)
}
AppStore.apply { AppStore.apply {
token = null token = null
rememberMe = false rememberMe = false
isGuest = false // 清除游客状态
saveData() saveData()
} }
// 删除推送渠道 // 删除推送渠道
@@ -280,10 +286,32 @@ fun IndexScreen() {
.padding(top = 2.dp) .padding(top = 2.dp)
.noRippleClickable { .noRippleClickable {
if (it.route === NavigationItem.Add.route) { if (it.route === NavigationItem.Add.route) {
// 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_POST)) {
navController.navigate(NavigationRoute.Login.route)
return@noRippleClickable
}
NewPostViewModel.asNewPost() NewPostViewModel.asNewPost()
navController.navigate(NavigationRoute.NewPost.route) navController.navigate(NavigationRoute.NewPost.route)
return@noRippleClickable return@noRippleClickable
} }
// 检查消息tab的游客模式
if (it.route === NavigationItem.Notification.route) {
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.VIEW_MESSAGES)) {
navController.navigate(NavigationRoute.Login.route)
return@noRippleClickable
}
}
// 检查我的tab的游客模式
if (it.route === NavigationItem.Profile.route) {
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.VIEW_PROFILE)) {
navController.navigate(NavigationRoute.Login.route)
return@noRippleClickable
}
}
coroutineScope.launch { coroutineScope.launch {
pagerState.scrollToPage(idx) pagerState.scrollToPage(idx)
} }

View File

@@ -46,6 +46,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@@ -70,7 +73,9 @@ 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()
var pagerState = rememberPagerState { 2 } // 游客模式下只显示热门Agent正常用户显示我的Agent和热门Agent
val tabCount = if (AppStore.isGuest) 1 else 2
var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = viewModel()
@@ -137,10 +142,15 @@ fun Agent() {
.size(36.dp) .size(36.dp)
.noRippleClickable { .noRippleClickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
// 导航到添加智能体页面 // 检查游客模式,如果是游客则跳转登录
navController.navigate( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
NavigationRoute.AddAgent.route navController.navigate(NavigationRoute.Login.route)
) } else {
// 导航到添加智能体页面
navController.navigate(
NavigationRoute.AddAgent.route
)
}
}) { }) {
lastClickTime = System.currentTimeMillis() lastClickTime = System.currentTimeMillis()
} }
@@ -205,27 +215,33 @@ fun Agent() {
color = AppColors.text color = AppColors.text
) )
Spacer(modifier = Modifier.weight(1f)) Spacer(modifier = Modifier.weight(1f))
TabItem(
text = stringResource(R.string.agent_mine), // 只有非游客用户才显示"我的Agent"tab
isSelected = pagerState.currentPage == 0, if (!AppStore.isGuest) {
onClick = { TabItem(
if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) { text = stringResource(R.string.agent_mine),
scope.launch { isSelected = pagerState.currentPage == 0,
pagerState.animateScrollToPage(0) onClick = {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) {
scope.launch {
pagerState.animateScrollToPage(0)
}
}) {
lastClickTime = System.currentTimeMillis()
} }
}) {
lastClickTime = System.currentTimeMillis()
} }
} )
) TabSpacer()
TabSpacer() }
TabItem( TabItem(
text = stringResource(R.string.agent_hot), text = stringResource(R.string.agent_hot),
isSelected = pagerState.currentPage == 1, isSelected = if (AppStore.isGuest) pagerState.currentPage == 0 else pagerState.currentPage == 1,
onClick = { onClick = {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 300L) {
scope.launch { scope.launch {
pagerState.animateScrollToPage(1) val targetPage = if (AppStore.isGuest) 0 else 1
pagerState.animateScrollToPage(targetPage)
} }
}) { }) {
lastClickTime = System.currentTimeMillis() lastClickTime = System.currentTimeMillis()
@@ -261,23 +277,24 @@ fun Agent() {
.weight(1f), .weight(1f),
beyondBoundsPageCount = 1 // 预加载相邻页面,避免切换时重新加载 beyondBoundsPageCount = 1 // 预加载相邻页面,避免切换时重新加载
) { ) {
when (it) { if (AppStore.isGuest) {
0 -> { // 游客模式下只显示热门Agent
MineAgent() when (it) {
0 -> {
HotAgent()
}
} }
} else {
// 正常用户显示我的Agent和热门Agent
when (it) {
0 -> {
MineAgent()
}
1 -> { 1 -> {
HotAgent() HotAgent()
}
} }
2 -> {
}
3 -> {
}
} }
} }
} }

View File

@@ -39,6 +39,7 @@ import androidx.compose.ui.res.vectorResource
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
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@@ -62,7 +63,9 @@ fun MomentsList() {
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()
var pagerState = rememberPagerState { 4 } // 游客模式下不显示timeline只显示3个tabExplore、Dynamic、Hot
val tabCount = if (AppStore.isGuest) 3 else 4
var pagerState = rememberPagerState { tabCount }
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
Column( Column(
modifier = Modifier modifier = Modifier
@@ -141,36 +144,40 @@ fun MomentsList() {
) )
} }
//关注tab
Spacer(modifier = Modifier.width(16.dp))
Column(
modifier = Modifier
.noRippleClickable {
scope.launch {
pagerState.animateScrollToPage(2)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.index_following),
fontSize = if (pagerState.currentPage == 2)18.sp else 16.sp,
color = if (pagerState.currentPage == 2) AppColors.text else Color(0X993c3c43),
fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp))
Image( // 只有非游客用户才显示"关注"tab
painter = painterResource( if (!AppStore.isGuest) {
if (pagerState.currentPage == 2) R.mipmap.tab_indicator_selected //关注tab
else R.drawable.tab_indicator_unselected Spacer(modifier = Modifier.width(16.dp))
), Column(
contentDescription = "tab indicator",
modifier = Modifier modifier = Modifier
.width(34.dp) .noRippleClickable {
.height(4.dp) scope.launch {
) pagerState.animateScrollToPage(2)
}
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = stringResource(R.string.index_following),
fontSize = if (pagerState.currentPage == 2)18.sp else 16.sp,
color = if (pagerState.currentPage == 2) AppColors.text else Color(0X993c3c43),
fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp))
Image(
painter = painterResource(
if (pagerState.currentPage == 2) R.mipmap.tab_indicator_selected
else R.drawable.tab_indicator_unselected
),
contentDescription = "tab indicator",
modifier = Modifier
.width(34.dp)
.height(4.dp)
)
}
} }
//热门tab //热门tab
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
@@ -178,7 +185,8 @@ fun MomentsList() {
modifier = Modifier modifier = Modifier
.noRippleClickable { .noRippleClickable {
scope.launch { scope.launch {
pagerState.animateScrollToPage(3) val targetPage = if (AppStore.isGuest) 2 else 3
pagerState.animateScrollToPage(targetPage)
} }
}, },
verticalArrangement = Arrangement.Center, verticalArrangement = Arrangement.Center,
@@ -186,14 +194,14 @@ fun MomentsList() {
) { ) {
Text( Text(
text = stringResource(R.string.index_hot), text = stringResource(R.string.index_hot),
fontSize = if (pagerState.currentPage == 3)18.sp else 16.sp, fontSize = if ((AppStore.isGuest && pagerState.currentPage == 2) || (!AppStore.isGuest && pagerState.currentPage == 3)) 18.sp else 16.sp,
color = if (pagerState.currentPage == 3) AppColors.text else Color(0X993c3c43), color = if ((AppStore.isGuest && pagerState.currentPage == 2) || (!AppStore.isGuest && pagerState.currentPage == 3)) AppColors.text else Color(0X993c3c43),
fontWeight = FontWeight.W600) fontWeight = FontWeight.W600)
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
Image( Image(
painter = painterResource( painter = painterResource(
if (pagerState.currentPage == 3) R.mipmap.tab_indicator_selected if ((AppStore.isGuest && pagerState.currentPage == 2) || (!AppStore.isGuest && pagerState.currentPage == 3)) R.mipmap.tab_indicator_selected
else R.drawable.tab_indicator_unselected else R.drawable.tab_indicator_unselected
), ),
contentDescription = "tab indicator", contentDescription = "tab indicator",
@@ -228,22 +236,35 @@ fun MomentsList() {
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
) { ) {
when (it) { if (AppStore.isGuest) {
0 -> { // 游客模式Explore(0), Dynamic(1), Hot(2)
Explore() when (it) {
0 -> {
Explore()
}
1 -> {
Dynamic()
}
2 -> {
HotMomentsList()
}
} }
1 -> { } else {
Dynamic() // 正常用户Explore(0), Dynamic(1), Timeline(2), Hot(3)
when (it) {
0 -> {
Explore()
}
1 -> {
Dynamic()
}
2 -> {
TimelineMomentsList()
}
3 -> {
HotMomentsList()
}
} }
2 -> {
TimelineMomentsList()
}
3 -> {
HotMomentsList()
}
} }
} }
} }

View File

@@ -17,6 +17,10 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.MomentCard import com.aiosman.ravenow.ui.composables.MomentCard
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@@ -28,6 +32,7 @@ import kotlinx.coroutines.launch
fun Dynamic() { fun Dynamic() {
val model = DynamicViewModel val model = DynamicViewModel
var moments = model.moments var moments = model.moments
val navController = LocalNavController.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val state = rememberPullRefreshState(model.refreshing, onRefresh = { val state = rememberPullRefreshState(model.refreshing, onRefresh = {
@@ -73,30 +78,50 @@ fun Dynamic() {
val momentItem = moments[idx] ?: return@items val momentItem = moments[idx] ?: return@items
MomentCard(momentEntity = momentItem, MomentCard(momentEntity = momentItem,
onAddComment = { onAddComment = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
model.onAddComment(momentItem.id) if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
model.onAddComment(momentItem.id)
}
} }
}, },
onLikeClick = { onLikeClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
if (momentItem.liked) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
model.dislikeMoment(momentItem.id) navController.navigate(NavigationRoute.Login.route)
} else { } else {
model.likeMoment(momentItem.id) scope.launch {
if (momentItem.liked) {
model.dislikeMoment(momentItem.id)
} else {
model.likeMoment(momentItem.id)
}
} }
} }
}, },
onFavoriteClick = { onFavoriteClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
if (momentItem.isFavorite) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
model.unfavoriteMoment(momentItem.id) navController.navigate(NavigationRoute.Login.route)
} else { } else {
model.favoriteMoment(momentItem.id) scope.launch {
if (momentItem.isFavorite) {
model.unfavoriteMoment(momentItem.id)
} else {
model.favoriteMoment(momentItem.id)
}
} }
} }
}, },
onFollowClick = { onFollowClick = {
model.followAction(momentItem) // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
navController.navigate(NavigationRoute.Login.route)
} else {
model.followAction(momentItem)
}
}, },
showFollowButton = true showFollowButton = true
) )

View File

@@ -54,6 +54,8 @@ import androidx.compose.ui.res.stringResource
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import com.aiosman.ravenow.AppStore import com.aiosman.ravenow.AppStore
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@@ -236,8 +238,13 @@ fun Explore() {
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
.clickable { .clickable {
viewModel.createSingleChat(agentItem.openId) // 检查游客模式,如果是游客则跳转登录
viewModel.goToChatAi(agentItem.openId, navController = navController) if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
viewModel.createSingleChat(agentItem.openId)
viewModel.goToChatAi(agentItem.openId, navController = navController)
}
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -353,19 +360,24 @@ fun Explore() {
}, },
shape = RoundedCornerShape(12.dp)) shape = RoundedCornerShape(12.dp))
.clickable { .clickable {
// 调用加入房间接口 // 检查游客模式,如果是游客则跳转登录
viewModel.joinRoom( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.JOIN_GROUP_CHAT)) {
trtcId = roomItem.trtcId.toString(), navController.navigate(NavigationRoute.Login.route)
name = roomItem.title, } else {
avatar = roomItem.avatar, // 调用加入房间接口
navController = navController, viewModel.joinRoom(
onSuccess = { trtcId = roomItem.trtcId.toString(),
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() name = roomItem.title,
}, avatar = roomItem.avatar,
onError = { errorMessage -> navController = navController,
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show() onSuccess = {
} Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
) },
onError = { errorMessage ->
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show()
}
)
}
}, },
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -615,19 +627,24 @@ fun Explore() {
shape = RoundedCornerShape(8.dp) shape = RoundedCornerShape(8.dp)
) )
.clickable { .clickable {
// 调用加入房间接口 // 检查游客模式,如果是游客则跳转登录
viewModel.joinRoom( if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.JOIN_GROUP_CHAT)) {
trtcId = bannerItem.trtcId.toString(), navController.navigate(NavigationRoute.Login.route)
name = bannerItem.title, } else {
avatar = bannerItem.avatar, // 调用加入房间接口
navController = navController, viewModel.joinRoom(
onSuccess = { trtcId = bannerItem.trtcId.toString(),
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show() name = bannerItem.title,
}, avatar = bannerItem.avatar,
onError = { errorMessage -> navController = navController,
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show() onSuccess = {
} Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
) },
onError = { errorMessage ->
Toast.makeText(context, enterFailText, Toast.LENGTH_SHORT).show()
}
)
}
}, },
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
@@ -671,7 +688,12 @@ fun Explore() {
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
navController.navigate(NavigationRoute.CreateGroupChat.route) // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.JOIN_GROUP_CHAT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
navController.navigate(NavigationRoute.CreateGroupChat.route)
}
}, },
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@@ -706,8 +728,12 @@ fun Explore() {
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
navController.navigate( // 检查游客模式,如果是游客则跳转登录
NavigationRoute.AddAgent.route) if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
navController.navigate(NavigationRoute.AddAgent.route)
}
}, },
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
@@ -737,8 +763,13 @@ fun Explore() {
Column( Column(
modifier = Modifier modifier = Modifier
.clickable { .clickable {
NewPostViewModel.asNewPost() // 检查游客模式,如果是游客则跳转登录
navController.navigate("NewPost") if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_POST)) {
navController.navigate(NavigationRoute.Login.route)
} else {
NewPostViewModel.asNewPost()
navController.navigate("NewPost")
}
}, },
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {

View File

@@ -58,6 +58,12 @@ object MyProfileViewModel : ViewModel() {
} }
suspend fun loadUserProfile() { suspend fun loadUserProfile() {
// 游客模式下不获取用户资料
if (AppStore.isGuest) {
MyProfileViewModel.profile = null
return
}
val profile = accountService.getMyAccountProfile() val profile = accountService.getMyAccountProfile()
MyProfileViewModel.profile = profile MyProfileViewModel.profile = profile
} }
@@ -71,6 +77,12 @@ object MyProfileViewModel : ViewModel() {
firstLoad = false firstLoad = false
loadUserProfile() loadUserProfile()
refreshing = false refreshing = false
// 游客模式下不加载个人动态和智能体
if (AppStore.isGuest) {
return@launch
}
profile?.let { profile?.let {
try { try {
momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id)) momentLoader.loadData(extra = MomentLoaderExtraArgs(authorId = it.id))
@@ -85,6 +97,12 @@ object MyProfileViewModel : ViewModel() {
} }
fun loadMoreMoment() { fun loadMoreMoment() {
// 游客模式下不加载更多动态
if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "loadMoreMoment: 游客模式下跳过加载更多动态")
return
}
viewModelScope.launch { viewModelScope.launch {
profile?.let { profileData -> profile?.let { profileData ->
try { try {
@@ -100,19 +118,30 @@ object MyProfileViewModel : ViewModel() {
fun logout(context: Context) { fun logout(context: Context) {
viewModelScope.launch { viewModelScope.launch {
Messaging.unregisterDevice(context) // 只有非游客用户才需要取消注册推送设备
if (!AppStore.isGuest) {
Messaging.unregisterDevice(context)
}
AppStore.apply { AppStore.apply {
token = null token = null
rememberMe = false rememberMe = false
isGuest = false // 清除游客状态
saveData() saveData()
} }
// 删除推送渠道 // 删除推送渠道和重置应用状态
AppState.ReloadAppState(context) AppState.ReloadAppState(context)
} }
} }
fun updateUserProfileBanner(bannerImageUrl: Uri?, file: File, context: Context) { fun updateUserProfileBanner(bannerImageUrl: Uri?, file: File, context: Context) {
// 游客模式下不允许更新用户资料
if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "updateUserProfileBanner: 游客模式下无法更新用户资料")
return
}
viewModelScope.launch { viewModelScope.launch {
val newBanner = bannerImageUrl?.let { val newBanner = bannerImageUrl?.let {
val cursor = context.contentResolver.query(it, null, null, null, null) val cursor = context.contentResolver.query(it, null, null, null, null)
@@ -141,6 +170,12 @@ object MyProfileViewModel : ViewModel() {
} }
fun likeMoment(momentLMomentEntity: MomentEntity) { fun likeMoment(momentLMomentEntity: MomentEntity) {
// 游客模式下不允许点赞
if (AppStore.isGuest) {
Log.d("MyProfileViewModel", "likeMoment: 游客模式下无法点赞")
return
}
viewModelScope.launch { viewModelScope.launch {
if (momentLMomentEntity.liked) { if (momentLMomentEntity.liked) {
momentService.dislikeMoment(momentLMomentEntity.id) momentService.dislikeMoment(momentLMomentEntity.id)

View File

@@ -61,6 +61,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.MainActivity import com.aiosman.ravenow.MainActivity

View File

@@ -29,8 +29,12 @@ import androidx.compose.ui.text.font.FontWeight
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.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
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.entity.AccountProfileEntity import com.aiosman.ravenow.entity.AccountProfileEntity
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@@ -41,6 +45,7 @@ fun OtherProfileAction(
onChat: (() -> Unit)? = null onChat: (() -> Unit)? = null
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
// 定义渐变色 // 定义渐变色
val followGradient = Brush.horizontalGradient( val followGradient = Brush.horizontalGradient(
@@ -84,7 +89,12 @@ fun OtherProfileAction(
} }
.padding(horizontal = 16.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onFollow?.invoke() // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
navController.navigate(NavigationRoute.Login.route)
} else {
onFollow?.invoke()
}
} }
) { ) {
Text( Text(
@@ -112,7 +122,12 @@ fun OtherProfileAction(
.background(AppColors.nonActive) // 使用主题灰色背景 .background(AppColors.nonActive) // 使用主题灰色背景
.padding(horizontal = 16.dp, vertical = 12.dp) .padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable { .noRippleClickable {
onChat?.invoke() // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
onChat?.invoke()
}
} }
) { ) {
Text( Text(

View File

@@ -35,8 +35,12 @@ import androidx.compose.ui.text.font.FontWeight
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.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
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.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.utils.DebounceUtils import com.aiosman.ravenow.utils.DebounceUtils
@@ -82,6 +86,7 @@ fun UserAgentCard(
onAvatarClick: (AgentEntity) -> Unit = {} onAvatarClick: (AgentEntity) -> Unit = {}
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
// 防抖状态 // 防抖状态
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
@@ -165,7 +170,12 @@ fun UserAgentCard(
) )
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
onAgentClick(agent) // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
onAgentClick(agent)
}
}) { }) {
lastClickTime = System.currentTimeMillis() lastClickTime = System.currentTimeMillis()
} }

View File

@@ -149,6 +149,7 @@ fun EmailSignupScreen() {
AppStore.apply { AppStore.apply {
token = authResp.token token = authResp.token
this.rememberMe = rememberMe this.rememberMe = rememberMe
isGuest = false // 清除游客状态
saveData() saveData()
} }
// 获取token 信息 // 获取token 信息

View File

@@ -153,6 +153,7 @@ fun LoginPage() {
AppStore.apply { AppStore.apply {
token = authResp.token token = authResp.token
this.rememberMe = true this.rememberMe = true
isGuest = false // 清除游客状态
saveData() saveData()
} }
// 获取token 信息 // 获取token 信息
@@ -180,6 +181,76 @@ fun LoginPage() {
} }
} }
fun guestLogin() {
coroutineScope.launch {
try {
// 生成设备ID
val deviceId = android.provider.Settings.Secure.getString(
context.contentResolver,
android.provider.Settings.Secure.ANDROID_ID
) ?: "unknown_device"
// 获取设备信息
val deviceInfo = "${android.os.Build.MANUFACTURER} ${android.os.Build.MODEL}"
// 调用游客登录API
val authResp = accountService.guestLogin(deviceId, deviceInfo)
// 保存token和游客状态
AppStore.apply {
token = authResp.token
isGuest = true
rememberMe = true
saveData()
}
// 显示成功提示
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(
context,
"游客登录成功",
Toast.LENGTH_SHORT
).show()
}
// 初始化应用状态游客模式会自动跳过推送和TRTC初始化
try {
AppState.initWithAccount(coroutineScope, context)
} catch (e: Exception) {
Log.e(TAG, "Failed to init with guest account", e)
// 游客模式下初始化失败不是致命错误,可以继续
}
// 导航到主页
coroutineScope.launch(Dispatchers.Main) {
navController.navigate(NavigationRoute.Index.route) {
popUpTo(NavigationRoute.Login.route) { inclusive = true }
}
}
} catch (e: ServiceException) {
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(
context,
"游客登录失败: ${e.message}",
Toast.LENGTH_SHORT
).show()
}
Log.e(TAG, "Guest login failed", e)
} catch (e: Exception) {
coroutineScope.launch(Dispatchers.Main) {
Toast.makeText(
context,
"游客登录失败",
Toast.LENGTH_SHORT
).show()
}
Log.e(TAG, "Guest login failed", e)
}
}
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -260,6 +331,16 @@ fun LoginPage() {
NavigationRoute.UserAuth.route, NavigationRoute.UserAuth.route,
) )
} }
// 游客登录按钮
Spacer(modifier = Modifier.height(16.dp))
ActionButton(
modifier = Modifier.fillMaxWidth(),
text = "游客模式",
color = AppColors.text.copy(alpha = 0.7f),
) {
guestLogin()
}
Spacer(modifier = Modifier.height(70.dp)) Spacer(modifier = Modifier.height(70.dp))
} }
} }

View File

@@ -89,6 +89,7 @@ fun SignupScreen() {
AppStore.apply { AppStore.apply {
token = authResp.token token = authResp.token
this.rememberMe = true this.rememberMe = true
isGuest = false // 清除游客状态
saveData() saveData()
} }
// 获取token 信息 // 获取token 信息

View File

@@ -111,6 +111,7 @@ fun UserAuthScreen() {
AppStore.apply { AppStore.apply {
token = authResp.token token = authResp.token
this.rememberMe = rememberMe this.rememberMe = rememberMe
isGuest = false // 清除游客状态
saveData() saveData()
} }
AppState.initWithAccount(scope, context) AppState.initWithAccount(scope, context)
@@ -163,6 +164,7 @@ fun UserAuthScreen() {
AppStore.apply { AppStore.apply {
token = authResp.token token = authResp.token
this.rememberMe = rememberMe this.rememberMe = rememberMe
isGuest = false // 清除游客状态
saveData() saveData()
} }
navController.navigate(NavigationRoute.Index.route) { navController.navigate(NavigationRoute.Index.route) {

View File

@@ -91,6 +91,8 @@ import androidx.paging.LoadState
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.GuestLoginCheckOut
import com.aiosman.ravenow.GuestLoginCheckOutScene
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
@@ -194,27 +196,45 @@ fun PostScreen(
}, },
isSelf = AppState.UserId?.toLong() == contextComment?.author, isSelf = AppState.UserId?.toLong() == contextComment?.author,
onLikeClick = { onLikeClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
commentModalState.hide() if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
showCommentMenu = false scope.launch {
} commentModalState.hide()
contextComment?.let { showCommentMenu = false
viewModel.viewModelScope.launch { }
if (it.liked) { navController.navigate(NavigationRoute.Login.route)
viewModel.unlikeComment(it.id) } else {
} else { scope.launch {
viewModel.likeComment(it.id) commentModalState.hide()
showCommentMenu = false
}
contextComment?.let {
viewModel.viewModelScope.launch {
if (it.liked) {
viewModel.unlikeComment(it.id)
} else {
viewModel.likeComment(it.id)
}
} }
} }
} }
}, },
onReplyClick = { onReplyClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
commentModalState.hide() if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
showCommentMenu = false scope.launch {
replyComment = contextComment commentModalState.hide()
showCommentModal = true showCommentMenu = false
}
navController.navigate(NavigationRoute.Login.route)
} else {
scope.launch {
commentModalState.hide()
showCommentMenu = false
replyComment = contextComment
showCommentModal = true
}
} }
} }
) )
@@ -293,24 +313,39 @@ fun PostScreen(
if (!viewModel.isError) { if (!viewModel.isError) {
PostBottomBar( PostBottomBar(
onLikeClick = { onLikeClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
if (viewModel.moment?.liked == true) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
viewModel.dislikeMoment() navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.likeMoment() scope.launch {
if (viewModel.moment?.liked == true) {
viewModel.dislikeMoment()
} else {
viewModel.likeMoment()
}
} }
} }
}, },
onCreateCommentClick = { onCreateCommentClick = {
replyComment = null // 检查游客模式,如果是游客则跳转登录
showCommentModal = true if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
replyComment = null
showCommentModal = true
}
}, },
onFavoriteClick = { onFavoriteClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
if (viewModel.moment?.isFavorite == true) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
viewModel.unfavoriteMoment() navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.favoriteMoment() scope.launch {
if (viewModel.moment?.isFavorite == true) {
viewModel.unfavoriteMoment()
} else {
viewModel.favoriteMoment()
}
} }
} }
}, },
@@ -356,11 +391,16 @@ fun PostScreen(
userId = viewModel.moment?.authorId, userId = viewModel.moment?.authorId,
isFollowing = viewModel.moment?.followStatus == true, isFollowing = viewModel.moment?.followStatus == true,
onFollowClick = { onFollowClick = {
scope.launch { // 检查游客模式,如果是游客则跳转登录
if (viewModel.moment?.followStatus == true) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
viewModel.unfollowUser() navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.followUser() scope.launch {
if (viewModel.moment?.followStatus == true) {
viewModel.unfollowUser()
} else {
viewModel.followUser()
}
} }
} }
}, },
@@ -370,7 +410,12 @@ fun PostScreen(
} }
}, },
onReportClick = { onReportClick = {
showReportDialog = true // 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.REPORT_CONTENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
showReportDialog = true
}
} }
) )
LazyColumn( LazyColumn(
@@ -430,8 +475,13 @@ fun PostScreen(
contextComment = comment contextComment = comment
}, },
onReply = { parentComment, _, _, _ -> onReply = { parentComment, _, _, _ ->
replyComment = parentComment // 检查游客模式,如果是游客则跳转登录
showCommentModal = true if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
replyComment = parentComment
showCommentModal = true
}
} }
) )
} }
@@ -454,6 +504,7 @@ fun CommentContent(
onReply: (CommentEntity, Long?, String?, String?) -> Unit onReply: (CommentEntity, Long?, String?, String?) -> Unit
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val commentsPagging = viewModel.commentsFlow.collectAsLazyPagingItems() val commentsPagging = viewModel.commentsFlow.collectAsLazyPagingItems()
val addedTopLevelComment = viewModel.addedCommentList.filter { val addedTopLevelComment = viewModel.addedCommentList.filter {
@@ -468,11 +519,16 @@ fun CommentContent(
CommentItem( CommentItem(
it, it,
onLike = { comment -> onLike = { comment ->
viewModel.viewModelScope.launch { // 检查游客模式,如果是游客则跳转登录
if (comment.liked) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
viewModel.unlikeComment(comment.id) navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.likeComment(comment.id) viewModel.viewModelScope.launch {
if (comment.liked) {
viewModel.unlikeComment(comment.id)
} else {
viewModel.likeComment(comment.id)
}
} }
} }
}, },
@@ -480,12 +536,17 @@ fun CommentContent(
onLongClick(comment) onLongClick(comment)
}, },
onReply = { parentComment, _, _, _ -> onReply = { parentComment, _, _, _ ->
onReply( // 检查游客模式,如果是游客则跳转登录
parentComment, if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
parentComment.author, navController.navigate(NavigationRoute.Login.route)
parentComment.name, } else {
parentComment.avatar onReply(
) parentComment,
parentComment.author,
parentComment.name,
parentComment.avatar
)
}
}, },
onLoadMoreSubComments = { onLoadMoreSubComments = {
viewModel.viewModelScope.launch { viewModel.viewModelScope.launch {
@@ -512,11 +573,16 @@ fun CommentContent(
CommentItem( CommentItem(
item, item,
onLike = { comment -> onLike = { comment ->
viewModel.viewModelScope.launch { // 检查游客模式,如果是游客则跳转登录
if (comment.liked) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
viewModel.unlikeComment(comment.id) navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.likeComment(comment.id) viewModel.viewModelScope.launch {
if (comment.liked) {
viewModel.unlikeComment(comment.id)
} else {
viewModel.likeComment(comment.id)
}
} }
} }
}, },
@@ -560,11 +626,16 @@ fun CommentContent(
CommentItem( CommentItem(
item, item,
onLike = { comment -> onLike = { comment ->
viewModel.viewModelScope.launch { // 检查游客模式,如果是游客则跳转登录
if (comment.liked) { if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
viewModel.unlikeComment(comment.id) navController.navigate(NavigationRoute.Login.route)
} else { } else {
viewModel.likeComment(comment.id) viewModel.viewModelScope.launch {
if (comment.liked) {
viewModel.unlikeComment(comment.id)
} else {
viewModel.likeComment(comment.id)
}
} }
} }
}, },
@@ -572,12 +643,17 @@ fun CommentContent(
onLongClick(comment) onLongClick(comment)
}, },
onReply = { parentComment, _, _, _ -> onReply = { parentComment, _, _, _ ->
onReply( // 检查游客模式,如果是游客则跳转登录
parentComment, if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
parentComment.author, navController.navigate(NavigationRoute.Login.route)
parentComment.name, } else {
parentComment.avatar onReply(
) parentComment,
parentComment.author,
parentComment.name,
parentComment.avatar
)
}
}, },
onLoadMoreSubComments = { onLoadMoreSubComments = {
viewModel.viewModelScope.launch { viewModel.viewModelScope.launch {