Compare commits
3 Commits
new_create
...
update_age
| Author | SHA1 | Date | |
|---|---|---|---|
| a215d79ce8 | |||
| 0e0d622864 | |||
| a1196715d0 |
@@ -4,11 +4,12 @@ object ConstVars {
|
||||
// api 地址 - 根据构建类型自动选择
|
||||
// Debug: http://192.168.0.201:8088
|
||||
// Release: https://rider-pro.aiosman.com/beta_api
|
||||
val BASE_SERVER = if (BuildConfig.DEBUG) {
|
||||
"http://47.109.137.67:6363" // Debug环境
|
||||
} else {
|
||||
"https://rider-pro.aiosman.com/beta_api" // Release环境
|
||||
}
|
||||
// val BASE_SERVER = if (!BuildConfig.DEBUG) {
|
||||
// "http://47.109.137.67:6363" // Debug环境
|
||||
// } else {
|
||||
// "https://rider-pro.aiosman.com/beta_api" // Release环境
|
||||
// }
|
||||
val BASE_SERVER = "https://rider-pro.aiosman.com/beta_api"
|
||||
|
||||
const val MOMENT_LIKE_CHANNEL_ID = "moment_like"
|
||||
const val MOMENT_LIKE_CHANNEL_NAME = "Moment Like"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.aiosman.ravenow.data
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
/**
|
||||
* 分类翻译数据模型
|
||||
*/
|
||||
data class CategoryTranslation(
|
||||
@SerializedName("name")
|
||||
val name: String,
|
||||
@SerializedName("description")
|
||||
val description: String
|
||||
)
|
||||
|
||||
/**
|
||||
* 分类翻译集合
|
||||
*/
|
||||
data class CategoryTranslations(
|
||||
@SerializedName("ja")
|
||||
val ja: CategoryTranslation? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* 分类数据模型
|
||||
*/
|
||||
data class Category(
|
||||
@SerializedName("id")
|
||||
val id: Int,
|
||||
@SerializedName("name")
|
||||
val name: String,
|
||||
@SerializedName("description")
|
||||
val description: String,
|
||||
@SerializedName("avatar")
|
||||
val avatar: String,
|
||||
@SerializedName("parentId")
|
||||
val parentId: Int? = null,
|
||||
@SerializedName("sort")
|
||||
val sort: Int,
|
||||
@SerializedName("isActive")
|
||||
val isActive: Boolean,
|
||||
@SerializedName("promptCount")
|
||||
val promptCount: Int,
|
||||
@SerializedName("createdAt")
|
||||
val createdAt: String,
|
||||
@SerializedName("updatedAt")
|
||||
val updatedAt: String,
|
||||
@SerializedName("translations")
|
||||
val translations: CategoryTranslations? = null
|
||||
)
|
||||
@@ -32,6 +32,28 @@ data class Moment(
|
||||
val time: String,
|
||||
@SerializedName("isFollowed")
|
||||
val isFollowed: Boolean,
|
||||
@SerializedName("isNews")
|
||||
val isNews: Boolean? = null,
|
||||
@SerializedName("newsTitle")
|
||||
val newsTitle: String? = null,
|
||||
@SerializedName("newsUrl")
|
||||
val newsUrl: String? = null,
|
||||
@SerializedName("newsSource")
|
||||
val newsSource: String? = null,
|
||||
@SerializedName("newsCategory")
|
||||
val newsCategory: String? = null,
|
||||
@SerializedName("newsLanguage")
|
||||
val newsLanguage: String? = null,
|
||||
@SerializedName("newsContent")
|
||||
val newsContent: String? = null,
|
||||
@SerializedName("hasFullText")
|
||||
val hasFullText: Boolean? = null,
|
||||
@SerializedName("summary")
|
||||
val summary: String? = null,
|
||||
@SerializedName("publishedAt")
|
||||
val publishedAt: String? = null,
|
||||
@SerializedName("imageCached")
|
||||
val imageCached: Boolean? = null,
|
||||
) {
|
||||
fun toMomentItem(): MomentEntity {
|
||||
return MomentEntity(
|
||||
@@ -60,6 +82,17 @@ data class Moment(
|
||||
authorId = user.id.toInt(),
|
||||
liked = isLiked,
|
||||
isFavorite = isFavorite,
|
||||
isNews = isNews,
|
||||
newsTitle = newsTitle,
|
||||
newsUrl = newsUrl,
|
||||
newsSource = newsSource,
|
||||
newsCategory = newsCategory,
|
||||
newsLanguage = newsLanguage,
|
||||
newsContent = newsContent,
|
||||
hasFullText = hasFullText,
|
||||
summary = summary,
|
||||
publishedAt = publishedAt,
|
||||
imageCached = imageCached,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import com.aiosman.ravenow.data.AccountLike
|
||||
import com.aiosman.ravenow.data.AccountNotice
|
||||
import com.aiosman.ravenow.data.AccountProfile
|
||||
import com.aiosman.ravenow.data.Agent
|
||||
import com.aiosman.ravenow.data.Category
|
||||
import com.aiosman.ravenow.data.Comment
|
||||
import com.aiosman.ravenow.data.DataContainer
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
@@ -316,6 +317,7 @@ interface RaveNowAPI {
|
||||
suspend fun getPosts(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("id") postId: Int? = null,
|
||||
@Query("timelineId") timelineId: Int? = null,
|
||||
@Query("authorId") authorId: Int? = null,
|
||||
@Query("contentSearch") contentSearch: String? = null,
|
||||
@@ -323,6 +325,15 @@ interface RaveNowAPI {
|
||||
@Query("trend") trend: String? = null,
|
||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||
@Query("explore") explore: String? = null,
|
||||
@Query("imageTag") imageTag: String? = null,
|
||||
@Query("search") search: String? = null,
|
||||
@Query("advancedSearch") advancedSearch: String? = null,
|
||||
@Query("newsFilter") newsFilter: String? = null,
|
||||
@Query("onlyNews") onlyNews: Boolean? = null,
|
||||
@Query("newsSource") newsSource: String? = null,
|
||||
@Query("newsLanguage") newsLanguage: String? = null,
|
||||
@Query("newsCategory") newsCategory: String? = null,
|
||||
@Query("requireImageCache") requireImageCache: Boolean? = null,
|
||||
): Response<ListContainer<Moment>>
|
||||
|
||||
@Multipart
|
||||
@@ -560,19 +571,39 @@ interface RaveNowAPI {
|
||||
@Body body: RemoveAccountRequestBody
|
||||
): Response<Unit>
|
||||
|
||||
|
||||
|
||||
@GET("outside/prompts")
|
||||
suspend fun getAgent(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("withWorkflow") withWorkflow: Int = 1,
|
||||
@Query("order") order: String? = null,
|
||||
@Query("orderKey") orderKey: String? = null,
|
||||
@Query("createdAt") createdAt: String? = null,
|
||||
@Query("updatedAt") updatedAt: String? = null,
|
||||
@Query("createdStart") createdStart: String? = null,
|
||||
@Query("createdEnd") createdEnd: String? = null,
|
||||
@Query("updatedStart") updatedStart: String? = null,
|
||||
@Query("updatedEnd") updatedEnd: String? = null,
|
||||
@Query("title") title: String? = null,
|
||||
@Query("authorId") authorId: Int? = null,
|
||||
@Query("authorOpenId") authorOpenId: String? = null,
|
||||
@Query("showPrivate") showPrivate: String? = null,
|
||||
@Query("explore") explore: String? = null,
|
||||
@Query("desc") desc: String? = null,
|
||||
@Query("withWorkflow") withWorkflow: String? = null,
|
||||
@Query("hasAvatar") hasAvatar: String? = null,
|
||||
@Query("random") random: String? = null,
|
||||
@Query("categoryName") categoryName: String? = null,
|
||||
@Query("categoryIds") categoryIds: List<Int>? = null,
|
||||
@Query("uncategorized") uncategorized: String? = null,
|
||||
): Response<DataContainer<ListContainer<Agent>>>
|
||||
|
||||
@GET("outside/my/prompts")
|
||||
suspend fun getMyAgent(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("withWorkflow") withWorkflow: Int = 1,
|
||||
@Query("withWorkflow") withWorkflow: String = "1",
|
||||
): Response<ListContainer<Agent>>
|
||||
|
||||
@Multipart
|
||||
@@ -609,6 +640,7 @@ interface RaveNowAPI {
|
||||
suspend fun getRooms(@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("isRecommended") isRecommended: Int = 1,
|
||||
@Query("random") random: Int? = null
|
||||
): Response<ListContainer<Room>>
|
||||
|
||||
@GET("outside/rooms/detail")
|
||||
@@ -622,5 +654,12 @@ interface RaveNowAPI {
|
||||
@POST("outside/generate/agent-info")
|
||||
suspend fun generateAgentInfo(@Body body: GenerateAgentInfoRequestBody): Response<DataContainer<GenerateAgentInfoResponseBody>>
|
||||
|
||||
@GET("outside/categories")
|
||||
suspend fun getCategories(
|
||||
@Query("page") page: Int = 1,
|
||||
@Query("pageSize") pageSize: Int = 20,
|
||||
@Query("lang") lang: String? = null
|
||||
): Response<ListContainer<Category>>
|
||||
|
||||
}
|
||||
|
||||
|
||||
160
app/src/main/java/com/aiosman/ravenow/entity/Category.kt
Normal file
160
app/src/main/java/com/aiosman/ravenow/entity/Category.kt
Normal file
@@ -0,0 +1,160 @@
|
||||
package com.aiosman.ravenow.entity
|
||||
|
||||
import androidx.paging.PagingSource
|
||||
import androidx.paging.PagingState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.data.Category
|
||||
import com.aiosman.ravenow.data.CategoryTranslation
|
||||
import com.aiosman.ravenow.data.CategoryTranslations
|
||||
import com.aiosman.ravenow.data.ListContainer
|
||||
import com.aiosman.ravenow.data.ServiceException
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.utils.Utils
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
* 分类实体类
|
||||
*/
|
||||
data class CategoryEntity(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val avatar: String,
|
||||
val parentId: Int? = null,
|
||||
val sort: Int,
|
||||
val isActive: Boolean,
|
||||
val promptCount: Int,
|
||||
val createdAt: String,
|
||||
val updatedAt: String,
|
||||
val translations: CategoryTranslations? = null
|
||||
) {
|
||||
/**
|
||||
* 获取本地化名称
|
||||
*/
|
||||
fun getLocalizedName(): String {
|
||||
return when (Utils.getCurrentLanguage()) {
|
||||
"ja" -> translations?.ja?.name ?: name
|
||||
else -> name
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取本地化描述
|
||||
*/
|
||||
fun getLocalizedDescription(): String {
|
||||
return when (Utils.getCurrentLanguage()) {
|
||||
"ja" -> translations?.ja?.description ?: description
|
||||
else -> description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Category 数据模型扩展函数
|
||||
*/
|
||||
fun Category.toCategoryEntity(): CategoryEntity {
|
||||
return CategoryEntity(
|
||||
id = id,
|
||||
name = name,
|
||||
description = description,
|
||||
avatar = if (avatar.isNotEmpty()) "${ApiClient.BASE_API_URL}/outside$avatar?token=${AppStore.token}" else "",
|
||||
parentId = parentId,
|
||||
sort = sort,
|
||||
isActive = isActive,
|
||||
promptCount = promptCount,
|
||||
createdAt = createdAt,
|
||||
updatedAt = updatedAt,
|
||||
translations = translations
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类数据后端服务
|
||||
*/
|
||||
class CategoryBackend {
|
||||
private val DataBatchSize = 20
|
||||
|
||||
suspend fun getCategories(pageNumber: Int): ListContainer<CategoryEntity>? {
|
||||
try {
|
||||
val resp = ApiClient.api.getCategories(
|
||||
page = pageNumber,
|
||||
pageSize = DataBatchSize,
|
||||
lang = Utils.getCurrentLanguageTag()
|
||||
)
|
||||
|
||||
if (!resp.isSuccessful) {
|
||||
throw ServiceException("获取分类失败: ${resp.code()}")
|
||||
}
|
||||
|
||||
val body = resp.body() ?: return null
|
||||
|
||||
return ListContainer(
|
||||
total = body.total,
|
||||
page = pageNumber,
|
||||
pageSize = DataBatchSize,
|
||||
list = body.list.map { it.toCategoryEntity() }
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
throw ServiceException("网络请求失败: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类数据加载器参数
|
||||
*/
|
||||
class CategoryLoaderExtraArgs
|
||||
|
||||
/**
|
||||
* 分类数据加载器
|
||||
*/
|
||||
class CategoryLoader : DataLoader<CategoryEntity, CategoryLoaderExtraArgs>() {
|
||||
override suspend fun fetchData(
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
extra: CategoryLoaderExtraArgs
|
||||
): ListContainer<CategoryEntity> {
|
||||
val backend = CategoryBackend()
|
||||
return backend.getCategories(page) ?: ListContainer(
|
||||
total = 0,
|
||||
page = page,
|
||||
pageSize = pageSize,
|
||||
list = emptyList()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分类分页数据源
|
||||
*/
|
||||
class CategoryPagingSource(
|
||||
private val backend: CategoryBackend
|
||||
) : PagingSource<Int, CategoryEntity>() {
|
||||
|
||||
override fun getRefreshKey(state: PagingState<Int, CategoryEntity>): Int? {
|
||||
return state.anchorPosition?.let { anchorPosition ->
|
||||
state.closestPageToPosition(anchorPosition)?.prevKey?.plus(1)
|
||||
?: state.closestPageToPosition(anchorPosition)?.nextKey?.minus(1)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, CategoryEntity> {
|
||||
return try {
|
||||
val page = params.key ?: 1
|
||||
val response = backend.getCategories(page)
|
||||
|
||||
if (response == null) {
|
||||
LoadResult.Error(IOException("获取分类数据失败"))
|
||||
} else {
|
||||
val hasMore = response.list.size == response.pageSize
|
||||
LoadResult.Page(
|
||||
data = response.list,
|
||||
prevKey = if (page == 1) null else page - 1,
|
||||
nextKey = if (hasMore) page + 1 else null
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
LoadResult.Error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -299,12 +299,35 @@ data class MomentEntity(
|
||||
// 关联动态
|
||||
var relMoment: MomentEntity? = null,
|
||||
// 是否收藏
|
||||
var isFavorite: Boolean = false
|
||||
var isFavorite: Boolean = false,
|
||||
// 是否为新闻
|
||||
val isNews: Boolean? = null,
|
||||
// 新闻标题
|
||||
val newsTitle: String? = null,
|
||||
// 新闻链接
|
||||
val newsUrl: String? = null,
|
||||
// 新闻来源
|
||||
val newsSource: String? = null,
|
||||
// 新闻分类
|
||||
val newsCategory: String? = null,
|
||||
// 新闻语言
|
||||
val newsLanguage: String? = null,
|
||||
// 新闻内容
|
||||
val newsContent: String? = null,
|
||||
// 是否有完整文本
|
||||
val hasFullText: Boolean? = null,
|
||||
// 摘要
|
||||
val summary: String? = null,
|
||||
// 发布时间
|
||||
val publishedAt: String? = null,
|
||||
// 图片是否已缓存
|
||||
val imageCached: Boolean? = null
|
||||
)
|
||||
class MomentLoaderExtraArgs(
|
||||
val explore: Boolean? = false,
|
||||
val timelineId: Int? = null,
|
||||
val authorId : Int? = null
|
||||
val authorId : Int? = null,
|
||||
val newsOnly: Boolean? = false
|
||||
)
|
||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
override suspend fun fetchData(
|
||||
@@ -317,7 +340,8 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||
pageSize = pageSize,
|
||||
explore = if (extra.explore == true) "true" else "",
|
||||
timelineId = extra.timelineId,
|
||||
authorId = extra.authorId
|
||||
authorId = extra.authorId,
|
||||
newsFilter = if (extra.newsOnly == true) "news_only" else ""
|
||||
)
|
||||
val data = result.body()?.let {
|
||||
ListContainer(
|
||||
|
||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.WindowInsets
|
||||
import androidx.compose.foundation.layout.asPaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
@@ -36,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@@ -69,13 +71,11 @@ 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.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.ui.zIndex
|
||||
|
||||
@OptIn( ExperimentalFoundationApi::class)
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun Agent() {
|
||||
val AppColors = LocalAppTheme.current
|
||||
@@ -95,10 +95,22 @@ fun Agent() {
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.ensureDataLoaded()
|
||||
}
|
||||
|
||||
|
||||
// 监听滚动状态,实现自动加载更多
|
||||
LaunchedEffect(scrollState) {
|
||||
snapshotFlow { scrollState.value }
|
||||
.collect { scrollValue ->
|
||||
val maxScroll = scrollState.maxValue
|
||||
if (scrollValue >= maxScroll - 100 && !viewModel.isLoading) {
|
||||
// 滚动到接近底部时加载更多
|
||||
viewModel.loadMoreAgentData()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
|
||||
// 页面退出时只清理必要的资源,不清理推荐Agent数据
|
||||
DisposableEffect(Unit) {
|
||||
onDispose {
|
||||
@@ -107,49 +119,61 @@ fun Agent() {
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(
|
||||
top = statusBarPaddingValues.calculateTopPadding(),
|
||||
bottom = navigationBarPaddings,
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Row(
|
||||
// 固定顶部搜索条
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.wrapContentHeight()
|
||||
.height(44.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.fillMaxWidth()
|
||||
.background(AppColors.background)
|
||||
.zIndex(999.0f)
|
||||
.height(44.dp + statusBarPaddingValues.calculateTopPadding())
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = "Rave AI",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W900,
|
||||
color = AppColors.text,
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight().padding(top = 32.dp, start = 16.dp, end = 16.dp),
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = "Rave AI",
|
||||
fontSize = 20.sp,
|
||||
fontWeight = FontWeight.W900,
|
||||
color = AppColors.text,
|
||||
modifier = Modifier
|
||||
.align(Alignment.CenterVertically)
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
||||
contentDescription = "search",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
navController.navigate(NavigationRoute.Search.route)
|
||||
},
|
||||
colorFilter = ColorFilter.tint(AppColors.text)
|
||||
)
|
||||
Image(
|
||||
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
||||
contentDescription = "search",
|
||||
modifier = Modifier
|
||||
.size(24.dp)
|
||||
.noRippleClickable {
|
||||
navController.navigate(NavigationRoute.Search.route)
|
||||
},
|
||||
colorFilter = ColorFilter.tint(AppColors.text)
|
||||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.height(15.dp))
|
||||
|
||||
// 可滚动的内容区域
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.verticalScroll(scrollState)
|
||||
.padding(
|
||||
top = 44.dp + statusBarPaddingValues.calculateTopPadding() + 15.dp,
|
||||
bottom = navigationBarPaddings,
|
||||
start = 16.dp,
|
||||
end = 16.dp
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
// // 搜索框
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
@@ -229,9 +253,10 @@ fun Agent() {
|
||||
// )
|
||||
// }
|
||||
|
||||
var selectedTabIndex by remember { mutableStateOf(0) }
|
||||
// 使用 ViewModel 中的选中状态
|
||||
val selectedTabIndex = viewModel.selectedCategoryIndex
|
||||
|
||||
// 标签页
|
||||
// 动态标签页
|
||||
LazyRow(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -240,134 +265,129 @@ fun Agent() {
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = stringResource(R.string.agent_recommend),
|
||||
isSelected = selectedTabIndex == 0,
|
||||
onClick = {
|
||||
selectedTabIndex = 0
|
||||
viewModel.categories.forEachIndexed { index, category ->
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = category.getLocalizedName(),
|
||||
isSelected = selectedTabIndex == index,
|
||||
onClick = {
|
||||
viewModel.selectCategory(index)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
if (index < viewModel.categories.size - 1) {
|
||||
item {
|
||||
TabSpacer()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TabSpacer()
|
||||
}
|
||||
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = "scenes",
|
||||
isSelected = selectedTabIndex == 1,
|
||||
onClick = {
|
||||
selectedTabIndex = 1
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TabSpacer()
|
||||
}
|
||||
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = "voices",
|
||||
isSelected = selectedTabIndex == 2,
|
||||
onClick = {
|
||||
selectedTabIndex = 2
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TabSpacer()
|
||||
}
|
||||
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = "anime",
|
||||
isSelected = selectedTabIndex == 3,
|
||||
onClick = {
|
||||
selectedTabIndex = 3
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TabSpacer()
|
||||
}
|
||||
|
||||
item {
|
||||
CustomTabItem(
|
||||
text = "assist",
|
||||
isSelected = selectedTabIndex == 4,
|
||||
onClick = {
|
||||
selectedTabIndex = 4
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when (selectedTabIndex) {
|
||||
0 -> {
|
||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
||||
}
|
||||
else -> {
|
||||
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
|
||||
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
|
||||
}
|
||||
}
|
||||
// 显示当前选中分类的 Agent 数据
|
||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
// center the tabs
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent2),
|
||||
contentDescription = "agent",
|
||||
modifier = Modifier.size(28.dp),
|
||||
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.agent_find),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||
color = AppColors.text
|
||||
// 推荐聊天房间
|
||||
ChatRoomsSection(
|
||||
chatRooms = viewModel.chatRooms,
|
||||
navController = LocalNavController.current
|
||||
)
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.height(50.dp))
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
) {
|
||||
val agentItems = viewModel.agentItems.take(15)
|
||||
LazyVerticalGrid(
|
||||
columns = GridCells.Fixed(2),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(50.dp)
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.wrapContentHeight(),
|
||||
// center the tabs
|
||||
horizontalArrangement = Arrangement.Start,
|
||||
verticalAlignment = Alignment.Bottom
|
||||
) {
|
||||
items(agentItems) { agentItem ->
|
||||
AgentCardSquare(
|
||||
agentItem = agentItem,
|
||||
viewModel = viewModel,
|
||||
navController = LocalNavController.current
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent2),
|
||||
contentDescription = "agent",
|
||||
modifier = Modifier.size(28.dp),
|
||||
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.agent_find),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||
color = AppColors.text
|
||||
)
|
||||
|
||||
}
|
||||
Spacer(modifier = Modifier.height(20.dp))
|
||||
|
||||
// Agent两列网格布局
|
||||
AgentGridLayout(
|
||||
agentItems = viewModel.agentItems,
|
||||
viewModel = viewModel,
|
||||
navController = LocalNavController.current
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
@Composable
|
||||
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) {
|
||||
fun AgentCardSquare(
|
||||
agentItem: AgentItem,
|
||||
viewModel: AgentViewModel,
|
||||
navController: NavHostController
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val cardHeight = 180.dp
|
||||
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
|
||||
@@ -379,7 +399,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(cardHeight)
|
||||
.background(Color(0xFFE0E0E0), RoundedCornerShape(12.dp)) // 灰色背景
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp)) // 主题背景
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
viewModel.goToProfile(agentItem.openId, navController)
|
||||
@@ -394,7 +414,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
modifier = Modifier
|
||||
.offset(y = -avatarSize / 2)
|
||||
.size(avatarSize)
|
||||
.background(Color.White, RoundedCornerShape(avatarSize / 2))
|
||||
.background(AppColors.background, RoundedCornerShape(avatarSize / 2))
|
||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
@@ -421,7 +441,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 8.dp),
|
||||
.padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
@@ -443,49 +463,50 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f, fill = false)
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
|
||||
// 聊天按钮,位于底部居中
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.width(80.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = Color(0X147c7480),
|
||||
shape = RoundedCornerShape(8.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
|
||||
// 聊天按钮,固定在底部居中,距离底部有一定边距
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.padding(bottom = 12.dp) // 距离底部的边距
|
||||
.width(80.dp)
|
||||
.height(32.dp)
|
||||
.background(
|
||||
color = AppColors.inputBackground,
|
||||
shape = RoundedCornerShape(8.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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel) {
|
||||
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
// 每页显示5个agent
|
||||
@@ -558,7 +579,13 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier,navController: NavHostController) {
|
||||
fun AgentPage(
|
||||
viewModel: AgentViewModel,
|
||||
agentItems: List<AgentItem>,
|
||||
page: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
navController: NavHostController
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
@@ -566,7 +593,11 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
||||
) {
|
||||
// 显示3个agent
|
||||
agentItems.forEachIndexed { index, agentItem ->
|
||||
AgentCard2(agentItem = agentItem, viewModel = viewModel, navController = LocalNavController.current)
|
||||
AgentCard2(
|
||||
agentItem = agentItem,
|
||||
viewModel = viewModel,
|
||||
navController = LocalNavController.current
|
||||
)
|
||||
if (index < agentItems.size - 1) {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
@@ -576,9 +607,9 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
||||
|
||||
@SuppressLint("SuspiciousIndentation")
|
||||
@Composable
|
||||
fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: NavHostController) {
|
||||
fun AgentCard2(viewModel: AgentViewModel, agentItem: AgentItem, navController: NavHostController) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
@@ -592,7 +623,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(48.dp)
|
||||
.background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp))
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(24.dp))
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
viewModel.goToProfile(agentItem.openId, navController)
|
||||
@@ -656,7 +687,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
modifier = Modifier
|
||||
.size(width = 60.dp, height = 32.dp)
|
||||
.background(
|
||||
color = Color(0X147c7480),
|
||||
color = AppColors.inputBackground,
|
||||
shape = RoundedCornerShape(8.dp)
|
||||
)
|
||||
.clickable {
|
||||
@@ -686,3 +717,135 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@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_agent2),
|
||||
contentDescription = "chat room",
|
||||
modifier = Modifier.size(28.dp)
|
||||
)
|
||||
Spacer(modifier = Modifier.width(4.dp))
|
||||
androidx.compose.material3.Text(
|
||||
text = stringResource(R.string.agent_chat_room),
|
||||
fontSize = 16.sp,
|
||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||
color = AppColors.text
|
||||
)
|
||||
}
|
||||
|
||||
// 3行宫格布局
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
// 将聊天房间按3个一组分组
|
||||
chatRooms.chunked(3).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)
|
||||
)
|
||||
}
|
||||
// 如果这一行不足3个,添加空白占位
|
||||
repeat(3 - rowRooms.size) {
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChatRoomCard(
|
||||
chatRoom: ChatRoom,
|
||||
navController: NavHostController,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val AppColors = LocalAppTheme.current
|
||||
val cardSize = 100.dp
|
||||
|
||||
// 防抖状态
|
||||
var lastClickTime by remember { mutableStateOf(0L) }
|
||||
|
||||
// 正方形卡片,文字重叠在底部
|
||||
Box(
|
||||
modifier = modifier
|
||||
.size(cardSize)
|
||||
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||
.clickable {
|
||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||
// 这里可以添加进入聊天房间的逻辑
|
||||
// navController.navigate(NavigationRoute.ChatRoom.route.replace("{id}", chatRoom.id))
|
||||
}) {
|
||||
lastClickTime = System.currentTimeMillis()
|
||||
}
|
||||
}
|
||||
) {
|
||||
// 优先显示banner,如果没有banner则显示头像
|
||||
val imageUrl = if (chatRoom.banner.isNotEmpty()) chatRoom.banner else chatRoom.avatar
|
||||
|
||||
if (imageUrl.isNotEmpty()) {
|
||||
CustomAsyncImage(
|
||||
imageUrl = imageUrl,
|
||||
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
||||
modifier = Modifier
|
||||
.size(cardSize)
|
||||
.clip(RoundedCornerShape(12.dp)),
|
||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||
)
|
||||
} else {
|
||||
// 默认房间图标
|
||||
Image(
|
||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||
contentDescription = "默认房间图标",
|
||||
modifier = Modifier.size(cardSize * 0.4f),
|
||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||
)
|
||||
}
|
||||
|
||||
// 房间名称,重叠在底部
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.align(Alignment.BottomCenter)
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = Color.Black.copy(alpha = 0.6f),
|
||||
shape = RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 6.dp)
|
||||
) {
|
||||
androidx.compose.material3.Text(
|
||||
text = chatRoom.name,
|
||||
fontSize = 12.sp,
|
||||
color = Color.White,
|
||||
maxLines = 1,
|
||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.navigation.NavHostController
|
||||
import com.aiosman.ravenow.AppState
|
||||
import com.aiosman.ravenow.AppStore
|
||||
import com.aiosman.ravenow.ConstVars
|
||||
import com.aiosman.ravenow.data.Room
|
||||
import com.aiosman.ravenow.data.api.ApiClient
|
||||
import com.aiosman.ravenow.data.api.RaveNowAPI
|
||||
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
||||
@@ -13,8 +17,18 @@ import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createG
|
||||
import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||
import com.aiosman.ravenow.entity.CategoryEntity
|
||||
import com.aiosman.ravenow.entity.CategoryBackend
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class ChatRoom(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val avatar: String = "",
|
||||
val banner: String = "",
|
||||
val memberCount: Int = 0
|
||||
)
|
||||
|
||||
object AgentViewModel: ViewModel() {
|
||||
|
||||
private val apiClient: RaveNowAPI = ApiClient.api
|
||||
@@ -22,32 +36,65 @@ object AgentViewModel: ViewModel() {
|
||||
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||
private set
|
||||
|
||||
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
||||
private set
|
||||
|
||||
var rooms by mutableStateOf<List<Room>>(emptyList())
|
||||
private set
|
||||
|
||||
var categories by mutableStateOf<List<CategoryEntity>>(emptyList())
|
||||
private set
|
||||
|
||||
var selectedCategoryIndex by mutableStateOf(0)
|
||||
private set
|
||||
|
||||
var errorMessage by mutableStateOf<String?>(null)
|
||||
private set
|
||||
|
||||
|
||||
var isRefreshing by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
var isLoading by mutableStateOf(false)
|
||||
private set
|
||||
|
||||
private var currentPage = 1
|
||||
private var hasMoreData = true
|
||||
|
||||
init {
|
||||
loadAgentData()
|
||||
loadChatRooms()
|
||||
loadCategories()
|
||||
}
|
||||
|
||||
private fun loadAgentData() {
|
||||
private fun loadAgentData(categoryIndex: Int = 0) {
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
errorMessage = null
|
||||
currentPage = 1
|
||||
hasMoreData = true
|
||||
try {
|
||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
||||
val selectedCategory = if (categoryIndex < categories.size) categories[categoryIndex] else null
|
||||
|
||||
val response = if (categoryIndex == 0 || selectedCategory == null) {
|
||||
// 推荐分类或无效分类,加载所有 Agent
|
||||
apiClient.getAgent(page = currentPage, pageSize = 20, withWorkflow = "1", random = "true")
|
||||
} else {
|
||||
// 特定分类,使用 categoryName 参数
|
||||
apiClient.getAgent(
|
||||
page = currentPage,
|
||||
pageSize = 20,
|
||||
withWorkflow = "1",
|
||||
categoryName = selectedCategory.name,
|
||||
random = "true"
|
||||
)
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val agents = response.body()?.data?.list ?: emptyList()
|
||||
agentItems = agents.map { agent ->
|
||||
AgentItem.fromAgent(agent)
|
||||
}
|
||||
hasMoreData = agents.size >= 20
|
||||
} else {
|
||||
errorMessage = "获取Agent数据失败: ${response.code()}"
|
||||
}
|
||||
@@ -58,6 +105,129 @@ object AgentViewModel: ViewModel() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadChatRooms() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val response = apiClient.getRooms(page = 1, pageSize = 21, isRecommended = 1, random = 1) // 请求21个,确保是3的倍数
|
||||
if (response.isSuccessful) {
|
||||
val allRooms = response.body()?.list ?: emptyList()
|
||||
// 确保房间数量是3的倍数,如果不足则截取,如果超出则取前几个
|
||||
val targetCount = (allRooms.size / 3) * 3 // 向下取整到最近的3的倍数
|
||||
rooms = allRooms.take(targetCount)
|
||||
|
||||
// 转换为ChatRoom格式用于兼容现有UI
|
||||
chatRooms = rooms.map { room ->
|
||||
ChatRoom(
|
||||
id = room.trtcRoomId,
|
||||
name = room.name,
|
||||
avatar = room.avatar,
|
||||
banner = ConstVars.BASE_SERVER + "/api/v1/outside/" + room.recommendBanner + "?token=${AppStore.token}",
|
||||
memberCount = room.userCount
|
||||
)
|
||||
}
|
||||
} else {
|
||||
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 如果网络请求失败,使用默认数据
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadCategories() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val categoryBackend = CategoryBackend()
|
||||
val response = categoryBackend.getCategories(1)
|
||||
if (response != null) {
|
||||
// 添加一个默认的"推荐"分类在第一位
|
||||
val recommendCategory = CategoryEntity(
|
||||
id = 0,
|
||||
name = "推荐",
|
||||
description = "推荐内容",
|
||||
avatar = "",
|
||||
parentId = null,
|
||||
sort = 0,
|
||||
isActive = true,
|
||||
promptCount = 0,
|
||||
createdAt = "",
|
||||
updatedAt = "",
|
||||
translations = null
|
||||
)
|
||||
categories = listOf(recommendCategory) + response.list
|
||||
|
||||
// 分类加载完成后,重新加载当前选中分类的 Agent 数据
|
||||
if (agentItems.isEmpty()) {
|
||||
loadAgentData(selectedCategoryIndex)
|
||||
}
|
||||
} else {
|
||||
// 如果请求失败,使用默认分类
|
||||
categories = listOf()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// 如果网络请求失败,使用默认分类
|
||||
categories = listOf()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 加载更多Agent数据
|
||||
*/
|
||||
fun loadMoreAgentData() {
|
||||
if (!hasMoreData || isLoading) return
|
||||
|
||||
viewModelScope.launch {
|
||||
isLoading = true
|
||||
try {
|
||||
val nextPage = currentPage + 1
|
||||
val selectedCategory = if (selectedCategoryIndex < categories.size) categories[selectedCategoryIndex] else null
|
||||
|
||||
val response = if (selectedCategoryIndex == 0 || selectedCategory == null) {
|
||||
// 推荐分类或无效分类,加载所有 Agent
|
||||
apiClient.getAgent(page = nextPage, pageSize = 20, withWorkflow = "1")
|
||||
} else {
|
||||
// 特定分类,使用 categoryName 参数
|
||||
apiClient.getAgent(
|
||||
page = nextPage,
|
||||
pageSize = 20,
|
||||
withWorkflow = "1",
|
||||
categoryName = selectedCategory.name
|
||||
)
|
||||
}
|
||||
|
||||
if (response.isSuccessful) {
|
||||
val agents = response.body()?.data?.list ?: emptyList()
|
||||
val newAgentItems = agents.map { agent ->
|
||||
AgentItem.fromAgent(agent)
|
||||
}
|
||||
agentItems = agentItems + newAgentItems
|
||||
currentPage = nextPage
|
||||
hasMoreData = agents.size >= 20
|
||||
} else {
|
||||
errorMessage = "加载更多Agent数据失败: ${response.code()}"
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
errorMessage = "网络请求失败: ${e.message}"
|
||||
} finally {
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 选择分类并加载对应的 Agent 数据
|
||||
*/
|
||||
fun selectCategory(categoryIndex: Int) {
|
||||
if (categoryIndex != selectedCategoryIndex) {
|
||||
selectedCategoryIndex = categoryIndex
|
||||
loadAgentData(categoryIndex)
|
||||
}
|
||||
}
|
||||
|
||||
fun createSingleChat(
|
||||
openId: String,
|
||||
) {
|
||||
@@ -116,9 +286,13 @@ object AgentViewModel: ViewModel() {
|
||||
*/
|
||||
fun ResetModel() {
|
||||
agentItems = emptyList()
|
||||
categories = emptyList()
|
||||
selectedCategoryIndex = 0
|
||||
errorMessage = null
|
||||
isRefreshing = false
|
||||
isLoading = false
|
||||
currentPage = 1
|
||||
hasMoreData = true
|
||||
}
|
||||
|
||||
}
|
||||
@@ -44,6 +44,7 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.Dynamic
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.Explore
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentsList
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.News
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList
|
||||
import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel
|
||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||
@@ -66,8 +67,8 @@ fun MomentsList() {
|
||||
val navigationBarPaddings =
|
||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||
// 游客模式下不显示timeline,只显示2个tab:Dynamic、Hot // 游客模式下不显示timeline,只显示3个tab:Explore、Dynamic、Hot
|
||||
val tabCount = if (AppStore.isGuest) 2 else 3 // val tabCount = if (AppStore.isGuest) 3 else 4
|
||||
// 游客模式下显示3个tab:Worldwide、Hot、News,非游客模式显示4个tab:Worldwide、Following、Hot、News
|
||||
val tabCount = if (AppStore.isGuest) 3 else 4
|
||||
var pagerState = rememberPagerState { tabCount }
|
||||
var scope = rememberCoroutineScope()
|
||||
Column(
|
||||
@@ -204,6 +205,23 @@ fun MomentsList() {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
TabSpacer()
|
||||
|
||||
// 新闻标签
|
||||
Box {
|
||||
CustomTabItem(
|
||||
text = stringResource(R.string.index_news),
|
||||
isSelected = pagerState.currentPage == 3,
|
||||
onClick = {
|
||||
tabDebouncer {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(3)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// 热门标签 (游客模式)
|
||||
Box {
|
||||
@@ -219,6 +237,23 @@ fun MomentsList() {
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
TabSpacer()
|
||||
|
||||
// 新闻标签 (游客模式)
|
||||
Box {
|
||||
CustomTabItem(
|
||||
text = stringResource(R.string.index_news),
|
||||
isSelected = pagerState.currentPage == 2,
|
||||
onClick = {
|
||||
tabDebouncer {
|
||||
scope.launch {
|
||||
pagerState.animateScrollToPage(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +264,7 @@ fun MomentsList() {
|
||||
.weight(1f)
|
||||
) {
|
||||
if (AppStore.isGuest) {
|
||||
// 游客模式:Dynamic(0), Hot(1)
|
||||
// 游客模式:Worldwide(0), Hot(1), News(2)
|
||||
when (it) {
|
||||
0 -> {
|
||||
Dynamic()
|
||||
@@ -237,9 +272,12 @@ fun MomentsList() {
|
||||
1 -> {
|
||||
HotMomentsList()
|
||||
}
|
||||
2 -> {
|
||||
News()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 正常用户:Dynamic(0), Timeline(1), Hot(2)
|
||||
// 正常用户:Worldwide(0), Following(1), Hot(2), News(3)
|
||||
when (it) {
|
||||
0 -> {
|
||||
Dynamic()
|
||||
@@ -250,6 +288,9 @@ fun MomentsList() {
|
||||
2 -> {
|
||||
HotMomentsList()
|
||||
}
|
||||
3 -> {
|
||||
News()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class ExploreViewModel : ViewModel() {
|
||||
isRefreshing = true
|
||||
errorMessage = null
|
||||
try {
|
||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||
if (response.isSuccessful) {
|
||||
val agents = response.body()?.data?.list ?: emptyList()
|
||||
agentItems = agents.map { agent ->
|
||||
@@ -114,7 +114,7 @@ class ExploreViewModel : ViewModel() {
|
||||
isLoading = true
|
||||
errorMessage = null
|
||||
try {
|
||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||
if (response.isSuccessful) {
|
||||
val agents = response.body()?.data?.list ?: emptyList()
|
||||
agentItems = agents.map { agent ->
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.ExperimentalMaterialApi
|
||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||
import androidx.compose.material.pullrefresh.pullRefresh
|
||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
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.rememberDebouncer
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
/**
|
||||
* 新闻动态列表
|
||||
*/
|
||||
@OptIn(ExperimentalMaterialApi::class)
|
||||
@Composable
|
||||
fun News() {
|
||||
val model = NewsViewModel
|
||||
val moments = model.moments
|
||||
val navController = LocalNavController.current
|
||||
|
||||
val scope = rememberCoroutineScope()
|
||||
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||
model.refreshPager(
|
||||
pullRefresh = true
|
||||
)
|
||||
})
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
// 用于跟踪是否已经触发过加载更多
|
||||
var hasTriggeredLoadMore by remember { mutableStateOf(false) }
|
||||
|
||||
// observe list scrolling with simplified logic
|
||||
val reachedBottom by remember {
|
||||
derivedStateOf {
|
||||
val layoutInfo = listState.layoutInfo
|
||||
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||
val totalItems = layoutInfo.totalItemsCount
|
||||
|
||||
if (lastVisibleItem == null || totalItems == 0) {
|
||||
false
|
||||
} else {
|
||||
val isLastItemVisible = lastVisibleItem.index >= totalItems - 2
|
||||
|
||||
// 简化逻辑:只要滑动到底部且还没有触发过,就触发加载
|
||||
isLastItemVisible && !hasTriggeredLoadMore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// load more if scrolled to bottom
|
||||
LaunchedEffect(reachedBottom) {
|
||||
if (reachedBottom) {
|
||||
hasTriggeredLoadMore = true
|
||||
model.loadMore()
|
||||
}
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
model.refreshPager()
|
||||
}
|
||||
|
||||
// 监听数据变化,重置加载状态
|
||||
LaunchedEffect(moments.size) {
|
||||
if (moments.size > 0) {
|
||||
// 延迟重置,确保数据已经稳定
|
||||
kotlinx.coroutines.delay(500)
|
||||
hasTriggeredLoadMore = false
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
|
||||
) {
|
||||
Box(Modifier.pullRefresh(state)) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
state = listState
|
||||
) {
|
||||
items(
|
||||
moments.size,
|
||||
key = { idx -> idx }
|
||||
) { idx ->
|
||||
//处理下标越界
|
||||
if (idx < 0 || idx >= moments.size) return@items
|
||||
val momentItem = moments[idx] ?: return@items
|
||||
|
||||
val commentDebouncer = rememberDebouncer()
|
||||
val likeDebouncer = rememberDebouncer()
|
||||
val favoriteDebouncer = rememberDebouncer()
|
||||
val followDebouncer = rememberDebouncer()
|
||||
|
||||
MomentCard(
|
||||
momentEntity = momentItem,
|
||||
onAddComment = {
|
||||
commentDebouncer {
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
scope.launch {
|
||||
model.onAddComment(momentItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onLikeClick = {
|
||||
likeDebouncer {
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
scope.launch {
|
||||
if (momentItem.liked) {
|
||||
model.dislikeMoment(momentItem.id)
|
||||
} else {
|
||||
model.likeMoment(momentItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onFavoriteClick = {
|
||||
favoriteDebouncer {
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
scope.launch {
|
||||
if (momentItem.isFavorite) {
|
||||
model.unfavoriteMoment(momentItem.id)
|
||||
} else {
|
||||
model.favoriteMoment(momentItem.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onFollowClick = {
|
||||
followDebouncer {
|
||||
// 检查游客模式,如果是游客则跳转登录
|
||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
|
||||
navController.navigate(NavigationRoute.Login.route)
|
||||
} else {
|
||||
model.followAction(momentItem)
|
||||
}
|
||||
}
|
||||
},
|
||||
showFollowButton = true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
PullRefreshIndicator(
|
||||
refreshing = model.refreshing,
|
||||
state = state,
|
||||
modifier = Modifier.align(Alignment.TopCenter)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
||||
|
||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
||||
|
||||
object NewsViewModel : BaseMomentModel() {
|
||||
init {
|
||||
EventBus.getDefault().register(this)
|
||||
}
|
||||
|
||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
||||
return MomentLoaderExtraArgs(explore = true, newsOnly = true)
|
||||
}
|
||||
}
|
||||
@@ -63,6 +63,23 @@ object Utils {
|
||||
return Locale.getDefault().language
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前系统语言的完整标签,包含国家/地区代码
|
||||
* 返回格式:en, ja, zh-CN, zh-TW 等
|
||||
*/
|
||||
fun getCurrentLanguageTag(): String {
|
||||
val locale = Locale.getDefault()
|
||||
val language = locale.language
|
||||
val country = locale.country
|
||||
|
||||
return when {
|
||||
// 中文需要区分简体和繁体
|
||||
language == "zh" && country.isNotEmpty() -> "$language-$country"
|
||||
// 其他语言通常只需要语言代码
|
||||
else -> language
|
||||
}
|
||||
}
|
||||
|
||||
fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File {
|
||||
val inputStream = context.contentResolver.openInputStream(uri)
|
||||
val originalBitmap = BitmapFactory.decodeStream(inputStream)
|
||||
|
||||
@@ -26,4 +26,35 @@
|
||||
<string name="create_agent_v2_ai_enhance_icon_desc">AI美化アイコン</string>
|
||||
<string name="create_agent_v2_edit_icon_desc">編集アイコン</string>
|
||||
<string name="create_agent_v2_select_avatar_desc">アバター選択</string>
|
||||
|
||||
<!-- Index tabs -->
|
||||
<string name="index_worldwide">ワールドワイド</string>
|
||||
<string name="index_dynamic">ダイナミック</string>
|
||||
<string name="index_following">フォロー中</string>
|
||||
<string name="index_hot">ホット</string>
|
||||
<string name="index_news">ニュース</string>
|
||||
|
||||
<!-- Main navigation -->
|
||||
<string name="main_home">ホーム</string>
|
||||
<string name="main_ai">エージェント</string>
|
||||
<string name="main_message">メッセージ</string>
|
||||
<string name="main_profile">プロフィール</string>
|
||||
|
||||
<!-- Agent related strings -->
|
||||
<string name="agent_mine">マイ</string>
|
||||
<string name="agent_hot">ホット</string>
|
||||
<string name="agent_recommend">おすすめ</string>
|
||||
<string name="agent_other">その他</string>
|
||||
<string name="agent_find">AI発見</string>
|
||||
<string name="chat">チャット</string>
|
||||
<string name="agent_chat_room">チャット</string>
|
||||
<string name="agent_recommended_chat_rooms">おすすめチャットルーム</string>
|
||||
<string name="search">検索</string>
|
||||
<string name="agent_recommend_agent">おすすめエージェント</string>
|
||||
|
||||
<!-- Chat related -->
|
||||
<string name="chat_ai">AI</string>
|
||||
<string name="chat_group">グループ</string>
|
||||
<string name="chat_friend">友達</string>
|
||||
<string name="hot_rooms">人気チャットルーム</string>
|
||||
</resources>
|
||||
|
||||
@@ -188,7 +188,7 @@
|
||||
<string name="group_room_enter_success">成功加入房间</string>
|
||||
<string name="group_room_enter_fail">加入房间失败</string>
|
||||
<string name="agent_createing">创建中...</string>
|
||||
<string name="agent_find">发现</string>
|
||||
<string name="agent_find">发现AI</string>
|
||||
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
|
||||
|
||||
<!-- Create Bottom Sheet -->
|
||||
@@ -217,5 +217,9 @@
|
||||
<string name="create_agent_v2_ai_enhance_icon_desc">AI美化图标</string>
|
||||
<string name="create_agent_v2_edit_icon_desc">编辑图标</string>
|
||||
<string name="create_agent_v2_select_avatar_desc">选择头像</string>
|
||||
|
||||
<!-- Agent related strings -->
|
||||
<string name="agent_chat_room">聊天</string>
|
||||
<string name="agent_recommended_chat_rooms">推荐聊天房间</string>
|
||||
|
||||
</resources>
|
||||
@@ -122,6 +122,7 @@
|
||||
<string name="index_dynamic">Dynamic</string>
|
||||
<string name="index_following">Following</string>
|
||||
<string name="index_hot">Hot</string>
|
||||
<string name="index_news">News</string>
|
||||
<string name="main_home">Home</string>
|
||||
<string name="main_ai">Agent</string>
|
||||
<string name="main_message">Message</string>
|
||||
@@ -184,7 +185,7 @@
|
||||
<string name="group_room_enter_success">成功加入房间</string>
|
||||
<string name="group_room_enter_fail">加入房间失败</string>
|
||||
<string name="agent_createing">创建中...</string>
|
||||
<string name="agent_find">发现</string>
|
||||
<string name="agent_find">发现AI</string>
|
||||
<string name="text_error_password_too_long">Password cannot exceed %1$d characters</string>
|
||||
|
||||
<!-- Create Bottom Sheet -->
|
||||
@@ -213,5 +214,9 @@
|
||||
<string name="create_agent_v2_ai_enhance_icon_desc">AI Enhancement Icon</string>
|
||||
<string name="create_agent_v2_edit_icon_desc">Edit Icon</string>
|
||||
<string name="create_agent_v2_select_avatar_desc">Select Avatar</string>
|
||||
|
||||
<!-- Agent related strings -->
|
||||
<string name="agent_chat_room">Chat</string>
|
||||
<string name="agent_recommended_chat_rooms">Recommended Chat Rooms</string>
|
||||
|
||||
</resources>
|
||||
Reference in New Issue
Block a user