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 依赖 // 添加 lifecycle-runtime-ktx 依赖
implementation(libs.androidx.lifecycle.runtime.ktx.v262) implementation(libs.androidx.lifecycle.runtime.ktx.v262)
implementation (libs.eventbus) implementation (libs.eventbus)
implementation(libs.lottie)
} }

View File

@@ -5,7 +5,8 @@ object ConstVars {
// Debug: http://192.168.0.201:8088 // Debug: http://192.168.0.201:8088
// Release: https://rider-pro.aiosman.com/beta_api // Release: https://rider-pro.aiosman.com/beta_api
val BASE_SERVER = if (BuildConfig.DEBUG) { 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 { } else {
"https://rider-pro.aiosman.com/beta_api" // Release环境 "https://rider-pro.aiosman.com/beta_api" // Release环境
} }

View File

@@ -271,6 +271,13 @@ data class RemoveAccountRequestBody(
val password: String, val password: String,
) )
data class CategoryTranslation(
@SerializedName("name")
val name: String?,
@SerializedName("description")
val description: String?
)
data class CategoryTemplate( data class CategoryTemplate(
@SerializedName("id") @SerializedName("id")
val id: Int, val id: Int,
@@ -295,8 +302,58 @@ data class CategoryTemplate(
@SerializedName("createdAt") @SerializedName("createdAt")
val createdAt: String, val createdAt: String,
@SerializedName("updatedAt") @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( data class CategoryListResponse(
@SerializedName("page") @SerializedName("page")
@@ -591,6 +648,7 @@ interface RaveNowAPI {
@Query("withWorkflow") withWorkflow: Int = 1, @Query("withWorkflow") withWorkflow: Int = 1,
@Query("authorId") authorId: Int? = null, @Query("authorId") authorId: Int? = null,
@Query("categoryIds") categoryIds: List<Int>? = null, @Query("categoryIds") categoryIds: List<Int>? = null,
@Query("random") random: Int? = null,
): Response<DataContainer<ListContainer<Agent>>> ): Response<DataContainer<ListContainer<Agent>>>
@GET("outside/my/prompts") @GET("outside/my/prompts")
@@ -654,8 +712,9 @@ interface RaveNowAPI {
@Query("withChildren") withChildren: Boolean? = null, @Query("withChildren") withChildren: Boolean? = null,
@Query("withParent") withParent: Boolean? = null, @Query("withParent") withParent: Boolean? = null,
@Query("withCount") withCount: Boolean? = null, @Query("withCount") withCount: Boolean? = null,
@Query("hideEmpty") hideEmpty: Boolean? = null @Query("hideEmpty") hideEmpty: Boolean? = null,
): Response<DataContainer<CategoryListResponse>> @Query("lang") lang: String? = null
): Response<CategoryListResponse>
@GET("outside/categories/tree") @GET("outside/categories/tree")
suspend fun getCategoryTree( suspend fun getCategoryTree(

View File

@@ -146,11 +146,14 @@ fun Agent() {
topBar = { topBar = {
TopAppBar( TopAppBar(
title = { title = {
androidx.compose.material3.Text( Image(
text = "Rave AI", painter = painterResource(id = R.drawable.home_logo),
fontSize = 20.sp, contentDescription = "Rave AI Logo",
fontWeight = FontWeight.W900, modifier = Modifier
color = AppColors.text .height(44.dp)
.padding(top =9.dp,bottom=9.dp)
.wrapContentSize(),
// colorFilter = ColorFilter.tint(AppColors.text)
) )
}, },
actions = { actions = {
@@ -158,7 +161,8 @@ fun Agent() {
painter = painterResource(id = R.drawable.rider_pro_nav_search), painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search", contentDescription = "search",
modifier = Modifier modifier = Modifier
.size(24.dp) .size(44.dp)
.padding(top = 9.dp,bottom=9.dp)
.noRippleClickable { .noRippleClickable {
navController.navigate(NavigationRoute.Search.route) navController.navigate(NavigationRoute.Search.route)
}, },
@@ -167,7 +171,11 @@ fun Agent() {
}, },
colors = TopAppBarDefaults.topAppBarColors( colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColors.background containerColor = AppColors.background
) ),
windowInsets = WindowInsets(0, 0, 0, 0),
modifier = Modifier
.height(44.dp + statusBarPaddingValues.calculateTopPadding())
.padding(top = statusBarPaddingValues.calculateTopPadding())
) )
}, },
containerColor = AppColors.background, containerColor = AppColors.background,
@@ -180,8 +188,8 @@ fun Agent() {
.padding(paddingValues) .padding(paddingValues)
.padding( .padding(
bottom = navigationBarPaddings, bottom = navigationBarPaddings,
start = 16.dp, start = 8.dp,
end = 16.dp end = 8.dp
) )
) { ) {
@@ -217,7 +225,7 @@ fun Agent() {
} }
// 动态添加分类标签 // 动态添加分类标签
viewModel.categories.take(4).forEachIndexed { index, category -> viewModel.categories.forEachIndexed { index, category ->
item { item {
CustomTabItem( CustomTabItem(
text = category.name, text = category.name,
@@ -233,58 +241,6 @@ fun Agent() {
TabSpacer() 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 com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
/**
* 缓存数据结构用于存储每个分类的Agent列表
*/
data class AgentCacheData(
val items: List<AgentItem>,
val currentPage: Int,
val hasMoreData: Boolean
)
object AgentViewModel: ViewModel() { object AgentViewModel: ViewModel() {
private val apiClient: RaveNowAPI = ApiClient.api private val apiClient: RaveNowAPI = ApiClient.api
@@ -51,13 +60,30 @@ object AgentViewModel: ViewModel() {
private val pageSize = 20 private val pageSize = 20
private var currentCategoryId: Int? = null private var currentCategoryId: Int? = null
// 缓存使用分类ID作为keynull表示推荐列表
private val agentCache = mutableMapOf<Int?, AgentCacheData>()
init { init {
loadAgentData() loadAgentData()
loadCategories() 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 { 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) { if (isLoadMore) {
isLoadingMore = true isLoadingMore = true
} else { } else {
@@ -76,11 +102,18 @@ object AgentViewModel: ViewModel() {
page = page, page = page,
pageSize = pageSize, pageSize = pageSize,
withWorkflow = 1, withWorkflow = 1,
categoryIds = listOf(categoryId) categoryIds = listOf(categoryId),
random = 1
) )
} else { } else {
// 获取所有智能体 // 获取推荐智能体使用random=1
apiClient.getAgent(page = page, pageSize = pageSize, withWorkflow = 1) apiClient.getAgent(
page = page,
pageSize = pageSize,
withWorkflow = 1,
categoryIds = null,
random = 1
)
} }
if (response.isSuccessful) { if (response.isSuccessful) {
@@ -103,6 +136,14 @@ object AgentViewModel: ViewModel() {
// 检查是否还有更多数据 // 检查是否还有更多数据
hasMoreData = agents.size >= pageSize hasMoreData = agents.size >= pageSize
// 更新缓存
agentCache[categoryId] = AgentCacheData(
items = agentItems,
currentPage = currentPage,
hasMoreData = hasMoreData
)
println("更新缓存分类ID: $categoryId, 数据数量: ${agentItems.size}")
} else { } else {
errorMessage = "获取Agent数据失败: ${response.code()}" errorMessage = "获取Agent数据失败: ${response.code()}"
} }
@@ -120,20 +161,32 @@ object AgentViewModel: ViewModel() {
private fun loadCategories() { private fun loadCategories() {
viewModelScope.launch { viewModelScope.launch {
// 如果分类已经加载,不重复请求
if (categories.isNotEmpty()) {
println("使用已缓存的分类数据,数量: ${categories.size}")
return@launch
}
try { try {
// 获取完整的语言标记(如 "zh-CN"
val sysLang = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag()
val response = apiClient.getCategories( val response = apiClient.getCategories(
pageSize = 20, page = 1,
pageSize = 100,
isActive = true,
withChildren = false, withChildren = false,
withParent = false, withParent = false,
withCount = true, withCount = true,
hideEmpty = true hideEmpty = true,
lang = sysLang
) )
println("分类数据请求完成,响应成功: ${response.isSuccessful}") println("分类数据请求完成,响应成功: ${response.isSuccessful}, 语言标记: $sysLang")
if (response.isSuccessful) { if (response.isSuccessful) {
val categoryList = response.body()?.data?.list ?: emptyList() val categoryList = response.body()?.list ?: emptyList()
println("获取到 ${categoryList.size} 个分类") println("获取到 ${categoryList.size} 个分类")
// 使用当前语言获取翻译后的分类名称
categories = categoryList.map { category -> categories = categoryList.map { category ->
CategoryItem.fromCategoryTemplate(category) CategoryItem.fromCategoryTemplate(category, sysLang)
} }
println("成功处理并映射了 ${categories.size} 个分类") println("成功处理并映射了 ${categories.size} 个分类")
} else { } else {
@@ -210,10 +263,12 @@ object AgentViewModel: ViewModel() {
} }
/** /**
* 刷新推荐Agent数据 * 刷新当前分类的Agent数据(强制刷新,清除缓存)
*/ */
fun refreshAgentData() { fun refreshAgentData() {
loadAgentData() // 清除当前分类的缓存
agentCache.remove(currentCategoryId)
loadAgentData(categoryId = currentCategoryId, forceRefresh = true)
} }
/** /**
@@ -230,6 +285,7 @@ object AgentViewModel: ViewModel() {
*/ */
fun ResetModel() { fun ResetModel() {
agentItems = emptyList() agentItems = emptyList()
categories = emptyList()
errorMessage = null errorMessage = null
isRefreshing = false isRefreshing = false
isLoading = false isLoading = false
@@ -237,6 +293,8 @@ object AgentViewModel: ViewModel() {
currentPage = 1 currentPage = 1
hasMoreData = true hasMoreData = true
currentCategoryId = null currentCategoryId = null
// 清空缓存
agentCache.clear()
} }
} }
@@ -248,11 +306,11 @@ data class CategoryItem(
val promptCount: Int? val promptCount: Int?
) { ) {
companion object { companion object {
fun fromCategoryTemplate(template: CategoryTemplate): CategoryItem { fun fromCategoryTemplate(template: CategoryTemplate, lang: String): CategoryItem {
return CategoryItem( return CategoryItem(
id = template.id, id = template.id,
name = template.name, name = template.getLocalizedName(lang),
description = template.description, description = template.getLocalizedDescription(lang),
avatar = "${ApiClient.BASE_API_URL}${template.avatar}", avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
promptCount = template.promptCount promptCount = template.promptCount
) )

View File

@@ -63,6 +63,23 @@ object Utils {
return Locale.getDefault().language 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 { fun compressImage(context: Context, uri: Uri, maxSize: Int = 512, quality: Int = 85): File {
val inputStream = context.contentResolver.openInputStream(uri) val inputStream = context.contentResolver.openInputStream(uri)
val originalBitmap = BitmapFactory.decodeStream(inputStream) 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" } rendering = { group = "com.google.ar.sceneform", name = "rendering", version.ref = "rendering" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" }
zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" } zoomable = { module = "net.engawapg.lib:zoomable", version.ref = "zoomable" }
lottie = { module="com.airbnb.android:lottie-compose", version="6.6.10"}
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }
jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }

View File

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