Compare commits
3 Commits
revert-48-
...
zhong
| Author | SHA1 | Date | |
|---|---|---|---|
| 85141fde1b | |||
| 234c07142c | |||
| 89227edccf |
@@ -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)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ object AppState {
|
|||||||
var enableGoogleLogin: Boolean = false
|
var enableGoogleLogin: Boolean = false
|
||||||
var enableChat = false
|
var enableChat = false
|
||||||
var agentCreatedSuccess by mutableStateOf(false)
|
var agentCreatedSuccess by mutableStateOf(false)
|
||||||
var chatBackgroundUrl by mutableStateOf<String?>(null)
|
|
||||||
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
|
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
|
||||||
// 如果是游客模式,使用简化的初始化流程
|
// 如果是游客模式,使用简化的初始化流程
|
||||||
if (AppStore.isGuest) {
|
if (AppStore.isGuest) {
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ 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环境
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,21 +32,6 @@ data class Moment(
|
|||||||
val time: String,
|
val time: String,
|
||||||
@SerializedName("isFollowed")
|
@SerializedName("isFollowed")
|
||||||
val isFollowed: Boolean,
|
val isFollowed: Boolean,
|
||||||
// 新闻相关字段
|
|
||||||
@SerializedName("isNews")
|
|
||||||
val isNews: Boolean = false,
|
|
||||||
@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,
|
|
||||||
) {
|
) {
|
||||||
fun toMomentItem(): MomentEntity {
|
fun toMomentItem(): MomentEntity {
|
||||||
return MomentEntity(
|
return MomentEntity(
|
||||||
@@ -75,14 +60,6 @@ data class Moment(
|
|||||||
authorId = user.id.toInt(),
|
authorId = user.id.toInt(),
|
||||||
liked = isLiked,
|
liked = isLiked,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
// 新闻相关字段
|
|
||||||
isNews = isNews,
|
|
||||||
newsTitle = newsTitle ?: "",
|
|
||||||
newsUrl = newsUrl ?: "",
|
|
||||||
newsSource = newsSource ?: "",
|
|
||||||
newsCategory = newsCategory ?: "",
|
|
||||||
newsLanguage = newsLanguage ?: "",
|
|
||||||
newsContent = newsContent ?: ""
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ data class SendChatAiRequestBody(
|
|||||||
@SerializedName("skipTrtc")
|
@SerializedName("skipTrtc")
|
||||||
val skipTrtc: Boolean? = true,
|
val skipTrtc: Boolean? = true,
|
||||||
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
data class CreateGroupChatRequestBody(
|
data class CreateGroupChatRequestBody(
|
||||||
@@ -77,7 +77,7 @@ data class CreateGroupChatRequestBody(
|
|||||||
val userIds: List<String>,
|
val userIds: List<String>,
|
||||||
@SerializedName("promptIds")
|
@SerializedName("promptIds")
|
||||||
val promptIds: List<String>,
|
val promptIds: List<String>,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class JoinGroupChatRequestBody(
|
data class JoinGroupChatRequestBody(
|
||||||
@SerializedName("trtcId")
|
@SerializedName("trtcId")
|
||||||
@@ -392,13 +392,11 @@ data class GroupChatResponse(
|
|||||||
@SerializedName("source")
|
@SerializedName("source")
|
||||||
val source: String
|
val source: String
|
||||||
)
|
)
|
||||||
|
class CategoryTemplateTranslation(
|
||||||
|
|
||||||
data class CategoryTranslation(
|
|
||||||
@SerializedName("name")
|
@SerializedName("name")
|
||||||
val name: String?,
|
val name: String,
|
||||||
@SerializedName("description")
|
@SerializedName("description")
|
||||||
val description: String?
|
val description: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class CategoryTemplate(
|
data class CategoryTemplate(
|
||||||
@@ -427,196 +425,18 @@ data class CategoryTemplate(
|
|||||||
@SerializedName("updatedAt")
|
@SerializedName("updatedAt")
|
||||||
val updatedAt: String,
|
val updatedAt: String,
|
||||||
@SerializedName("translations")
|
@SerializedName("translations")
|
||||||
val translations: Map<String, CategoryTranslation>?
|
val translations: Map<String, CategoryTemplateTranslation>?
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* 根据语言代码获取翻译后的名称,如果没有翻译则返回默认名称
|
* 获取本地化名称,优先使用当前语言的翻译,如果没有则使用默认名称
|
||||||
*/
|
*/
|
||||||
fun getLocalizedName(lang: String): String {
|
fun getLocalizedName(): 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
|
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")
|
|
||||||
val page: Int,
|
|
||||||
@SerializedName("pageSize")
|
|
||||||
val pageSize: Int,
|
|
||||||
@SerializedName("total")
|
|
||||||
val total: Int,
|
|
||||||
@SerializedName("list")
|
|
||||||
val list: List<CategoryTemplate>
|
|
||||||
)
|
|
||||||
|
|
||||||
// ========== Prompt Rule 相关数据类 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建规则请求体
|
|
||||||
* @param rule 规则内容,不能为空
|
|
||||||
* @param promptId 智能体ID,与 openId 二选一,promptId 优先
|
|
||||||
* @param openId 智能体的 OpenID(UUID格式),与 promptId 二选一
|
|
||||||
*/
|
|
||||||
data class CreatePromptRuleRequestBody(
|
|
||||||
@SerializedName("rule")
|
|
||||||
val rule: String,
|
|
||||||
@SerializedName("promptId")
|
|
||||||
val promptId: Int? = null,
|
|
||||||
@SerializedName("openId")
|
|
||||||
val openId: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改规则请求体
|
|
||||||
* @param id 规则ID,必填
|
|
||||||
* @param rule 新的规则内容,不能为空
|
|
||||||
* @param promptId 要更改关联的智能体ID(可选)
|
|
||||||
* @param openId 要更改关联的智能体 OpenID(可选)
|
|
||||||
*/
|
|
||||||
data class UpdatePromptRuleRequestBody(
|
|
||||||
@SerializedName("id")
|
|
||||||
val id: Int,
|
|
||||||
@SerializedName("rule")
|
|
||||||
val rule: String,
|
|
||||||
@SerializedName("promptId")
|
|
||||||
val promptId: Int? = null,
|
|
||||||
@SerializedName("openId")
|
|
||||||
val openId: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规则关联的智能体信息
|
|
||||||
* @param id 智能体ID
|
|
||||||
* @param title 智能体标题
|
|
||||||
* @param avatar 智能体头像URL
|
|
||||||
*/
|
|
||||||
data class PromptRuleAgent(
|
|
||||||
@SerializedName("id")
|
|
||||||
val id: Int,
|
|
||||||
@SerializedName("title")
|
|
||||||
val title: String,
|
|
||||||
@SerializedName("avatar")
|
|
||||||
val avatar: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规则详情
|
|
||||||
* @param id 规则ID
|
|
||||||
* @param rule 规则内容
|
|
||||||
* @param creator 创建者名称
|
|
||||||
* @param creatorType 创建者类型(如 "user")
|
|
||||||
* @param scope 作用域(如 "personal")
|
|
||||||
* @param prompt 关联的智能体信息
|
|
||||||
* @param createdAt 创建时间(ISO 8601 格式)
|
|
||||||
* @param updatedAt 更新时间(ISO 8601 格式)
|
|
||||||
*/
|
|
||||||
data class PromptRule(
|
|
||||||
@SerializedName("id")
|
|
||||||
val id: Int,
|
|
||||||
@SerializedName("rule")
|
|
||||||
val rule: String,
|
|
||||||
@SerializedName("creator")
|
|
||||||
val creator: String,
|
|
||||||
@SerializedName("creator_type")
|
|
||||||
val creatorType: String,
|
|
||||||
@SerializedName("scope")
|
|
||||||
val scope: String,
|
|
||||||
@SerializedName("prompt")
|
|
||||||
val prompt: PromptRuleAgent,
|
|
||||||
@SerializedName("created_at")
|
|
||||||
val createdAt: String,
|
|
||||||
@SerializedName("updated_at")
|
|
||||||
val updatedAt: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规则列表响应
|
|
||||||
* @param page 当前页码
|
|
||||||
* @param pageSize 每页数量
|
|
||||||
* @param total 总记录数
|
|
||||||
* @param list 规则列表
|
|
||||||
*/
|
|
||||||
data class PromptRuleListResponse(
|
|
||||||
@SerializedName("page")
|
|
||||||
val page: Int,
|
|
||||||
@SerializedName("pageSize")
|
|
||||||
val pageSize: Int,
|
|
||||||
@SerializedName("total")
|
|
||||||
val total: Int,
|
|
||||||
@SerializedName("list")
|
|
||||||
val list: List<PromptRule>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规则配额信息
|
|
||||||
* @param promptId 智能体ID
|
|
||||||
* @param promptTitle 智能体标题
|
|
||||||
* @param baseMaxCount 基础条数限制(免费配额)
|
|
||||||
* @param purchasedCount 用户购买的额外条数
|
|
||||||
* @param totalMaxCount 总可用条数(基础+购买)
|
|
||||||
* @param currentCount 当前已创建的规则条数
|
|
||||||
* @param remainingCount 剩余可用条数
|
|
||||||
* @param usagePercent 使用百分比(0-100)
|
|
||||||
*/
|
|
||||||
data class PromptRuleQuota(
|
|
||||||
@SerializedName("promptId")
|
|
||||||
val promptId: Int,
|
|
||||||
@SerializedName("promptTitle")
|
|
||||||
val promptTitle: String,
|
|
||||||
@SerializedName("baseMaxCount")
|
|
||||||
val baseMaxCount: Int,
|
|
||||||
@SerializedName("purchasedCount")
|
|
||||||
val purchasedCount: Int,
|
|
||||||
@SerializedName("totalMaxCount")
|
|
||||||
val totalMaxCount: Int,
|
|
||||||
@SerializedName("currentCount")
|
|
||||||
val currentCount: Int,
|
|
||||||
@SerializedName("remainingCount")
|
|
||||||
val remainingCount: Int,
|
|
||||||
@SerializedName("usagePercent")
|
|
||||||
val usagePercent: Double
|
|
||||||
)
|
|
||||||
|
|
||||||
interface RaveNowAPI {
|
interface RaveNowAPI {
|
||||||
@GET("membership/config")
|
@GET("membership/config")
|
||||||
@retrofit2.http.Headers("X-Requires-Auth: true")
|
@retrofit2.http.Headers("X-Requires-Auth: true")
|
||||||
@@ -655,7 +475,6 @@ interface RaveNowAPI {
|
|||||||
@Query("trend") trend: String? = null,
|
@Query("trend") trend: String? = null,
|
||||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||||
@Query("explore") explore: String? = null,
|
@Query("explore") explore: String? = null,
|
||||||
@Query("newsFilter") newsFilter: String? = null,
|
|
||||||
): Response<ListContainer<Moment>>
|
): Response<ListContainer<Moment>>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@@ -897,10 +716,26 @@ interface RaveNowAPI {
|
|||||||
suspend fun getAgent(
|
suspend fun getAgent(
|
||||||
@Query("page") page: Int = 1,
|
@Query("page") page: Int = 1,
|
||||||
@Query("pageSize") pageSize: Int = 20,
|
@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("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("categoryIds") categoryIds: List<Int>? = null,
|
||||||
@Query("random") random: Int? = null,
|
@Query("uncategorized") uncategorized: String? = null,
|
||||||
): Response<DataContainer<ListContainer<Agent>>>
|
): Response<DataContainer<ListContainer<Agent>>>
|
||||||
|
|
||||||
@GET("outside/my/prompts")
|
@GET("outside/my/prompts")
|
||||||
@@ -968,9 +803,8 @@ 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
|
||||||
@Query("lang") lang: String? = null
|
): Response<ListContainer<CategoryTemplate>>
|
||||||
): Response<CategoryListResponse>
|
|
||||||
|
|
||||||
@GET("outside/categories/tree")
|
@GET("outside/categories/tree")
|
||||||
suspend fun getCategoryTree(
|
suspend fun getCategoryTree(
|
||||||
@@ -983,215 +817,6 @@ interface RaveNowAPI {
|
|||||||
@Path("id") id: Int
|
@Path("id") id: Int
|
||||||
): Response<DataContainer<CategoryTemplate>>
|
): Response<DataContainer<CategoryTemplate>>
|
||||||
|
|
||||||
@GET("outside/prompts")
|
|
||||||
suspend fun getPromptsByCategory(
|
|
||||||
@Query("categoryIds") categoryIds: List<Int>? = null,
|
|
||||||
@Query("categoryName") categoryName: String? = null,
|
|
||||||
@Query("uncategorized") uncategorized: String? = null,
|
|
||||||
@Query("page") page: Int? = null,
|
|
||||||
@Query("pageSize") pageSize: Int? = null
|
|
||||||
): Response<ListContainer<Agent>>
|
|
||||||
|
|
||||||
// ========== Prompt Rule API ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建智能体规则
|
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* - 为指定的智能体创建一条新规则
|
|
||||||
* - 规则必须关联到存在的智能体(通过 promptId 或 openId 指定)
|
|
||||||
* - 创建前会自动检查配额限制,如果超出会尝试自动扩容
|
|
||||||
* - 只有创建者可以修改和删除该规则
|
|
||||||
*
|
|
||||||
* @param body 创建规则请求体
|
|
||||||
* - rule: 规则内容,不能为空
|
|
||||||
* - promptId: 智能体ID(与 openId 二选一,promptId 优先)
|
|
||||||
* - openId: 智能体的 OpenID(UUID格式,与 promptId 二选一)
|
|
||||||
*
|
|
||||||
* @return 成功时返回空 Unit,失败时返回错误信息
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* ```kotlin
|
|
||||||
* val request = CreatePromptRuleRequestBody(
|
|
||||||
* rule = "禁止讨论政治话题",
|
|
||||||
* promptId = 123
|
|
||||||
* )
|
|
||||||
* val response = api.createPromptRule(request)
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@POST("outside/prompt/rule")
|
|
||||||
suspend fun createPromptRule(
|
|
||||||
@Body body: CreatePromptRuleRequestBody
|
|
||||||
): Response<Unit>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改智能体规则
|
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* - 修改已存在的规则内容或关联的智能体
|
|
||||||
* - 只有规则的创建者可以修改
|
|
||||||
* - 可以同时修改规则内容和关联的智能体
|
|
||||||
* - 修改关联智能体时会重新验证配额限制
|
|
||||||
*
|
|
||||||
* @param body 修改规则请求体
|
|
||||||
* - id: 规则ID,必填
|
|
||||||
* - rule: 新的规则内容,不能为空
|
|
||||||
* - promptId: 要更改关联的智能体ID(可选)
|
|
||||||
* - openId: 要更改关联的智能体 OpenID(可选)
|
|
||||||
*
|
|
||||||
* @return 成功时返回空 Unit,失败时返回错误信息
|
|
||||||
*
|
|
||||||
* 权限要求:
|
|
||||||
* - 必须是规则的创建者(creator_type 为 "user" 且 create_id 匹配)
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* ```kotlin
|
|
||||||
* val request = UpdatePromptRuleRequestBody(
|
|
||||||
* id = 456,
|
|
||||||
* rule = "禁止讨论政治和敏感话题"
|
|
||||||
* )
|
|
||||||
* val response = api.updatePromptRule(request)
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@retrofit2.http.PUT("outside/prompt/rule")
|
|
||||||
suspend fun updatePromptRule(
|
|
||||||
@Body body: UpdatePromptRuleRequestBody
|
|
||||||
): Response<Unit>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除智能体规则
|
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* - 删除指定的规则
|
|
||||||
* - 只有规则的创建者可以删除
|
|
||||||
* - 删除操作不可恢复,请谨慎操作
|
|
||||||
*
|
|
||||||
* @param id 规则ID
|
|
||||||
*
|
|
||||||
* @return 成功时返回空 Unit,失败时返回错误信息
|
|
||||||
*
|
|
||||||
* 权限要求:
|
|
||||||
* - 必须是规则的创建者(creator_type 为 "user" 且 create_id 匹配)
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* ```kotlin
|
|
||||||
* val response = api.deletePromptRule(456)
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@DELETE("outside/prompt/rule/{id}")
|
|
||||||
suspend fun deletePromptRule(
|
|
||||||
@Path("id") id: Int
|
|
||||||
): Response<Unit>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询智能体规则列表
|
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* - 查询指定智能体下当前用户创建的规则列表
|
|
||||||
* - 支持分页和关键词模糊搜索
|
|
||||||
* - 只返回当前用户创建的规则,不会返回其他用户的规则
|
|
||||||
*
|
|
||||||
* @param promptId 智能体ID,支持数字ID或UUID格式的 openId
|
|
||||||
* @param rule 规则内容关键词(模糊搜索),可选
|
|
||||||
* @param page 页码,默认 1
|
|
||||||
* @param pageSize 每页数量,默认 10
|
|
||||||
*
|
|
||||||
* @return 返回分页的规则列表,包含规则详情和关联的智能体信息
|
|
||||||
*
|
|
||||||
* 响应数据说明:
|
|
||||||
* - page: 当前页码
|
|
||||||
* - pageSize: 每页数量
|
|
||||||
* - total: 总记录数
|
|
||||||
* - list: 规则列表
|
|
||||||
* - id: 规则ID
|
|
||||||
* - rule: 规则内容
|
|
||||||
* - creator: 创建者名称
|
|
||||||
* - creator_type: 创建者类型
|
|
||||||
* - scope: 作用域
|
|
||||||
* - prompt: 关联的智能体信息(id, title, avatar)
|
|
||||||
* - created_at: 创建时间
|
|
||||||
* - updated_at: 更新时间
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* ```kotlin
|
|
||||||
* // 使用数字ID查询
|
|
||||||
* val response1 = api.getPromptRuleList("123", page = 1, pageSize = 10)
|
|
||||||
*
|
|
||||||
* // 使用 OpenID 查询
|
|
||||||
* val response2 = api.getPromptRuleList(
|
|
||||||
* "550e8400-e29b-41d4-a716-446655440000",
|
|
||||||
* page = 1,
|
|
||||||
* pageSize = 10
|
|
||||||
* )
|
|
||||||
*
|
|
||||||
* // 带关键词搜索
|
|
||||||
* val response3 = api.getPromptRuleList(
|
|
||||||
* "123",
|
|
||||||
* rule = "政治",
|
|
||||||
* page = 1,
|
|
||||||
* pageSize = 10
|
|
||||||
* )
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@GET("outside/prompt/{promptId}/rule/list")
|
|
||||||
suspend fun getPromptRuleList(
|
|
||||||
@Path("promptId") promptId: String,
|
|
||||||
@Query("rule") rule: String? = null,
|
|
||||||
@Query("page") page: Int = 1,
|
|
||||||
@Query("pageSize") pageSize: Int = 10
|
|
||||||
): Response<DataContainer<PromptRuleListResponse>>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询智能体规则配额信息
|
|
||||||
*
|
|
||||||
* 功能说明:
|
|
||||||
* - 查询指定智能体的规则条数使用情况
|
|
||||||
* - 包括基础配额、已购买配额、已使用数量等完整信息
|
|
||||||
* - 用于判断是否还能创建新规则
|
|
||||||
*
|
|
||||||
* @param promptId 智能体ID,支持数字ID或UUID格式的 openId
|
|
||||||
*
|
|
||||||
* @return 返回配额详细信息
|
|
||||||
*
|
|
||||||
* 响应数据说明:
|
|
||||||
* - promptId: 智能体ID
|
|
||||||
* - promptTitle: 智能体标题
|
|
||||||
* - baseMaxCount: 基础条数限制(免费配额,由智能体等级决定)
|
|
||||||
* - purchasedCount: 用户购买的额外条数
|
|
||||||
* - totalMaxCount: 总可用条数(baseMaxCount + purchasedCount)
|
|
||||||
* - currentCount: 当前已创建的规则条数(只统计当前用户创建的)
|
|
||||||
* - remainingCount: 剩余可用条数(totalMaxCount - currentCount)
|
|
||||||
* - usagePercent: 使用百分比,0-100(currentCount / totalMaxCount * 100)
|
|
||||||
*
|
|
||||||
* 使用场景:
|
|
||||||
* 1. 创建规则前检查是否有足够配额
|
|
||||||
* 2. 展示规则使用情况统计
|
|
||||||
* 3. 提示用户购买额外配额
|
|
||||||
*
|
|
||||||
* 示例:
|
|
||||||
* ```kotlin
|
|
||||||
* // 使用数字ID查询
|
|
||||||
* val response1 = api.getPromptRuleQuota("123")
|
|
||||||
*
|
|
||||||
* // 使用 OpenID 查询
|
|
||||||
* val response2 = api.getPromptRuleQuota("550e8400-e29b-41d4-a716-446655440000")
|
|
||||||
*
|
|
||||||
* // 处理响应
|
|
||||||
* response1.body()?.data?.let { quota ->
|
|
||||||
* if (quota.remainingCount > 0) {
|
|
||||||
* // 可以创建新规则
|
|
||||||
* println("还可以创建 ${quota.remainingCount} 条规则")
|
|
||||||
* } else {
|
|
||||||
* // 配额已用完
|
|
||||||
* println("规则配额已用完,已使用 ${quota.currentCount}/${quota.totalMaxCount}")
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
@GET("outside/prompt/{promptId}/rule/count")
|
|
||||||
suspend fun getPromptRuleQuota(
|
|
||||||
@Path("promptId") promptId: String
|
|
||||||
): Response<DataContainer<PromptRuleQuota>>
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,21 +299,12 @@ data class MomentEntity(
|
|||||||
// 关联动态
|
// 关联动态
|
||||||
var relMoment: MomentEntity? = null,
|
var relMoment: MomentEntity? = null,
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
var isFavorite: Boolean = false,
|
var isFavorite: Boolean = false
|
||||||
// 新闻相关字段
|
|
||||||
val isNews: Boolean = false,
|
|
||||||
val newsTitle: String = "",
|
|
||||||
val newsUrl: String = "",
|
|
||||||
val newsSource: String = "",
|
|
||||||
val newsCategory: String = "",
|
|
||||||
val newsLanguage: String = "",
|
|
||||||
val newsContent: String = ""
|
|
||||||
)
|
)
|
||||||
class MomentLoaderExtraArgs(
|
class MomentLoaderExtraArgs(
|
||||||
val explore: Boolean? = false,
|
val explore: Boolean? = false,
|
||||||
val timelineId: Int? = null,
|
val timelineId: Int? = null,
|
||||||
val authorId : Int? = null,
|
val authorId : Int? = null
|
||||||
val newsOnly: Boolean? = null
|
|
||||||
)
|
)
|
||||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||||
override suspend fun fetchData(
|
override suspend fun fetchData(
|
||||||
@@ -326,8 +317,7 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
explore = if (extra.explore == true) "true" else "",
|
explore = if (extra.explore == true) "true" else "",
|
||||||
timelineId = extra.timelineId,
|
timelineId = extra.timelineId,
|
||||||
authorId = extra.authorId,
|
authorId = extra.authorId
|
||||||
newsFilter = if (extra.newsOnly == true) "news_only" else ""
|
|
||||||
)
|
)
|
||||||
val data = result.body()?.let {
|
val data = result.body()?.let {
|
||||||
ListContainer(
|
ListContainer(
|
||||||
@@ -365,18 +355,6 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
|||||||
onListChanged?.invoke(this.list)
|
onListChanged?.invoke(this.list)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCommentCount(id: Int, delta: Int) {
|
|
||||||
this.list = this.list.map { momentItem ->
|
|
||||||
if (momentItem.id == id) {
|
|
||||||
val newCount = (momentItem.commentCount + delta).coerceAtLeast(0)
|
|
||||||
momentItem.copy(commentCount = newCount)
|
|
||||||
} else {
|
|
||||||
momentItem
|
|
||||||
}
|
|
||||||
}.toMutableList()
|
|
||||||
onListChanged?.invoke(this.list)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun removeMoment(id: Int) {
|
fun removeMoment(id: Int) {
|
||||||
this.list = this.list.filter { it.id != id }.toMutableList()
|
this.list = this.list.filter { it.id != id }.toMutableList()
|
||||||
onListChanged?.invoke(this.list)
|
onListChanged?.invoke(this.list)
|
||||||
|
|||||||
@@ -30,12 +30,6 @@ object AppStore {
|
|||||||
AppState.appTheme = DarkThemeColors()
|
AppState.appTheme = DarkThemeColors()
|
||||||
}
|
}
|
||||||
|
|
||||||
// load chat background
|
|
||||||
val savedBgUrl = sharedPreferences.getString("chatBackgroundUrl", null)
|
|
||||||
if (savedBgUrl != null) {
|
|
||||||
AppState.chatBackgroundUrl = savedBgUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveData() {
|
suspend fun saveData() {
|
||||||
@@ -60,15 +54,5 @@ object AppStore {
|
|||||||
}.apply()
|
}.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveChatBackgroundUrl(url: String?) {
|
|
||||||
sharedPreferences.edit().apply {
|
|
||||||
if (url != null) {
|
|
||||||
putString("chatBackgroundUrl", url)
|
|
||||||
} else {
|
|
||||||
remove("chatBackgroundUrl")
|
|
||||||
}
|
|
||||||
}.apply()
|
|
||||||
AppState.chatBackgroundUrl = url
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -41,7 +41,6 @@ import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
|||||||
import com.aiosman.ravenow.ui.agent.AgentImageCropScreen
|
import com.aiosman.ravenow.ui.agent.AgentImageCropScreen
|
||||||
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
|
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
import com.aiosman.ravenow.ui.chat.ChatAiScreen
|
||||||
import com.aiosman.ravenow.ui.chat.ChatSettingScreen
|
|
||||||
import com.aiosman.ravenow.ui.chat.ChatScreen
|
import com.aiosman.ravenow.ui.chat.ChatScreen
|
||||||
import com.aiosman.ravenow.ui.chat.GroupChatScreen
|
import com.aiosman.ravenow.ui.chat.GroupChatScreen
|
||||||
import com.aiosman.ravenow.ui.comment.CommentsScreen
|
import com.aiosman.ravenow.ui.comment.CommentsScreen
|
||||||
@@ -107,7 +106,6 @@ sealed class NavigationRoute(
|
|||||||
data object FavouriteList : NavigationRoute("FavouriteList")
|
data object FavouriteList : NavigationRoute("FavouriteList")
|
||||||
data object Chat : NavigationRoute("Chat/{id}")
|
data object Chat : NavigationRoute("Chat/{id}")
|
||||||
data object ChatAi : NavigationRoute("ChatAi/{id}")
|
data object ChatAi : NavigationRoute("ChatAi/{id}")
|
||||||
data object ChatSetting : NavigationRoute("ChatSetting")
|
|
||||||
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
|
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
|
||||||
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
|
||||||
data object ImageCrop : NavigationRoute("ImageCrop")
|
data object ImageCrop : NavigationRoute("ImageCrop")
|
||||||
@@ -493,14 +491,6 @@ fun NavigationController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(route = NavigationRoute.ChatSetting.route) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAnimatedContentScope provides this,
|
|
||||||
) {
|
|
||||||
ChatSettingScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
composable(
|
composable(
|
||||||
route = NavigationRoute.ChatGroup.route,
|
route = NavigationRoute.ChatGroup.route,
|
||||||
arguments = listOf(navArgument("id") { type = NavType.StringType },
|
arguments = listOf(navArgument("id") { type = NavType.StringType },
|
||||||
|
|||||||
@@ -79,12 +79,11 @@ import com.aiosman.ravenow.R
|
|||||||
import com.aiosman.ravenow.entity.ChatItem
|
import com.aiosman.ravenow.entity.ChatItem
|
||||||
import com.aiosman.ravenow.exp.formatChatTime
|
import com.aiosman.ravenow.exp.formatChatTime
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.composables.DropdownMenu
|
||||||
|
import com.aiosman.ravenow.ui.composables.MenuItem
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import com.aiosman.ravenow.utils.NetworkUtils
|
import com.aiosman.ravenow.utils.NetworkUtils
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import io.openim.android.sdk.enums.MessageType
|
import io.openim.android.sdk.enums.MessageType
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -92,10 +91,10 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatAiScreen(userId: String) {
|
fun ChatAiScreen(userId: String) {
|
||||||
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalNavController.current.context
|
val context = LocalNavController.current.context
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val chatBackgroundUrl = AppState.chatBackgroundUrl
|
|
||||||
var goToNewCount by remember { mutableStateOf(0) }
|
var goToNewCount by remember { mutableStateOf(0) }
|
||||||
val viewModel = viewModel<ChatAiViewModel>(
|
val viewModel = viewModel<ChatAiViewModel>(
|
||||||
key = "ChatAiViewModel_$userId",
|
key = "ChatAiViewModel_$userId",
|
||||||
@@ -159,25 +158,14 @@ fun ChatAiScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
if (chatBackgroundUrl != null && chatBackgroundUrl.isNotEmpty()) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = chatBackgroundUrl,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentDescription = "chat_background",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
backgroundColor = Color.Transparent,
|
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color.Transparent)
|
.background(AppColors.background)
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
Row(
|
Row(
|
||||||
@@ -225,24 +213,49 @@ fun ChatAiScreen(userId: String) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(28.dp)
|
.size(28.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
navController.navigate(NavigationRoute.ChatSetting.route)
|
isMenuExpanded = true
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(
|
colorFilter = ColorFilter.tint(
|
||||||
AppColors.text)
|
AppColors.text)
|
||||||
)
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = isMenuExpanded,
|
||||||
|
onDismissRequest = {
|
||||||
|
isMenuExpanded = false
|
||||||
|
},
|
||||||
|
menuItems = listOf(
|
||||||
|
MenuItem(
|
||||||
|
title = if (viewModel.notificationStrategy == "mute") "Unmute" else "Mute",
|
||||||
|
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
|
||||||
|
) {
|
||||||
|
|
||||||
|
isMenuExpanded = false
|
||||||
|
if (NetworkUtils.isNetworkAvailable(context)) {
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
if (viewModel.notificationStrategy == "mute") {
|
||||||
|
viewModel.updateNotificationStrategy("active")
|
||||||
|
} else {
|
||||||
|
viewModel.updateNotificationStrategy("mute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
val hasChatBackground = AppState.chatBackgroundUrl != null && AppState.chatBackgroundUrl!!.isNotEmpty()
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.imePadding()
|
.imePadding()
|
||||||
) {
|
) {
|
||||||
if (!hasChatBackground) {
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -250,7 +263,6 @@ fun ChatAiScreen(userId: String) {
|
|||||||
.background(
|
.background(
|
||||||
AppColors.decentBackground)
|
AppColors.decentBackground)
|
||||||
)
|
)
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
ChatAiInput(
|
ChatAiInput(
|
||||||
onSendImage = {
|
onSendImage = {
|
||||||
@@ -271,7 +283,7 @@ fun ChatAiScreen(userId: String) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Transparent)
|
.background(Color.White)
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -334,7 +346,8 @@ fun ChatAiScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,8 +571,7 @@ fun ChatAiInput(
|
|||||||
}
|
}
|
||||||
Box( modifier = Modifier
|
Box( modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color.Transparent)
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 45.dp),){
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ import androidx.compose.ui.platform.LocalFocusManager
|
|||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -75,12 +74,10 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.entity.ChatItem
|
import com.aiosman.ravenow.entity.ChatItem
|
||||||
import com.aiosman.ravenow.exp.formatChatTime
|
import com.aiosman.ravenow.exp.formatChatTime
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.ravenow.ui.composables.DropdownMenu
|
import com.aiosman.ravenow.ui.composables.DropdownMenu
|
||||||
import com.aiosman.ravenow.ui.composables.MenuItem
|
import com.aiosman.ravenow.ui.composables.MenuItem
|
||||||
@@ -94,6 +91,7 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen(userId: String) {
|
fun ChatScreen(userId: String) {
|
||||||
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalNavController.current.context
|
val context = LocalNavController.current.context
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
@@ -160,37 +158,14 @@ fun ChatScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
// 背景图层
|
|
||||||
val bgUrl = AppState.chatBackgroundUrl
|
|
||||||
if (bgUrl != null) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = bgUrl,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentDescription = "chat_background",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 无背景时使用主题背景色
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.background)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
backgroundColor = Color.Transparent,
|
|
||||||
topBar = {
|
topBar = {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color.Transparent)
|
.background(AppColors.background)
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
Row(
|
Row(
|
||||||
@@ -239,12 +214,39 @@ fun ChatScreen(userId: String) {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(28.dp)
|
.size(28.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
navController.navigate(NavigationRoute.ChatSetting.route)
|
isMenuExpanded = true
|
||||||
},
|
},
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
colorFilter = ColorFilter.tint(
|
colorFilter = ColorFilter.tint(
|
||||||
AppColors.text)
|
AppColors.text)
|
||||||
)
|
)
|
||||||
|
DropdownMenu(
|
||||||
|
expanded = isMenuExpanded,
|
||||||
|
onDismissRequest = {
|
||||||
|
isMenuExpanded = false
|
||||||
|
},
|
||||||
|
menuItems = listOf(
|
||||||
|
MenuItem(
|
||||||
|
title = if (viewModel.notificationStrategy == "mute") "取消静音" else "静音",
|
||||||
|
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
|
||||||
|
) {
|
||||||
|
|
||||||
|
isMenuExpanded = false
|
||||||
|
if (NetworkUtils.isNetworkAvailable(context)) {
|
||||||
|
viewModel.viewModelScope.launch {
|
||||||
|
if (viewModel.notificationStrategy == "mute") {
|
||||||
|
viewModel.updateNotificationStrategy("active")
|
||||||
|
} else {
|
||||||
|
viewModel.updateNotificationStrategy("mute")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -281,7 +283,7 @@ fun ChatScreen(userId: String) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color.Transparent)
|
.background(AppColors.background)
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -345,7 +347,6 @@ fun ChatScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -571,7 +572,7 @@ fun ChatInput(
|
|||||||
}
|
}
|
||||||
Box( modifier = Modifier
|
Box( modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 45.dp),){
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -1,362 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.chat
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.SheetState
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
|
||||||
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ChatSettingScreen() {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
var showThemeSheet by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(appColors.secondaryBackground)
|
|
||||||
) {
|
|
||||||
StatusBarSpacer()
|
|
||||||
Box(modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)) {
|
|
||||||
NoticeScreenHeader(title = stringResource(R.string.chat_settings), moreIcon = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
|
||||||
SettingCard(
|
|
||||||
title = stringResource(R.string.chat_theme_settings),
|
|
||||||
onClick = { showThemeSheet = true }
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
SettingCard(
|
|
||||||
title = stringResource(R.string.report),
|
|
||||||
onClick = { /* TODO: 跳转举报 */ }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showThemeSheet) {
|
|
||||||
ThemePickerSheet(onClose = { showThemeSheet = false })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SettingCard(title: String, onClick: () -> Unit) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(appColors.background)
|
|
||||||
.clickable { onClick() }
|
|
||||||
.padding(horizontal = 12.dp, vertical = 14.dp)
|
|
||||||
) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
|
|
||||||
Text(
|
|
||||||
text = title,
|
|
||||||
color = appColors.text,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.W500,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rave_now_nav_right),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = appColors.text,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
private fun ThemePickerSheet(onClose: () -> Unit) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
|
||||||
val sheetHeight = screenHeight * 0.9f
|
|
||||||
var previewUrl by remember { mutableStateOf<String?>(null) }
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = onClose,
|
|
||||||
sheetState = sheetState,
|
|
||||||
containerColor = appColors.secondaryBackground,
|
|
||||||
dragHandle = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
) {
|
|
||||||
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 4.dp, bottom = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.size(24.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.custom_background),
|
|
||||||
color = appColors.text,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
modifier = Modifier.weight(1f).padding(start = 90.dp),
|
|
||||||
)
|
|
||||||
IconButton(onClick = onClose) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_close),
|
|
||||||
contentDescription = "close",
|
|
||||||
tint = appColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 从相册选择
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(appColors.background)
|
|
||||||
.clickable { /* TODO: 打开相册选择 */ }
|
|
||||||
.padding(horizontal = 16.dp, vertical = 14.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.select_from_gallery), color = appColors.text, fontSize = 15.sp, modifier = Modifier.weight(1f))
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.group_info_edit),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = appColors.text,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Text(text = stringResource(R.string.featured_backgrounds), color = appColors.text, fontSize = 12.sp)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
val presets = remember {
|
|
||||||
listOf(
|
|
||||||
"https://picsum.photos/seed/ai1/400/600",
|
|
||||||
"https://picsum.photos/seed/ai2/400/600",
|
|
||||||
"https://picsum.photos/seed/ai3/400/600",
|
|
||||||
"https://picsum.photos/seed/ai4/400/600",
|
|
||||||
"https://picsum.photos/seed/ai5/400/600",
|
|
||||||
"https://picsum.photos/seed/ai6/400/600",
|
|
||||||
"https://picsum.photos/seed/ai7/400/600",
|
|
||||||
"https://picsum.photos/seed/ai8/400/600",
|
|
||||||
"https://picsum.photos/seed/ai9/400/600",
|
|
||||||
"https://picsum.photos/seed/ai10/400/600",
|
|
||||||
"https://picsum.photos/seed/ai11/400/600",
|
|
||||||
"https://picsum.photos/seed/ai12/400/600",
|
|
||||||
"https://picsum.photos/seed/ai13/400/600",
|
|
||||||
"https://picsum.photos/seed/ai14/400/600",
|
|
||||||
"https://picsum.photos/seed/ai15/400/600",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(3),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
items(presets) { url ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clickable { previewUrl = url }
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(appColors.decentBackground)
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = url,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(3f / 4f),
|
|
||||||
contentDescription = "preset",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(6.dp))
|
|
||||||
Text(
|
|
||||||
text = "Heart Drive",
|
|
||||||
color = appColors.text,
|
|
||||||
fontSize = 12.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 预览自定义背景弹窗
|
|
||||||
if (previewUrl != null) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { previewUrl = null },
|
|
||||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
|
||||||
containerColor = appColors.secondaryBackground,
|
|
||||||
dragHandle = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight)
|
|
||||||
) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize()) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = previewUrl!!,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentDescription = "preview_bg",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
|
|
||||||
Column(modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.previewing_custom_background), color = Color.White, fontSize = 15.sp)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Column(modifier = Modifier.padding(8.dp)) {
|
|
||||||
Row {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(24.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(horizontal = 14.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.each_theme_unique_experience), color = Color.Black, fontSize = 12.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Row {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(24.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(horizontal = 14.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.select_apply_to_use_theme), color = Color.Black, fontSize = 12.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
Row {
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.clip(RoundedCornerShape(20.dp))
|
|
||||||
.background(Color(0xFF7C4DFF))
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.tap_cancel_to_preview_other_themes), color = Color.White, fontSize = 12.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
// 底部按钮
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
.padding(bottom = 60.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.clickable { previewUrl = null }
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.cancel), color = Color.Black, fontSize = 14.sp)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.size(12.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(
|
|
||||||
brush = Brush.horizontalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color(0xFFEE2A33),
|
|
||||||
Color(0xFFD80264),
|
|
||||||
Color(0xFF664C92)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.clickable {
|
|
||||||
previewUrl?.let { url ->
|
|
||||||
com.aiosman.ravenow.AppStore.saveChatBackgroundUrl(url)
|
|
||||||
previewUrl = null
|
|
||||||
onClose()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(vertical = 12.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(text = stringResource(R.string.moment_ai_apply), color = Color.White, fontSize = 14.sp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -17,7 +17,6 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -33,7 +32,6 @@ fun ActionButton(
|
|||||||
text: String,
|
text: String,
|
||||||
color: Color? = null,
|
color: Color? = null,
|
||||||
backgroundColor: Color? = null,
|
backgroundColor: Color? = null,
|
||||||
backgroundBrush: Brush? = null,
|
|
||||||
leading: @Composable (() -> Unit)? = null,
|
leading: @Composable (() -> Unit)? = null,
|
||||||
expandText: Boolean = false,
|
expandText: Boolean = false,
|
||||||
contentPadding: PaddingValues = PaddingValues(vertical = 16.dp),
|
contentPadding: PaddingValues = PaddingValues(vertical = 16.dp),
|
||||||
@@ -67,11 +65,7 @@ fun ActionButton(
|
|||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.clip(RoundedCornerShape(roundCorner.dp))
|
.clip(RoundedCornerShape(roundCorner.dp))
|
||||||
.background(
|
.background(animatedBackgroundColor)
|
||||||
brush = backgroundBrush ?: Brush.linearGradient(
|
|
||||||
colors = listOf(animatedBackgroundColor, animatedBackgroundColor)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
if (enabled && !isLoading) {
|
if (enabled && !isLoading) {
|
||||||
click()
|
click()
|
||||||
|
|||||||
@@ -56,14 +56,15 @@ fun AnimatedFavouriteIcon(
|
|||||||
}) {
|
}) {
|
||||||
Image(
|
Image(
|
||||||
painter = if (isFavourite) {
|
painter = if (isFavourite) {
|
||||||
painterResource(id = R.mipmap.icon_variant_2)
|
painterResource(id = R.drawable.rider_pro_favourited)
|
||||||
} else {
|
} else {
|
||||||
painterResource(id = R.mipmap.icon_collect)
|
painterResource(id = R.drawable.rider_pro_favourite)
|
||||||
},
|
},
|
||||||
contentDescription = "Favourite",
|
contentDescription = "Favourite",
|
||||||
modifier = modifier.graphicsLayer {
|
modifier = modifier.graphicsLayer {
|
||||||
rotationZ = animatableRotation.value
|
rotationZ = animatableRotation.value
|
||||||
},
|
},
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,7 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|||||||
@Composable
|
@Composable
|
||||||
fun EditCommentBottomModal(
|
fun EditCommentBottomModal(
|
||||||
replyComment: CommentEntity? = null,
|
replyComment: CommentEntity? = null,
|
||||||
autoFocus: Boolean = false,
|
onSend: (String) -> Unit = {}
|
||||||
onSend: (String) -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
var text by remember { mutableStateOf("") }
|
var text by remember { mutableStateOf("") }
|
||||||
@@ -60,11 +59,9 @@ fun EditCommentBottomModal(
|
|||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
LaunchedEffect(autoFocus) {
|
LaunchedEffect(Unit) {
|
||||||
if (autoFocus) {
|
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -364,6 +364,16 @@ fun MomentContentGroup(
|
|||||||
onPageChange: (Int) -> Unit = {}
|
onPageChange: (Int) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
if (momentEntity.momentTextContent.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
text = momentEntity.momentTextContent,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
if (momentEntity.relMoment != null) {
|
if (momentEntity.relMoment != null) {
|
||||||
RelPostCard(
|
RelPostCard(
|
||||||
momentEntity = momentEntity.relMoment!!,
|
momentEntity = momentEntity.relMoment!!,
|
||||||
@@ -379,17 +389,6 @@ fun MomentContentGroup(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (momentEntity.momentTextContent.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = momentEntity.momentTextContent,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -402,8 +401,8 @@ fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
|
|||||||
.size(width = 24.dp, height = 24.dp),
|
.size(width = 24.dp, height = 24.dp),
|
||||||
painter = painterResource(id = icon),
|
painter = painterResource(id = icon),
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
if (count.isNotEmpty()) {
|
|
||||||
Text(
|
Text(
|
||||||
text = count,
|
text = count,
|
||||||
modifier = Modifier.padding(start = 7.dp),
|
modifier = Modifier.padding(start = 7.dp),
|
||||||
@@ -411,7 +410,6 @@ fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
|
|||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -512,11 +510,49 @@ fun MomentBottomOperateRowGroup(
|
|||||||
.weight(1f),
|
.weight(1f),
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Row(
|
Box(
|
||||||
modifier = Modifier.weight(1f).fillMaxHeight(),
|
modifier = Modifier
|
||||||
verticalAlignment = Alignment.CenterVertically
|
.weight(1f)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
||||||
|
AnimatedFavouriteIcon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
isFavourite = momentEntity.isFavorite
|
||||||
|
) {
|
||||||
|
onFavoriteClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.noRippleClickable {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastClickTime.value > clickDelay) {
|
||||||
|
lastClickTime.value = currentTime
|
||||||
|
onCommentClick()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(
|
||||||
|
icon = R.drawable.rider_pro_comment,
|
||||||
|
count = momentEntity.commentCount.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
) {
|
) {
|
||||||
// 点赞按钮
|
|
||||||
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
||||||
AnimatedLikeIcon(
|
AnimatedLikeIcon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
@@ -525,44 +561,6 @@ fun MomentBottomOperateRowGroup(
|
|||||||
onLikeClick()
|
onLikeClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(10.dp))
|
|
||||||
// 评论按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.noRippleClickable {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastClickTime.value > clickDelay) {
|
|
||||||
lastClickTime.value = currentTime
|
|
||||||
onCommentClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
MomentOperateBtn(
|
|
||||||
icon = R.mipmap.icon_comment,
|
|
||||||
count = momentEntity.commentCount.toString()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.width(28.dp))
|
|
||||||
// 转发按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.noRippleClickable {
|
|
||||||
// TODO: 实现转发功能
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
MomentOperateBtn(
|
|
||||||
icon = R.mipmap.icon_share,
|
|
||||||
count = ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 收藏按钮
|
|
||||||
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
|
||||||
AnimatedFavouriteIcon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
isFavourite = momentEntity.isFavorite
|
|
||||||
) {
|
|
||||||
onFavoriteClick()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,10 @@
|
|||||||
package com.aiosman.ravenow.ui.composables
|
package com.aiosman.ravenow.ui.composables
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
@@ -17,14 +12,11 @@ import androidx.compose.runtime.Composable
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
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.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
/**
|
/**
|
||||||
* 可复用的标签页组件
|
* 可复用的标签页组件
|
||||||
*/
|
*/
|
||||||
@@ -62,43 +54,3 @@ fun TabItem(
|
|||||||
fun TabSpacer() {
|
fun TabSpacer() {
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun UnderlineTabItem(
|
|
||||||
text: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.noRippleClickable { onClick() },
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.ExtraBold,
|
|
||||||
color = if (isSelected) AppColors.text else AppColors.text.copy(alpha = 0.6f),
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp).padding(top = 13.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 选中状态下显示图标
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
if (isSelected) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.underline),
|
|
||||||
contentDescription = "selected indicator",
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -134,7 +134,7 @@ fun FavouriteListPage() {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if (com.aiosman.ravenow.AppState.darkMode) R.mipmap.invalid_dark
|
id = if (com.aiosman.ravenow.AppState.darkMode) R.mipmap.syss_yh_qs_as_img
|
||||||
else R.mipmap.invalid_name_1),
|
else R.mipmap.invalid_name_1),
|
||||||
contentDescription = "No favourites",
|
contentDescription = "No favourites",
|
||||||
modifier = Modifier.size(110.dp)
|
modifier = Modifier.size(110.dp)
|
||||||
|
|||||||
@@ -276,15 +276,12 @@ fun IndexScreen() {
|
|||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar(
|
NavigationBar(
|
||||||
modifier = Modifier.height(58.dp + navigationBarHeight),
|
modifier = Modifier.height(58.dp + navigationBarHeight),
|
||||||
containerColor = AppColors.tabUnselectedBackground
|
containerColor = AppColors.background
|
||||||
) {
|
) {
|
||||||
item.forEachIndexed { idx, it ->
|
item.forEachIndexed { idx, it ->
|
||||||
val isSelected = model.tabIndex == idx
|
val isSelected = model.tabIndex == idx
|
||||||
// 定义新的选中颜色
|
|
||||||
val selectedColor = Color(0xFF7C45ED)
|
|
||||||
|
|
||||||
val iconTint by animateColorAsState(
|
val iconTint by animateColorAsState(
|
||||||
targetValue = if (isSelected) selectedColor else AppColors.text,
|
targetValue = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
||||||
animationSpec = tween(durationMillis = 250), label = ""
|
animationSpec = tween(durationMillis = 250), label = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -346,7 +343,7 @@ fun IndexScreen() {
|
|||||||
.width(48.dp)
|
.width(48.dp)
|
||||||
.height(32.dp)
|
.height(32.dp)
|
||||||
.background(
|
.background(
|
||||||
color = if (isSelected) selectedColor.copy(alpha = 0.15f) else Color.Transparent,
|
color = if (isSelected) AppColors.brandColorsColor.copy(alpha = 0.15f) else Color.Transparent,
|
||||||
shape = RoundedCornerShape(12.dp)
|
shape = RoundedCornerShape(12.dp)
|
||||||
),
|
),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
@@ -365,7 +362,7 @@ fun IndexScreen() {
|
|||||||
Text(
|
Text(
|
||||||
text = it.label(),
|
text = it.label(),
|
||||||
fontSize = 10.sp,
|
fontSize = 10.sp,
|
||||||
color = if (isSelected) selectedColor else AppColors.text,
|
color = if (isSelected) Color.Blue else AppColors.text,
|
||||||
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ sealed class NavigationItem(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
data object Home : NavigationItem("Home",
|
data object Home : NavigationItem("Home",
|
||||||
icon = { R.mipmap.bars_x_buttons_home_n_copy },
|
icon = { R.drawable.rider_pro_nav_home },
|
||||||
selectedIcon = { R.mipmap.bars_x_buttons_home_n_copy_2 },
|
selectedIcon = { R.mipmap.bars_x_buttons_home_s },
|
||||||
label = { stringResource(R.string.main_ai) }
|
label = { stringResource(R.string.main_home) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Ai : NavigationItem("Ai",
|
data object Ai : NavigationItem("Ai",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -28,19 +29,15 @@ import androidx.compose.foundation.pager.rememberPagerState
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.derivedStateOf
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -52,6 +49,10 @@ import androidx.compose.ui.res.stringResource
|
|||||||
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 androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
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.AppStore
|
||||||
@@ -78,14 +79,7 @@ import androidx.compose.foundation.lazy.LazyColumn
|
|||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.ui.zIndex
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.window.Dialog
|
|
||||||
import androidx.compose.ui.window.DialogProperties
|
|
||||||
import androidx.compose.foundation.lazy.grid.items as gridItems
|
|
||||||
import androidx.compose.material.CircularProgressIndicator
|
|
||||||
import androidx.compose.ui.draw.alpha
|
|
||||||
|
|
||||||
// 检测是否接近列表底部的扩展函数
|
// 检测是否接近列表底部的扩展函数
|
||||||
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
|
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
|
||||||
@@ -96,7 +90,7 @@ fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
|
|||||||
return lastVisibleItemIndex >= (totalItemsCount - buffer)
|
return lastVisibleItemIndex >= (totalItemsCount - buffer)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun Agent() {
|
fun Agent() {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
@@ -116,6 +110,19 @@ fun Agent() {
|
|||||||
viewModel.ensureDataLoaded()
|
viewModel.ensureDataLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 列表滚动状态(用于吸顶与加载更多)
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
// 监听滚动到底部,加载更多网格数据
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.isScrolledToEnd() }
|
||||||
|
.collect { atEnd ->
|
||||||
|
if (atEnd && !viewModel.isLoading) {
|
||||||
|
viewModel.loadMoreGridAgentData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 防抖状态
|
// 防抖状态
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
var lastClickTime by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
@@ -127,78 +134,61 @@ fun Agent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val agentItems = viewModel.agentItems
|
Box(
|
||||||
var selectedTabIndex by remember { mutableStateOf(0) }
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
// 无限滚动状态
|
// 固定顶部搜索条
|
||||||
val listState = rememberLazyListState()
|
Box(
|
||||||
|
|
||||||
// 创建一个可观察的滚动到底部状态
|
|
||||||
val isScrolledToEnd by remember {
|
|
||||||
derivedStateOf {
|
|
||||||
listState.isScrolledToEnd()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检测滚动到底部并加载更多数据
|
|
||||||
LaunchedEffect(isScrolledToEnd) {
|
|
||||||
if (isScrolledToEnd && !viewModel.isLoadingMore && agentItems.isNotEmpty() && viewModel.hasMoreData) {
|
|
||||||
viewModel.loadMoreAgents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Scaffold(
|
|
||||||
topBar = {
|
|
||||||
TopAppBar(
|
|
||||||
title = {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.home_logo),
|
|
||||||
contentDescription = "Rave AI Logo",
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(44.dp)
|
.fillMaxWidth()
|
||||||
.padding(top =9.dp,bottom=9.dp)
|
.background(AppColors.background)
|
||||||
.wrapContentSize(),
|
.zIndex(999.0f)
|
||||||
// colorFilter = ColorFilter.tint(AppColors.text)
|
.height(44.dp + statusBarPaddingValues.calculateTopPadding())
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.fillMaxHeight().padding(top = 32.dp, start = 16.dp, end = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "Rave AI",
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.W900,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
)
|
)
|
||||||
},
|
|
||||||
actions = {
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
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(44.dp)
|
.size(24.dp)
|
||||||
.padding(top = 9.dp,bottom=9.dp)
|
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
navController.navigate(NavigationRoute.Search.route)
|
navController.navigate(NavigationRoute.Search.route)
|
||||||
},
|
},
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
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,
|
|
||||||
contentWindowInsets = WindowInsets(0, 0, 0, 0)
|
|
||||||
) { paddingValues ->
|
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
|
||||||
.padding(
|
.padding(
|
||||||
|
top = 44.dp + statusBarPaddingValues.calculateTopPadding() + 15.dp,
|
||||||
bottom = navigationBarPaddings,
|
bottom = navigationBarPaddings,
|
||||||
start = 8.dp,
|
start = 16.dp,
|
||||||
end = 8.dp
|
end = 16.dp
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
// 类别标签页 - 吸顶
|
// 动态标签页
|
||||||
stickyHeader(key = "category_tabs") {
|
stickyHeader(key = "category_tabs") {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -206,49 +196,33 @@ fun Agent() {
|
|||||||
.background(AppColors.background)
|
.background(AppColors.background)
|
||||||
.padding(top = 4.dp, bottom = 8.dp)
|
.padding(top = 4.dp, bottom = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
val selectedTabIndex = viewModel.selectedCategoryIndex
|
||||||
|
if (viewModel.categories.isNotEmpty()) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight()
|
.wrapContentHeight()
|
||||||
.padding(bottom = 8.dp),
|
.padding(bottom = 16.dp),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
item {
|
|
||||||
CustomTabItem(
|
|
||||||
text = stringResource(R.string.agent_recommend),
|
|
||||||
isSelected = selectedTabIndex == 0,
|
|
||||||
onClick = {
|
|
||||||
selectedTabIndex = 0
|
|
||||||
viewModel.loadAllAgents()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
TabSpacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 动态添加分类标签
|
|
||||||
viewModel.categories.forEachIndexed { index, category ->
|
viewModel.categories.forEachIndexed { index, category ->
|
||||||
item {
|
item {
|
||||||
CustomTabItem(
|
CustomTabItem(
|
||||||
text = category.name,
|
text = category.getLocalizedName(),
|
||||||
isSelected = selectedTabIndex == index + 1,
|
isSelected = selectedTabIndex == index,
|
||||||
onClick = {
|
onClick = { viewModel.selectCategory(index) }
|
||||||
selectedTabIndex = index + 1
|
|
||||||
viewModel.loadAgentsByCategory(category.id)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (index < viewModel.categories.size - 1) {
|
||||||
|
item { TabSpacer() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
item {
|
|
||||||
TabSpacer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 推荐内容区域
|
// 推荐内容区域
|
||||||
item {
|
item {
|
||||||
Column(
|
Column(
|
||||||
@@ -256,18 +230,7 @@ fun Agent() {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.padding(vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
when {
|
AgentViewPagerSection(agentItems = viewModel.topAgentItems, viewModel)
|
||||||
selectedTabIndex == 0 -> {
|
|
||||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
|
||||||
}
|
|
||||||
selectedTabIndex in 1..viewModel.categories.size -> {
|
|
||||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
|
|
||||||
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,7 +282,7 @@ fun Agent() {
|
|||||||
|
|
||||||
item { Spacer(modifier = Modifier.height(20.dp)) }
|
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||||
|
|
||||||
// "发现更多" 标题 - 吸顶
|
// 发现区域
|
||||||
stickyHeader(key = "discover_more") {
|
stickyHeader(key = "discover_more") {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -338,43 +301,47 @@ fun Agent() {
|
|||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = stringResource(R.string.agent_find),
|
text = stringResource(R.string.agent_find),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||||
|
|
||||||
// Agent网格 - 使用行式布局
|
// Agent 两列网格行
|
||||||
items(
|
items(viewModel.gridAgentItems.chunked(2)) { rowItems ->
|
||||||
items = agentItems.chunked(2),
|
|
||||||
key = { row -> row.firstOrNull()?.openId ?: "" }
|
|
||||||
) { rowItems ->
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 16.dp),
|
.padding(
|
||||||
|
top = 20.dp,
|
||||||
|
bottom = 20.dp
|
||||||
|
),
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
rowItems.forEach { agentItem ->
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
AgentCardSquare(
|
AgentCardSquare(
|
||||||
agentItem = agentItem,
|
agentItem = rowItems[0],
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = LocalNavController.current
|
navController = LocalNavController.current
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (rowItems.size > 1) {
|
||||||
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
|
AgentCardSquare(
|
||||||
|
agentItem = rowItems[1],
|
||||||
|
viewModel = viewModel,
|
||||||
|
navController = LocalNavController.current
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// 如果这一行只有一个item,添加一个空的占位符
|
} else {
|
||||||
if (rowItems.size == 1) {
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载更多指示器
|
// 加载更多指示器
|
||||||
if (viewModel.isLoadingMore) {
|
if (viewModel.isLoading) {
|
||||||
item {
|
item {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -389,7 +356,7 @@ fun Agent() {
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = "加载中...",
|
text = stringResource(R.string.agent_chat_loading),
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
@@ -532,20 +499,20 @@ fun AgentCardSquare(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 聊天按钮
|
// 聊天按钮,固定在底部居中,距离底部有一定边距
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.padding(bottom = 12.dp)
|
.padding(bottom = 12.dp) // 距离底部的边距
|
||||||
.width(60.dp)
|
.width(60.dp)
|
||||||
.height(32.dp)
|
.height(32.dp)
|
||||||
.background(
|
.background(
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
shape = RoundedCornerShape(
|
shape = RoundedCornerShape(
|
||||||
topStart = 14.dp,
|
topStart = 10.dp,
|
||||||
topEnd = 14.dp,
|
topEnd = 10.dp,
|
||||||
bottomStart = 0.dp,
|
bottomStart = 0.dp,
|
||||||
bottomEnd = 14.dp
|
bottomEnd = 10.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -575,9 +542,10 @@ fun AgentCardSquare(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel) {
|
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
// 每页显示5个agent
|
// 每页显示5个agent
|
||||||
@@ -591,7 +559,7 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
|
|||||||
// Agent内容
|
// Agent内容
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(310.dp)
|
.height(300.dp)
|
||||||
) {
|
) {
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
@@ -613,7 +581,7 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
|
|||||||
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
|
agentItems = agentItems.drop(page * itemsPerPage).take(itemsPerPage),
|
||||||
page = page,
|
page = page,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.height(310.dp)
|
.height(300.dp)
|
||||||
.graphicsLayer {
|
.graphicsLayer {
|
||||||
scaleX = scale
|
scaleX = scale
|
||||||
scaleY = scale
|
scaleY = scale
|
||||||
@@ -650,7 +618,13 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@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(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -658,7 +632,11 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
|||||||
) {
|
) {
|
||||||
// 显示3个agent
|
// 显示3个agent
|
||||||
agentItems.forEachIndexed { index, agentItem ->
|
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) {
|
if (index < agentItems.size - 1) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
@@ -668,7 +646,7 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
|||||||
|
|
||||||
@SuppressLint("SuspiciousIndentation")
|
@SuppressLint("SuspiciousIndentation")
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: NavHostController) {
|
fun AgentCard2(viewModel: AgentViewModel, agentItem: AgentItem, navController: NavHostController) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
// 防抖状态
|
// 防抖状态
|
||||||
@@ -684,7 +662,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.background(Color(0x00F5F5F5), RoundedCornerShape(24.dp))
|
.background(AppColors.secondaryBackground, RoundedCornerShape(24.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
viewModel.goToProfile(agentItem.openId, navController)
|
viewModel.goToProfile(agentItem.openId, navController)
|
||||||
@@ -694,12 +672,6 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Image(
|
|
||||||
painter = painterResource(R.mipmap.group_copy),
|
|
||||||
contentDescription = "默认头像",
|
|
||||||
modifier = Modifier.size(48.dp),
|
|
||||||
)
|
|
||||||
|
|
||||||
if (agentItem.avatar.isNotEmpty()) {
|
if (agentItem.avatar.isNotEmpty()) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
imageUrl = agentItem.avatar,
|
imageUrl = agentItem.avatar,
|
||||||
@@ -709,6 +681,13 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
.clip(RoundedCornerShape(24.dp)),
|
.clip(RoundedCornerShape(24.dp)),
|
||||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||||
|
contentDescription = "默认头像",
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -747,13 +726,8 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 60.dp, height = 32.dp)
|
.size(width = 60.dp, height = 32.dp)
|
||||||
.background(
|
.background(
|
||||||
color = Color(0X147c7480),
|
color = AppColors.inputBackground,
|
||||||
shape = RoundedCornerShape(
|
shape = RoundedCornerShape(8.dp)
|
||||||
topStart = 14.dp,
|
|
||||||
topEnd = 14.dp,
|
|
||||||
bottomStart = 0.dp,
|
|
||||||
bottomEnd = 14.dp
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
@@ -782,6 +756,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatRoomsSection(
|
fun ChatRoomsSection(
|
||||||
chatRooms: List<ChatRoom>,
|
chatRooms: List<ChatRoom>,
|
||||||
@@ -843,7 +818,7 @@ fun ChatRoomCard(
|
|||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val cardSize = 180.dp
|
val cardSize = 160.dp
|
||||||
val viewModel: AgentViewModel = viewModel()
|
val viewModel: AgentViewModel = viewModel()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -891,7 +866,7 @@ fun ChatRoomCard(
|
|||||||
Box(
|
Box(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.size(cardSize)
|
.size(cardSize)
|
||||||
.background(AppColors.tabUnselectedBackground, RoundedCornerShape(12.dp))
|
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||||
.clickable(enabled = !viewModel.isJoiningRoom) {
|
.clickable(enabled = !viewModel.isJoiningRoom) {
|
||||||
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
// 加入群聊房间
|
// 加入群聊房间
|
||||||
@@ -921,13 +896,8 @@ fun ChatRoomCard(
|
|||||||
imageUrl = imageUrl,
|
imageUrl = imageUrl,
|
||||||
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(cardSize)
|
.size(cardSize)
|
||||||
.height(120.dp)
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
.clip(RoundedCornerShape(
|
|
||||||
topStart = 12.dp,
|
|
||||||
topEnd = 12.dp,
|
|
||||||
bottomStart = 0.dp,
|
|
||||||
bottomEnd = 0.dp)),
|
|
||||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -945,47 +915,21 @@ fun ChatRoomCard(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.BottomCenter)
|
.align(Alignment.BottomCenter)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(bottom = 32.dp, start = 10.dp, end = 10.dp)
|
.background(
|
||||||
.clip(RoundedCornerShape(12.dp))
|
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(
|
androidx.compose.material3.Text(
|
||||||
text = chatRoom.name,
|
text = chatRoom.name,
|
||||||
fontSize = 14.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
color = Color.White,
|
||||||
color = AppColors.text,
|
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
textAlign = androidx.compose.ui.text.style.TextAlign.Left
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// 显示人数
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomCenter)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 10.dp, start = 10.dp, end = 10.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.rider_pro_nav_profile),
|
|
||||||
contentDescription = "chat",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
|
||||||
text = "${chatRoom.memberCount} ${stringResource(R.string.chatting_now)}",
|
|
||||||
fontSize = 12.sp,
|
|
||||||
modifier = Modifier.alpha(0.6f),
|
|
||||||
color = AppColors.text,
|
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,65 +1,52 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.ai
|
package com.aiosman.ravenow.ui.index.tabs.ai
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.aiosman.ravenow.data.Agent
|
|
||||||
import com.aiosman.ravenow.data.ListContainer
|
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
|
||||||
import com.aiosman.ravenow.data.api.CategoryTemplate
|
|
||||||
import com.aiosman.ravenow.data.api.RaveNowAPI
|
|
||||||
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
|
||||||
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 kotlinx.coroutines.launch
|
|
||||||
import android.util.Log
|
|
||||||
import com.aiosman.ravenow.data.Room
|
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.ConstVars
|
import com.aiosman.ravenow.ConstVars
|
||||||
|
import com.aiosman.ravenow.data.Room
|
||||||
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
|
import com.aiosman.ravenow.data.api.CategoryTemplate
|
||||||
import com.aiosman.ravenow.data.api.CreateGroupChatRequestBody
|
import com.aiosman.ravenow.data.api.CreateGroupChatRequestBody
|
||||||
|
import com.aiosman.ravenow.data.api.RaveNowAPI
|
||||||
|
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
||||||
|
import com.aiosman.ravenow.data.api.JoinGroupChatRequestBody
|
||||||
|
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
||||||
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
|
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
|
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
|
||||||
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||||
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.aiosman.ravenow.data.api.JoinGroupChatRequestBody
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缓存数据结构,用于存储每个分类的Agent列表
|
|
||||||
*/
|
|
||||||
data class AgentCacheData(
|
|
||||||
val items: List<AgentItem>,
|
|
||||||
val currentPage: Int,
|
|
||||||
val hasMoreData: Boolean
|
|
||||||
)
|
|
||||||
data class ChatRoom(
|
data class ChatRoom(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val name: String,
|
val name: String,
|
||||||
val avatar: String = "",
|
val avatar: String = "",
|
||||||
val banner: String = "",
|
val banner: String = "",
|
||||||
val memberCount: Int
|
val memberCount: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
object AgentViewModel: ViewModel() {
|
object AgentViewModel : ViewModel() {
|
||||||
|
|
||||||
private val apiClient: RaveNowAPI = ApiClient.api
|
private val apiClient: RaveNowAPI = ApiClient.api
|
||||||
|
|
||||||
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
// 顶部Agent列表数据(用于ViewPager)
|
||||||
|
var topAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var categories by mutableStateOf<List<CategoryItem>>(emptyList())
|
// 底部网格布局Agent数据
|
||||||
private set
|
var gridAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||||
|
|
||||||
var errorMessage by mutableStateOf<String?>(null)
|
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
||||||
@@ -68,126 +55,133 @@ object AgentViewModel: ViewModel() {
|
|||||||
var rooms by mutableStateOf<List<Room>>(emptyList())
|
var rooms by mutableStateOf<List<Room>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var categories by mutableStateOf<List<CategoryTemplate>>(emptyList())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedCategoryIndex by mutableStateOf(0)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var errorMessage by mutableStateOf<String?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var isLoading by mutableStateOf(false)
|
var isLoading by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
// 分页相关状态
|
|
||||||
var isLoadingMore by mutableStateOf(false)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var currentPage by mutableStateOf(1)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var hasMoreData by mutableStateOf(true)
|
|
||||||
private set
|
|
||||||
|
|
||||||
var isJoiningRoom by mutableStateOf(false)
|
var isJoiningRoom by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
private val pageSize = 20
|
private var topCurrentPage = 1
|
||||||
private var currentCategoryId: Int? = null
|
private var topHasMoreData = true
|
||||||
|
|
||||||
// 缓存:使用分类ID作为key,null表示推荐列表
|
private var gridCurrentPage = 1
|
||||||
private val agentCache = mutableMapOf<Int?, AgentCacheData>()
|
private var gridHasMoreData = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadAgentData()
|
loadTopAgentData()
|
||||||
loadCategories()
|
loadGridAgentData()
|
||||||
loadChatRooms()
|
loadChatRooms()
|
||||||
|
loadCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) {
|
/**
|
||||||
|
* 加载顶部Agent列表数据(用于ViewPager)
|
||||||
|
*/
|
||||||
|
private fun loadTopAgentData(categoryIndex: Int = 0) {
|
||||||
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) {
|
|
||||||
isLoadingMore = true
|
|
||||||
} else {
|
|
||||||
isLoading = true
|
isLoading = true
|
||||||
// 重置分页状态
|
|
||||||
currentPage = 1
|
|
||||||
hasMoreData = true
|
|
||||||
currentCategoryId = categoryId
|
|
||||||
}
|
|
||||||
|
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
|
topCurrentPage = 1
|
||||||
|
topHasMoreData = true
|
||||||
try {
|
try {
|
||||||
val response = if (categoryId != null) {
|
val selectedCategory =
|
||||||
// 根据分类ID获取智能体
|
if (categoryIndex < categories.size) categories[categoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (categoryIndex == 0 || selectedCategory == null) {
|
||||||
|
// 推荐分类或无效分类,加载所有 Agent
|
||||||
apiClient.getAgent(
|
apiClient.getAgent(
|
||||||
page = page,
|
page = topCurrentPage,
|
||||||
pageSize = pageSize,
|
pageSize = 15,
|
||||||
withWorkflow = 1,
|
withWorkflow = "1",
|
||||||
categoryIds = listOf(categoryId),
|
random = "1"
|
||||||
random = 1
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// 获取推荐智能体,使用random=1
|
// 特定分类,使用 categoryName 参数
|
||||||
apiClient.getAgent(
|
apiClient.getAgent(
|
||||||
page = page,
|
page = topCurrentPage,
|
||||||
pageSize = pageSize,
|
pageSize = 15,
|
||||||
withWorkflow = 1,
|
withWorkflow = "1",
|
||||||
categoryIds = null,
|
categoryName = selectedCategory.name,
|
||||||
random = 1
|
random = "1"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val responseData = response.body()?.data
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
val agents = responseData?.list ?: emptyList<Agent>()
|
topAgentItems = agents.map { agent ->
|
||||||
val newAgentItems = agents.map { agent ->
|
|
||||||
AgentItem.fromAgent(agent)
|
AgentItem.fromAgent(agent)
|
||||||
}
|
}
|
||||||
|
topHasMoreData = agents.size >= 15
|
||||||
if (isLoadMore) {
|
|
||||||
// 加载更多:追加到现有列表
|
|
||||||
agentItems = agentItems + newAgentItems
|
|
||||||
currentPage = page
|
|
||||||
} else {
|
} else {
|
||||||
// 首次加载或刷新:替换整个列表
|
errorMessage = "获取顶部Agent数据失败: ${response.code()}"
|
||||||
agentItems = newAgentItems
|
|
||||||
currentPage = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否还有更多数据
|
|
||||||
hasMoreData = agents.size >= pageSize
|
|
||||||
|
|
||||||
// 更新缓存
|
|
||||||
agentCache[categoryId] = AgentCacheData(
|
|
||||||
items = agentItems,
|
|
||||||
currentPage = currentPage,
|
|
||||||
hasMoreData = hasMoreData
|
|
||||||
)
|
|
||||||
println("更新缓存,分类ID: $categoryId, 数据数量: ${agentItems.size}")
|
|
||||||
|
|
||||||
} else {
|
|
||||||
errorMessage = "获取Agent数据失败: ${response.code()}"
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorMessage = "网络请求失败: ${e.message}"
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
} finally {
|
} finally {
|
||||||
if (isLoadMore) {
|
|
||||||
isLoadingMore = false
|
|
||||||
} else {
|
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载底部网格布局Agent数据
|
||||||
|
*/
|
||||||
|
private fun loadGridAgentData(categoryIndex: Int = 0) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
errorMessage = null
|
||||||
|
gridCurrentPage = 1
|
||||||
|
gridHasMoreData = true
|
||||||
|
try {
|
||||||
|
val selectedCategory =
|
||||||
|
if (categoryIndex < categories.size) categories[categoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (categoryIndex == 0 || selectedCategory == null) {
|
||||||
|
// 推荐分类或无效分类,加载所有 Agent
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = gridCurrentPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 特定分类,使用 categoryName 参数
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = gridCurrentPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
categoryName = selectedCategory.name,
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
|
gridAgentItems = agents.map { agent ->
|
||||||
|
AgentItem.fromAgent(agent)
|
||||||
|
}
|
||||||
|
gridHasMoreData = agents.size >= 20
|
||||||
|
} else {
|
||||||
|
errorMessage = "获取网格Agent数据失败: ${response.code()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChatRooms() {
|
private fun loadChatRooms() {
|
||||||
@@ -195,13 +189,14 @@ object AgentViewModel: ViewModel() {
|
|||||||
try {
|
try {
|
||||||
val response = apiClient.getRooms(
|
val response = apiClient.getRooms(
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 20,
|
pageSize = 21,
|
||||||
isRecommended = 1,
|
isRecommended = 1,
|
||||||
random = 1
|
random = 1
|
||||||
)
|
) // 请求21个,确保是3的倍数
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val allRooms = response.body()?.list ?: emptyList()
|
val allRooms = response.body()?.list ?: emptyList()
|
||||||
val targetCount = (allRooms.size / 2) * 2
|
// 确保房间数量是3的倍数,如果不足则截取,如果超出则取前几个
|
||||||
|
val targetCount = (allRooms.size / 3) * 3 // 向下取整到最近的3的倍数
|
||||||
rooms = allRooms.take(targetCount)
|
rooms = allRooms.take(targetCount)
|
||||||
|
|
||||||
// 转换为ChatRoom格式用于兼容现有UI
|
// 转换为ChatRoom格式用于兼容现有UI
|
||||||
@@ -226,85 +221,138 @@ 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 categoriesResponse = apiClient.getCategories(
|
||||||
val sysLang = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag()
|
|
||||||
val response = apiClient.getCategories(
|
|
||||||
page = 1,
|
page = 1,
|
||||||
pageSize = 100,
|
pageSize = 100,
|
||||||
isActive = true,
|
|
||||||
withChildren = false,
|
|
||||||
withParent = false,
|
|
||||||
withCount = true,
|
|
||||||
hideEmpty = true,
|
|
||||||
lang = sysLang
|
|
||||||
)
|
)
|
||||||
println("分类数据请求完成,响应成功: ${response.isSuccessful}, 语言标记: $sysLang")
|
if (categoriesResponse.isSuccessful && categoriesResponse.body() != null) {
|
||||||
if (response.isSuccessful) {
|
// 添加一个默认的"推荐"分类在第一位
|
||||||
val categoryList = response.body()?.list ?: emptyList()
|
val recommendCategory = createRecommendCategory()
|
||||||
println("获取到 ${categoryList.size} 个分类")
|
val categoriesList = categoriesResponse.body()?.list ?: emptyList()
|
||||||
// 使用当前语言获取翻译后的分类名称
|
categories = listOf(recommendCategory) + categoriesList
|
||||||
categories = categoryList.map { category ->
|
|
||||||
CategoryItem.fromCategoryTemplate(category, sysLang)
|
// 分类加载完成后,重新加载当前选中分类的 Agent 数据
|
||||||
|
if (topAgentItems.isEmpty()) {
|
||||||
|
loadTopAgentData(selectedCategoryIndex)
|
||||||
|
}
|
||||||
|
if (gridAgentItems.isEmpty()) {
|
||||||
|
loadGridAgentData(selectedCategoryIndex)
|
||||||
}
|
}
|
||||||
println("成功处理并映射了 ${categories.size} 个分类")
|
|
||||||
} else {
|
} else {
|
||||||
errorMessage = "获取分类数据失败: ${response.code()}"
|
// 如果请求失败,使用默认分类
|
||||||
println("获取分类数据失败: ${response.code()}")
|
categories = listOf(createRecommendCategory())
|
||||||
|
errorMessage = "获取分类失败: ${categoriesResponse.code()}"
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorMessage = "获取分类数据失败: ${e.message}"
|
// 如果网络请求失败,使用默认分类
|
||||||
println("获取分类数据异常: ${e.message}")
|
categories = listOf(createRecommendCategory())
|
||||||
e.printStackTrace()
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAgentsByCategory(categoryId: Int) {
|
|
||||||
loadAgentData(categoryId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadAllAgents() {
|
|
||||||
loadAgentData()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载更多Agent数据
|
* 创建推荐分类
|
||||||
*/
|
*/
|
||||||
fun loadMoreAgents() {
|
private fun createRecommendCategory(): CategoryTemplate {
|
||||||
// 检查是否正在加载或没有更多数据
|
return CategoryTemplate(
|
||||||
if (isLoadingMore || !hasMoreData) {
|
id = 0,
|
||||||
return
|
name = "推荐",
|
||||||
}
|
description = "推荐内容",
|
||||||
|
avatar = "",
|
||||||
val nextPage = currentPage + 1
|
parentId = null,
|
||||||
loadAgentData(
|
parent = null,
|
||||||
categoryId = currentCategoryId,
|
children = null,
|
||||||
page = nextPage,
|
sort = 0,
|
||||||
isLoadMore = true
|
isActive = true,
|
||||||
|
promptCount = 0,
|
||||||
|
createdAt = "",
|
||||||
|
updatedAt = "",
|
||||||
|
translations = null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载更多网格Agent数据
|
||||||
|
*/
|
||||||
|
fun loadMoreGridAgentData() {
|
||||||
|
if (!gridHasMoreData || isLoading) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
try {
|
||||||
|
val nextPage = gridCurrentPage + 1
|
||||||
|
val selectedCategory =
|
||||||
|
if (selectedCategoryIndex < categories.size) categories[selectedCategoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (selectedCategoryIndex == 0 || selectedCategory == null) {
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = nextPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = nextPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
categoryName = selectedCategory.name,
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
|
val newAgentItems = agents.map { agent ->
|
||||||
|
AgentItem.fromAgent(agent)
|
||||||
|
}
|
||||||
|
gridAgentItems = gridAgentItems + newAgentItems
|
||||||
|
gridCurrentPage = nextPage
|
||||||
|
gridHasMoreData = 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 && categoryIndex >= 0 && categoryIndex < categories.size) {
|
||||||
|
selectedCategoryIndex = categoryIndex
|
||||||
|
// 同时加载顶部和网格的数据
|
||||||
|
loadTopAgentData(categoryIndex)
|
||||||
|
loadGridAgentData(categoryIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createSingleChat(
|
fun createSingleChat(
|
||||||
openId: String,
|
openId: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
val response =
|
||||||
|
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||||
|
Log.d("debug", response.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goToChatAi(
|
fun goToChatAi(
|
||||||
openId: String,
|
openId: String,
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val profile = userService.getUserProfileByOpenId(openId)
|
val profile = userService.getUserProfileByOpenId(openId)
|
||||||
createGroup2ChatAi(profile.trtcUserId,"ai_group",navController,profile.id)
|
createGroup2ChatAi(profile.trtcUserId, "ai_group", navController, profile.id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -328,20 +376,26 @@ object AgentViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新当前分类的Agent数据(强制刷新,清除缓存)
|
* 刷新推荐Agent数据
|
||||||
*/
|
*/
|
||||||
fun refreshAgentData() {
|
fun refreshAgentData() {
|
||||||
// 清除当前分类的缓存
|
loadTopAgentData()
|
||||||
agentCache.remove(currentCategoryId)
|
loadGridAgentData()
|
||||||
loadAgentData(categoryId = currentCategoryId, forceRefresh = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查数据是否为空,如果为空则重新加载
|
* 检查数据是否为空,如果为空则重新加载
|
||||||
*/
|
*/
|
||||||
fun ensureDataLoaded() {
|
fun ensureDataLoaded() {
|
||||||
if (agentItems.isEmpty() && !isLoading) {
|
if (topAgentItems.isEmpty() && !isLoading) {
|
||||||
loadAgentData()
|
loadTopAgentData()
|
||||||
|
}
|
||||||
|
if (gridAgentItems.isEmpty() && !isLoading) {
|
||||||
|
loadGridAgentData()
|
||||||
|
}
|
||||||
|
// 同时确保分类数据已加载
|
||||||
|
if (categories.isEmpty() && !isLoading) {
|
||||||
|
loadCategories()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -424,36 +478,18 @@ object AgentViewModel: ViewModel() {
|
|||||||
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
||||||
*/
|
*/
|
||||||
fun ResetModel() {
|
fun ResetModel() {
|
||||||
agentItems = emptyList()
|
topAgentItems = emptyList()
|
||||||
|
gridAgentItems = emptyList()
|
||||||
categories = emptyList()
|
categories = emptyList()
|
||||||
|
selectedCategoryIndex = 0
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
isLoading = false
|
isLoading = false
|
||||||
isLoadingMore = false
|
isJoiningRoom = false
|
||||||
currentPage = 1
|
topCurrentPage = 1
|
||||||
hasMoreData = true
|
topHasMoreData = true
|
||||||
currentCategoryId = null
|
gridCurrentPage = 1
|
||||||
// 清空缓存
|
gridHasMoreData = true
|
||||||
agentCache.clear()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
data class CategoryItem(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val description: String,
|
|
||||||
val avatar: String,
|
|
||||||
val promptCount: Int?
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
fun fromCategoryTemplate(template: CategoryTemplate, lang: String): CategoryItem {
|
|
||||||
return CategoryItem(
|
|
||||||
id = template.id,
|
|
||||||
name = template.getLocalizedName(lang),
|
|
||||||
description = template.getLocalizedDescription(lang),
|
|
||||||
avatar = "${ApiClient.BASE_API_URL}${template.avatar}",
|
|
||||||
promptCount = template.promptCount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -72,12 +72,9 @@ open class BaseMomentModel :ViewModel(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun onAddComment(id: Int) {
|
suspend fun onAddComment(id: Int) {
|
||||||
momentLoader.updateCommentCount(id, +1)
|
// val currentPagingData = _momentsFlow.value
|
||||||
}
|
// updateCommentCount(id)
|
||||||
|
|
||||||
fun onDeleteComment(id: Int) {
|
|
||||||
momentLoader.updateCommentCount(id, -1)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -86,7 +83,6 @@ open class BaseMomentModel :ViewModel(){
|
|||||||
fun onMomentFavoriteChangeEvent(event: MomentFavouriteChangeEvent) {
|
fun onMomentFavoriteChangeEvent(event: MomentFavouriteChangeEvent) {
|
||||||
momentLoader.updateFavoriteCount(event.postId, event.isFavourite)
|
momentLoader.updateFavoriteCount(event.postId, event.isFavourite)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun favoriteMoment(id: Int) {
|
suspend fun favoriteMoment(id: Int) {
|
||||||
momentService.favoriteMoment(id)
|
momentService.favoriteMoment(id)
|
||||||
momentLoader.updateFavoriteCount(id, true)
|
momentLoader.updateFavoriteCount(id, true)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.aiosman.ravenow.ui.index.tabs.moment
|
|||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -21,7 +20,6 @@ import androidx.compose.foundation.layout.systemBars
|
|||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
@@ -47,7 +45,6 @@ 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.expolre.Explore
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentsList
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.hot.HotMomentsList
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.timeline.TimelineMomentsList
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.NewsScreen
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel
|
import com.aiosman.ravenow.ui.index.tabs.search.SearchViewModel
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -57,7 +54,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
import com.aiosman.ravenow.ui.composables.TabItem
|
import com.aiosman.ravenow.ui.composables.TabItem
|
||||||
import com.aiosman.ravenow.ui.composables.UnderlineTabItem
|
import com.aiosman.ravenow.ui.composables.TabSpacer
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -71,8 +68,8 @@ 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()
|
||||||
// 现在有6个tab:推荐、短视频、新闻、探索、关注、热门
|
// 游客模式下不显示timeline,只显示2个tab:Dynamic、Hot // 游客模式下不显示timeline,只显示3个tab:Explore、Dynamic、Hot
|
||||||
val tabCount = 6
|
val tabCount = if (AppStore.isGuest) 2 else 3 // val tabCount = if (AppStore.isGuest) 3 else 4
|
||||||
var pagerState = rememberPagerState { tabCount }
|
var pagerState = rememberPagerState { tabCount }
|
||||||
var scope = rememberCoroutineScope()
|
var scope = rememberCoroutineScope()
|
||||||
Column(
|
Column(
|
||||||
@@ -84,127 +81,60 @@ fun MomentsList() {
|
|||||||
),
|
),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
// 顶部区域:可滚动的标签页 + 搜索按钮
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(44.dp)
|
.height(44.dp)
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
// center the tabs
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 可滚动的标签页行
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.horizontalScroll(rememberScrollState()),
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
val tabDebouncer = rememberDebouncer()
|
//原探索//
|
||||||
|
// Column(
|
||||||
// 推荐标签
|
// modifier = Modifier
|
||||||
UnderlineTabItem(
|
// .noRippleClickable {
|
||||||
text = stringResource(R.string.tab_recommend),
|
// scope.launch {
|
||||||
isSelected = pagerState.currentPage == 0,
|
// pagerState.animateScrollToPage(0)
|
||||||
onClick = {
|
// }
|
||||||
tabDebouncer {
|
// }.padding(start = 16.dp),
|
||||||
scope.launch {
|
// verticalArrangement = Arrangement.Center,
|
||||||
pagerState.animateScrollToPage(0)
|
// horizontalAlignment = Alignment.CenterHorizontally
|
||||||
}
|
//
|
||||||
}
|
// ) {
|
||||||
}
|
// Text(
|
||||||
)
|
// text = stringResource(R.string.index_worldwide),
|
||||||
|
// fontSize = if (pagerState.currentPage == 0)18.sp else 16.sp,
|
||||||
|
// color = if (pagerState.currentPage == 0) AppColors.text else AppColors.nonActiveText,
|
||||||
// 短视频标签
|
// fontWeight = FontWeight.W600)
|
||||||
UnderlineTabItem(
|
// Spacer(modifier = Modifier.height(4.dp))
|
||||||
text = stringResource(R.string.tab_short_video),
|
//
|
||||||
isSelected = pagerState.currentPage == 1,
|
// Image(
|
||||||
onClick = {
|
// painter = painterResource(
|
||||||
tabDebouncer {
|
// if (pagerState.currentPage == 0) R.mipmap.tab_indicator_selected
|
||||||
scope.launch {
|
// else R.drawable.tab_indicator_unselected
|
||||||
pagerState.animateScrollToPage(1)
|
// ),
|
||||||
}
|
// contentDescription = "tab indicator",
|
||||||
}
|
// modifier = Modifier
|
||||||
}
|
// .width(34.dp)
|
||||||
)
|
// .height(4.dp)
|
||||||
|
// )
|
||||||
|
//
|
||||||
// 动态标签
|
// }
|
||||||
UnderlineTabItem(
|
// Spacer(modifier = Modifier.width(16.dp))
|
||||||
text = stringResource(R.string.moment),
|
|
||||||
isSelected = pagerState.currentPage == 2,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// 只有非游客用户才显示"关注"tab
|
|
||||||
if (!AppStore.isGuest) {
|
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.index_following),
|
|
||||||
isSelected = pagerState.currentPage == 3,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// 热门标签
|
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.index_hot),
|
|
||||||
isSelected = pagerState.currentPage == 4,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 热门标签 (游客模式)
|
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.index_hot),
|
|
||||||
isSelected = pagerState.currentPage == 4,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(4)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// 新闻标签
|
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.tab_news),
|
|
||||||
isSelected = pagerState.currentPage == 5,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(5)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 搜索按钮
|
|
||||||
val lastClickTime = remember { mutableStateOf(0L) }
|
val lastClickTime = remember { mutableStateOf(0L) }
|
||||||
val clickDelay = 500L
|
val clickDelay = 500L
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.moment),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.W900,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.CenterVertically)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
||||||
contentDescription = "search",
|
contentDescription = "search",
|
||||||
@@ -221,39 +151,113 @@ fun MomentsList() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(23.dp))
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.wrapContentHeight()
|
||||||
|
.padding(start = 16.dp, bottom = 16.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.Bottom
|
||||||
|
) {
|
||||||
|
val tabDebouncer = rememberDebouncer()
|
||||||
|
|
||||||
|
// 新探索标签
|
||||||
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
|
text = stringResource(R.string.index_worldwide),
|
||||||
|
isSelected = pagerState.currentPage == 0,
|
||||||
|
onClick = {
|
||||||
|
tabDebouncer {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabSpacer()
|
||||||
|
|
||||||
|
// 只有非游客用户才显示"关注"tab
|
||||||
|
if (!AppStore.isGuest) {
|
||||||
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
|
text = stringResource(R.string.index_following),
|
||||||
|
isSelected = pagerState.currentPage == 1,
|
||||||
|
onClick = {
|
||||||
|
tabDebouncer {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabSpacer()
|
||||||
|
|
||||||
|
// 热门标签
|
||||||
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
|
text = stringResource(R.string.index_hot),
|
||||||
|
isSelected = pagerState.currentPage == 2,
|
||||||
|
onClick = {
|
||||||
|
tabDebouncer {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 热门标签 (游客模式)
|
||||||
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
|
text = stringResource(R.string.index_hot),
|
||||||
|
isSelected = pagerState.currentPage == 1,
|
||||||
|
onClick = {
|
||||||
|
tabDebouncer {
|
||||||
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
|
if (AppStore.isGuest) {
|
||||||
|
// 游客模式:Dynamic(0), Hot(1)
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> {
|
0 -> {
|
||||||
// 推荐页面
|
|
||||||
NewsScreen()
|
|
||||||
}
|
|
||||||
1 -> {
|
|
||||||
// 短视频页面
|
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
// 动态页面 - 暂时显示时间线内容
|
|
||||||
Dynamic()
|
Dynamic()
|
||||||
}
|
}
|
||||||
3 -> {
|
1 -> {
|
||||||
// 关注页面 (仅非游客用户) 或 热门页面 (游客用户)
|
|
||||||
if (AppStore.isGuest) {
|
|
||||||
HotMomentsList()
|
HotMomentsList()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// 正常用户:Dynamic(0), Timeline(1), Hot(2)
|
||||||
|
when (it) {
|
||||||
|
0 -> {
|
||||||
|
Dynamic()
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
TimelineMomentsList()
|
TimelineMomentsList()
|
||||||
}
|
}
|
||||||
}
|
2 -> {
|
||||||
4 -> {
|
|
||||||
// 热门页面 (仅非游客用户)
|
|
||||||
HotMomentsList()
|
HotMomentsList()
|
||||||
}
|
}
|
||||||
5 -> {
|
|
||||||
// 新闻页面
|
|
||||||
NewsScreen()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ fun Dynamic() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -524,7 +524,7 @@ fun Explore() {
|
|||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
Image(
|
/* Image(
|
||||||
painter = painterResource(R.drawable.rider_pro_nav_profile),
|
painter = painterResource(R.drawable.rider_pro_nav_profile),
|
||||||
contentDescription = "chat",
|
contentDescription = "chat",
|
||||||
modifier = Modifier.size(16.dp),
|
modifier = Modifier.size(16.dp),
|
||||||
@@ -536,7 +536,7 @@ fun Explore() {
|
|||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||||
)
|
)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部:标题和描述
|
// 底部:标题和描述
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ class ExploreViewModel : ViewModel() {
|
|||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
try {
|
try {
|
||||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val agents = response.body()?.data?.list ?: emptyList()
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
agentItems = agents.map { agent ->
|
agentItems = agents.map { agent ->
|
||||||
@@ -118,7 +118,7 @@ class ExploreViewModel : ViewModel() {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
try {
|
try {
|
||||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val agents = response.body()?.data?.list ?: emptyList()
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
agentItems = agents.map { agent ->
|
agentItems = agents.map { agent ->
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import androidx.compose.foundation.Image
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
import androidx.compose.foundation.layout.aspectRatio
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
@@ -14,12 +13,11 @@ import androidx.compose.foundation.layout.navigationBars
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
@@ -41,7 +39,6 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
@@ -107,8 +104,8 @@ fun DiscoverView() {
|
|||||||
val isLoading by model.isLoading.collectAsState()
|
val isLoading by model.isLoading.collectAsState()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val gridState = rememberLazyStaggeredGridState()
|
val gridState = rememberLazyGridState()
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
// 监听滚动到底部,自动加载更多
|
// 监听滚动到底部,自动加载更多
|
||||||
LaunchedEffect(gridState, moments.size) {
|
LaunchedEffect(gridState, moments.size) {
|
||||||
snapshotFlow {
|
snapshotFlow {
|
||||||
@@ -127,40 +124,18 @@ fun DiscoverView() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LazyVerticalStaggeredGrid(
|
LazyVerticalGrid(
|
||||||
columns = StaggeredGridCells.Fixed(2),
|
columns = GridCells.Fixed(3),
|
||||||
state = gridState,
|
state = gridState,
|
||||||
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
|
modifier = Modifier.fillMaxSize().padding(bottom = 8.dp),
|
||||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 8.dp, vertical = 4.dp)
|
// contentPadding = PaddingValues(8.dp)
|
||||||
) {
|
) {
|
||||||
items(moments) { momentItem ->
|
items(moments) { momentItem ->
|
||||||
val debouncer = rememberDebouncer()
|
val debouncer = rememberDebouncer()
|
||||||
|
|
||||||
val textContent = momentItem.momentTextContent
|
|
||||||
val textLines = if (textContent.isNotEmpty()) {
|
|
||||||
val estimatedCharsPerLine = 20
|
|
||||||
val estimatedLines = (textContent.length / estimatedCharsPerLine) + 1
|
|
||||||
minOf(estimatedLines, 2) // 最多2行
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
|
|
||||||
val baseHeight = 200.dp
|
|
||||||
val singleLineTextHeight = 20.dp
|
|
||||||
val doubleLineTextHeight = 40.dp
|
|
||||||
val authorInfoHeight = 25.dp
|
|
||||||
val paddingHeight = 10.dp
|
|
||||||
val paddingHeight2 =3.dp
|
|
||||||
val totalHeight = baseHeight + when (textLines) {
|
|
||||||
0 -> authorInfoHeight + paddingHeight
|
|
||||||
1 -> singleLineTextHeight + authorInfoHeight + paddingHeight
|
|
||||||
else -> doubleLineTextHeight + authorInfoHeight +paddingHeight2
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(totalHeight)
|
.aspectRatio(1f)
|
||||||
.padding(2.dp)
|
.padding(2.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
debouncer {
|
debouncer {
|
||||||
@@ -171,69 +146,15 @@ fun DiscoverView() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize().background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
imageUrl = momentItem.images[0].thumbnail,
|
imageUrl = momentItem.images[0].thumbnail,
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize(),
|
||||||
.height(baseHeight)
|
|
||||||
.clip(RoundedCornerShape(
|
|
||||||
topStart = 12.dp,
|
|
||||||
topEnd = 12.dp,
|
|
||||||
bottomStart = 0.dp,
|
|
||||||
bottomEnd = 0.dp)),
|
|
||||||
context = context,
|
context = context,
|
||||||
showShimmer = true
|
showShimmer = true
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(totalHeight - baseHeight)
|
|
||||||
.padding(horizontal = 8.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
if (momentItem.momentTextContent.isNotEmpty()) {
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = momentItem.momentTextContent,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(top = 5.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
imageUrl = momentItem.avatar,
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(16.dp)
|
|
||||||
.clip(RoundedCornerShape(8.dp)),
|
|
||||||
context = context,
|
|
||||||
showShimmer = true
|
|
||||||
)
|
|
||||||
|
|
||||||
androidx.compose.material3.Text(
|
|
||||||
text = momentItem.nickname,
|
|
||||||
modifier = Modifier.padding(start = 4.dp),
|
|
||||||
fontSize = 11.sp,
|
|
||||||
color = AppColors.text.copy(alpha = 0.6f),
|
|
||||||
maxLines = 1,
|
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (momentItem.images.size > 1) {
|
if (momentItem.images.size > 1) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -1,205 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.foundation.verticalScroll
|
|
||||||
import androidx.compose.material3.Button
|
|
||||||
import androidx.compose.material3.ButtonDefaults
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
|
||||||
import com.aiosman.ravenow.exp.formatPostTime2
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun FullArticleModal(
|
|
||||||
moment: MomentEntity,
|
|
||||||
onDismiss: () -> Unit
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
|
||||||
val sheetHeight = screenHeight * 0.9f // 90% 高度
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
sheetState = rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
containerColor = appColors.background,
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
|
||||||
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
// 滚动内容
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.verticalScroll(rememberScrollState())
|
|
||||||
) {
|
|
||||||
// 新闻图片区域 - 固定高度和宽度
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(250.dp)
|
|
||||||
|
|
||||||
.background(color = appColors.secondaryBackground)
|
|
||||||
) {
|
|
||||||
if (moment.images.isNotEmpty()) {
|
|
||||||
val firstImage = moment.images[0]
|
|
||||||
CustomAsyncImage(
|
|
||||||
context = context,
|
|
||||||
imageUrl = firstImage.url,
|
|
||||||
contentDescription = "新闻图片",
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
blurHash = firstImage.blurHash,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = R.drawable.default_moment_img),
|
|
||||||
contentDescription = "默认图片",
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻标题
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsTitle.isNotEmpty()) moment.newsTitle else moment.nickname,
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = appColors.text,
|
|
||||||
lineHeight = 28.sp,
|
|
||||||
modifier = Modifier.padding(horizontal = 10.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
// 新闻来源和发布时间
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 10.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 来源按钮
|
|
||||||
Button(
|
|
||||||
onClick = { },
|
|
||||||
modifier = Modifier.height(28.dp),
|
|
||||||
colors = ButtonDefaults.buttonColors(
|
|
||||||
containerColor = Color(0xFF7c68ef)
|
|
||||||
),
|
|
||||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
|
||||||
shape = RoundedCornerShape(14.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsSource.isNotEmpty()) moment.newsSource else moment.nickname,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = Color.White,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发布时间
|
|
||||||
Text(
|
|
||||||
text = moment.time.formatPostTime2(),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = appColors.secondaryText
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 帖子内容
|
|
||||||
NewsContent(
|
|
||||||
content = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
|
|
||||||
images = moment.images,
|
|
||||||
context = context
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(200.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun NewsContent(
|
|
||||||
content: String,
|
|
||||||
images: List<com.aiosman.ravenow.entity.MomentImageEntity>,
|
|
||||||
context: android.content.Context
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = content,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
lineHeight = 24.sp
|
|
||||||
)
|
|
||||||
|
|
||||||
// 图片内容
|
|
||||||
if (images.isNotEmpty()) {
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
images.forEach { image ->
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
context = context,
|
|
||||||
imageUrl = image.url,
|
|
||||||
contentDescription = "内容图片",
|
|
||||||
contentScale = ContentScale.Fit,
|
|
||||||
blurHash = image.blurHash,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
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.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.data.CommentService
|
|
||||||
import com.aiosman.ravenow.data.CommentServiceImpl
|
|
||||||
import com.aiosman.ravenow.entity.CommentEntity
|
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
|
|
||||||
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentContent
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentMenuModal
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentsViewModel
|
|
||||||
import com.aiosman.ravenow.ui.post.OrderSelectionComponent
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
class NewsCommentModalViewModel(
|
|
||||||
val postId: Int?
|
|
||||||
) : ViewModel() {
|
|
||||||
var commentsViewModel: CommentsViewModel = CommentsViewModel(postId.toString())
|
|
||||||
var commentService: CommentService = CommentServiceImpl()
|
|
||||||
|
|
||||||
init {
|
|
||||||
commentsViewModel.preTransit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun likeComment(commentId: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.likeComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unlikeComment(commentId: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.unlikeComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createComment(
|
|
||||||
content: String,
|
|
||||||
parentCommentId: Int? = null,
|
|
||||||
replyUserId: Int? = null,
|
|
||||||
replyCommentId: Int? = null
|
|
||||||
) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.createComment(
|
|
||||||
content = content,
|
|
||||||
parentCommentId = parentCommentId,
|
|
||||||
replyUserId = replyUserId,
|
|
||||||
replyCommentId = replyCommentId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteComment(commentId: Int) {
|
|
||||||
commentsViewModel.deleteComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新闻评论弹窗
|
|
||||||
// @param postId 新闻帖子ID
|
|
||||||
// @param commentCount 评论数量
|
|
||||||
// @param onDismiss 关闭回调
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun NewsCommentModal(
|
|
||||||
postId: Int? = null,
|
|
||||||
commentCount: Int = 0,
|
|
||||||
onDismiss: () -> Unit = {},
|
|
||||||
onCommentAdded: () -> Unit = {},
|
|
||||||
onCommentDeleted: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val debouncedNavigation = rememberDebouncedNavigation()
|
|
||||||
|
|
||||||
// 实时评论数状态
|
|
||||||
var currentCommentCount by remember { mutableStateOf(commentCount) }
|
|
||||||
|
|
||||||
val model = viewModel<NewsCommentModalViewModel>(
|
|
||||||
key = "NewsCommentModalViewModel_$postId",
|
|
||||||
factory = object : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return NewsCommentModalViewModel(postId) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val commentViewModel = model.commentsViewModel
|
|
||||||
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
|
||||||
|
|
||||||
var showCommentMenu by remember { mutableStateOf(false) }
|
|
||||||
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
|
|
||||||
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
|
||||||
|
|
||||||
// 菜单弹窗
|
|
||||||
if (showCommentMenu) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = {
|
|
||||||
showCommentMenu = false
|
|
||||||
},
|
|
||||||
containerColor = AppColors.background,
|
|
||||||
sheetState = rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
dragHandle = {},
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
|
||||||
windowInsets = WindowInsets(0)
|
|
||||||
) {
|
|
||||||
CommentMenuModal(
|
|
||||||
onDeleteClick = {
|
|
||||||
showCommentMenu = false
|
|
||||||
contextComment?.let {
|
|
||||||
model.deleteComment(it.id)
|
|
||||||
onCommentDeleted()
|
|
||||||
currentCommentCount = (currentCommentCount - 1).coerceAtLeast(0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
commentEntity = contextComment,
|
|
||||||
onCloseClick = {
|
|
||||||
showCommentMenu = false
|
|
||||||
},
|
|
||||||
isSelf = AppState.UserId?.toLong() == contextComment?.author,
|
|
||||||
onLikeClick = {
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showCommentMenu = false
|
|
||||||
contextComment?.let {
|
|
||||||
if (it.liked) {
|
|
||||||
model.unlikeComment(it.id)
|
|
||||||
} else {
|
|
||||||
model.likeComment(it.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReplyClick = {
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showCommentMenu = false
|
|
||||||
replyComment = contextComment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${currentCommentCount}条评论",
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
|
|
||||||
// 排序选择
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.End
|
|
||||||
) {
|
|
||||||
OrderSelectionComponent {
|
|
||||||
commentViewModel.order = it
|
|
||||||
commentViewModel.reloadComment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 评论列表
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
LazyColumn {
|
|
||||||
item {
|
|
||||||
CommentContent(
|
|
||||||
viewModel = commentViewModel,
|
|
||||||
onLongClick = { comment ->
|
|
||||||
showCommentMenu = true
|
|
||||||
contextComment = comment
|
|
||||||
},
|
|
||||||
onReply = { parentComment, _, _, _ ->
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
replyComment = parentComment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部输入栏
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
HorizontalDivider(color = AppColors.inputBackground)
|
|
||||||
|
|
||||||
EditCommentBottomModal(
|
|
||||||
replyComment = replyComment,
|
|
||||||
autoFocus = false
|
|
||||||
) {
|
|
||||||
if (replyComment != null) {
|
|
||||||
if (replyComment?.parentCommentId != null) {
|
|
||||||
// 第三级评论
|
|
||||||
model.createComment(
|
|
||||||
content = it,
|
|
||||||
parentCommentId = replyComment?.parentCommentId,
|
|
||||||
replyUserId = replyComment?.author?.toInt(),
|
|
||||||
replyCommentId = replyComment?.id
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 子级评论
|
|
||||||
model.createComment(
|
|
||||||
content = it,
|
|
||||||
parentCommentId = replyComment?.id,
|
|
||||||
replyCommentId = replyComment?.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 顶级评论
|
|
||||||
model.createComment(content = it)
|
|
||||||
}
|
|
||||||
replyComment = null
|
|
||||||
onCommentAdded()
|
|
||||||
currentCommentCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(navBarHeight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.statusBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.pager.VerticalPager
|
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
|
||||||
import com.aiosman.ravenow.exp.timeAgo
|
|
||||||
import com.aiosman.ravenow.exp.formatPostTime2
|
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.NewsViewModel
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun NewsScreen() {
|
|
||||||
val model = NewsViewModel
|
|
||||||
val moments = model.moments
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
// 评论弹窗状态
|
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
|
||||||
var selectedMoment by remember { mutableStateOf<MomentEntity?>(null) }
|
|
||||||
|
|
||||||
// 查看全文弹窗状态
|
|
||||||
var showFullArticleModal by remember { mutableStateOf(false) }
|
|
||||||
var selectedArticleMoment by remember { mutableStateOf<MomentEntity?>(null) }
|
|
||||||
// 垂直翻页状态
|
|
||||||
val pagerState = rememberPagerState(pageCount = { moments.size })
|
|
||||||
|
|
||||||
// 防抖器
|
|
||||||
val likeDebouncer = rememberDebouncer()
|
|
||||||
val favoriteDebouncer = rememberDebouncer()
|
|
||||||
|
|
||||||
// 初始化加载数据
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
model.refreshPager()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听数据变化,重置加载状态
|
|
||||||
LaunchedEffect(moments.size) {
|
|
||||||
// 当数据增加时,如果接近列表末尾,Pager会自动更新页数
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当翻页接近末尾时加载更多
|
|
||||||
LaunchedEffect(pagerState.currentPage, moments.size) {
|
|
||||||
if (moments.isNotEmpty() && pagerState.currentPage >= moments.size - 2) {
|
|
||||||
model.loadMore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
if (moments.isEmpty()) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(text = "暂无新闻内容", color = AppColors.text, fontSize = 16.sp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VerticalPager(
|
|
||||||
state = pagerState,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) { page ->
|
|
||||||
val momentItem = moments.getOrNull(page) ?: return@VerticalPager
|
|
||||||
NewsItem(
|
|
||||||
moment = momentItem,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
onCommentClick = {
|
|
||||||
selectedMoment = momentItem
|
|
||||||
showCommentModal = true
|
|
||||||
},
|
|
||||||
onReadFullClick = {
|
|
||||||
selectedArticleMoment = momentItem
|
|
||||||
showFullArticleModal = true
|
|
||||||
},
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看全文弹窗
|
|
||||||
if (showFullArticleModal && selectedArticleMoment != null) {
|
|
||||||
FullArticleModal(
|
|
||||||
moment = selectedArticleMoment!!,
|
|
||||||
onDismiss = {
|
|
||||||
showFullArticleModal = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 评论弹窗
|
|
||||||
if (showCommentModal && selectedMoment != null) {
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
|
||||||
val sheetHeight = screenHeight * 0.67f // 三分之二高度
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = {
|
|
||||||
showCommentModal = false
|
|
||||||
},
|
|
||||||
sheetState = rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
containerColor = AppColors.background,
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
|
||||||
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
|
|
||||||
) {
|
|
||||||
NewsCommentModal(
|
|
||||||
postId = selectedMoment?.id,
|
|
||||||
commentCount = selectedMoment?.commentCount ?: 0,
|
|
||||||
onDismiss = {
|
|
||||||
showCommentModal = false
|
|
||||||
},
|
|
||||||
onCommentAdded = {
|
|
||||||
selectedMoment?.id?.let { model.onAddComment(it) }
|
|
||||||
},
|
|
||||||
onCommentDeleted = {
|
|
||||||
selectedMoment?.id?.let { model.onDeleteComment(it) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//单个新闻项
|
|
||||||
@Composable
|
|
||||||
fun NewsItem(
|
|
||||||
moment: MomentEntity,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onCommentClick: () -> Unit = {},
|
|
||||||
onReadFullClick: () -> Unit = {},
|
|
||||||
onLikeClick: () -> Unit = {},
|
|
||||||
onFavoriteClick: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.background)
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(bottom = 30.dp)
|
|
||||||
) {
|
|
||||||
// 新闻图片
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(300.dp)
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
if (moment.images.isNotEmpty()) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
context = context,
|
|
||||||
imageUrl = moment.images[0].thumbnail,
|
|
||||||
contentDescription = "新闻图片",
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
blurHash = moment.images[0].blurHash,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = R.drawable.default_moment_img),
|
|
||||||
contentDescription = "默认图片",
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻标题
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsTitle.isNotEmpty()) moment.newsTitle else moment.nickname,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻内容(超出使用省略号)
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp,
|
|
||||||
maxLines = 6,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻信息
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 来源和时间(显示月份与具体时间)
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsSource.isNotEmpty()) "${moment.newsSource} • ${moment.time.formatPostTime2()}" else "${moment.nickname} • ${moment.time.formatPostTime2()}",
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.secondaryText,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = 8.dp),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
// 查看全文
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.noRippleClickable { onReadFullClick() }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.read_full_article),
|
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
color = Color(0xFF7c45ed)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = R.mipmap.arrow),
|
|
||||||
contentDescription = "箭头",
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color(0xFF7c45ed))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 互动栏
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.padding(bottom = 25.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
// 点赞
|
|
||||||
NewsActionButton(
|
|
||||||
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
|
||||||
count = moment.likeCount.toString(),
|
|
||||||
isActive = moment.liked,
|
|
||||||
modifier = Modifier.noRippleClickable { onLikeClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 评论
|
|
||||||
NewsActionButton(
|
|
||||||
icon = R.mipmap.icon_comment,
|
|
||||||
count = moment.commentCount.toString(),
|
|
||||||
isActive = false,
|
|
||||||
modifier = Modifier.noRippleClickable { onCommentClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 收藏
|
|
||||||
NewsActionButton(
|
|
||||||
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
|
||||||
count = moment.favoriteCount.toString(),
|
|
||||||
isActive = moment.isFavorite,
|
|
||||||
modifier = Modifier.noRippleClickable { onFavoriteClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 分享
|
|
||||||
NewsActionButton(
|
|
||||||
icon = R.mipmap.icon_share,
|
|
||||||
count = "",
|
|
||||||
isActive = false,
|
|
||||||
text = stringResource(R.string.share),
|
|
||||||
textSize = 8.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 互动栏按钮
|
|
||||||
@Composable
|
|
||||||
fun NewsActionButton(
|
|
||||||
icon: Int,
|
|
||||||
count: String,
|
|
||||||
isActive: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
text: String? = null,
|
|
||||||
textSize: androidx.compose.ui.unit.TextUnit = 12.sp
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.width(60.dp)
|
|
||||||
.background(
|
|
||||||
color = AppColors.secondaryBackground,
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = icon),
|
|
||||||
contentDescription = "操作图标",
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (count.isNotEmpty()) {
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
|
||||||
text = count,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (text != null) {
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = textSize,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
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
|
|
||||||
|
|
||||||
object NewsViewModel : BaseMomentModel() {
|
|
||||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
|
||||||
// 只拉取新闻
|
|
||||||
return MomentLoaderExtraArgs(
|
|
||||||
explore = false,
|
|
||||||
timelineId = null,
|
|
||||||
authorId = null,
|
|
||||||
newsOnly = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,178 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.profile
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 拉黑确认弹窗
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun BlockConfirmDialog(
|
|
||||||
userProfile: AccountProfileEntity?,
|
|
||||||
onConfirmBlock: () -> Unit = {},
|
|
||||||
onDismiss: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.BottomCenter
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
color = AppColors.background,
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
|
||||||
)
|
|
||||||
.padding(24.dp)
|
|
||||||
) {
|
|
||||||
// 用户头像
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(bottom = 16.dp)
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
LocalContext.current,
|
|
||||||
userProfile?.avatar,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(60.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(
|
|
||||||
color = AppColors.background,
|
|
||||||
shape = CircleShape
|
|
||||||
),
|
|
||||||
contentDescription = "用户头像",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认文本
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.confirm_block_user, userProfile?.nickName ?: "该用户"),
|
|
||||||
|
|
||||||
fontSize = 18.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterHorizontally)
|
|
||||||
.padding(bottom = 24.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 说明信息
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.padding(bottom = 32.dp)
|
|
||||||
) {
|
|
||||||
// 第一条说明
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(bottom = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.icons_infor_off_eye),
|
|
||||||
contentDescription = "",
|
|
||||||
tint = AppColors.text,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.block_description_1),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二条说明
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(bottom = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_notice_mute),
|
|
||||||
contentDescription = "",
|
|
||||||
tint = AppColors.text,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.block_description_2),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第三条说明
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.icons_infor_off_bell),
|
|
||||||
contentDescription = "",
|
|
||||||
tint = AppColors.text,
|
|
||||||
modifier = Modifier.size(20.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.block_description_3),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 确认拉黑按钮
|
|
||||||
androidx.compose.material3.Button(
|
|
||||||
onClick = {
|
|
||||||
onConfirmBlock()
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(48.dp),
|
|
||||||
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
|
|
||||||
containerColor = AppColors.text
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(24.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.block),
|
|
||||||
color = AppColors.background,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -99,8 +99,6 @@ import kotlinx.coroutines.delay
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileV3(
|
fun ProfileV3(
|
||||||
@@ -129,8 +127,6 @@ fun ProfileV3(
|
|||||||
var showAgentMenu by remember { mutableStateOf(false) }
|
var showAgentMenu by remember { mutableStateOf(false) }
|
||||||
var contextAgent by remember { mutableStateOf<AgentEntity?>(null) }
|
var contextAgent by remember { mutableStateOf<AgentEntity?>(null) }
|
||||||
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
|
var showDeleteConfirmDialog by remember { mutableStateOf(false) }
|
||||||
var showOtherUserMenu by remember { mutableStateOf(false) }
|
|
||||||
var showBlockConfirmDialog by remember { mutableStateOf(false) }
|
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
@@ -158,15 +154,11 @@ fun ProfileV3(
|
|||||||
|
|
||||||
val toolbarAlpha by remember {
|
val toolbarAlpha by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
if (!isSelf) {
|
|
||||||
1f
|
|
||||||
} else {
|
|
||||||
val maxScroll = 500f // 最大滚动距离,可调整
|
val maxScroll = 500f // 最大滚动距离,可调整
|
||||||
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
||||||
progress
|
progress
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// observe list scrolling
|
// observe list scrolling
|
||||||
val reachedListBottom by remember {
|
val reachedListBottom by remember {
|
||||||
@@ -486,10 +478,7 @@ fun ProfileV3(
|
|||||||
isSelf = isSelf,
|
isSelf = isSelf,
|
||||||
profile = profile,
|
profile = profile,
|
||||||
navController = navController,
|
navController = navController,
|
||||||
alpha = toolbarAlpha,
|
alpha = toolbarAlpha
|
||||||
onMenuClick = {
|
|
||||||
showOtherUserMenu = true
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
@@ -497,69 +486,7 @@ fun ProfileV3(
|
|||||||
refreshState,
|
refreshState,
|
||||||
Modifier.align(Alignment.TopCenter)
|
Modifier.align(Alignment.TopCenter)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 其他用户菜单弹窗
|
|
||||||
if (showOtherUserMenu) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = { showOtherUserMenu = false },
|
|
||||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
|
||||||
containerColor = Color.Transparent, // 设置容器背景透明
|
|
||||||
contentColor = Color.Transparent, // 设置内容背景透明
|
|
||||||
dragHandle = null, // 移除拖拽手柄
|
|
||||||
windowInsets = androidx.compose.foundation.layout.WindowInsets(0) // 移除窗口边距
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(Color.Transparent)
|
|
||||||
) {
|
|
||||||
OtherUserMenuModal(
|
|
||||||
onBlockClick = {
|
|
||||||
showBlockConfirmDialog = true
|
|
||||||
},
|
|
||||||
onReportClick = {
|
|
||||||
// 实现举报逻辑
|
|
||||||
},
|
|
||||||
onCancelClick = {
|
|
||||||
showOtherUserMenu = false
|
|
||||||
},
|
|
||||||
onDismiss = { showOtherUserMenu = false }
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// 拉黑确认弹窗
|
|
||||||
if (showBlockConfirmDialog) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = {
|
|
||||||
showBlockConfirmDialog = false
|
|
||||||
},
|
|
||||||
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
|
|
||||||
containerColor = Color.Transparent,
|
|
||||||
contentColor = Color.Transparent,
|
|
||||||
dragHandle = null,
|
|
||||||
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(Color.Transparent)
|
|
||||||
) {
|
|
||||||
BlockConfirmDialog(
|
|
||||||
userProfile = profile,
|
|
||||||
onConfirmBlock = {
|
|
||||||
// 实现拉黑逻辑
|
|
||||||
},
|
|
||||||
onDismiss = {
|
|
||||||
showBlockConfirmDialog = false
|
|
||||||
showOtherUserMenu = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//顶部导航栏组件
|
//顶部导航栏组件
|
||||||
@@ -569,14 +496,14 @@ fun TopNavigationBar(
|
|||||||
isSelf: Boolean,
|
isSelf: Boolean,
|
||||||
profile: AccountProfileEntity?,
|
profile: AccountProfileEntity?,
|
||||||
navController: androidx.navigation.NavController,
|
navController: androidx.navigation.NavController,
|
||||||
alpha: Float,
|
|
||||||
onMenuClick: () -> Unit = {}
|
alpha: Float
|
||||||
) {
|
) {
|
||||||
val appColors = LocalAppTheme.current
|
val appColors = LocalAppTheme.current
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.graphicsLayer { this.alpha = alpha }
|
.graphicsLayer { this.alpha = alpha } // 应用透明度
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -628,21 +555,6 @@ fun TopNavigationBar(
|
|||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
)
|
)
|
||||||
} else if (!isSelf) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.noRippleClickable {
|
|
||||||
onMenuClick()
|
|
||||||
}
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
|
||||||
contentDescription = "菜单",
|
|
||||||
tint = appColors.text,
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
@@ -749,7 +661,7 @@ fun AgentMenuModal(
|
|||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
MenuActionItem(
|
MenuActionItem(
|
||||||
icon = R.drawable.rider_pro_moment_delete,
|
icon = R.drawable.rider_pro_moment_delete,
|
||||||
text = stringResource(R.string.delete)
|
text = "删除"
|
||||||
) {
|
) {
|
||||||
onDeleteClick()
|
onDeleteClick()
|
||||||
}
|
}
|
||||||
@@ -825,96 +737,3 @@ fun DeleteConfirmDialog(
|
|||||||
containerColor = AppColors.background
|
containerColor = AppColors.background
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 其他用户主页菜单弹窗
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun OtherUserMenuModal(
|
|
||||||
onBlockClick: () -> Unit = {},
|
|
||||||
onReportClick: () -> Unit = {},
|
|
||||||
onCancelClick: () -> Unit = {},
|
|
||||||
onDismiss: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.BottomCenter
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
.padding(bottom = 11.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
color = AppColors.background,
|
|
||||||
shape = RoundedCornerShape(8.dp)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
// 拉黑选项
|
|
||||||
androidx.compose.material3.TextButton(
|
|
||||||
onClick = {
|
|
||||||
onBlockClick()
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.block),
|
|
||||||
color = AppColors.error,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 分割线
|
|
||||||
androidx.compose.material3.HorizontalDivider(
|
|
||||||
color = AppColors.divider,
|
|
||||||
thickness = 0.5.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
// 举报选项
|
|
||||||
androidx.compose.material3.TextButton(
|
|
||||||
onClick = {
|
|
||||||
onReportClick()
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.report),
|
|
||||||
color = AppColors.error,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 取消按钮
|
|
||||||
androidx.compose.material3.TextButton(
|
|
||||||
onClick = {
|
|
||||||
onCancelClick()
|
|
||||||
onDismiss()
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
color = AppColors.background,
|
|
||||||
shape = RoundedCornerShape(8.dp)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
stringResource(R.string.cancel),
|
|
||||||
color = AppColors.text,
|
|
||||||
fontSize = 16.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import androidx.compose.foundation.layout.Arrangement
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -48,72 +47,66 @@ fun SelfProfileAction(
|
|||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(60.dp).height(25.dp)
|
.weight(1f)
|
||||||
.clip(RoundedCornerShape(12.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(androidx.compose.ui.graphics.Color(0x229284BD))
|
.background(AppColors.nonActive)
|
||||||
|
.padding(horizontal = 5.dp, vertical = 12.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
editProfileDebouncer {
|
editProfileDebouncer {
|
||||||
onEditProfile()
|
onEditProfile()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.fill_and_sign),
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier.size(12.dp),
|
|
||||||
colorFilter = ColorFilter.tint(androidx.compose.ui.graphics.Color(0xFF9284BD))
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.edit_profile),
|
text = stringResource(R.string.edit_profile),
|
||||||
fontSize = 12.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
color = androidx.compose.ui.graphics.Color(0xFF9284BD),
|
color = AppColors.text,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 预留按钮位置
|
// 预留按钮位置
|
||||||
// Row(
|
Row(
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
// horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .weight(1f)
|
.weight(1f)
|
||||||
// .clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
// .padding(horizontal = 16.dp, vertical = 12.dp)
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
// .noRippleClickable {
|
.noRippleClickable {
|
||||||
//
|
|
||||||
// }
|
}
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = "",
|
text = "",
|
||||||
// fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
// fontWeight = FontWeight.W900,
|
fontWeight = FontWeight.W900,
|
||||||
// color = AppColors.text,
|
color = AppColors.text,
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // 分享按钮
|
// 分享按钮
|
||||||
// Row(
|
Row(
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
// horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .weight(1f)
|
.weight(1f)
|
||||||
// .clip(RoundedCornerShape(10.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
// .background(AppColors.nonActive)
|
.background(AppColors.nonActive)
|
||||||
// .padding(horizontal = 16.dp, vertical = 12.dp)
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
// .noRippleClickable {
|
.noRippleClickable {
|
||||||
// shareDebouncer {
|
shareDebouncer {
|
||||||
// // TODO: 添加分享逻辑
|
// TODO: 添加分享逻辑
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = stringResource(R.string.share),
|
text = stringResource(R.string.share),
|
||||||
// fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
// fontWeight = FontWeight.W900,
|
fontWeight = FontWeight.W900,
|
||||||
// color = AppColors.text,
|
color = AppColors.text,
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
|
|
||||||
// // Rave Premium 按钮(右侧)
|
// // Rave Premium 按钮(右侧)
|
||||||
// Row(
|
// Row(
|
||||||
|
|||||||
@@ -212,12 +212,10 @@ fun EmptyAgentsView() {
|
|||||||
if (isNetworkAvailable) {
|
if (isNetworkAvailable) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.ai_dark
|
id =if(AppState.darkMode) R.mipmap.qs_ai_qs_as_img
|
||||||
else R.mipmap.ai),
|
else R.mipmap.ai),
|
||||||
contentDescription = "暂无Agent",
|
contentDescription = "暂无Agent",
|
||||||
modifier = Modifier
|
modifier = Modifier.size(181.dp),
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
.align(Alignment.CenterHorizontally),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|||||||
@@ -65,94 +65,94 @@ fun UserAgentsRow(
|
|||||||
viewModel.loadUserAgents(userId)
|
viewModel.loadUserAgents(userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 总是显示智能体区域,即使没有数据也显示标题和状态
|
// 总是显示智能体区域,即使没有数据也显示标题和状态
|
||||||
// Column(
|
Column(
|
||||||
// modifier = modifier
|
modifier = modifier
|
||||||
// .fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// .padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = if (isSelf) "我的智能体" else "TA的智能体",
|
text = if (isSelf) "我的智能体" else "TA的智能体",
|
||||||
// fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
// fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W600,
|
||||||
// color = AppColors.text,
|
color = AppColors.text,
|
||||||
// modifier = Modifier.padding(bottom = 12.dp)
|
modifier = Modifier.padding(bottom = 12.dp)
|
||||||
// )
|
)
|
||||||
//
|
|
||||||
// when {
|
when {
|
||||||
// viewModel.isLoading -> {
|
viewModel.isLoading -> {
|
||||||
// // 显示加载状态
|
// 显示加载状态
|
||||||
// Box(
|
Box(
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// .height(60.dp),
|
.height(60.dp),
|
||||||
// contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = "加载中...",
|
text = "加载中...",
|
||||||
// fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
// color = AppColors.text.copy(alpha = 0.6f)
|
color = AppColors.text.copy(alpha = 0.6f)
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// viewModel.error != null -> {
|
viewModel.error != null -> {
|
||||||
// // 显示错误状态
|
// 显示错误状态
|
||||||
// Box(
|
Box(
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// .height(60.dp),
|
.height(60.dp),
|
||||||
// contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = "加载失败: ${viewModel.error}",
|
text = "加载失败: ${viewModel.error}",
|
||||||
// fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
// color = AppColors.text.copy(alpha = 0.6f)
|
color = AppColors.text.copy(alpha = 0.6f)
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// viewModel.agents.isEmpty() -> {
|
viewModel.agents.isEmpty() -> {
|
||||||
// // 显示空状态
|
// 显示空状态
|
||||||
// Box(
|
Box(
|
||||||
// modifier = Modifier
|
modifier = Modifier
|
||||||
// .fillMaxWidth()
|
.fillMaxWidth()
|
||||||
// .height(60.dp),
|
.height(60.dp),
|
||||||
// contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
// ) {
|
) {
|
||||||
// Text(
|
Text(
|
||||||
// text = if (isSelf) "您还没有创建智能体" else "TA还没有创建智能体",
|
text = if (isSelf) "您还没有创建智能体" else "TA还没有创建智能体",
|
||||||
// fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
// color = AppColors.text.copy(alpha = 0.6f)
|
color = AppColors.text.copy(alpha = 0.6f)
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// else -> {
|
else -> {
|
||||||
// // 显示智能体列表
|
// 显示智能体列表
|
||||||
// LazyRow(
|
LazyRow(
|
||||||
// horizontalArrangement = Arrangement.spacedBy(12.dp),
|
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||||
// modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
// ) {
|
) {
|
||||||
// // 显示智能体项目
|
// 显示智能体项目
|
||||||
// items(viewModel.agents) { agent ->
|
items(viewModel.agents) { agent ->
|
||||||
// AgentItem(
|
AgentItem(
|
||||||
// agent = agent,
|
agent = agent,
|
||||||
// onClick = { onAgentClick(agent) },
|
onClick = { onAgentClick(agent) },
|
||||||
// onAvatarClick = { onAvatarClick(agent) },
|
onAvatarClick = { onAvatarClick(agent) },
|
||||||
// onLongClick = { onAgentLongClick(agent) }
|
onLongClick = { onAgentLongClick(agent) }
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // 添加"更多"按钮
|
// 添加"更多"按钮
|
||||||
// item {
|
item {
|
||||||
// MoreAgentItem(
|
MoreAgentItem(
|
||||||
// onClick = onMoreClick
|
onClick = onMoreClick
|
||||||
// )
|
)
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
|
|||||||
@@ -6,11 +6,9 @@ import android.util.Log
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.rememberScrollableState
|
import androidx.compose.foundation.gestures.rememberScrollableState
|
||||||
import androidx.compose.foundation.gestures.scrollable
|
import androidx.compose.foundation.gestures.scrollable
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
@@ -42,14 +40,12 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.graphics.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -74,11 +70,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import com.airbnb.lottie.compose.LottieAnimation
|
|
||||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
|
||||||
import com.airbnb.lottie.compose.LottieConstants
|
|
||||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginPage() {
|
fun LoginPage() {
|
||||||
@@ -218,14 +209,14 @@ fun LoginPage() {
|
|||||||
saveData()
|
saveData()
|
||||||
}
|
}
|
||||||
|
|
||||||
// // 显示成功提示
|
// 显示成功提示
|
||||||
// coroutineScope.launch(Dispatchers.Main) {
|
coroutineScope.launch(Dispatchers.Main) {
|
||||||
// Toast.makeText(
|
Toast.makeText(
|
||||||
// context,
|
context,
|
||||||
// "游客登录成功",
|
"游客登录成功",
|
||||||
// Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
// ).show()
|
).show()
|
||||||
// }
|
}
|
||||||
|
|
||||||
// 初始化应用状态(游客模式会自动跳过推送和TRTC初始化)
|
// 初始化应用状态(游客模式会自动跳过推送和TRTC初始化)
|
||||||
try {
|
try {
|
||||||
@@ -268,38 +259,14 @@ fun LoginPage() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(AppColors.background)
|
.background(AppColors.background)
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 24.dp)
|
|
||||||
.padding(top = 60.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(30.dp)
|
.fillMaxSize()
|
||||||
.background(
|
|
||||||
color = AppColors.text.copy(alpha = 0.1f),
|
|
||||||
shape = androidx.compose.foundation.shape.CircleShape
|
|
||||||
)
|
|
||||||
.noRippleClickable {
|
|
||||||
guestLogin()
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
) {
|
||||||
Image(
|
val localContext = LocalContext.current // 获取 Context
|
||||||
painter = painterResource(id = R.drawable.rider_pro_close),
|
MovingImageWall(localContext.resources) // 将 resources 传递给 MovingImageWall
|
||||||
contentDescription = "Close",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -307,109 +274,78 @@ fun LoginPage() {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Box(
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.invalid_name),
|
||||||
|
contentDescription = "Rave Now",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.size(52.dp)
|
||||||
.height(400.dp)
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
|
||||||
) {
|
|
||||||
val lottieFile = if (AppState.darkMode) "login.lottie" else "login_light.lottie"
|
|
||||||
LottieAnimation(
|
|
||||||
composition = rememberLottieComposition(LottieCompositionSpec.Asset(lottieFile)).value,
|
|
||||||
iterations = LottieConstants.IterateForever,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.join_party_carnival),
|
"Rave Now",
|
||||||
fontSize = 17.sp,
|
fontSize = 28.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
// Image(
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
// painter = painterResource(id = R.mipmap.invalid_name),
|
Text(
|
||||||
// contentDescription = "Rave Now",
|
"Your Night Starts Here",
|
||||||
// modifier = Modifier
|
fontSize = 20.sp,
|
||||||
// .size(52.dp)
|
fontWeight = FontWeight.W700,
|
||||||
// .clip(RoundedCornerShape(10.dp))
|
color = AppColors.text
|
||||||
// )
|
)
|
||||||
// Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
// Text(
|
|
||||||
// "Rave Now",
|
|
||||||
// fontSize = 28.sp,
|
|
||||||
// fontWeight = FontWeight.W900,
|
|
||||||
// color = AppColors.text
|
|
||||||
// )
|
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
// Text(
|
|
||||||
// "Your Night Starts Here",
|
|
||||||
// fontSize = 20.sp,
|
|
||||||
// fontWeight = FontWeight.W700,
|
|
||||||
// color = AppColors.text
|
|
||||||
// )
|
|
||||||
//注册tab
|
|
||||||
Spacer(modifier = Modifier.height(48.dp))
|
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.sign_up_upper),
|
text = stringResource(R.string.sign_up_upper),
|
||||||
color = if (AppState.darkMode) Color.Black else Color.White,
|
color = AppColors.mainText,
|
||||||
backgroundColor = if (AppState.darkMode) Color.White else Color.Black
|
backgroundColor = AppColors.main
|
||||||
) {
|
) {
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
NavigationRoute.EmailSignUp.route,
|
NavigationRoute.EmailSignUp.route,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
//谷歌登录tab
|
if (showGoogleLogin) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.height(52.dp)
|
|
||||||
.border(
|
|
||||||
width = 1.5.dp,
|
|
||||||
color = if (AppState.darkMode) Color.White else Color.Black,
|
|
||||||
shape = RoundedCornerShape(24.dp)
|
|
||||||
),
|
|
||||||
text = stringResource(R.string.sign_in_with_google),
|
text = stringResource(R.string.sign_in_with_google),
|
||||||
color = if (AppState.darkMode) Color.White else Color.Black,
|
color = AppColors.text,
|
||||||
backgroundColor = if (AppState.darkMode) Color.Black else Color.White,
|
|
||||||
leading = {
|
leading = {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.rider_pro_signup_google),
|
painter = painterResource(id = R.drawable.rider_pro_google),
|
||||||
contentDescription = "Google",
|
contentDescription = "Google",
|
||||||
modifier = Modifier.size(18.dp),
|
modifier = Modifier.size(36.dp)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
expandText = true,
|
expandText = true,
|
||||||
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 10.dp)
|
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
|
||||||
) {
|
) {
|
||||||
googleLogin()
|
googleLogin()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
//登录tab
|
//登录tab
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
ActionButton(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.login_upper),
|
text = stringResource(R.string.login_upper),
|
||||||
color = AppColors.text.copy(alpha = 0.5f),
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
) {
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
NavigationRoute.UserAuth.route,
|
NavigationRoute.UserAuth.route,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
|
||||||
// // 游客登录按钮
|
// 游客登录按钮
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
// ActionButton(
|
ActionButton(
|
||||||
// modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
// text = "游客模式",
|
text = "游客模式",
|
||||||
// color = AppColors.text.copy(alpha = 0.7f),
|
color = AppColors.text.copy(alpha = 0.7f),
|
||||||
// ) {
|
) {
|
||||||
// guestLogin()
|
guestLogin()
|
||||||
// }
|
}
|
||||||
Spacer(modifier = Modifier.height(70.dp))
|
Spacer(modifier = Modifier.height(70.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,6 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
@@ -62,7 +60,7 @@ fun UserAuthScreen() {
|
|||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
var email by remember { mutableStateOf("") }
|
var email by remember { mutableStateOf("") }
|
||||||
var password by remember { mutableStateOf("") }
|
var password by remember { mutableStateOf("") }
|
||||||
var rememberMe by remember { mutableStateOf(true) }
|
var rememberMe by remember { mutableStateOf(false) }
|
||||||
val accountService: AccountService = AccountServiceImpl()
|
val accountService: AccountService = AccountServiceImpl()
|
||||||
val captchaService: CaptchaService = CaptchaServiceImpl()
|
val captchaService: CaptchaService = CaptchaServiceImpl()
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
@@ -301,38 +299,32 @@ fun UserAuthScreen() {
|
|||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
text = stringResource(R.string.lets_ride_upper),
|
text = stringResource(R.string.lets_ride_upper),
|
||||||
backgroundBrush = Brush.linearGradient(
|
backgroundColor = AppColors.main,
|
||||||
colors = listOf(
|
|
||||||
Color(0xFF7c45ed),
|
|
||||||
Color(0x777c68ef),
|
|
||||||
Color(0x777bd8f8)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
color = AppColors.mainText,
|
color = AppColors.mainText,
|
||||||
) {
|
) {
|
||||||
onLogin()
|
onLogin()
|
||||||
}
|
}
|
||||||
// if (AppState.enableGoogleLogin) {
|
if (AppState.enableGoogleLogin) {
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
// Text(stringResource(R.string.or_login_with), color = AppColors.secondaryText)
|
Text(stringResource(R.string.or_login_with), color = AppColors.secondaryText)
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
// ActionButton(
|
ActionButton(
|
||||||
// modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
// text = stringResource(R.string.sign_in_with_google),
|
text = stringResource(R.string.sign_in_with_google),
|
||||||
// color = AppColors.text,
|
color = AppColors.text,
|
||||||
// leading = {
|
leading = {
|
||||||
// Image(
|
Image(
|
||||||
// painter = painterResource(id = R.drawable.rider_pro_google),
|
painter = painterResource(id = R.drawable.rider_pro_google),
|
||||||
// contentDescription = "Google",
|
contentDescription = "Google",
|
||||||
// modifier = Modifier.size(36.dp)
|
modifier = Modifier.size(36.dp)
|
||||||
// )
|
)
|
||||||
// },
|
},
|
||||||
// expandText = true,
|
expandText = true,
|
||||||
// contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
|
contentPadding = PaddingValues(vertical = 8.dp, horizontal = 8.dp)
|
||||||
// ) {
|
) {
|
||||||
// googleLogin()
|
googleLogin()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,23 +63,6 @@ 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)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 356 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 335 B |
|
Before Width: | Height: | Size: 509 B |
|
Before Width: | Height: | Size: 461 B |
|
Before Width: | Height: | Size: 446 B |
|
Before Width: | Height: | Size: 384 B |
|
Before Width: | Height: | Size: 348 B |
|
Before Width: | Height: | Size: 477 B |
|
Before Width: | Height: | Size: 617 B |
|
Before Width: | Height: | Size: 291 B |
|
Before Width: | Height: | Size: 206 B |
|
Before Width: | Height: | Size: 277 B |
|
Before Width: | Height: | Size: 381 B |
|
Before Width: | Height: | Size: 376 B |
|
Before Width: | Height: | Size: 358 B |
|
Before Width: | Height: | Size: 333 B |
|
Before Width: | Height: | Size: 248 B |
|
Before Width: | Height: | Size: 396 B |
|
Before Width: | Height: | Size: 483 B |
|
Before Width: | Height: | Size: 1.3 KiB |
|
Before Width: | Height: | Size: 423 B |
|
Before Width: | Height: | Size: 259 B |
|
Before Width: | Height: | Size: 377 B |
|
Before Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 595 B |
|
Before Width: | Height: | Size: 552 B |
|
Before Width: | Height: | Size: 450 B |
|
Before Width: | Height: | Size: 374 B |
|
Before Width: | Height: | Size: 560 B |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 646 B |
|
Before Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 541 B |
|
Before Width: | Height: | Size: 324 B |
|
Before Width: | Height: | Size: 521 B |
|
Before Width: | Height: | Size: 790 B |
|
Before Width: | Height: | Size: 746 B |
|
Before Width: | Height: | Size: 722 B |
|
Before Width: | Height: | Size: 564 B |
|
Before Width: | Height: | Size: 510 B |
|
Before Width: | Height: | Size: 741 B |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 620 B |
|
Before Width: | Height: | Size: 368 B |
|
Before Width: | Height: | Size: 614 B |
|
Before Width: | Height: | Size: 1.0 KiB |
|
Before Width: | Height: | Size: 1007 B |
|
Before Width: | Height: | Size: 975 B |
|
Before Width: | Height: | Size: 682 B |
|
Before Width: | Height: | Size: 623 B |
|
Before Width: | Height: | Size: 897 B |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -147,7 +147,6 @@
|
|||||||
<string name="chat_group">グループ</string>
|
<string name="chat_group">グループ</string>
|
||||||
<string name="chat_friend">友達</string>
|
<string name="chat_friend">友達</string>
|
||||||
<string name="chat_all">すべて</string>
|
<string name="chat_all">すべて</string>
|
||||||
<string name="chatting_now">人はおしゃべりをしている…</string>
|
|
||||||
<string name="agent_chat_list_title">AIエージェントチャット</string>
|
<string name="agent_chat_list_title">AIエージェントチャット</string>
|
||||||
<string name="agent_chat_empty_title">AIエージェントチャットがありません</string>
|
<string name="agent_chat_empty_title">AIエージェントチャットがありません</string>
|
||||||
<string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string>
|
<string name="agent_chat_empty_subtitle">AIエージェントと対話してみましょう</string>
|
||||||
@@ -158,6 +157,7 @@
|
|||||||
<string name="agent_chat_file">[ファイル]</string>
|
<string name="agent_chat_file">[ファイル]</string>
|
||||||
<string name="agent_chat_message">[メッセージ]</string>
|
<string name="agent_chat_message">[メッセージ]</string>
|
||||||
<string name="agent_chat_load_failed">読み込みに失敗しました</string>
|
<string name="agent_chat_load_failed">読み込みに失敗しました</string>
|
||||||
|
<string name="agent_chat_loading">読み込み中</string>
|
||||||
<string name="agent_chat_load_more_failed">さらに読み込むのに失敗しました</string>
|
<string name="agent_chat_load_more_failed">さらに読み込むのに失敗しました</string>
|
||||||
<string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string>
|
<string name="agent_chat_user_info_failed">ユーザー情報の取得に失敗しました: %s</string>
|
||||||
<string name="group_chat_empty">グループチャットがありません</string>
|
<string name="group_chat_empty">グループチャットがありません</string>
|
||||||
@@ -192,9 +192,6 @@
|
|||||||
<string name="agent_createing">作成中…</string>
|
<string name="agent_createing">作成中…</string>
|
||||||
<string name="agent_find">発見</string>
|
<string name="agent_find">発見</string>
|
||||||
<string name="text_error_password_too_long">パスワードは%1$d文字を超えることはできません</string>
|
<string name="text_error_password_too_long">パスワードは%1$d文字を超えることはできません</string>
|
||||||
<string name="block">ブロック</string>
|
|
||||||
<string name="read_full_article">全文を読む</string>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Create Bottom Sheet -->
|
<!-- Create Bottom Sheet -->
|
||||||
<string name="create_title">作成</string>
|
<string name="create_title">作成</string>
|
||||||
@@ -222,31 +219,5 @@
|
|||||||
<string name="friend_chat_no_network_title">オフラインだ..</string>
|
<string name="friend_chat_no_network_title">オフラインだ..</string>
|
||||||
<string name="friend_chat_no_network_subtitle">ネットワークを確認して、この宇宙に接続してください</string>
|
<string name="friend_chat_no_network_subtitle">ネットワークを確認して、この宇宙に接続してください</string>
|
||||||
<string name="Reload">再ロード</string>
|
<string name="Reload">再ロード</string>
|
||||||
|
|
||||||
<!-- Login page -->
|
|
||||||
<string name="join_party_carnival">パーティーに参加して、一緒に盛り上がろう</string>
|
|
||||||
|
|
||||||
<!-- Tab labels -->
|
|
||||||
<string name="tab_recommend">おすすめ</string>
|
|
||||||
<string name="tab_short_video">ショート動画</string>
|
|
||||||
<string name="tab_news">ニュース</string>
|
|
||||||
|
|
||||||
<!-- Block Confirm Dialog -->
|
|
||||||
<string name="confirm_block_user">%1$sをブロックしますか?</string>
|
|
||||||
<string name="block_description_1">相手はあなたにメッセージを送信したり、あなたのプロフィールやコンテンツを見つけることができなくなります</string>
|
|
||||||
<string name="block_description_2">相手はあなたにブロックされた通知を受け取りません</string>
|
|
||||||
<string name="block_description_3">「設定」でいつでもブロックを解除できます</string>
|
|
||||||
|
|
||||||
<!-- Chat Settings -->
|
|
||||||
<string name="chat_settings">チャット設定</string>
|
|
||||||
<string name="chat_theme_settings">チャットテーマを設定</string>
|
|
||||||
<string name="custom_background">カスタム背景</string>
|
|
||||||
<string name="select_from_gallery">ギャラリーから選択</string>
|
|
||||||
<string name="featured_backgrounds">おすすめ背景</string>
|
|
||||||
<string name="previewing_custom_background">カスタム背景をプレビュー中</string>
|
|
||||||
<string name="each_theme_unique_experience">各テーマには独自の体験があります</string>
|
|
||||||
<string name="select_apply_to_use_theme">「適用」を選択してこのテーマを使用</string>
|
|
||||||
<string name="tap_cancel_to_preview_other_themes">「キャンセル」をタップして他のテーマをプレビュー</string>
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
<string name="text_hint_password">输入密码</string>
|
<string name="text_hint_password">输入密码</string>
|
||||||
<string name="sign_up_upper">注册</string>
|
<string name="sign_up_upper">注册</string>
|
||||||
<string name="sign_in_with_email">使用邮箱注册</string>
|
<string name="sign_in_with_email">使用邮箱注册</string>
|
||||||
<string name="sign_in_with_google">使用Google账号登录</string>
|
<string name="sign_in_with_google">使用 Google 账号登录</string>
|
||||||
<string name="back_upper">返回</string>
|
<string name="back_upper">返回</string>
|
||||||
<string name="text_hint_confirm_password">再次输入密码</string>
|
<string name="text_hint_confirm_password">再次输入密码</string>
|
||||||
<string name="login_confirm_password_label">再次输入密码</string>
|
<string name="login_confirm_password_label">再次输入密码</string>
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
|
<string name="error_not_accept_term">"为了提供更好的服务,请您在注册前仔细阅读并同意《用户协议》。 "</string>
|
||||||
<string name="empty_my_post_title">还没有发布任何动态</string>
|
<string name="empty_my_post_title">还没有发布任何动态</string>
|
||||||
<string name="empty_my_post_content">发布一个动态吧</string>
|
<string name="empty_my_post_content">发布一个动态吧</string>
|
||||||
<string name="edit_profile">编辑</string>
|
<string name="edit_profile">编辑资料</string>
|
||||||
<string name="share">分享</string>
|
<string name="share">分享</string>
|
||||||
<string name="logout">登出</string>
|
<string name="logout">登出</string>
|
||||||
<string name="change_password">修改密码</string>
|
<string name="change_password">修改密码</string>
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
<string name="index_following">关注</string>
|
<string name="index_following">关注</string>
|
||||||
<string name="index_hot">热门</string>
|
<string name="index_hot">热门</string>
|
||||||
<string name="main_home">首页</string>
|
<string name="main_home">首页</string>
|
||||||
<string name="main_ai">AI</string>
|
<string name="main_ai">智能体</string>
|
||||||
<string name="main_message">消息</string>
|
<string name="main_message">消息</string>
|
||||||
<string name="main_profile">我的</string>
|
<string name="main_profile">我的</string>
|
||||||
<string name="agent_mine">我的</string>
|
<string name="agent_mine">我的</string>
|
||||||
@@ -149,7 +149,7 @@
|
|||||||
<string name="chat_group">群聊</string>
|
<string name="chat_group">群聊</string>
|
||||||
<string name="chat_friend">朋友</string>
|
<string name="chat_friend">朋友</string>
|
||||||
<string name="chat_all">全部</string>
|
<string name="chat_all">全部</string>
|
||||||
<string name="favourites_null">暂无数据</string>
|
<string name="favourites_null">咦,什么都没有...</string>
|
||||||
|
|
||||||
<string name="agent_chat_list_title">智能体聊天</string>
|
<string name="agent_chat_list_title">智能体聊天</string>
|
||||||
<string name="agent_chat_empty_title">AI 在等你的开场白</string>
|
<string name="agent_chat_empty_title">AI 在等你的开场白</string>
|
||||||
@@ -161,14 +161,15 @@
|
|||||||
<string name="agent_chat_file">[文件]</string>
|
<string name="agent_chat_file">[文件]</string>
|
||||||
<string name="agent_chat_message">[消息]</string>
|
<string name="agent_chat_message">[消息]</string>
|
||||||
<string name="agent_chat_load_failed">加载失败</string>
|
<string name="agent_chat_load_failed">加载失败</string>
|
||||||
|
<string name="agent_chat_loading">加载中</string>
|
||||||
<string name="agent_chat_load_more_failed">加载更多失败</string>
|
<string name="agent_chat_load_more_failed">加载更多失败</string>
|
||||||
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
|
<string name="agent_chat_user_info_failed">获取用户信息失败: %s</string>
|
||||||
<string name="group_chat_empty">没有群聊,宇宙好安静</string>
|
<string name="group_chat_empty">没有群聊,宇宙好安静</string>
|
||||||
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
|
<string name="group_chat_empty_title">没有群聊消息的宇宙太安静了</string>
|
||||||
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
|
<string name="group_chat_empty_subtitle">在首页探索感兴趣的主题房间</string>
|
||||||
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
|
<string name="group_chat_empty_join">去首页探索感兴趣的高能对话</string>
|
||||||
<string name="friend_chat_empty_title">和朋友,还没有对话哦~</string>
|
<string name="friend_chat_empty_title">你和朋友,还没说第一句话呢</string>
|
||||||
<string name="friend_chat_empty_subtitle">点击好友头像,即刻发起聊天</string>
|
<string name="friend_chat_empty_subtitle">一段崭新的友谊 等待被唤醒</string>
|
||||||
<string name="friend_chat_me_prefix">我: </string>
|
<string name="friend_chat_me_prefix">我: </string>
|
||||||
<string name="friend_chat_load_failed">加载失败</string>
|
<string name="friend_chat_load_failed">加载失败</string>
|
||||||
<string name="create_group_chat">创建群聊</string>
|
<string name="create_group_chat">创建群聊</string>
|
||||||
@@ -191,13 +192,9 @@
|
|||||||
<string name="group_room_enter">进入</string>
|
<string name="group_room_enter">进入</string>
|
||||||
<string name="group_room_enter_success">成功加入房间</string>
|
<string name="group_room_enter_success">成功加入房间</string>
|
||||||
<string name="group_room_enter_fail">加入房间失败</string>
|
<string name="group_room_enter_fail">加入房间失败</string>
|
||||||
<string name="agent_createing">创建中…</string>
|
<string name="agent_createing">创建中...</string>
|
||||||
<string name="agent_find">发现</string>
|
<string name="agent_find">发现</string>
|
||||||
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
|
<string name="text_error_password_too_long">密码不能超过 %1$d 个字符</string>
|
||||||
<string name="chatting_now">人正在热聊…</string>
|
|
||||||
<string name="block">拉黑</string>
|
|
||||||
<string name="read_full_article">查看全文</string>
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Create Bottom Sheet -->
|
<!-- Create Bottom Sheet -->
|
||||||
<string name="create_title">创建</string>
|
<string name="create_title">创建</string>
|
||||||
@@ -226,28 +223,4 @@
|
|||||||
<string name="friend_chat_no_network_subtitle">确认一下网络,连接这个宇宙</string>
|
<string name="friend_chat_no_network_subtitle">确认一下网络,连接这个宇宙</string>
|
||||||
<string name="Reload">重新加载</string>
|
<string name="Reload">重新加载</string>
|
||||||
|
|
||||||
<!-- Login page -->
|
|
||||||
<string name="join_party_carnival">加入派派,一起狂欢</string>
|
|
||||||
|
|
||||||
<!-- Tab labels -->
|
|
||||||
<string name="tab_recommend">推荐</string>
|
|
||||||
<string name="tab_short_video">短视频</string>
|
|
||||||
<string name="tab_news">新闻</string>
|
|
||||||
|
|
||||||
<!-- Block Confirm Dialog -->
|
|
||||||
<string name="confirm_block_user">确认拉黑%s?</string>
|
|
||||||
<string name="block_description_1">对方将无法发消息给你,也无法找到你的主页或内容</string>
|
|
||||||
<string name="block_description_2">对方不会收到自己被你拉黑的通知</string>
|
|
||||||
<string name="block_description_3">你可以随时"设置"中取消拉黑TA</string>
|
|
||||||
|
|
||||||
<!-- Chat Settings -->
|
|
||||||
<string name="chat_settings">聊天设置</string>
|
|
||||||
<string name="chat_theme_settings">设置聊天主题</string>
|
|
||||||
<string name="custom_background">自定义背景</string>
|
|
||||||
<string name="select_from_gallery">从相册选择</string>
|
|
||||||
<string name="featured_backgrounds">精选背景</string>
|
|
||||||
<string name="previewing_custom_background">正在预览自定义背景</string>
|
|
||||||
<string name="each_theme_unique_experience">每个主题都有自己独特的体验</string>
|
|
||||||
<string name="select_apply_to_use_theme">选择"应用"可选中这个主题</string>
|
|
||||||
<string name="tap_cancel_to_preview_other_themes">轻触"取消"可预览其他主题</string>
|
|
||||||
</resources>
|
</resources>
|
||||||