7 Commits

Author SHA1 Message Date
28fb94a824 顶部 推荐 agent 列表实现 2025-10-17 17:21:52 +08:00
4bdbbb0231 主页分类动态获取 2025-10-17 16:56:25 +08:00
58a2013a8f Merge pull request #40 from Kevinlinpr/lottie_deps
lottie 依赖
2025-10-17 16:11:15 +08:00
2f2da0a159 lottie 依赖 2025-10-17 15:27:12 +08:00
4ffaf3c3a8 Merge pull request #38 from Kevinlinpr/zhong
编辑资料页面UI调整:添加横幅图片区域
2025-10-16 18:10:11 +08:00
0442925ae9 Merge pull request #37 from Kevinlinpr/zhong
解决问题:首页智能体头像为默认头像时显示为空
2025-10-14 18:20:22 +08:00
bd01ae39d0 Merge pull request #36 from Kevinlinpr/zhong
UI调整
2025-10-10 21:42:24 +08:00
12 changed files with 198 additions and 84 deletions

View File

@@ -125,7 +125,7 @@ dependencies {
// 添加 lifecycle-runtime-ktx 依赖
implementation(libs.androidx.lifecycle.runtime.ktx.v262)
implementation (libs.eventbus)
implementation(libs.lottie)
}

View File

@@ -5,7 +5,8 @@ object ConstVars {
// 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环境
// "http://47.109.137.67:6363" // Debug环境
"https://rider-pro.aiosman.com/beta_api" // Release环境
} else {
"https://rider-pro.aiosman.com/beta_api" // Release环境
}

View File

@@ -271,6 +271,13 @@ data class RemoveAccountRequestBody(
val password: String,
)
data class CategoryTranslation(
@SerializedName("name")
val name: String?,
@SerializedName("description")
val description: String?
)
data class CategoryTemplate(
@SerializedName("id")
val id: Int,
@@ -295,8 +302,58 @@ data class CategoryTemplate(
@SerializedName("createdAt")
val createdAt: String,
@SerializedName("updatedAt")
val updatedAt: String
)
val updatedAt: String,
@SerializedName("translations")
val translations: Map<String, CategoryTranslation>?
) {
/**
* 根据语言代码获取翻译后的名称,如果没有翻译则返回默认名称
*/
fun getLocalizedName(lang: String): String {
// 尝试获取完整的语言标记(如 "zh-CN"
val translation = translations?.get(lang)
if (translation?.name != null && translation.name.isNotEmpty()) {
return translation.name
}
// 如果没有找到,尝试语言代码的前缀(如 "zh"
val langPrefix = lang.split("-", "_").firstOrNull()
if (langPrefix != null) {
translations?.entries?.forEach { (key, value) ->
if (key.startsWith(langPrefix) && value.name != null && value.name.isNotEmpty()) {
return value.name
}
}
}
// 如果没有翻译,返回默认名称
return name
}
/**
* 根据语言代码获取翻译后的描述,如果没有翻译则返回默认描述
*/
fun getLocalizedDescription(lang: String): String {
// 尝试获取完整的语言标记(如 "zh-CN"
val translation = translations?.get(lang)
if (translation?.description != null && translation.description.isNotEmpty()) {
return translation.description
}
// 如果没有找到,尝试语言代码的前缀(如 "zh"
val langPrefix = lang.split("-", "_").firstOrNull()
if (langPrefix != null) {
translations?.entries?.forEach { (key, value) ->
if (key.startsWith(langPrefix) && value.description != null && value.description.isNotEmpty()) {
return value.description
}
}
}
// 如果没有翻译,返回默认描述
return description
}
}
data class CategoryListResponse(
@SerializedName("page")
@@ -591,6 +648,7 @@ interface RaveNowAPI {
@Query("withWorkflow") withWorkflow: Int = 1,
@Query("authorId") authorId: Int? = null,
@Query("categoryIds") categoryIds: List<Int>? = null,
@Query("random") random: Int? = null,
): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts")
@@ -654,8 +712,9 @@ interface RaveNowAPI {
@Query("withChildren") withChildren: Boolean? = null,
@Query("withParent") withParent: Boolean? = null,
@Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null
): Response<DataContainer<CategoryListResponse>>
@Query("hideEmpty") hideEmpty: Boolean? = null,
@Query("lang") lang: String? = null
): Response<CategoryListResponse>
@GET("outside/categories/tree")
suspend fun getCategoryTree(

View File

@@ -146,11 +146,14 @@ fun Agent() {
topBar = {
TopAppBar(
title = {
androidx.compose.material3.Text(
text = "Rave AI",
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text
Image(
painter = painterResource(id = R.drawable.home_logo),
contentDescription = "Rave AI Logo",
modifier = Modifier
.height(44.dp)
.padding(top =9.dp,bottom=9.dp)
.wrapContentSize(),
// colorFilter = ColorFilter.tint(AppColors.text)
)
},
actions = {
@@ -158,7 +161,8 @@ fun Agent() {
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search",
modifier = Modifier
.size(24.dp)
.size(44.dp)
.padding(top = 9.dp,bottom=9.dp)
.noRippleClickable {
navController.navigate(NavigationRoute.Search.route)
},
@@ -167,7 +171,11 @@ fun Agent() {
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColors.background
)
),
windowInsets = WindowInsets(0, 0, 0, 0),
modifier = Modifier
.height(44.dp + statusBarPaddingValues.calculateTopPadding())
.padding(top = statusBarPaddingValues.calculateTopPadding())
)
},
containerColor = AppColors.background,
@@ -180,8 +188,8 @@ fun Agent() {
.padding(paddingValues)
.padding(
bottom = navigationBarPaddings,
start = 16.dp,
end = 16.dp
start = 8.dp,
end = 8.dp
)
) {
@@ -217,7 +225,7 @@ fun Agent() {
}
// 动态添加分类标签
viewModel.categories.take(4).forEachIndexed { index, category ->
viewModel.categories.forEachIndexed { index, category ->
item {
CustomTabItem(
text = category.name,
@@ -233,58 +241,6 @@ fun Agent() {
TabSpacer()
}
}
item {
CustomTabItem(
text = "scenes",
isSelected = selectedTabIndex == 1,
onClick = {
selectedTabIndex = 1
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "voices",
isSelected = selectedTabIndex == 6,
onClick = {
selectedTabIndex = 6
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "anime",
isSelected = selectedTabIndex == 7,
onClick = {
selectedTabIndex = 7
}
)
}
item {
TabSpacer()
}
item {
CustomTabItem(
text = "assist",
isSelected = selectedTabIndex == 8,
onClick = {
selectedTabIndex = 8
}
)
}
}
}
}

View File

@@ -18,6 +18,15 @@ import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userServic
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import kotlinx.coroutines.launch
/**
* 缓存数据结构用于存储每个分类的Agent列表
*/
data class AgentCacheData(
val items: List<AgentItem>,
val currentPage: Int,
val hasMoreData: Boolean
)
object AgentViewModel: ViewModel() {
private val apiClient: RaveNowAPI = ApiClient.api
@@ -51,13 +60,30 @@ object AgentViewModel: ViewModel() {
private val pageSize = 20
private var currentCategoryId: Int? = null
// 缓存使用分类ID作为keynull表示推荐列表
private val agentCache = mutableMapOf<Int?, AgentCacheData>()
init {
loadAgentData()
loadCategories()
}
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false) {
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) {
viewModelScope.launch {
// 如果不是强制刷新且不是加载更多,检查缓存
if (!forceRefresh && !isLoadMore) {
val cached = agentCache[categoryId]
if (cached != null && cached.items.isNotEmpty()) {
// 使用缓存数据
agentItems = cached.items
currentPage = cached.currentPage
hasMoreData = cached.hasMoreData
currentCategoryId = categoryId
println("使用缓存数据分类ID: $categoryId, 数据数量: ${cached.items.size}")
return@launch
}
}
if (isLoadMore) {
isLoadingMore = true
} else {
@@ -76,11 +102,18 @@ object AgentViewModel: ViewModel() {
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = listOf(categoryId)
categoryIds = listOf(categoryId),
random = 1
)
} else {
// 获取所有智能体
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1)
// 获取推荐智能体使用random=1
apiClient.getAgent(
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = null,
random = 1
)
}
if (response.isSuccessful) {
@@ -103,6 +136,14 @@ object AgentViewModel: ViewModel() {
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
// 更新缓存
agentCache[categoryId] = AgentCacheData(
items = agentItems,
currentPage = currentPage,
hasMoreData = hasMoreData
)
println("更新缓存分类ID: $categoryId, 数据数量: ${agentItems.size}")
} else {
errorMessage = "获取Agent数据失败: ${response.code()}"
}
@@ -120,20 +161,32 @@ object AgentViewModel: ViewModel() {
private fun loadCategories() {
viewModelScope.launch {
// 如果分类已经加载,不重复请求
if (categories.isNotEmpty()) {
println("使用已缓存的分类数据,数量: ${categories.size}")
return@launch
}
try {
// 获取完整的语言标记(如 "zh-CN"
val sysLang = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag()
val response = apiClient.getCategories(
pageSize = 20,
page = 1,
pageSize = 100,
isActive = true,
withChildren = false,
withParent = false,
withCount = true,
hideEmpty = true
hideEmpty = true,
lang = sysLang
)
println("分类数据请求完成,响应成功: ${response.isSuccessful}")
println("分类数据请求完成,响应成功: ${response.isSuccessful}, 语言标记: $sysLang")
if (response.isSuccessful) {
val categoryList = response.body()?.data?.list ?: emptyList()
val categoryList = response.body()?.list ?: emptyList()
println("获取到 ${categoryList.size} 个分类")
// 使用当前语言获取翻译后的分类名称
categories = categoryList.map { category ->
CategoryItem.fromCategoryTemplate(category)
CategoryItem.fromCategoryTemplate(category, sysLang)
}
println("成功处理并映射了 ${categories.size} 个分类")
} else {
@@ -210,10 +263,12 @@ object AgentViewModel: ViewModel() {
}
/**
* 刷新推荐Agent数据
* 刷新当前分类的Agent数据(强制刷新,清除缓存)
*/
fun refreshAgentData() {
loadAgentData()
// 清除当前分类的缓存
agentCache.remove(currentCategoryId)
loadAgentData(categoryId = currentCategoryId, forceRefresh = true)
}
/**
@@ -230,6 +285,7 @@ object AgentViewModel: ViewModel() {
*/
fun ResetModel() {
agentItems = emptyList()
categories = emptyList()
errorMessage = null
isRefreshing = false
isLoading = false
@@ -237,6 +293,8 @@ object AgentViewModel: ViewModel() {
currentPage = 1
hasMoreData = true
currentCategoryId = null
// 清空缓存
agentCache.clear()
}
}
@@ -248,11 +306,11 @@ data class CategoryItem(
val promptCount: Int?
) {
companion object {
fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem {
fun fromCategoryTemplate(template: CategoryTemplate, lang: String): CategoryItem {
return CategoryItem(
id = template.id,
name = template.name,
description = template.description,
name = template.getLocalizedName(lang),
description = template.getLocalizedDescription(lang),
avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
promptCount = template.promptCount
)

View File

@@ -63,6 +63,23 @@ object Utils {
return Locale.getDefault().language
}
/**
* 获取完整的语言标记,如 "zh-CN", "en-US"
* 优先使用完整的 BCP-47 语言标记,提升与后端 translations 键的匹配率
*/
fun getPreferredLanguageTag(): String {
val locale = Locale.getDefault()
val language = locale.language
val country = locale.country
// 如果有国家/地区代码,返回完整的语言标记
return if (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)

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 123 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View File

@@ -98,7 +98,7 @@ play-services-auth = { module = "com.google.android.gms:play-services-auth", ver
rendering = { group = "com.google.ar.sceneform", name = "rendering", version.ref = "rendering" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" }
zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" }
lottie = { module="com.airbnb.android:lottie-compose", version="6.6.10"}
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

View File

@@ -16,6 +16,7 @@ dependencyResolutionManagement {
repositories {
google()
mavenCentral()
maven(url = "https://jitpack.io")
}
}