1 Commits

Author SHA1 Message Date
d8df67bae5 动态模块新增推荐Tab,UI优化及调整
- 新增推荐Tab,采用垂直滑动样式,展示推荐动态内容。
- 推荐Tab支持预加载周围图片,提升滑动体验,并增加loading和错误状态指示。
- 优化评论弹窗UI,移除自动聚焦,调整背景色和输入框样式。
- 动态Tab样式调整,使用下划线指示当前选中Tab。
- 调整MomentLoaderExtraArgs,增加trend参数用于推荐动态加载。
- 新增字符串资源 `index_recommend`。
2025-09-23 10:58:50 +08:00
235 changed files with 1804 additions and 6332 deletions

32
.idea/gradle.xml generated
View File

@@ -1,20 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" /> <component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="CHOOSE_PER_TEST" /> <option name="testRunner" value="CHOOSE_PER_TEST" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option> </option>
</component> <option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings>
</option>
</component>
</project> </project>

View File

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

Binary file not shown.

Binary file not shown.

View File

@@ -45,8 +45,6 @@ object AppState {
var googleClientId: String? = null var googleClientId: String? = null
var enableGoogleLogin: Boolean = false var enableGoogleLogin: Boolean = false
var enableChat = false var enableChat = 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) {

View File

@@ -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环境
} }

View File

@@ -43,12 +43,7 @@ import com.google.firebase.analytics.analytics
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.runtime.remember
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import com.aiosman.ravenow.ui.splash.SplashScreen
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
// Firebase Analytics // Firebase Analytics
@@ -127,84 +122,75 @@ class MainActivity : ComponentActivity() {
} }
setContent { setContent {
var showSplash by remember { mutableStateOf(true) } CompositionLocalProvider(
LocalAppTheme provides AppState.appTheme
LaunchedEffect(Unit) { ) {
kotlinx.coroutines.delay(2000) CheckUpdateDialog()
showSplash = false Navigation(startDestination) { navController ->
} // 处理带有 postId 的通知点击
val postId = intent.getStringExtra("POST_ID")
if (showSplash) { var commentId = intent.getStringExtra("COMMENT_ID")
SplashScreen() val action = intent.getStringExtra("ACTION")
} else { if (action == "newFollow") {
CompositionLocalProvider( navController.navigate(NavigationRoute.Followers.route)
LocalAppTheme provides AppState.appTheme return@Navigation
) { }
CheckUpdateDialog() if (action == "followCount") {
Navigation(startDestination) { navController -> navController.navigate(NavigationRoute.Followers.route)
return@Navigation
// 处理带有 postId 的通知点击 }
val postId = intent.getStringExtra("POST_ID") if (action == "TRTC_NEW_MESSAGE") {
var commentId = intent.getStringExtra("COMMENT_ID") val userService:UserService = UserServiceImpl()
val action = intent.getStringExtra("ACTION") val sender = intent.getStringExtra("SENDER")
if (action == "newFollow") { sender?.let {
navController.navigate(NavigationRoute.Followers.route) scope.launch {
return@Navigation try {
} val profile = userService.getUserProfileByTrtcUserId(it,0)
if (action == "followCount") { navController.navigate(NavigationRoute.Chat.route.replace(
navController.navigate(NavigationRoute.Followers.route) "{id}",
return@Navigation profile.id.toString()
} ))
if (action == "TRTC_NEW_MESSAGE") { }catch (e:Exception){
val userService:UserService = UserServiceImpl() e.printStackTrace()
val sender = intent.getStringExtra("SENDER")
sender?.let {
scope.launch {
try {
val profile = userService.getUserProfileByTrtcUserId(it,0)
navController.navigate(NavigationRoute.Chat.route.replace(
"{id}",
profile.id.toString()
))
}catch (e:Exception){
e.printStackTrace()
}
} }
} }
return@Navigation
} }
return@Navigation
}
if (commentId == null) { if (commentId == null) {
commentId = "0" commentId = "0"
} }
if (postId != null) { if (postId != null) {
Log.d("MainActivity", "Navigation to Post$postId") Log.d("MainActivity", "Navigation to Post$postId")
navController.navigateToPost( navController.navigateToPost(
id = postId.toInt(), id = postId.toInt(),
highlightCommentId = commentId.toInt(), highlightCommentId = commentId.toInt(),
initImagePagerIndex = 0 initImagePagerIndex = 0
) )
} }
// 处理分享过来的图片 // 处理分享过来的图片
if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) { if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_SEND_MULTIPLE) {
val imageUris: List<Uri>? = if (intent.action == Intent.ACTION_SEND) { val imageUris: List<Uri>? = if (intent.action == Intent.ACTION_SEND) {
listOf(intent.getParcelableExtra(Intent.EXTRA_STREAM)!!) listOf(intent.getParcelableExtra(Intent.EXTRA_STREAM)!!)
} else { } else {
intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM)
}
NewPostViewModel.asNewPostWithImageUris(imageUris!!.map { it.toString() })
navController.navigate(NavigationRoute.NewPost.route)
} }
NewPostViewModel.asNewPostWithImageUris(imageUris!!.map { it.toString() })
navController.navigate(NavigationRoute.NewPost.route)
} }
} }
} }
} }
} }
} }
/** /**
* 请求通知权限 * 请求通知权限
*/ */

View File

@@ -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 ?: ""
) )
} }
} }

View File

@@ -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 智能体的 OpenIDUUID格式与 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: 智能体的 OpenIDUUID格式与 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-100currentCount / 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>>
} }

View File

@@ -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)

View File

@@ -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
}
} }

View File

@@ -41,12 +41,10 @@ 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
import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen import com.aiosman.ravenow.ui.comment.notice.CommentNoticeScreen
import com.aiosman.ravenow.ui.composables.AgentCreatedSuccessIndicator
import com.aiosman.ravenow.ui.crop.ImageCropScreen import com.aiosman.ravenow.ui.crop.ImageCropScreen
import com.aiosman.ravenow.ui.favourite.FavouriteListPage import com.aiosman.ravenow.ui.favourite.FavouriteListPage
import com.aiosman.ravenow.ui.favourite.FavouriteNoticeScreen import com.aiosman.ravenow.ui.favourite.FavouriteNoticeScreen
@@ -72,7 +70,6 @@ import com.aiosman.ravenow.ui.post.NewPostScreen
import com.aiosman.ravenow.ui.post.PostScreen import com.aiosman.ravenow.ui.post.PostScreen
import com.aiosman.ravenow.ui.profile.AccountProfileV2 import com.aiosman.ravenow.ui.profile.AccountProfileV2
import com.aiosman.ravenow.ui.index.tabs.profile.vip.VipSelPage import com.aiosman.ravenow.ui.index.tabs.profile.vip.VipSelPage
import com.aiosman.ravenow.ui.notification.NotificationScreen
sealed class NavigationRoute( sealed class NavigationRoute(
val route: String, val route: String,
@@ -107,7 +104,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")
@@ -119,7 +115,6 @@ sealed class NavigationRoute(
data object GroupInfo : NavigationRoute("GroupInfo/{id}") data object GroupInfo : NavigationRoute("GroupInfo/{id}")
data object VipSelPage : NavigationRoute("VipSelPage") data object VipSelPage : NavigationRoute("VipSelPage")
data object RemoveAccountScreen: NavigationRoute("RemoveAccount") data object RemoveAccountScreen: NavigationRoute("RemoveAccount")
data object NotificationScreen : NavigationRoute("NotificationScreen")
} }
@@ -493,14 +488,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 },
@@ -603,13 +590,6 @@ fun NavigationController(
} }
} }
composable(route = NavigationRoute.NotificationScreen.route) {
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
NotificationScreen()
}
}
} }
@@ -635,7 +615,6 @@ fun Navigation(
navController = navController, navController = navController,
startDestination = startDestination startDestination = startDestination
) )
AgentCreatedSuccessIndicator()
} }
} }
} }

View File

@@ -1,8 +1,5 @@
package com.aiosman.ravenow.ui.account package com.aiosman.ravenow.ui.account
import android.content.Context
import android.content.Intent
import android.net.Uri
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
@@ -50,24 +47,12 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import android.util.Log import android.util.Log
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.width
import androidx.compose.material3.Text
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
import java.io.File
/** /**
* 编辑用户资料界面 * 编辑用户资料界面
*/ */
@Composable @Composable
fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,) { fun AccountEditScreen2() {
val model = AccountEditViewModel val model = AccountEditViewModel
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalContext.current val context = LocalContext.current
@@ -76,19 +61,6 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
// 防抖导航器 // 防抖导航器
val debouncedNavigation = rememberDebouncedNavigation() val debouncedNavigation = rememberDebouncedNavigation()
// 添加图片选择启动器
val scope = rememberCoroutineScope()
val pickBannerImageLauncher = pickupAndCompressLauncher(
context,
scope,
maxSize = ConstVars.BANNER_IMAGE_MAX_SIZE,
quality = 100
) { uri, file ->
// 处理选中的图片
onUpdateBanner?.invoke(uri, file, context)
}
fun onNicknameChange(value: String) { fun onNicknameChange(value: String) {
// 去除换行符,确保昵称不包含换行 // 去除换行符,确保昵称不包含换行
val cleanValue = value.replace("\n", "").replace("\r", "") val cleanValue = value.replace("\n", "").replace("\r", "")
@@ -180,60 +152,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
) )
} }
} }
Spacer(modifier = Modifier.height(44.dp))
// 添加横幅图片区域
val banner = model.profile?.banner
Box(
modifier = Modifier
.fillMaxWidth()
.height(300.dp)
.clip(RoundedCornerShape(12.dp))
) {
if (banner != null) {
CustomAsyncImage(
context = LocalContext.current,
imageUrl = banner,
modifier = Modifier.fillMaxSize(),
contentDescription = "Banner",
contentScale = ContentScale.Crop
)
} else {
Box(
modifier = Modifier
.fillMaxSize()
.background(Color.Gray.copy(alpha = 0.1f))
)
}
Box(
modifier = Modifier
.width(120.dp)
.height(42.dp)
.align(Alignment.BottomEnd)
.padding(end = 12.dp, bottom = 12.dp)
.background(
color = Color.Black.copy(alpha = 0.4f),
shape = RoundedCornerShape(9.dp)
)
.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
}
){
Text(
text = "change",
fontSize = 14.sp,
fontWeight = FontWeight.W600,
color = Color.White,
modifier = Modifier.align(Alignment.Center)
)
}
}
Spacer(modifier = Modifier.height(20.dp))
// 显示内容或加载状态 // 显示内容或加载状态
Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}") Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}")
@@ -261,15 +180,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
modifier = Modifier modifier = Modifier
.size(32.dp) .size(32.dp)
.clip(CircleShape) .clip(CircleShape)
.background( .background(appColors.main)
brush = Brush.linearGradient(
colors = listOf(
Color(0xFF7c45ed),
Color(0x997c68ef),
Color(0xFF7bd8f8)
)
),
)
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.debouncedClickable( .debouncedClickable(
debounceTime = 800L debounceTime = 800L
@@ -287,7 +198,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
) )
} }
} }
Spacer(modifier = Modifier.height(18.dp)) Spacer(modifier = Modifier.height(58.dp))
Column( Column(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)

View File

@@ -74,17 +74,6 @@ import androidx.compose.animation.core.infiniteRepeatable
import androidx.compose.animation.core.rememberInfiniteTransition import androidx.compose.animation.core.rememberInfiniteTransition
import androidx.compose.animation.core.tween import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.asImageBitmap
import androidx.compose.ui.text.TextStyle
import com.aiosman.ravenow.ui.agent.AddAgentViewModel.showManualCreation
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
/** /**
* 添加智能体界面 * 添加智能体界面
*/ */
@@ -98,10 +87,7 @@ fun AddAgentScreen() {
var errorMessage by remember { mutableStateOf<String?>(null) } var errorMessage by remember { mutableStateOf<String?>(null) }
var isProcessing by remember { mutableStateOf(false) } var isProcessing by remember { mutableStateOf(false) }
var showWaveAnimation by remember { mutableStateOf(false) } var showWaveAnimation by remember { mutableStateOf(false) }
var isCreatingAgent by remember { mutableStateOf(false) } // 控制是否处于创建状态
var showManualCreationForm by remember { mutableStateOf(false) } // 控制是否显示手动创建表单
var tempDesc by remember { mutableStateOf("") } // 独立的临时描述变量
val keyboardController = LocalSoftwareKeyboardController.current
fun onNameChange(value: String) { fun onNameChange(value: String) {
model.name = value.trim() model.name = value.trim()
@@ -115,37 +101,15 @@ fun AddAgentScreen() {
fun onDescChange(value: String) { fun onDescChange(value: String) {
model.desc = value.trim() model.desc = value.trim()
agnetDescError = when { agnetDescError = when {
value.length > 512 -> "简介长度不能大于512" value.length > 100 -> "简介长度不能大于100"
else -> null
}
}
fun onTempDescChange(value: String) {
tempDesc = value.trim()
agnetDescError = when {
value.length > 512 -> "简介长度不能大于512"
else -> null else -> null
} }
} }
fun validate(): Boolean { fun validate(): Boolean {
return agnetNameError == null && agnetDescError == null return agnetNameError == null && agnetDescError == null
} }
// AI文案优化
suspend fun optimizeTextWithAI(content: String): String? {
return try {
val sessionId = ""
val response = com.aiosman.ravenow.data.api.ApiClient.api.agentMoment(
com.aiosman.ravenow.data.api.AgentMomentRequestBody(
generateText = content,
sessionId = sessionId
)
)
response.body()?.data
} catch (e: Exception) {
e.printStackTrace()
null
}
}
// 处理系统返回键 // 处理系统返回键
BackHandler { BackHandler {
@@ -159,32 +123,15 @@ fun AddAgentScreen() {
// 页面进入时重置头像选择状态 // 页面进入时重置头像选择状态
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
model.isSelectingAvatar = false model.isSelectingAvatar = false
// 根据标记恢复相应的状态
if (model.isAutoModeManualForm) {
// 恢复自动模式下的手动表单状态
showManualCreationForm = model.showManualCreationForm
isCreatingAgent = model.isCreatingAgent
showWaveAnimation = model.showWaveAnimation
showManualCreation = model.showManualCreation
} else {
// 恢复手动模式下的状态
showManualCreation = model.showManualCreation
showManualCreationForm = model.showManualCreationForm
isCreatingAgent = model.isCreatingAgent
showWaveAnimation = model.showWaveAnimation
}
} }
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(color = appColors.decentBackground), .background(color = appColors.decentBackground),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
var showManualCreation by remember { var showManualCreation by remember { mutableStateOf(false) }
mutableStateOf(model.showManualCreation)
}
StatusBarSpacer() StatusBarSpacer()
Box( Box(
modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp) modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp)
@@ -221,57 +168,51 @@ fun AddAgentScreen() {
} }
} }
Spacer(modifier = Modifier.height(1.dp)) Spacer(modifier = Modifier.height(1.dp))
Column(
if (!isCreatingAgent) { modifier = Modifier
Column( .fillMaxWidth()
.height(50.dp)
.padding(horizontal = 20.dp),
) {
Image(
painter = painterResource(id = R.mipmap.group_copy),
contentDescription = "",
modifier = Modifier modifier = Modifier
.fillMaxWidth() .size(48.dp)
.height(50.dp) .clip(
.padding(horizontal = 20.dp), RoundedCornerShape(48.dp)
) { ),
Image( contentScale = ContentScale.Crop
painter = painterResource(id = R.mipmap.group_copy), )
contentDescription = "",
modifier = Modifier
.size(48.dp)
.clip(
RoundedCornerShape(48.dp)
),
contentScale = ContentScale.Crop
)
}
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
.padding(start = 20.dp) .padding(start = 20.dp)
) { ) {
Text(
text = "${AppState.profile?.nickName ?: "User"} 你好呀!今天想创造什么?",
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
}
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier.fillMaxWidth()
.padding(start = 20.dp)
) {
if (!showManualCreation) {
Text( Text(
text = "${AppState.profile?.nickName ?: "User"} ${stringResource(R.string.welcome_1)}", text = "只需要一句话你的专属AI将在这里诞生。",
fontSize = 16.sp, fontSize = 14.sp,
color = appColors.text, color = LocalAppTheme.current.text.copy(alpha = 0.6f),
fontWeight = FontWeight.W600
) )
} }
if (!isCreatingAgent) {
Spacer(modifier = Modifier.height(8.dp))
Column(
modifier = Modifier.fillMaxWidth()
.padding(start = 20.dp)
) {
if (!showManualCreation) {
Text(
text = stringResource(R.string.welcome_2),
fontSize = 14.sp,
color = appColors.text.copy(alpha = 0.6f),
)
}
}
} }
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
if (!showManualCreation) { if (!showManualCreation) {
//自动创造AI界面
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 20.dp) .padding(horizontal = 20.dp)
@@ -304,6 +245,7 @@ fun AddAgentScreen() {
) )
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Box( Box(
modifier = Modifier modifier = Modifier
@@ -316,71 +258,36 @@ fun AddAgentScreen() {
} }
) )
TextField( FormTextInput2(
value = tempDesc, value = model.desc,
onValueChange = { value -> onTempDescChange(value) }, hint = "一个会写诗的AI一个懂你笑点的AI...",
background = appColors.inputBackground2,
focusRequester = focusRequester,
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(95.dp) .height(95.dp)
.focusRequester(focusRequester), ) { value ->
placeholder = { onDescChange(value)
Text( }
text = stringResource(R.string.agent_desc_hint_auto),
color = Color.Gray
)
},
textStyle = TextStyle(
color = LocalAppTheme.current.text,
fontSize = 16.sp
),
colors = TextFieldDefaults.colors(
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
disabledIndicatorColor = Color.Transparent,
focusedContainerColor = appColors.inputBackground2,
unfocusedContainerColor = appColors.inputBackground2,
),
shape = RoundedCornerShape(10.dp),
supportingText = null,
trailingIcon = null,
leadingIcon = null
)
Row( Row(
modifier = Modifier modifier = Modifier
.align(Alignment.BottomEnd) .align(Alignment.BottomEnd)
.padding(end = 12.dp, bottom = 12.dp) .padding(end = 12.dp, bottom = 12.dp)
.noRippleClickable { .noRippleClickable {
// 只有在有内容且未处理中且未创建中时才可点击 if (!isProcessing && model.desc.isNotEmpty()) {
if (tempDesc.isNotEmpty() && !isProcessing && !isCreatingAgent) {
isProcessing = true isProcessing = true
showWaveAnimation = true // 显示构思动画
keyboardController?.hide()
model.viewModelScope.launch { model.viewModelScope.launch {
try { try {
val optimizedText = optimizeTextWithAI(tempDesc) //AI美化功能待实现
if (optimizedText != null) {
onTempDescChange(optimizedText)
}
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} finally { } finally {
isProcessing = false isProcessing = false
showWaveAnimation = false
isCreatingAgent = true
showManualCreationForm = true
onDescChange(tempDesc)
} }
} }
} }
} },
.then(
if (tempDesc.isEmpty() || isProcessing || isCreatingAgent) {
Modifier.alpha(0.5f)
} else {
Modifier
}
),
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Icon( Icon(
@@ -391,7 +298,7 @@ fun AddAgentScreen() {
) )
Spacer(modifier = Modifier.width(5.dp)) Spacer(modifier = Modifier.width(5.dp))
Text( Text(
text = stringResource(R.string.agent_text_beautify), text = "AI美化",
color = Color(0xFF6246FF), color = Color(0xFF6246FF),
fontSize = 14.sp fontSize = 14.sp
) )
@@ -399,8 +306,7 @@ fun AddAgentScreen() {
} }
} }
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
if ((isCreatingAgent && showWaveAnimation) || (isProcessing && showWaveAnimation)) { if (showWaveAnimation) {
// 显示构思动画
Row( Row(
modifier = Modifier modifier = Modifier
.align(Alignment.Start) .align(Alignment.Start)
@@ -408,113 +314,83 @@ fun AddAgentScreen() {
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Box( Box(
modifier = Modifier.size(32.dp) modifier = Modifier.size(18.dp)
) { ) {
LottieAnimation( val infiniteTransition = rememberInfiniteTransition()
composition = rememberLottieComposition(LottieCompositionSpec.Asset("loading.lottie")).value, val dot1Translation by infiniteTransition.animateFloat(
iterations = LottieConstants.IterateForever, initialValue = 0f,
modifier = Modifier.matchParentSize() targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(0)
)
)
val dot2Translation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(333)
)
)
val dot3Translation by infiniteTransition.animateFloat(
initialValue = 0f,
targetValue = -12f,
animationSpec = infiniteRepeatable(
animation = tween(
durationMillis = 1000,
easing = FastOutSlowInEasing
),
repeatMode = RepeatMode.Reverse,
initialStartOffset = StartOffset(666)
)
)
// 三个彩色圆点
Box(
modifier = Modifier
.size(6.dp)
.align(Alignment.BottomStart)
.offset(y = dot1Translation.dp)
.background(Color(0xFFFFD400), CircleShape)
)
Box(
modifier = Modifier
.size(6.dp)
.align(Alignment.BottomCenter)
.offset(y = dot2Translation.dp)
.background(Color(0xFF2F80FF), CircleShape)
)
Box(
modifier = Modifier
.size(6.dp)
.align(Alignment.BottomEnd)
.offset(y = dot3Translation.dp)
.background(Color(0xFF27C84D), CircleShape)
) )
} }
Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringResource(R.string.ideaing), text = "正在为你构思",
color = appColors.text.copy(alpha = 0.6f), color = Color.Black.copy(alpha = 0.6f),
fontSize = 14.sp fontSize = 14.sp
) )
} }
} else if (isCreatingAgent && !showWaveAnimation && showManualCreationForm) { } else {
Column(
modifier = Modifier
.padding(horizontal = 16.dp)
.align(Alignment.Start)
) {
Text(
text = stringResource(R.string.avatar),
fontSize = 12.sp,
color = appColors.text,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
Box(
modifier = Modifier
.size(72.dp)
.clip(CircleShape)
.background(
brush = Brush.linearGradient(
colors = listOf(
Color(0x777c45ed),
Color(0x777c68ef),
Color(0x557bd8f8)
)
)
)
.align(Alignment.Start)
.noRippleClickable {
// 保存当前状态
model.showManualCreationForm = showManualCreationForm
model.isCreatingAgent = isCreatingAgent
model.showWaveAnimation = showWaveAnimation
model.showManualCreation = showManualCreation
model.isAutoModeManualForm = true // 标记为自动模式下的手动表单
// 设置正在选择头像的标志
model.isSelectingAvatar = true
navController.navigate(NavigationRoute.AgentImageCrop.route)
},
contentAlignment = Alignment.Center
){
// 如果已有裁剪后的头像,则显示头像,否则显示编辑图标
if (model.croppedBitmap != null) {
Image(
bitmap = model.croppedBitmap!!.asImageBitmap(),
contentDescription = "Avatar",
modifier = Modifier
.size(72.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
} else {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = "Edit",
tint = Color.White,
modifier = Modifier.size(20.dp),
)
}
}
Spacer(modifier = Modifier.height(18.dp))
// 原版两个输入框
Text(
text = stringResource(R.string.agent_name),
fontSize = 12.sp,
color = appColors.text,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
FormTextInput(
value = model.name,
hint = stringResource(R.string.agent_name_hint_1),
background = appColors.inputBackground2,
modifier = Modifier.fillMaxWidth(),
) { value ->
onNameChange(value)
}
Text(
text = stringResource(R.string.agent_desc),
fontSize = 12.sp,
color = appColors.text,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(4.dp))
FormTextInput2(
value = model.desc,
hint = stringResource(R.string.agent_desc_hint),
background = appColors.inputBackground2,
modifier = Modifier.fillMaxWidth(),
) { value ->
onDescChange(value)
}
}
} else if (!isCreatingAgent && !showWaveAnimation) {
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.Start) .align(Alignment.Start)
@@ -532,8 +408,6 @@ fun AddAgentScreen() {
) )
.noRippleClickable { .noRippleClickable {
showManualCreation = true showManualCreation = true
tempDesc = ""
agnetDescError = null
} }
) { ) {
Row( Row(
@@ -545,13 +419,12 @@ fun AddAgentScreen() {
Icon( Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit), painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = null, contentDescription = null,
tint = appColors.text,
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringResource(R.string.create_agent_hand), text = "手动创造Ai",
color = appColors.text, color = Color.Black,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
fontSize = 14.sp fontSize = 14.sp
) )
@@ -559,12 +432,12 @@ fun AddAgentScreen() {
} }
} }
}else { }else {
//手动创造AI界面
Column( Column(
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.align(Alignment.Start) .align(Alignment.Start)
) { ) {
// 添加新的一句话创造AI按钮
Box( Box(
modifier = Modifier modifier = Modifier
.align(Alignment.Start) .align(Alignment.Start)
@@ -593,13 +466,6 @@ fun AddAgentScreen() {
) )
.noRippleClickable { .noRippleClickable {
showManualCreation = false showManualCreation = false
model.name = ""
model.desc = ""
model.croppedBitmap = null
isCreatingAgent = false
showManualCreationForm = false
showWaveAnimation = false
isProcessing = false
} }
) { ) {
Row( Row(
@@ -611,13 +477,13 @@ fun AddAgentScreen() {
Icon( Icon(
painter = painterResource(id = R.mipmap.icons_info_magic), painter = painterResource(id = R.mipmap.icons_info_magic),
contentDescription = null, contentDescription = null,
tint = appColors.text, tint = Color.Black,
modifier = Modifier.size(18.dp), modifier = Modifier.size(18.dp),
) )
Spacer(modifier = Modifier.width(8.dp)) Spacer(modifier = Modifier.width(8.dp))
Text( Text(
text = stringResource(R.string.create_agent_auto), text = "一句话创造AI",
color = appColors.text, color = Color.Black,
fontWeight = FontWeight.W600, fontWeight = FontWeight.W600,
fontSize = 14.sp fontSize = 14.sp
) )
@@ -625,60 +491,41 @@ fun AddAgentScreen() {
} }
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( Text(
text = stringResource(R.string.avatar), text = "头像",
fontSize = 12.sp, fontSize = 12.sp,
color = appColors.text, fontWeight = FontWeight.W600
fontWeight = FontWeight.W600 )
) Spacer(modifier = Modifier.height(4.dp))
Spacer(modifier = Modifier.height(4.dp)) Box(
Box( modifier = Modifier
modifier = Modifier .size(72.dp)
.size(72.dp) .clip(CircleShape)
.clip(CircleShape) .background(
.background( brush = Brush.linearGradient(
brush = Brush.linearGradient( colors = listOf(
colors = listOf( Color(0x777c45ed),
Color(0x777c45ed), Color(0x777c68ef),
Color(0x777c68ef), Color(0x557bd8f8)
Color(0x557bd8f8)
)
) )
) )
.align(Alignment.Start) )
.noRippleClickable { .align(Alignment.Start)
// 保存当前状态 .noRippleClickable {
model.showManualCreation = showManualCreation // 设置正在选择头像的标志
model.showManualCreationForm = showManualCreationForm model.isSelectingAvatar = true
model.isCreatingAgent = isCreatingAgent navController.navigate(NavigationRoute.AgentImageCrop.route)
model.showWaveAnimation = showWaveAnimation },
model.isAutoModeManualForm = false // 标记为手动模式下的手动表单 contentAlignment = Alignment.Center
// 设置正在选择头像的标志 ) {
model.isSelectingAvatar = true Icon(
navController.navigate(NavigationRoute.AgentImageCrop.route) painter = painterResource(id = R.mipmap.icons_infor_edit),
}, contentDescription = "Edit",
contentAlignment = Alignment.Center tint = Color.White,
) { modifier = Modifier.size(20.dp),
// 如果已有裁剪后的头像,则显示头像,否则显示编辑图标 )
if (model.croppedBitmap != null) {
Image(
bitmap = model.croppedBitmap!!.asImageBitmap(),
contentDescription = "Avatar",
modifier = Modifier
.size(72.dp)
.clip(CircleShape),
contentScale = ContentScale.Crop
)
} else {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = "Edit",
tint = Color.White,
modifier = Modifier.size(20.dp),
)
}
}
} }
}
Spacer(modifier = Modifier.height(18.dp)) Spacer(modifier = Modifier.height(18.dp))
// 原版两个输入框 // 原版两个输入框
Column( Column(
@@ -688,13 +535,12 @@ fun AddAgentScreen() {
Text( Text(
text = stringResource(R.string.agent_name), text = stringResource(R.string.agent_name),
fontSize = 12.sp, fontSize = 12.sp,
color = appColors.text,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
FormTextInput( FormTextInput(
value = model.name, value = model.name,
hint = stringResource(R.string.agent_name_hint_1), hint = "给它取个名字,让它成为独一无二的你",
background = appColors.inputBackground2, background = appColors.inputBackground2,
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
) { value -> ) { value ->
@@ -703,7 +549,6 @@ fun AddAgentScreen() {
Text( Text(
text = stringResource(R.string.agent_desc), text = stringResource(R.string.agent_desc),
fontSize = 12.sp, fontSize = 12.sp,
color = appColors.text,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600
) )
Spacer(modifier = Modifier.height(4.dp)) Spacer(modifier = Modifier.height(4.dp))
@@ -716,7 +561,6 @@ fun AddAgentScreen() {
onDescChange(value) onDescChange(value)
} }
} }
//手动创造AI界面
} }
// 错误信息显示 // 错误信息显示
@@ -733,6 +577,7 @@ fun AddAgentScreen() {
) )
} }
} }
ActionButton( ActionButton(
modifier = Modifier modifier = Modifier
.width(345.dp) .width(345.dp)
@@ -768,6 +613,9 @@ fun AddAgentScreen() {
// 清除之前的错误信息 // 清除之前的错误信息
errorMessage = null errorMessage = null
// 显示波动动画
showWaveAnimation = true
// 调用创建智能体API // 调用创建智能体API
model.viewModelScope.launch { model.viewModelScope.launch {
try { try {
@@ -776,15 +624,14 @@ fun AddAgentScreen() {
// 创建成功,清空数据并关闭页面 // 创建成功,清空数据并关闭页面
model.clearData() model.clearData()
navController.popBackStack() navController.popBackStack()
AppState.agentCreatedSuccess = true
} }
} catch (e: Exception) { } catch (e: Exception) {
// 隐藏波动动画
showWaveAnimation = false
// 显示错误信息 // 显示错误信息
errorMessage = "创建智能体失败: ${e.message}" errorMessage = "创建智能体失败: ${e.message}"
e.printStackTrace() e.printStackTrace()
// 3秒后清除错误信息
kotlinx.coroutines.delay(3000)
errorMessage = null
} }
} }
} }

View File

@@ -24,12 +24,7 @@ object AddAgentViewModel : ViewModel() {
var croppedBitmap by mutableStateOf<Bitmap?>(null) var croppedBitmap by mutableStateOf<Bitmap?>(null)
var isUpdating by mutableStateOf(false) var isUpdating by mutableStateOf(false)
var isSelectingAvatar by mutableStateOf(false) // 标记是否正在选择头像 var isSelectingAvatar by mutableStateOf(false) // 标记是否正在选择头像
var showManualCreationForm by mutableStateOf(false)
var isCreatingAgent by mutableStateOf(false)
var showWaveAnimation by mutableStateOf(false)
var showManualCreation by mutableStateOf(false)
// 添加一个标志来区分两种手动表单状态
var isAutoModeManualForm by mutableStateOf(false)
suspend fun updateAgentAvatar(context: Context) { suspend fun updateAgentAvatar(context: Context) {
croppedBitmap?.let { croppedBitmap?.let {
val file = File(context.cacheDir, "agent_avatar.jpg") val file = File(context.cacheDir, "agent_avatar.jpg")
@@ -71,11 +66,11 @@ object AddAgentViewModel : ViewModel() {
fun validate(): String? { fun validate(): String? {
return when { return when {
name.isEmpty() -> "智能体名称不能为空" // name.isEmpty() -> "智能体名称不能为空"
name.length < 2 -> "智能体名称长度不能少于2个字符" // name.length < 2 -> "智能体名称长度不能少于2个字符"
name.length > 20 -> "智能体名称长度不能超过20个字符" // name.length > 20 -> "智能体名称长度不能超过20个字符"
desc.isEmpty() -> "智能体描述不能为空" desc.isEmpty() -> "智能体描述不能为空"
desc.length > 512 -> "智能体描述长度不能超过512个字符" desc.length > 100 -> "智能体描述长度不能超过100个字符"
else -> null else -> null
} }
} }
@@ -89,10 +84,5 @@ object AddAgentViewModel : ViewModel() {
croppedBitmap = null croppedBitmap = null
isUpdating = false isUpdating = false
isSelectingAvatar = false isSelectingAvatar = false
showManualCreationForm = false
isCreatingAgent = false
showWaveAnimation = false
showManualCreation = false
isAutoModeManualForm = false
} }
} }

View File

@@ -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()) { Scaffold(
if (chatBackgroundUrl != null && chatBackgroundUrl.isNotEmpty()) { modifier = Modifier
CustomAsyncImage( .fillMaxSize(),
imageUrl = chatBackgroundUrl, topBar = {
modifier = Modifier.fillMaxSize(),
contentDescription = "chat_background",
contentScale = ContentScale.Crop
)
}
Scaffold(
modifier = Modifier
.fillMaxSize(),
backgroundColor = Color.Transparent,
topBar = {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.background(Color.Transparent) .background(AppColors.background)
) { ) {
StatusBarSpacer() StatusBarSpacer()
Row( Row(
@@ -225,32 +213,56 @@ 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() .height(1.dp)
.height(1.dp) .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

View File

@@ -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( Scaffold(
modifier = Modifier modifier = Modifier
.fillMaxSize() .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(
modifier = Modifier
.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()
}
}
),
)
} }
} }
} }
@@ -277,11 +279,11 @@ fun ChatScreen(userId: String) {
} }
} }
} }
) { paddingValues -> ) { paddingValues ->
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

View File

@@ -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)
}
}
}
}
}
}
}
}
}

View File

@@ -47,8 +47,7 @@ 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.ui.navigateToPost import com.aiosman.ravenow.ui.navigateToPost
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
import com.aiosman.ravenow.ui.network.ReloadButton
@Composable @Composable
fun CommentNoticeScreen() { fun CommentNoticeScreen() {
val viewModel = viewModel<CommentNoticeListViewModel>( val viewModel = viewModel<CommentNoticeListViewModel>(
@@ -72,47 +71,14 @@ fun CommentNoticeScreen() {
modifier = Modifier.fillMaxSize().background(color = AppColors.background) modifier = Modifier.fillMaxSize().background(color = AppColors.background)
) { ) {
StatusBarSpacer() StatusBarSpacer()
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) Box(
modifier = Modifier
if (!isNetworkAvailable) { .fillMaxWidth()
Box( .padding(16.dp)
modifier = Modifier ) {
.fillMaxSize() NoticeScreenHeader(stringResource(R.string.comment), moreIcon = false)
.padding(top = 149.dp), }
contentAlignment = Alignment.TopCenter if (comments.itemCount == 0 && comments.loadState.refresh is LoadState.NotLoading) {
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
androidx.compose.foundation.Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.text,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
viewModel.initData(context, force = true)
}
)
}
}
} else if (comments.itemCount == 0 && comments.loadState.refresh is LoadState.NotLoading) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -126,7 +92,7 @@ fun CommentNoticeScreen() {
androidx.compose.foundation.Image( androidx.compose.foundation.Image(
painter = painterResource( painter = painterResource(
id =if(AppState.darkMode) R.mipmap.qst_pl_qs_as_img id =if(AppState.darkMode) R.mipmap.qst_pl_qs_as_img
else R.mipmap.invalid_name_11), else R.mipmap.qst_pl_qs_img),
contentDescription = "No Comment", contentDescription = "No Comment",
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -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()

View File

@@ -1,75 +0,0 @@
package com.aiosman.ravenow.ui.composables
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
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.RoundedCornerShape
import androidx.compose.material3.Icon
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.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.toolbar.CollapsingToolbarScaffoldScopeInstance.align
import kotlinx.coroutines.delay
@Composable
fun AgentCreatedSuccessIndicator() {
val appColors = LocalAppTheme.current
if (AppState.agentCreatedSuccess) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 70.dp),
contentAlignment = Alignment.BottomCenter
) {
Box(
modifier = Modifier
.width(150.dp)
.height(40.dp)
.background(appColors.text.copy(alpha = 0.5f), shape = RoundedCornerShape(15.dp)),
contentAlignment = Alignment.CenterStart
) {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 8.dp, vertical = 6.dp)
) {
Icon(
painter = painterResource(id = R.mipmap.bars_x_buttons_home_n_copy_2),
contentDescription = null,
modifier = Modifier.size(20.dp),
tint = Color.Unspecified
)
Spacer(modifier = Modifier.width(7.dp))
Text(
text = stringResource(R.string.create_success),
color = appColors.background,
fontSize = 13.sp
)
}
}
}
LaunchedEffect(Unit) {
delay(3000)
AppState.agentCreatedSuccess = false
}
}
}

View File

@@ -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)
) )
} }
} }

View File

@@ -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,10 +59,8 @@ 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(

View File

@@ -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,15 +401,14 @@ 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)
)
Text(
text = count,
modifier = Modifier.padding(start = 7.dp),
fontSize = 14.sp,
color = AppColors.text
) )
if (count.isNotEmpty()) {
Text(
text = count,
modifier = Modifier.padding(start = 7.dp),
fontSize = 14.sp,
color = AppColors.text
)
}
} }
} }
@@ -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()
}
} }
} }
} }

View File

@@ -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",
)
}
}
}
}

View File

@@ -22,15 +22,11 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -38,7 +34,6 @@ 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.R
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
/** /**
* 水平布局的输入框 * 水平布局的输入框
@@ -54,9 +49,6 @@ fun FormTextInput(
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val focusRequester = remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Column( Column(
modifier = modifier modifier = modifier
) { ) {
@@ -71,11 +63,7 @@ fun FormTextInput(
it it
} }
} }
.padding(17.dp) .padding(17.dp),
.noRippleClickable {
focusRequester.requestFocus()
keyboardController?.show()
},
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
label?.let { label?.let {
@@ -100,7 +88,7 @@ fun FormTextInput(
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)
.align(Alignment.TopStart), .align(Alignment.TopStart),
tint = AppColors.text.copy(alpha = 0.4f) tint = Color.Unspecified.copy(alpha = 0.4f)
) )
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -126,8 +114,6 @@ fun FormTextInput(
onValueChange = { onValueChange = {
onValueChange(it) onValueChange(it)
}, },
modifier = Modifier
.focusRequester(focusRequester),
singleLine = true, singleLine = true,
textStyle = TextStyle( textStyle = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,

View File

@@ -22,7 +22,6 @@ import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
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
@@ -30,7 +29,6 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
@@ -38,7 +36,6 @@ 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.R
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
/** /**
* 垂直布局的输入框 * 垂直布局的输入框
@@ -55,9 +52,6 @@ fun FormTextInput2(
onValueChange: (String) -> Unit onValueChange: (String) -> Unit
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val localFocusRequester = focusRequester ?: remember { FocusRequester() }
val keyboardController = LocalSoftwareKeyboardController.current
Column( Column(
modifier = modifier.height(150.dp) modifier = modifier.height(150.dp)
) { ) {
@@ -72,11 +66,7 @@ fun FormTextInput2(
it it
} }
} }
.padding(17.dp) .padding(17.dp),
.noRippleClickable {
localFocusRequester.requestFocus()
keyboardController?.show()
},
) { ) {
label?.let { label?.let {
@@ -101,7 +91,7 @@ fun FormTextInput2(
modifier = Modifier modifier = Modifier
.size(16.dp) .size(16.dp)
.align(Alignment.TopStart), .align(Alignment.TopStart),
tint = AppColors.text.copy(alpha = 0.4f) tint = Color.Unspecified.copy(alpha = 0.4f)
) )
Row( Row(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
@@ -122,13 +112,19 @@ fun FormTextInput2(
} }
BasicTextField( BasicTextField(
maxLines = 6, maxLines = 5,
value = value, value = value,
onValueChange = { onValueChange = {
onValueChange(it) onValueChange(it)
}, },
modifier = Modifier modifier = Modifier
.focusRequester(localFocusRequester), .let {
if (focusRequester != null) {
it.focusRequester(focusRequester)
} else {
it
}
},
textStyle = TextStyle( textStyle = TextStyle(
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,

View File

@@ -4,23 +4,18 @@ 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.Spacer
import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.aspectRatio
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.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
@@ -29,9 +24,7 @@ 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.res.stringResource import androidx.compose.ui.res.stringResource
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.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController import com.aiosman.ravenow.LocalNavController
@@ -42,8 +35,6 @@ import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.favourite.FavouriteListViewModel.refreshPager import com.aiosman.ravenow.ui.favourite.FavouriteListViewModel.refreshPager
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.navigateToPost import com.aiosman.ravenow.ui.navigateToPost
import com.aiosman.ravenow.ui.network.ReloadButton
import com.aiosman.ravenow.utils.NetworkUtils
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -80,120 +71,55 @@ fun FavouriteListPage() {
) { ) {
NoticeScreenHeader(stringResource(R.string.favourites_upper), moreIcon = false) NoticeScreenHeader(stringResource(R.string.favourites_upper), moreIcon = false)
} }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) LazyVerticalGrid(
var moments = dataFlow.collectAsLazyPagingItems() columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)
if (!isNetworkAvailable) { ) {
Box( items(moments.itemCount) { idx ->
modifier = Modifier val momentItem = moments[idx] ?: return@items
.fillMaxSize() Box(
.padding(top=149.dp), modifier = Modifier
contentAlignment = Alignment.TopCenter .fillMaxWidth()
) { .aspectRatio(1f)
Column( .padding(2.dp)
horizontalAlignment = Alignment.CenterHorizontally, .noRippleClickable {
modifier = Modifier.fillMaxWidth() navController.navigateToPost(
) { id = momentItem.id,
Image( highlightCommentId = 0,
painter = painterResource(id = R.mipmap.invalid_name_10), initImagePagerIndex = 0
contentDescription = "network error", )
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
model.refreshPager(force = true)
} }
)
}
}
} else if(moments.itemCount == 0) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top=189.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) { ) {
Image( CustomAsyncImage(
painter = painterResource( imageUrl = momentItem.images[0].thumbnail,
id = if (com.aiosman.ravenow.AppState.darkMode) R.mipmap.invalid_dark contentDescription = "",
else R.mipmap.invalid_name_1),
contentDescription = "No favourites",
modifier = Modifier.size(110.dp)
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.favourites_null),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
}
}
}else{
LazyVerticalGrid(
columns = GridCells.Fixed(3),
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)
) {
items(moments.itemCount) { idx ->
val momentItem = moments[idx] ?: return@items
Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.aspectRatio(1f) .clip(RoundedCornerShape(8.dp)),
.padding(2.dp) context = context
.noRippleClickable { )
navController.navigateToPost( if (momentItem.images.size > 1) {
id = momentItem.id, Box(
highlightCommentId = 0,
initImagePagerIndex = 0
)
}
) {
CustomAsyncImage(
imageUrl = momentItem.images[0].thumbnail,
contentDescription = "",
modifier = Modifier modifier = Modifier
.fillMaxSize() .padding(top = 8.dp, end = 8.dp)
.clip(RoundedCornerShape(8.dp)), .align(Alignment.TopEnd)
context = context ) {
) Image(
if (momentItem.images.size > 1) { modifier = Modifier.size(24.dp),
Box( painter = painterResource(R.drawable.rider_pro_picture_more),
modifier = Modifier contentDescription = "",
.padding(top = 8.dp, end = 8.dp) )
.align(Alignment.TopEnd)
) {
Image(
modifier = Modifier.size(24.dp),
painter = painterResource(R.drawable.rider_pro_picture_more),
contentDescription = "",
)
}
} }
} }
} }
} }
} }
} }
PullRefreshIndicator( PullRefreshIndicator(
FavouriteListViewModel.isLoading, FavouriteListViewModel.isLoading,
state, state,

View File

@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.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
@@ -20,7 +19,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import 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
@@ -39,8 +37,6 @@ import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
import com.aiosman.ravenow.ui.network.ReloadButton
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -71,47 +67,7 @@ fun FollowerListScreen(userId: Int) {
) { ) {
NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false) NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false)
} }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) if (users.itemCount == 0) {
if (!isNetworkAvailable) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 149.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.size(24.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = appColors.text,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
model.loadData(userId, true)
}
)
}
}
} else if (users.itemCount == 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -125,7 +81,7 @@ fun FollowerListScreen(userId: Int) {
Image( Image(
painter = painterResource( painter = painterResource(
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
else R.mipmap.invalid_name_8), else R.mipmap.qst_fs_qs_img),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
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.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
@@ -38,9 +37,7 @@ import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.FollowButton import com.aiosman.ravenow.ui.composables.FollowButton
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.network.ReloadButton
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
/** /**
* 关注消息列表 * 关注消息列表
@@ -57,51 +54,19 @@ fun FollowerNoticeScreen() {
val model = FollowerNoticeViewModel val model = FollowerNoticeViewModel
var dataFlow = model.followerItemsFlow var dataFlow = model.followerItemsFlow
var followers = dataFlow.collectAsLazyPagingItems() var followers = dataFlow.collectAsLazyPagingItems()
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
.background(color = AppColors.background)
) {
NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false)
}
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
model.reload() model.reload()
model.updateNotice() model.updateNotice()
} }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) if (followers.itemCount == 0) {
if (!isNetworkAvailable) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top=149.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.size(24.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.text,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
model.reload(force = true)
}
)
}
}
} else if (followers.itemCount == 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -115,7 +80,7 @@ fun FollowerNoticeScreen() {
Image( Image(
painter = painterResource( painter = painterResource(
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
else R.mipmap.invalid_name_8), else R.mipmap.qst_fs_qs_img),
contentDescription = "No Followers", contentDescription = "No Followers",
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -20,7 +20,6 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import 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
@@ -38,9 +37,7 @@ import com.aiosman.ravenow.exp.viewModelFactory
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import com.aiosman.ravenow.ui.network.ReloadButton
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -72,48 +69,7 @@ fun FollowingListScreen(userId: Int) {
NoticeScreenHeader(stringResource(R.string.following_upper), moreIcon = false) NoticeScreenHeader(stringResource(R.string.following_upper), moreIcon = false)
} }
if(users.itemCount == 0) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
if (!isNetworkAvailable) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top=149.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.size(24.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
androidx.compose.material.Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = appColors.secondaryText,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
model.loadData(userId, true)
}
)
}
}
} else if(users.itemCount == 0) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -127,7 +83,7 @@ fun FollowingListScreen(userId: Int) {
Image( Image(
painter = painterResource( painter = painterResource(
id =if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img_my id =if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img_my
else R.mipmap.invalid_name_9), else R.mipmap.qst_gz_qs_img_my),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -43,7 +43,7 @@ fun CreateBottomSheet(
onMomentClick: () -> Unit onMomentClick: () -> Unit
) { ) {
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
//水平效果呈现镜像排列
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = onDismiss, onDismissRequest = onDismiss,
sheetState = sheetState, sheetState = sheetState,
@@ -58,59 +58,42 @@ fun CreateBottomSheet(
.padding(top = 24.dp, bottom = 24.dp), .padding(top = 24.dp, bottom = 24.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
Row( // 标题
modifier = Modifier.fillMaxWidth(), Text(
verticalAlignment = Alignment.CenterVertically text = stringResource(R.string.create_title),
) { fontSize = 18.sp,
Image( fontWeight = FontWeight.Bold,
painter = painterResource(R.mipmap.h_cj_rw_icon), color = appColors.text,
contentDescription = null, modifier = Modifier.padding(bottom = 32.dp)
modifier = Modifier )
.padding(start = 16.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(appColors.text)
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = stringResource(R.string.create_title),
fontSize = 18.sp,
fontWeight = FontWeight.Bold,
color = appColors.text,
modifier = Modifier
.padding(end = 3.dp)
)
Image(
painter = painterResource(R.mipmap.h_cj_x_img),
contentDescription = null,
modifier = Modifier
.padding(end = 18.dp),
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(appColors.text)
)
}
Spacer(modifier = Modifier.height(30.dp))
// 三个创建选项 // 三个创建选项
Row( Row(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly horizontalArrangement = Arrangement.SpaceEvenly
) { ) {
// 群聊选项
CreateOption(
icon = R.drawable.ic_create_group_chat,
label = stringResource(R.string.create_group_chat_option),
onClick = onGroupChatClick
)
// 动态选项 // 动态选项
CreateOption( CreateOption(
icon = R.drawable.ic_create_monent, icon = R.drawable.ic_create_monent,
label = stringResource(R.string.create_moment), label = stringResource(R.string.create_moment),
onClick = onMomentClick onClick = onMomentClick
) )
// 群聊选项
CreateOption(
icon = R.mipmap.icons_circle_camera,
label = stringResource(R.string.create_group_chat_option),
onClick = onGroupChatClick
)
// AI选项 // AI选项
CreateOption( CreateOption(
icon = R.mipmap.icons_circle_ai, icon = R.drawable.ic_create_ai,
label = stringResource(R.string.create_ai), label = stringResource(R.string.create_ai),
onClick = onAiClick onClick = onAiClick
) )
} }
Spacer(modifier = Modifier.height(40.dp)) Spacer(modifier = Modifier.height(40.dp))

View File

@@ -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
) )
} }

View File

@@ -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",

View File

@@ -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
@@ -74,29 +75,11 @@ import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.ResourceCleanupManager import com.aiosman.ravenow.utils.ResourceCleanupManager
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.verticalScroll
import androidx.compose.foundation.lazy.items import androidx.compose.ui.zIndex
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.lazy.grid.GridCells
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
// 检测是否接近列表底部的扩展函数 @OptIn(ExperimentalFoundationApi::class)
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
val layoutInfo = this.layoutInfo
val totalItemsCount = layoutInfo.totalItemsCount
val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
return lastVisibleItemIndex >= (totalItemsCount - buffer)
}
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
fun Agent() { fun Agent() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -110,12 +93,25 @@ fun Agent() {
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = viewModel()
val scrollState = rememberScrollState()
// 确保推荐Agent数据已加载 // 确保推荐Agent数据已加载
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.ensureDataLoaded() viewModel.ensureDataLoaded()
} }
// 监听滚动状态,实现自动加载更多
LaunchedEffect(scrollState) {
snapshotFlow { scrollState.value }
.collect { scrollValue ->
val maxScroll = scrollState.maxValue
if (scrollValue >= maxScroll - 100 && !viewModel.isLoading) {
// 滚动到接近底部时加载更多网格数据
viewModel.loadMoreGridAgentData()
}
}
}
// 防抖状态 // 防抖状态
var lastClickTime by remember { mutableStateOf(0L) } var lastClickTime by remember { mutableStateOf(0L) }
@@ -127,275 +123,143 @@ fun Agent() {
} }
} }
val agentItems = viewModel.agentItems Box(
var selectedTabIndex by remember { mutableStateOf(0) } modifier = Modifier.fillMaxSize()
) {
// 无限滚动状态 // 固定顶部搜索条
val listState = rememberLazyListState() Box(
modifier = Modifier
// 创建一个可观察的滚动到底部状态 .fillMaxWidth()
val isScrolledToEnd by remember { .background(AppColors.background)
derivedStateOf { .zIndex(999.0f)
listState.isScrolledToEnd() .height(44.dp + statusBarPaddingValues.calculateTopPadding())
} ) {
} Row(
// 检测滚动到底部并加载更多数据
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
.height(44.dp)
.padding(top =9.dp,bottom=9.dp)
.wrapContentSize(),
// colorFilter = ColorFilter.tint(AppColors.text)
)
},
actions = {
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search",
modifier = Modifier
.size(44.dp)
.padding(top = 9.dp,bottom=9.dp)
.noRippleClickable {
navController.navigate(NavigationRoute.Search.route)
},
colorFilter = ColorFilter.tint(AppColors.text)
)
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = AppColors.background
),
windowInsets = WindowInsets(0, 0, 0, 0),
modifier = Modifier modifier = Modifier
.height(44.dp + statusBarPaddingValues.calculateTopPadding()) .fillMaxWidth()
.padding(top = statusBarPaddingValues.calculateTopPadding()) .fillMaxHeight().padding(top = 32.dp, start = 16.dp, end = 16.dp),
) horizontalArrangement = Arrangement.Start,
}, verticalAlignment = Alignment.CenterVertically
containerColor = AppColors.background, ) {
contentWindowInsets = WindowInsets(0, 0, 0, 0) Text(
) { paddingValues -> text = "Rave AI",
LazyColumn( fontSize = 20.sp,
state = listState, fontWeight = FontWeight.W900,
color = AppColors.text,
modifier = Modifier
.align(Alignment.CenterVertically)
)
Spacer(modifier = Modifier.weight(1f))
Image(
painter = painterResource(id = R.drawable.rider_pro_nav_search),
contentDescription = "search",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
navController.navigate(NavigationRoute.Search.route)
},
colorFilter = ColorFilter.tint(AppColors.text)
)
}
}
// 可滚动的内容区域
Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(paddingValues) .verticalScroll(scrollState)
.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
) ),
horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
// 类别标签页 - 吸顶 Column(
stickyHeader(key = "category_tabs") { modifier = Modifier
Column( .fillMaxWidth()
modifier = Modifier .padding(vertical = 8.dp)
.fillMaxWidth()
.background(AppColors.background) ) {
.padding(top = 4.dp, bottom = 8.dp) // 使用 ViewModel 中的选中状态
) { 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 = {
selectedTabIndex = index + 1 viewModel.selectCategory(index)
viewModel.loadAgentsByCategory(category.id)
} }
) )
} }
item { if (index < viewModel.categories.size - 1) {
TabSpacer() item {
TabSpacer()
}
} }
} }
} }
} }
}
// 推荐内容区域 // 显示当前选中分类的 Agent 数据
item { AgentViewPagerSection(agentItems = viewModel.topAgentItems, viewModel)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp)
) {
when {
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)
}
}
}
} }
// 热门聊天室 // 推荐聊天房间
stickyHeader(key = "hot_rooms_header") { ChatRoomsSection(
Row( chatRooms = viewModel.chatRooms,
modifier = Modifier navController = LocalNavController.current
.fillMaxWidth() )
.background(AppColors.background) Spacer(modifier = Modifier.height(20.dp))
.padding(top = 8.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Start, Row(
verticalAlignment = Alignment.CenterVertically modifier = Modifier
) { .fillMaxWidth()
Image( .wrapContentHeight(),
painter = painterResource(R.mipmap.rider_pro_hot_room), // center the tabs
contentDescription = "chat room", horizontalArrangement = Arrangement.Start,
modifier = Modifier.size(28.dp) verticalAlignment = Alignment.Bottom
) {
Image(
painter = painterResource(R.mipmap.rider_pro_agent2),
contentDescription = "agent",
modifier = Modifier.size(28.dp),
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = stringResource(R.string.hot_rooms), 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
) )
}
}
// 热门聊天室网格
items(viewModel.chatRooms.chunked(2)) { rowRooms ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = 12.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
rowRooms.forEach { chatRoom ->
ChatRoomCard(
chatRoom = chatRoom,
navController = LocalNavController.current,
modifier = Modifier.weight(1f)
)
}
if (rowRooms.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
} }
Spacer(modifier = Modifier.height(20.dp))
item { Spacer(modifier = Modifier.height(20.dp)) } // Agent两列网格布局
AgentGridLayout(
// "发现更多" 标题 - 吸顶 agentItems = viewModel.gridAgentItems,
stickyHeader(key = "discover_more") { viewModel = viewModel,
Row( navController = LocalNavController.current
modifier = Modifier )
.fillMaxWidth()
.background(AppColors.background)
.padding(top = 8.dp, bottom = 12.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
Image(
painter = painterResource(R.mipmap.bars_x_buttons_home_n_copy_2),
contentDescription = "agent",
modifier = Modifier.size(28.dp)
)
Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text(
text = stringResource(R.string.agent_find),
fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
color = AppColors.text
)
}
}
// Agent网格 - 使用行式布局
items(
items = agentItems.chunked(2),
key = { row -> row.firstOrNull()?.openId ?: "" }
) { rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp),
horizontalArrangement = Arrangement.spacedBy(16.dp)
) {
rowItems.forEach { agentItem ->
Box(
modifier = Modifier.weight(1f)
) {
AgentCardSquare(
agentItem = agentItem,
viewModel = viewModel,
navController = LocalNavController.current
)
}
}
// 如果这一行只有一个item添加一个空的占位符
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
}
}
// 加载更多指示器
if (viewModel.isLoadingMore) {
item {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
horizontalArrangement = Arrangement.Center
) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.text,
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
androidx.compose.material3.Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
)
}
}
}
} }
} }
} }
@@ -469,7 +333,7 @@ fun AgentCardSquare(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(cardHeight) .height(cardHeight)
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp)) .background(AppColors.secondaryBackground, RoundedCornerShape(12.dp)) // 主题背景
.clickable { .clickable {
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) { if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
viewModel.goToProfile(agentItem.openId, navController) viewModel.goToProfile(agentItem.openId, navController)
@@ -479,19 +343,15 @@ fun AgentCardSquare(
}, },
contentAlignment = Alignment.TopCenter contentAlignment = Alignment.TopCenter
) { ) {
// 头像,位于方块上方居中,部分悬于方块外部
Box( Box(
modifier = Modifier modifier = Modifier
.offset(y = 4.dp) .offset(y = -avatarSize / 2)
.size(avatarSize) .size(avatarSize)
.background(AppColors.background, RoundedCornerShape(avatarSize / 2)) .background(AppColors.background, RoundedCornerShape(avatarSize / 2))
.clip(RoundedCornerShape(avatarSize / 2)), .clip(RoundedCornerShape(avatarSize / 2)),
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
Image(
painter = painterResource(R.mipmap.group_copy),
contentDescription = "默认头像",
modifier = Modifier.size(avatarSize),
)
if (agentItem.avatar.isNotEmpty()) { if (agentItem.avatar.isNotEmpty()) {
CustomAsyncImage( CustomAsyncImage(
imageUrl = agentItem.avatar, imageUrl = agentItem.avatar,
@@ -501,6 +361,13 @@ fun AgentCardSquare(
.clip(RoundedCornerShape(avatarSize / 2)), .clip(RoundedCornerShape(avatarSize / 2)),
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(avatarSize / 2),
colorFilter = ColorFilter.tint(AppColors.secondaryText)
)
} }
} }
@@ -508,12 +375,12 @@ fun AgentCardSquare(
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(top = 4.dp + avatarSize + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间 .padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = agentItem.title, text = agentItem.title,
fontSize = 14.sp, fontSize = 16.sp,
fontWeight = androidx.compose.ui.text.font.FontWeight.W600, fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
color = AppColors.text, color = AppColors.text,
maxLines = 1, maxLines = 1,
@@ -524,7 +391,7 @@ fun AgentCardSquare(
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = agentItem.desc, text = agentItem.desc,
fontSize = 12.sp, fontSize = 14.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
maxLines = 2, maxLines = 2,
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis, overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
@@ -532,21 +399,16 @@ 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(80.dp)
.height(32.dp) .height(32.dp)
.background( .background(
color = AppColors.text, 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) {
@@ -568,16 +430,17 @@ fun AgentCardSquare(
) { ) {
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = stringResource(R.string.chat), text = stringResource(R.string.chat),
fontSize = 15.sp, fontSize = 12.sp,
color = AppColors.background, color = AppColors.text,
fontWeight = androidx.compose.ui.text.font.FontWeight.W500 fontWeight = androidx.compose.ui.text.font.FontWeight.W500
) )
} }
} }
} }
@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 +454,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 +476,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 +513,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 +527,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 +541,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 +557,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 +567,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 +576,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 +621,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 +651,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
} }
} }
} }
@Composable @Composable
fun ChatRoomsSection( fun ChatRoomsSection(
chatRooms: List<ChatRoom>, chatRooms: List<ChatRoom>,
@@ -800,23 +670,25 @@ fun ChatRoomsSection(
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
Image( Image(
painter = painterResource(R.mipmap.rider_pro_hot_room), painter = painterResource(R.mipmap.rider_pro_agent2),
contentDescription = "chat room", contentDescription = "chat room",
modifier = Modifier.size(28.dp) modifier = Modifier.size(28.dp)
) )
Spacer(modifier = Modifier.width(4.dp)) Spacer(modifier = Modifier.width(4.dp))
androidx.compose.material3.Text( androidx.compose.material3.Text(
text = stringResource(R.string.hot_rooms), text = "群聊",
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
) )
} }
// 3行宫格布局
Column( Column(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
chatRooms.chunked(2).forEach { rowRooms -> // 将聊天房间按3个一组分组
chatRooms.chunked(3).forEach { rowRooms ->
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -830,6 +702,10 @@ fun ChatRoomsSection(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) )
} }
// 如果这一行不足3个添加空白占位
repeat(3 - rowRooms.size) {
Spacer(modifier = Modifier.weight(1f))
}
} }
} }
} }
@@ -843,7 +719,7 @@ fun ChatRoomCard(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val cardSize = 180.dp val cardSize = 100.dp
val viewModel: AgentViewModel = viewModel() val viewModel: AgentViewModel = viewModel()
val context = LocalContext.current val context = LocalContext.current
@@ -891,7 +767,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 +797,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 +816,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
)
}
}
} }
} }

View File

@@ -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,124 +55,131 @@ 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作为keynull表示推荐列表 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 {
// 如果不是强制刷新且不是加载更多,检查缓存 isLoading = true
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
// 重置分页状态
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 {
// 首次加载或刷新:替换整个列表
agentItems = newAgentItems
currentPage = 1
}
// 检查是否还有更多数据
hasMoreData = agents.size >= pageSize
// 更新缓存
agentCache[categoryId] = AgentCacheData(
items = agentItems,
currentPage = currentPage,
hasMoreData = hasMoreData
)
println("更新缓存分类ID: $categoryId, 数据数量: ${agentItems.size}")
} else { } else {
errorMessage = "获取Agent数据失败: ${response.code()}" errorMessage = "获取顶部Agent数据失败: ${response.code()}"
} }
} catch (e: Exception) { } catch (e: Exception) {
errorMessage = "网络请求失败: ${e.message}" errorMessage = "网络请求失败: ${e.message}"
} finally { } finally {
if (isLoadMore) { isLoading = false
isLoadingMore = 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 { } else {
isLoading = false // 特定分类,使用 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
} }
} }
} }
@@ -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,140 @@ 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) {
// 推荐分类或无效分类,加载所有 Agent
apiClient.getAgent(
page = nextPage,
pageSize = 20,
withWorkflow = "1",
random = "true"
)
} else {
// 特定分类,使用 categoryName 参数
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 +378,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 +480,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
)
}
}
}

View File

@@ -78,7 +78,7 @@ import com.aiosman.ravenow.ui.like.LikeNoticeViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.ui.index.tabs.message.tab.AllChatListScreen
/** /**
* 消息列表界面 * 消息列表界面
@@ -95,7 +95,7 @@ fun NotificationsScreen() {
val navController = LocalNavController.current val navController = LocalNavController.current
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val context = LocalContext.current val context = LocalContext.current
var pagerState = rememberPagerState (pageCount = { 4 }) var pagerState = rememberPagerState (pageCount = { 3 })
var scope = rememberCoroutineScope() var scope = rememberCoroutineScope()
val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = { val state = rememberPullRefreshState(MessageListViewModel.isLoading, onRefresh = {
MessageListViewModel.viewModelScope.launch { MessageListViewModel.viewModelScope.launch {
@@ -177,7 +177,7 @@ fun NotificationsScreen() {
modifier = Modifier modifier = Modifier
.size(24.dp) .size(24.dp)
.noRippleClickable { .noRippleClickable {
navController.navigate(NavigationRoute.NotificationScreen.route) // TODO: 实现通知功能
}, },
colorFilter = ColorFilter.tint(AppColors.text) colorFilter = ColorFilter.tint(AppColors.text)
) )
@@ -324,7 +324,7 @@ fun NotificationsScreen() {
Box { Box {
TabItem( TabItem(
text = stringResource(R.string.chat_all), text = stringResource(R.string.chat_ai),
isSelected = pagerState.currentPage == 0, isSelected = pagerState.currentPage == 0,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
@@ -335,38 +335,6 @@ fun NotificationsScreen() {
} }
) )
// 全部未读消息红点
val totalUnreadCount = AgentChatListViewModel.totalUnreadCount +
GroupChatListViewModel.totalUnreadCount +
FriendChatListViewModel.totalUnreadCount
if (totalUnreadCount > 0) {
Box(
modifier = Modifier
.size(8.dp)
.background(
color = Color(0xFFFF3B30),
shape = CircleShape
)
.align(Alignment.TopEnd)
.offset(x = 8.dp, y = (-4).dp)
)
}
}
TabSpacer()
Box {
TabItem(
text = stringResource(R.string.chat_ai),
isSelected = pagerState.currentPage == 1,
onClick = {
tabDebouncer {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
}
)
// 智能体未读消息红点 // 智能体未读消息红点
if (AgentChatListViewModel.totalUnreadCount > 0) { if (AgentChatListViewModel.totalUnreadCount > 0) {
Box( Box(
@@ -385,11 +353,11 @@ fun NotificationsScreen() {
Box { Box {
TabItem( TabItem(
text = stringResource(R.string.chat_group), text = stringResource(R.string.chat_group),
isSelected = pagerState.currentPage == 2, isSelected = pagerState.currentPage == 1,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { scope.launch {
pagerState.animateScrollToPage(2) pagerState.animateScrollToPage(1)
} }
} }
} }
@@ -410,15 +378,14 @@ fun NotificationsScreen() {
} }
} }
TabSpacer() TabSpacer()
Box { Box {
TabItem( TabItem(
text = stringResource(R.string.chat_friend), text = stringResource(R.string.chat_friend),
isSelected = pagerState.currentPage == 3, isSelected = pagerState.currentPage == 2,
onClick = { onClick = {
tabDebouncer { tabDebouncer {
scope.launch { scope.launch {
pagerState.animateScrollToPage(3) pagerState.animateScrollToPage(2)
} }
} }
} }
@@ -447,17 +414,14 @@ fun NotificationsScreen() {
) { ) {
when (it) { when (it) {
0 -> { 0 -> {
AllChatListScreen()
}
1 -> {
AgentChatListScreen() AgentChatListScreen()
} }
2 -> { 1 -> {
GroupChatListScreen() GroupChatListScreen()
} }
3 -> { 2 -> {
FriendChatListScreen() FriendChatListScreen()
} }
} }

View File

@@ -91,14 +91,11 @@ fun AgentChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
Image( Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_znt_qs_as_img id = if(AppState.darkMode) R.mipmap.qs_znt_qs_as_img
else R.mipmap.invalid_name_5), else R.mipmap.qs_znt_qs_img),
contentDescription = "null data", contentDescription = "null data",
modifier = Modifier modifier = Modifier
.size(181.dp) .size(181.dp)
@@ -117,35 +114,6 @@ fun AgentChatListScreen() {
fontSize = 14.sp fontSize = 14.sp
) )
} }
else {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
AgentChatListViewModel.refreshPager(context = context)
}
)
}
}
} else { } else {
LazyColumn( LazyColumn(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()

View File

@@ -1,390 +0,0 @@
package com.aiosman.ravenow.ui.index.tabs.message.tab
import android.widget.Toast
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
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.draw.clip
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
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.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.ui.text.font.FontFamily
data class CombinedConversation(
val type: String, // "agent", "group", or "friend"
val agentConversation: AgentConversation? = null,
val groupConversation: GroupConversation? = null,
val friendConversation: FriendConversation? = null
) {
val id: String
get() = when (type) {
"agent" -> "agent_${agentConversation?.id ?: 0}"
"group" -> "group_${groupConversation?.id ?: 0}"
"friend" -> "friend_${friendConversation?.id ?: 0}"
else -> ""
}
val avatar: String
get() = when (type) {
"agent" -> agentConversation?.avatar ?: ""
"group" -> groupConversation?.avatar ?: ""
"friend" -> friendConversation?.avatar ?: ""
else -> ""
}
val name: String
get() = when (type) {
"agent" -> agentConversation?.nickname ?: ""
"group" -> groupConversation?.groupName ?: ""
"friend" -> friendConversation?.nickname ?: ""
else -> ""
}
val lastMessageTime: String
get() = when (type) {
"agent" -> agentConversation?.lastMessageTime ?: ""
"group" -> groupConversation?.lastMessageTime ?: ""
"friend" -> friendConversation?.lastMessageTime ?: ""
else -> ""
}
val displayText: String
get() = when (type) {
"agent" -> agentConversation?.displayText ?: ""
"group" -> groupConversation?.displayText ?: ""
"friend" -> friendConversation?.displayText ?: ""
else -> ""
}
val unreadCount: Int
get() = when (type) {
"agent" -> agentConversation?.unreadCount ?: 0
"group" -> groupConversation?.unreadCount ?: 0
"friend" -> friendConversation?.unreadCount ?: 0
else -> 0
}
val isSelf: Boolean
get() = when (type) {
"agent" -> agentConversation?.isSelf ?: false
"group" -> groupConversation?.isSelf ?: false
"friend" -> friendConversation?.isSelf ?: false
else -> false
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun AllChatListScreen() {
val context = LocalContext.current
val navController = LocalNavController.current
val AppColors = LocalAppTheme.current
var allConversations by remember { mutableStateOf<List<CombinedConversation>>(emptyList()) }
var refreshing by remember { mutableStateOf(false) }
var isLoading by remember { mutableStateOf(false) }
var error by remember { mutableStateOf<String?>(null) }
val state = rememberPullRefreshState(
refreshing = refreshing,
onRefresh = {
refreshing = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
refreshing = false
},
onError = { errorMsg ->
error = errorMsg
refreshing = false
}
)
}
)
LaunchedEffect(Unit) {
isLoading = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
isLoading = false
},
onError = { errorMsg ->
error = errorMsg
isLoading = false
}
)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(AppColors.background)
) {
Box(
modifier = Modifier
.fillMaxSize()
.pullRefresh(state)
) {
if (allConversations.isEmpty() && !isLoading) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
else R.mipmap.invalid_name_2),
contentDescription = "null data",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_empty_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_empty_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
} else {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
isLoading = true
refreshAllData(context,
onSuccess = { conversations ->
allConversations = conversations
isLoading = false
},
onError = { errorMsg ->
error = errorMsg
isLoading = false
}
)
}
)
}
}
} else {
LazyColumn(
modifier = Modifier.fillMaxSize()
) {
itemsIndexed(
items = allConversations,
key = { _, item -> item.id }
) { index, item ->
when (item.type) {
"agent" -> {
item.agentConversation?.let { agent ->
AgentChatItem(
conversation = agent,
onUserAvatarClick = { conv ->
AgentChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
AgentChatListViewModel.createSingleChat(conv.trtcUserId)
AgentChatListViewModel.goToChatAi(conv.trtcUserId, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
"group" -> {
item.groupConversation?.let { group ->
GroupChatItem(
conversation = group,
onGroupAvatarClick = { conv ->
GroupChatListViewModel.goToGroupDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
GroupChatListViewModel.goToChat(conv, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
"friend" -> {
item.friendConversation?.let { friend ->
FriendChatItem(
conversation = friend,
onUserAvatarClick = { conv ->
FriendChatListViewModel.goToUserDetail(conv, navController)
},
onChatClick = { conv ->
if (NetworkUtils.isNetworkAvailable(context)) {
FriendChatListViewModel.goToChat(conv, navController)
} else {
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
}
}
)
}
}
}
// 分隔线
// if (index < allConversations.size - 1) {
// HorizontalDivider(
// modifier = Modifier.padding(horizontal = 24.dp),
// color = AppColors.divider
// )
// }
}
if (isLoading && allConversations.isNotEmpty()) {
item {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
)
}
}
}
}
}
PullRefreshIndicator(
refreshing = refreshing,
state = state,
modifier = Modifier.align(Alignment.TopCenter)
)
}
error?.let { errorMsg ->
Box(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = errorMsg,
color = AppColors.error,
fontSize = 14.sp
)
}
}
}
}
fun refreshAllData(
context: android.content.Context,
onSuccess: (List<CombinedConversation>) -> Unit,
onError: (String) -> Unit
) {
try {
// 同时刷新所有类型的数据
AgentChatListViewModel.refreshPager(context = context)
GroupChatListViewModel.refreshPager(context = context)
FriendChatListViewModel.refreshPager(context = context)
val combinedList = mutableListOf<CombinedConversation>()
AgentChatListViewModel.agentChatList.forEach { agent ->
combinedList.add(CombinedConversation(type = "agent", agentConversation = agent))
}
GroupChatListViewModel.groupChatList.forEach { group ->
combinedList.add(CombinedConversation(type = "group", groupConversation = group))
}
FriendChatListViewModel.friendChatList.forEach { friend ->
val isDuplicate = combinedList.any {//判断重复
it.type == "agent" && it.agentConversation?.trtcUserId == friend.trtcUserId
}
if (!isDuplicate) {
combinedList.add(CombinedConversation(type = "friend", friendConversation = friend))
}
}
// 按最后消息时间排序
val sortedList = combinedList.sortedByDescending {
it.lastMessageTime
}
onSuccess(sortedList)
} catch (e: Exception) {
onError("刷新数据失败: ${e.message}")
}
}

View File

@@ -33,14 +33,8 @@ import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.search.ReloadButton
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 androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.graphics.Brush
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.foundation.layout.PaddingValues
@OptIn(ExperimentalMaterialApi::class) @OptIn(ExperimentalMaterialApi::class)
@Composable @Composable
@@ -79,14 +73,12 @@ fun FriendChatListScreen() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
//verticalArrangement = Arrangement.Center //verticalArrangement = Arrangement.Center
) { ) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
Image( Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
else R.mipmap.invalid_name_2), else R.mipmap.qs_py_qs_img),
contentDescription = "null data", contentDescription = "null data",
modifier = Modifier modifier = Modifier
.size(181.dp) .size(181.dp)
@@ -104,34 +96,6 @@ fun FriendChatListScreen() {
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp
) )
}else {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
FriendChatListViewModel.refreshPager(pullRefresh = true, context = context)
}
)
}
} }
} else { } else {
LazyColumn( LazyColumn(
@@ -302,43 +266,4 @@ fun FriendChatItem(
} }
} }
} }
@Composable
fun ReloadButton(
onClick: () -> Unit
) {
val gradientBrush = Brush.linearGradient(
colors = listOf(
Color(0xFF7c45ed),
Color(0xFF7c68ef),
Color(0xFF7bd8f8)
)
)
Button(
onClick = onClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 120.dp)
.height(48.dp),
shape = RoundedCornerShape(30.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Transparent
),
contentPadding = PaddingValues(0.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(gradientBrush),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.Reload),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
textAlign = TextAlign.Center
)
}
}
}

View File

@@ -71,14 +71,11 @@ fun GroupChatListScreen() {
.padding(16.dp), .padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
) { ) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
Spacer(modifier = Modifier.height(39.dp)) Spacer(modifier = Modifier.height(39.dp))
Image( Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_ql_qs_as_img id = if(AppState.darkMode) R.mipmap.qs_ql_qs_as_img
else R.mipmap.invalid_name_12), else R.mipmap.qs_ql_qs_img),
contentDescription = "null data", contentDescription = "null data",
modifier = Modifier modifier = Modifier
.size(181.dp) .size(181.dp)
@@ -96,34 +93,6 @@ fun GroupChatListScreen() {
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontSize = 14.sp fontSize = 14.sp
) )
}else {
Spacer(modifier = Modifier.height(39.dp))
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier
.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.secondaryText,
fontSize = 14.sp
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
GroupChatListViewModel.refreshPager(context = context)
}
)
}
} }
} else { } else {
LazyColumn( LazyColumn(

View File

@@ -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)

View File

@@ -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个tabDynamic、Hot // 游客模式下不显示timeline只显示3个tabExplore、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
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.CenterVertically verticalAlignment = Alignment.CenterVertically
) { ) {
// 可滚动的标签页行 //原探索//
Row( // Column(
modifier = Modifier // modifier = Modifier
.weight(1f) // .noRippleClickable {
.horizontalScroll(rememberScrollState()), // scope.launch {
horizontalArrangement = Arrangement.Start, // pagerState.animateScrollToPage(0)
verticalAlignment = Alignment.CenterVertically // }
) { // }.padding(start = 16.dp),
val tabDebouncer = rememberDebouncer() // verticalArrangement = Arrangement.Center,
// horizontalAlignment = Alignment.CenterHorizontally
// 推荐标签 //
UnderlineTabItem( // ) {
text = stringResource(R.string.tab_recommend), // Text(
isSelected = pagerState.currentPage == 0, // text = stringResource(R.string.index_worldwide),
onClick = { // fontSize = if (pagerState.currentPage == 0)18.sp else 16.sp,
tabDebouncer { // color = if (pagerState.currentPage == 0) AppColors.text else AppColors.nonActiveText,
scope.launch { // fontWeight = FontWeight.W600)
pagerState.animateScrollToPage(0) // Spacer(modifier = Modifier.height(4.dp))
} //
} // Image(
} // painter = painterResource(
) // if (pagerState.currentPage == 0) R.mipmap.tab_indicator_selected
// else R.drawable.tab_indicator_unselected
// ),
// 短视频标签 // contentDescription = "tab indicator",
UnderlineTabItem( // modifier = Modifier
text = stringResource(R.string.tab_short_video), // .width(34.dp)
isSelected = pagerState.currentPage == 1, // .height(4.dp)
onClick = { // )
tabDebouncer { //
scope.launch { // }
pagerState.animateScrollToPage(1) // Spacer(modifier = Modifier.width(16.dp))
}
}
}
)
// 动态标签
UnderlineTabItem(
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)
) { ) {
when (it) { if (AppStore.isGuest) {
0 -> { // 游客模式Dynamic(0), Hot(1)
// 推荐页面 when (it) {
NewsScreen() 0 -> {
} Dynamic()
1 -> { }
// 短视频页面 1 -> {
}
2 -> {
// 动态页面 - 暂时显示时间线内容
Dynamic()
}
3 -> {
// 关注页面 (仅非游客用户) 或 热门页面 (游客用户)
if (AppStore.isGuest) {
HotMomentsList() HotMomentsList()
} else {
TimelineMomentsList()
} }
} }
4 -> { } else {
// 热门页面 (仅非游客用户) // 正常用户Dynamic(0), Timeline(1), Hot(2)
HotMomentsList() when (it) {
} 0 -> {
5 -> { Dynamic()
// 新闻页面 }
NewsScreen() 1 -> {
TimelineMomentsList()
}
2 -> {
HotMomentsList()
}
} }
} }
} }

View File

@@ -168,7 +168,6 @@ fun Dynamic() {
) )
} }
} }
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
} }
} }
} }

View File

@@ -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
) )*/
} }
// 底部:标题和描述 // 底部:标题和描述

View File

@@ -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 ->

View File

@@ -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 {
@@ -172,68 +147,14 @@ fun DiscoverView() {
} }
} }
) { ) {
Column( CustomAsyncImage(
modifier = Modifier.fillMaxSize().background(AppColors.secondaryBackground, RoundedCornerShape(12.dp)) imageUrl = momentItem.images[0].thumbnail,
) { contentDescription = "",
CustomAsyncImage( modifier = Modifier
imageUrl = momentItem.images[0].thumbnail, .fillMaxSize(),
contentDescription = "", context = context,
modifier = Modifier showShimmer = true
.fillMaxWidth() )
.height(baseHeight)
.clip(RoundedCornerShape(
topStart = 12.dp,
topEnd = 12.dp,
bottomStart = 0.dp,
bottomEnd = 0.dp)),
context = context,
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

View File

@@ -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()
)
}
}
}
}
}

View File

@@ -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))
}
}
}

View File

@@ -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
)
}
}
}

View File

@@ -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
)
}
}

View File

@@ -43,8 +43,6 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.MomentCard import com.aiosman.ravenow.ui.composables.MomentCard
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
import androidx.compose.ui.platform.LocalContext
/** /**
* 动态列表 * 动态列表
@@ -78,49 +76,7 @@ fun TimelineMomentsList() {
model.loadMore() model.loadMore()
} }
} }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) if (moments.isEmpty()) {
if (!isNetworkAvailable) {
Box(
modifier = Modifier
.fillMaxSize()
.padding(top = 188.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier.fillMaxWidth()
) {
val exploreDebouncer = rememberDebouncer()
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(140.dp)
)
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.size(16.dp))
ExploreButton(
onClick = {
exploreDebouncer {
/* TODO: 添加点击事件处理 */
} }
)
}
}
} else if (moments.isEmpty()) {
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -135,7 +91,7 @@ fun TimelineMomentsList() {
Image( Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img id = if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img
else R.mipmap.invalid_name_4), else R.mipmap.qst_gz_qs_img),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(140.dp) modifier = Modifier.size(140.dp)
) )
@@ -235,9 +191,9 @@ fun ExploreButton(
) { ) {
val gradientBrush = Brush.linearGradient( val gradientBrush = Brush.linearGradient(
colors = listOf( colors = listOf(
Color(0xFF7c45ed), Color(0xFFee2a33),
Color(0xFF7c68ef), Color(0xFFd80264),
Color(0xFF7bd8f8) Color(0xFF664c92)
) )
) )

View File

@@ -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
)
}
}
}
}

View File

@@ -98,8 +98,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.delay 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.ui.res.stringResource
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class) @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@Composable @Composable
@@ -121,6 +119,7 @@ fun ProfileV3(
postCount: Int? = null, // 新增参数用于传递帖子总数 postCount: Int? = null, // 新增参数用于传递帖子总数
) { ) {
val model = MyProfileViewModel val model = MyProfileViewModel
val state = rememberCollapsingToolbarScaffoldState()
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 }) val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
val enabled by remember { mutableStateOf(true) } val enabled by remember { mutableStateOf(true) }
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues() val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
@@ -129,8 +128,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
@@ -154,19 +151,6 @@ fun ProfileV3(
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()
val listState = rememberLazyListState() val listState = rememberLazyListState()
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val scrollState = rememberScrollState()
val toolbarAlpha by remember {
derivedStateOf {
if (!isSelf) {
1f
} else {
val maxScroll = 500f // 最大滚动距离,可调整
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
progress
}
}
}
// observe list scrolling // observe list scrolling
val reachedListBottom by remember { val reachedListBottom by remember {
@@ -217,6 +201,8 @@ fun ProfileV3(
} }
} }
fun switchTheme() { fun switchTheme() {
// delay // delay
scope.launch { scope.launch {
@@ -295,151 +281,303 @@ fun ProfileV3(
Box( Box(
modifier = Modifier.pullRefresh(refreshState) modifier = Modifier.pullRefresh(refreshState)
) { ) {
Column( CollapsingToolbarScaffold(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(scrollState) .background(AppColors.profileBackground),
.background(AppColors.profileBackground) state = state,
) { scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
// Banner toolbarScrollable = true,
val banner = profile?.banner enabled = enabled,
if (banner != null) { toolbar = { toolbarScrollState ->
Box( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(bannerHeight.dp) .height(miniToolbarHeight.dp)
// 保持在最低高度和当前高度之间
.background(AppColors.profileBackground) .background(AppColors.profileBackground)
) { ) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(bannerHeight.dp - 24.dp)
.let {
if (isSelf && isMain) {
it.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(this)
}
}
} else {
it
}
}
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(
bottomStart = 32.dp,
bottomEnd = 32.dp
),
)
) {
CustomAsyncImage(
LocalContext.current,
banner,
modifier = Modifier.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
} }
} else { // header
Spacer(modifier = Modifier.height(100.dp))
}
// 用户信息
Box(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.profileBackground)
.padding(horizontal = 16.dp)
) {
profile?.let {
UserItem(
accountProfileEntity = it,
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size
)
}
}
Spacer(modifier = Modifier.height(20.dp))
// 操作按钮
profile?.let {
Box( Box(
modifier = Modifier modifier = Modifier
.parallax(0.5f)
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .height(if (isAiAccount) 600.dp else 700.dp)
.background(AppColors.profileBackground)
.verticalScroll(toolbarScrollState)
) { ) {
if (isSelf) { Box(
SelfProfileAction( modifier = Modifier.fillMaxSize()
onEditProfile = { ) {
navController.navigate(NavigationRoute.AccountEdit.route) Column(
}, modifier = Modifier
onPremiumClick = { .fillMaxWidth()
navController.navigate(NavigationRoute.VipSelPage.route) .graphicsLayer {
} alpha = state.toolbarState.progress
)
} else {
if (it.id != AppState.UserId) {
OtherProfileAction(
it,
onFollow = {
onFollowClick()
},
onChat = {
onChatClick()
} }
) ) {
// banner
Box(
modifier = Modifier
.fillMaxWidth()
.height(bannerHeight.dp)
.background(AppColors.profileBackground)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(bannerHeight.dp - 24.dp)
.let {
if (isSelf&&isMain) {
it.noRippleClickable {
Intent(Intent.ACTION_PICK).apply {
type = "image/*"
pickBannerImageLauncher.launch(
this
)
}
}
} else {
it
}
}
.shadow(
elevation = 6.dp,
shape = RoundedCornerShape(
bottomStart = 32.dp,
bottomEnd = 32.dp
),
)
) {
val banner = profile?.banner
if (banner != null) {
CustomAsyncImage(
LocalContext.current,
banner,
modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
} else {
Image(
painter = painterResource(id = R.drawable.rave_now_profile_backgrount_demo_1),
modifier = Modifier
.fillMaxSize(),
contentDescription = "",
contentScale = ContentScale.Crop
)
}
}
}
Box(
modifier = Modifier
.fillMaxWidth()
.background(AppColors.profileBackground)
) {
// user info
Column(
modifier = Modifier
.fillMaxWidth()
) {
// Spacer(modifier = Modifier.height(16.dp))
// 个人信息
Box(
modifier = Modifier.padding(horizontal = 16.dp)
) {
profile?.let {
UserItem(
accountProfileEntity = it,
postCount = postCount ?: if (isSelf) MyProfileViewModel.momentLoader.total else moments.size
)
}
}
Spacer(modifier = Modifier.height(20.dp))
profile?.let {
Box(
modifier = Modifier.padding(horizontal = 16.dp)
) {
if (isSelf) {
SelfProfileAction(
onEditProfile = {
navController.navigate(
NavigationRoute.AccountEdit.route
)
},
onPremiumClick = {
navController.navigate(NavigationRoute.VipSelPage.route)
}
)
} else {
if (it.id != AppState.UserId) {
OtherProfileAction(
it,
onFollow = {
onFollowClick()
},
onChat = {
onChatClick()
}
)
}
}
}
}
// 添加用户智能体行(智能体用户不显示)
if (!isAiAccount) {
UserAgentsRow(
userId = if (isSelf) null else profile?.id,
modifier = Modifier.padding(top = 16.dp),
onMoreClick = {
// 导航到智能体列表页面
// TODO: 实现导航逻辑
},
onAgentClick = { agent ->
// 导航到智能体详情页面
// TODO: 实现导航逻辑
},
onAvatarClick = { agent ->
// 导航到智能体个人主页
scope.launch {
try {
val userService = com.aiosman.ravenow.data.UserServiceImpl()
val profile = userService.getUserProfileByOpenId(agent.openId)
navController.navigate(
NavigationRoute.AccountProfile.route
.replace("{id}", profile.id.toString())
.replace("{isAiAccount}", "true")
)
} catch (e: Exception) {
// 处理错误
}
}
},
onAgentLongClick = { agent ->
Log.d("ProfileV3", "onAgentLongClick called for agent: ${agent.title}, isSelf: $isSelf")
if (isSelf) { // 只有自己的智能体才能长按
Log.d("ProfileV3", "Setting contextAgent and showing menu")
contextAgent = agent
showAgentMenu = true
}
}
)
}
}
}
}
}
}
Box(modifier = Modifier.fillMaxWidth()) {
Column(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer {
alpha = 1 - state.toolbarState.progress
}
.background(AppColors.profileBackground)
.onGloballyPositioned {
miniToolbarHeight = with(density) {
it.size.height.toDp().value.toInt()
}
}
) {
StatusBarSpacer()
Row(
modifier = Modifier.padding(
horizontal = 16.dp,
vertical = 8.dp,
).noRippleClickable {
},
verticalAlignment = Alignment.CenterVertically
) {
if (!isMain) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
contentDescription = "Back",
modifier = Modifier
.noRippleClickable {
navController.navigateUp()
}
.size(24.dp),
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = profile?.nickName ?: "",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = AppColors.text
)
}
Spacer(modifier = Modifier.weight(1f))
if (isSelf&&isMain) {
Box(
modifier = Modifier
.size(24.dp)
.padding(16.dp)
)
}
}
Spacer(modifier = Modifier.height(8.dp))
}
if (isSelf&&isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(
top = 32.dp ,
end = 16.dp
)
.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier
.padding(16.dp)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = AppColors.text
)
}
} }
} }
} }
} }
) {
// 用户智能体行
if (!isAiAccount) {
UserAgentsRow(
userId = if (isSelf) null else profile?.id,
modifier = Modifier.padding(top = 16.dp),
onMoreClick = {
// 导航到智能体列表页面
},
onAgentClick = { agent ->
// 导航到智能体详情页面
},
onAvatarClick = { agent ->
// 导航到智能体个人主页
scope.launch {
try {
val userService = com.aiosman.ravenow.data.UserServiceImpl()
val profile = userService.getUserProfileByOpenId(agent.openId)
navController.navigate(
NavigationRoute.AccountProfile.route
.replace("{id}", profile.id.toString())
.replace("{isAiAccount}", "true")
)
} catch (e: Exception) {
// 处理错误
}
}
},
onAgentLongClick = { agent ->
Log.d("ProfileV3", "onAgentLongClick called for agent: ${agent.title}, isSelf: $isSelf")
if (isSelf) { // 只有自己的智能体才能长按
Log.d("ProfileV3", "Setting contextAgent and showing menu")
contextAgent = agent
showAgentMenu = true
}
}
)
}
// 内容
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.background(AppColors.profileBackground) .background(AppColors.profileBackground)
.padding(top = 8.dp)
) { ) {
UserContentPageIndicator( UserContentPageIndicator(
pagerState = pagerState, pagerState = pagerState,
@@ -448,7 +586,6 @@ fun ProfileV3(
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
HorizontalPager( HorizontalPager(
state = pagerState, state = pagerState,
modifier = Modifier.height(500.dp) // 固定滚动高度
) { idx -> ) { idx ->
when (idx) { when (idx) {
0 -> 0 ->
@@ -478,196 +615,15 @@ fun ProfileV3(
} }
} }
} }
} }
// 顶部导航栏
TopNavigationBar(
isMain = isMain,
isSelf = isSelf,
profile = profile,
navController = navController,
alpha = toolbarAlpha,
onMenuClick = {
showOtherUserMenu = true
}
)
PullRefreshIndicator( PullRefreshIndicator(
model.refreshing, model.refreshing,
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
}
)
}
}
}
} }
}
}
//顶部导航栏组件
@Composable
fun TopNavigationBar(
isMain: Boolean,
isSelf: Boolean,
profile: AccountProfileEntity?,
navController: androidx.navigation.NavController,
alpha: Float,
onMenuClick: () -> Unit = {}
) {
val appColors = LocalAppTheme.current
Box(
modifier = Modifier
.fillMaxWidth()
.graphicsLayer { this.alpha = alpha }
) {
Column(
modifier = Modifier
.fillMaxWidth()
.background(appColors.profileBackground)
) {
StatusBarSpacer()
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
.noRippleClickable {
},
verticalAlignment = Alignment.CenterVertically
) {
if (!isMain) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = "Back",
modifier = Modifier
.noRippleClickable {
navController.navigateUp()
}
.size(24.dp),
colorFilter = ColorFilter.tint(appColors.text)
)
Spacer(modifier = Modifier.width(8.dp))
CustomAsyncImage(
LocalContext.current,
profile?.avatar,
modifier = Modifier
.size(32.dp)
.clip(CircleShape),
contentDescription = "",
contentScale = ContentScale.Crop
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = profile?.nickName ?: "",
fontSize = 16.sp,
fontWeight = FontWeight.W600,
color = appColors.text
)
}
Spacer(modifier = Modifier.weight(1f))
if (isSelf && isMain) {
Box(
modifier = Modifier
.size(24.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))
}
if (isSelf && isMain) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.padding(top = 32.dp, end = 16.dp)
.noRippleClickable {
IndexViewModel.openDrawer = true
}
) {
Box(
modifier = Modifier.padding(16.dp)
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
contentDescription = "",
tint = appColors.text
)
}
}
}
}
} }
/** /**
@@ -749,7 +705,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 +781,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
)
}
}
}
}

View File

@@ -37,16 +37,9 @@ import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.lazy.grid.rememberLazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.stringResource
import com.aiosman.ravenow.AppState import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.ui.composables.rememberDebouncer import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.network.ReloadButton
import com.aiosman.ravenow.utils.NetworkUtils
@Composable @Composable
fun GalleryItem( fun GalleryItem(
moment: MomentEntity, moment: MomentEntity,
@@ -136,50 +129,8 @@ fun GalleryGrid(
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
val debouncer = rememberDebouncer() val debouncer = rememberDebouncer()
var refreshKey by remember { mutableStateOf(0) }
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
if (!isNetworkAvailable) { if (moments.isEmpty()) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
.padding(vertical = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp),
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
fontSize = 16.sp,
color = AppColors.text,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
fontSize = 14.sp,
color = AppColors.secondaryText,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
refreshKey++
MyProfileViewModel.ResetModel()
MyProfileViewModel.loadProfile(pullRefresh = true)
}
)
}
} else if (moments.isEmpty()) {
Column( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -190,7 +141,7 @@ fun GalleryGrid(
Image( Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.qs_dt_qs_as_img id = if(AppState.darkMode) R.mipmap.qs_dt_qs_as_img
else R.mipmap.invalid_name_7), else R.mipmap.qs_dt_qs_img),
contentDescription = "暂无图片", contentDescription = "暂无图片",
modifier = Modifier.size(181.dp), modifier = Modifier.size(181.dp),
) )

View File

@@ -50,8 +50,8 @@ fun OtherProfileAction(
// 定义渐变色 // 定义渐变色
val followGradient = Brush.horizontalGradient( val followGradient = Brush.horizontalGradient(
colors = listOf( colors = listOf(
Color(0xFF7c45ed), Color(0xFFE53E3E), // 红色
Color(0x777c68ef) Color(0xFF9F7AEA) // 紫色
) )
) )
@@ -100,7 +100,7 @@ fun OtherProfileAction(
Text( Text(
text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper), text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper),
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W900, fontWeight = FontWeight.W600,
color = if (profile.isFollowing) { color = if (profile.isFollowing) {
// 已关注状态 - 灰色文字 // 已关注状态 - 灰色文字
AppColors.text.copy(alpha = 0.6f) AppColors.text.copy(alpha = 0.6f)
@@ -133,37 +133,11 @@ fun OtherProfileAction(
Text( Text(
text = stringResource(R.string.chat_upper), text = stringResource(R.string.chat_upper),
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W900, fontWeight = FontWeight.W600,
color = AppColors.text, // 使用主题文字颜色 color = AppColors.text, // 使用主题文字颜色
) )
} }
} }
// 分享按钮 - 灰色背景样式
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.Center,
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(8.dp))
.background(AppColors.nonActive)
.padding(horizontal = 16.dp, vertical = 12.dp)
.noRippleClickable {
// 检查游客模式,如果是游客则跳转登录
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CHAT_WITH_AGENT)) {
navController.navigate(NavigationRoute.Login.route)
} else {
// TODO: 添加分享逻辑
}
}
) {
Text(
text = stringResource(R.string.share),
fontSize = 14.sp,
fontWeight = FontWeight.W900,
color = AppColors.text, // 使用主题文字颜色
)
}
} }
} }

View File

@@ -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
@@ -30,13 +29,11 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
@Composable @Composable
fun SelfProfileAction( fun SelfProfileAction(
onEditProfile: () -> Unit, onEditProfile: () -> Unit,
onPremiumClick: (() -> Unit), onPremiumClick: (() -> Unit)? = null
onShare: (() -> Unit)? = null
) { ) {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val editProfileDebouncer = rememberDebouncer() val editProfileDebouncer = rememberDebouncer()
val premiumClickDebouncer = rememberDebouncer() val premiumClickDebouncer = rememberDebouncer()
val shareDebouncer = rememberDebouncer()
Row( Row(
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
@@ -48,101 +45,52 @@ 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(8.dp))
.background(androidx.compose.ui.graphics.Color(0x229284BD)) .background(AppColors.nonActive)
.padding(horizontal = 16.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.W600,
color = androidx.compose.ui.graphics.Color(0xFF9284BD), color = AppColors.text,
) )
} }
// // 预留按钮位置 // Rave Premium 按钮(右侧)
// 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(8.dp))
// .padding(horizontal = 16.dp, vertical = 12.dp) .background(AppColors.premiumBackground)
// .noRippleClickable { .padding(horizontal = 16.dp, vertical = 12.dp)
// .noRippleClickable {
// } premiumClickDebouncer {
// ) { onPremiumClick?.invoke()
// Text( }
// text = "", }
// fontSize = 14.sp, ) {
// fontWeight = FontWeight.W900, Image(
// color = AppColors.text, painter = painterResource(id = R.drawable.ic_member),
// ) contentDescription = "",
// } modifier = Modifier.size(18.dp),
// colorFilter = ColorFilter.tint(AppColors.premiumText)
// // 分享按钮 )
// Row( Spacer(modifier = Modifier.width(8.dp))
// verticalAlignment = Alignment.CenterVertically, Text(
// horizontalArrangement = Arrangement.Center, text = "Rave Premium",
// modifier = Modifier fontSize = 14.sp,
// .weight(1f) fontWeight = FontWeight.W600,
// .clip(RoundedCornerShape(10.dp)) color = AppColors.premiumText,
// .background(AppColors.nonActive) )
// .padding(horizontal = 16.dp, vertical = 12.dp) }
// .noRippleClickable {
// shareDebouncer {
// // TODO: 添加分享逻辑
// }
// }
// ) {
// Text(
// text = stringResource(R.string.share),
// fontSize = 14.sp,
// fontWeight = FontWeight.W900,
// color = AppColors.text,
// )
// }
// // Rave Premium 按钮(右侧)
// Row(
// verticalAlignment = Alignment.CenterVertically,
// horizontalArrangement = Arrangement.Center,
// modifier = Modifier
// .weight(1f)
// .clip(RoundedCornerShape(8.dp))
// .background(AppColors.premiumBackground)
// .padding(horizontal = 16.dp, vertical = 12.dp)
// .noRippleClickable {
// premiumClickDebouncer {
// onPremiumClick?.invoke()
// }
// }
// ) {
// Image(
// painter = painterResource(id = R.drawable.ic_member),
// contentDescription = "",
// modifier = Modifier.size(18.dp),
// colorFilter = ColorFilter.tint(AppColors.premiumText)
// )
// Spacer(modifier = Modifier.width(8.dp))
// Text(
// text = "Rave Premium",
// fontSize = 14.sp,
// fontWeight = FontWeight.W600,
// color = AppColors.premiumText,
// )
// }
} }
} }

View File

@@ -19,7 +19,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
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
@@ -30,7 +29,6 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
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
@@ -46,10 +44,7 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.entity.AgentEntity import com.aiosman.ravenow.entity.AgentEntity
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.network.ReloadButton
import com.aiosman.ravenow.utils.DebounceUtils import com.aiosman.ravenow.utils.DebounceUtils
import com.aiosman.ravenow.utils.NetworkUtils
@Composable @Composable
fun UserAgentsList( fun UserAgentsList(
@@ -201,7 +196,6 @@ fun UserAgentCard(
@Composable @Composable
fun EmptyAgentsView() { fun EmptyAgentsView() {
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
Column( Column(
modifier = Modifier modifier = Modifier
@@ -209,65 +203,30 @@ fun EmptyAgentsView() {
.padding(vertical = 60.dp), .padding(vertical = 60.dp),
horizontalAlignment = Alignment.CenterHorizontally horizontalAlignment = Alignment.CenterHorizontally
) { ) {
if (isNetworkAvailable) { Image(
Image( painter = painterResource(
painter = painterResource( id =if(AppState.darkMode) R.mipmap.qs_ai_qs_as_img
id =if(AppState.darkMode) R.mipmap.ai_dark else R.mipmap.qs_ai_qs_img),
else R.mipmap.ai), contentDescription = "暂无Agent",
contentDescription = "暂无Agent", modifier = Modifier.size(181.dp),
modifier = Modifier )
.size(width = 181.dp, height = 153.dp)
.align(Alignment.CenterHorizontally),
)
Spacer(modifier = Modifier.height(24.dp)) Spacer(modifier = Modifier.height(24.dp))
Text( Text(
text = "专属AI等你召唤", text = "专属AI等你召唤",
fontSize = 16.sp, fontSize = 16.sp,
color = AppColors.text, color = AppColors.text,
fontWeight = FontWeight.W600 fontWeight = FontWeight.W600
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
Text( Text(
text = "AI将成为你的伙伴而不是工具", text = "AI将成为你的伙伴而不是工具",
fontSize = 14.sp, fontSize = 14.sp,
color = AppColors.secondaryText, color = AppColors.secondaryText,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400
) )
} else {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp),
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
fontSize = 16.sp,
color = AppColors.text,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
fontSize = 14.sp,
color = AppColors.secondaryText,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
MyProfileViewModel.ResetModel()
MyProfileViewModel.loadProfile(pullRefresh = true)
}
)
}
} }
} }

View File

@@ -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)

View File

@@ -5,7 +5,6 @@ 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.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.WindowInsets import androidx.compose.foundation.layout.WindowInsets
@@ -29,8 +28,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicTextField import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.Tab import androidx.compose.material.Tab
import androidx.compose.material.TabRow import androidx.compose.material.TabRow
@@ -47,7 +44,6 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
@@ -57,7 +53,6 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
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.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
@@ -75,7 +70,6 @@ import com.aiosman.ravenow.ui.index.tabs.message.tab.AgentChatListViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.google.accompanist.systemuicontroller.rememberSystemUiController
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.aiosman.ravenow.utils.NetworkUtils
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@@ -310,7 +304,6 @@ fun MomentResultTab() {
var dataFlow = model.momentsFlow var dataFlow = model.momentsFlow
var moments = dataFlow.collectAsLazyPagingItems() var moments = dataFlow.collectAsLazyPagingItems()
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
val context = LocalContext.current
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
@@ -324,13 +317,10 @@ fun MomentResultTab() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
androidx.compose.foundation.Image( androidx.compose.foundation.Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.syss_yh_qs_as_img id = if(AppState.darkMode) R.mipmap.syss_yh_qs_as_img
else R.mipmap.invalid_name_1), else R.mipmap.syss_yh_qs_img),
contentDescription = "No Comment", contentDescription = "No Comment",
modifier = Modifier.size(140.dp) modifier = Modifier.size(140.dp)
) )
@@ -347,33 +337,6 @@ fun MomentResultTab() {
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400
) )
} else {
androidx.compose.foundation.Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(140.dp)
)
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = LocalAppTheme.current.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = LocalAppTheme.current.secondaryText,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.size(16.dp))
ReloadButton(
onClick = {
SearchViewModel.ResetModel()
SearchViewModel.search()
}
)
}
} }
} else { } else {
LazyColumn( LazyColumn(
@@ -406,7 +369,6 @@ fun UserResultTab() {
val model = SearchViewModel val model = SearchViewModel
val users = model.usersFlow.collectAsLazyPagingItems() val users = model.usersFlow.collectAsLazyPagingItems()
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val context = LocalContext.current
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
@@ -418,13 +380,10 @@ fun UserResultTab() {
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
if (isNetworkAvailable) {
androidx.compose.foundation.Image( androidx.compose.foundation.Image(
painter = painterResource( painter = painterResource(
id = if(AppState.darkMode) R.mipmap.syss_yh_qs_as_img id = if(AppState.darkMode) R.mipmap.syss_yh_qs_as_img
else R.mipmap.invalid_name_1), else R.mipmap.syss_yh_qs_img),
contentDescription = "No Comment", contentDescription = "No Comment",
modifier = Modifier.size(140.dp) modifier = Modifier.size(140.dp)
) )
@@ -441,33 +400,6 @@ fun UserResultTab() {
fontSize = 14.sp, fontSize = 14.sp,
fontWeight = FontWeight.W400 fontWeight = FontWeight.W400
) )
} else {
androidx.compose.foundation.Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(140.dp)
)
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = LocalAppTheme.current.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.height(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = LocalAppTheme.current.secondaryText,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.size(16.dp))
ReloadButton(
onClick = {
SearchViewModel.ResetModel()
SearchViewModel.search()
}
)
}
} }
} else { } else {
LazyColumn( LazyColumn(
@@ -565,42 +497,3 @@ fun UserItem(
} }
} }
} }
@Composable
fun ReloadButton(
onClick: () -> Unit
) {
val gradientBrush = Brush.linearGradient(
colors = listOf(
Color(0xFF7c45ed),
Color(0xFF7c68ef),
Color(0xFF7bd8f8)
)
)
Button(
onClick = onClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 120.dp)
.height(48.dp),
shape = RoundedCornerShape(30.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Transparent
),
contentPadding = PaddingValues(0.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(gradientBrush),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.Reload),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
)
}
}
}

View File

@@ -39,14 +39,15 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.AccountLikeEntity import com.aiosman.ravenow.entity.AccountLikeEntity
import com.aiosman.ravenow.exp.timeAgo import com.aiosman.ravenow.exp.timeAgo
import com.aiosman.ravenow.ui.NavigationRoute import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.composables.BottomNavigationPlaceholder import com.aiosman.ravenow.ui.composables.BottomNavigationPlaceholder
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout import com.aiosman.ravenow.ui.composables.StatusBarMaskLayout
import com.aiosman.ravenow.ui.index.tabs.profile.MyProfileViewModel
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.ui.navigateToPost import com.aiosman.ravenow.ui.navigateToPost
import java.util.Date import java.util.Date
import com.aiosman.ravenow.utils.NetworkUtils
import com.aiosman.ravenow.ui.network.ReloadButton
@Preview @Preview
@Composable @Composable
fun LikeNoticeScreen() { fun LikeNoticeScreen() {
@@ -71,47 +72,18 @@ fun LikeNoticeScreen() {
.background(color = AppColors.background) .background(color = AppColors.background)
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
) { ) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
) {
NoticeScreenHeader(
stringResource(R.string.like_upper),
moreIcon = false
)
}
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current) if (likes.itemCount == 0) {
if (!isNetworkAvailable) {
Box(
modifier = Modifier.fillMaxSize()
.padding(top=149.dp),
contentAlignment = Alignment.TopCenter
) {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_10),
contentDescription = "network error",
modifier = Modifier.size(181.dp)
)
Spacer(modifier = Modifier.height(24.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_title),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600,
)
Spacer(modifier = Modifier.size(8.dp))
Text(
text = stringResource(R.string.friend_chat_no_network_subtitle),
color = AppColors.text,
fontSize = 14.sp,
fontWeight = FontWeight.W400
)
Spacer(modifier = Modifier.height(16.dp))
ReloadButton(
onClick = {
LikeNoticeViewModel.reload(force = true)
}
)
}
}
} else if (likes.itemCount == 0) {
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
.padding(top=149.dp), .padding(top=149.dp),
@@ -124,7 +96,7 @@ fun LikeNoticeScreen() {
Image( Image(
painter = painterResource( painter = painterResource(
id =if(AppState.darkMode) R.mipmap.qst_z_qs_as_img id =if(AppState.darkMode) R.mipmap.qst_z_qs_as_img
else R.mipmap.invalid_name_6), else R.mipmap.qst_z_qs_img),
contentDescription = "No Notice", contentDescription = "No Notice",
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -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 {
@@ -269,37 +260,13 @@ fun LoginPage() {
.fillMaxSize() .fillMaxSize()
.background(AppColors.background) .background(AppColors.background)
) { ) {
Row( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxSize()
.padding(horizontal = 24.dp)
.padding(top = 60.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) { ) {
Box( val localContext = LocalContext.current // 获取 Context
modifier = Modifier MovingImageWall(localContext.resources) // 将 resources 传递给 MovingImageWall
.size(30.dp)
.background(
color = AppColors.text.copy(alpha = 0.1f),
shape = androidx.compose.foundation.shape.CircleShape
)
.noRippleClickable {
guestLogin()
},
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_close),
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))
)
) { Spacer(modifier = Modifier.height(8.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()
)
}
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, navController.navigate(
modifier = Modifier NavigationRoute.UserAuth.route,
.fillMaxWidth() )
.noRippleClickable { }
navController.navigate(
NavigationRoute.UserAuth.route, // 游客登录按钮
) Spacer(modifier = Modifier.height(16.dp))
} ActionButton(
) modifier = Modifier.fillMaxWidth(),
// // 游客登录按钮 text = "游客模式",
// Spacer(modifier = Modifier.height(16.dp)) color = AppColors.text.copy(alpha = 0.7f),
// ActionButton( ) {
// modifier = Modifier.fillMaxWidth(), guestLogin()
// text = "游客模式", }
// color = AppColors.text.copy(alpha = 0.7f),
// ) {
// guestLogin()
// }
Spacer(modifier = Modifier.height(70.dp)) Spacer(modifier = Modifier.height(70.dp))
} }
} }

View File

@@ -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()
// } }
// } }
} }

View File

@@ -1,64 +0,0 @@
package com.aiosman.ravenow.ui.network
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
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.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.R
@Composable
fun ReloadButton(
onClick: () -> Unit
) {
val gradientBrush = Brush.linearGradient(
colors = listOf(
Color(0xFF7c45ed),
Color(0xFF7c68ef),
Color(0xFF7bd8f8)
)
)
Button(
onClick = onClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 120.dp)
.height(48.dp),
shape = RoundedCornerShape(30.dp),
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Transparent
),
contentPadding = PaddingValues(0.dp)
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(gradientBrush),
contentAlignment = Alignment.Center
) {
Text(
text = stringResource(R.string.Reload),
fontSize = 16.sp,
fontWeight = FontWeight.Bold,
color = Color.White,
textAlign = TextAlign.Center
)
}
}
}

View File

@@ -1,144 +0,0 @@
package com.aiosman.ravenow.ui.notification
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.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.layout.wrapContentHeight
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.ColorFilter
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.comment.notice.CommentNoticeScreen
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.composables.TabItem
import com.aiosman.ravenow.ui.composables.TabSpacer
import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.follower.FollowerNoticeScreen
import com.aiosman.ravenow.ui.like.LikeNoticeScreen
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import kotlinx.coroutines.launch
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun NotificationScreen() {
val AppColors = LocalAppTheme.current
val navController = LocalNavController.current
val scope = rememberCoroutineScope()
val pagerState = rememberPagerState(pageCount = { 3 })
val Debouncer = rememberDebouncer()
Column(
modifier = Modifier
.fillMaxSize()
.background(color = AppColors.background)
) {
StatusBarSpacer()
Row(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = "Back",
modifier = Modifier
.size(24.dp)
.noRippleClickable {
Debouncer {
navController.popBackStack()
}
},
colorFilter = ColorFilter.tint(AppColors.text)
)
Spacer(modifier = Modifier.width(16.dp))
Text(
text = stringResource(R.string.group_info_notice_setting),
fontSize = 20.sp,
fontWeight = FontWeight.W900,
color = AppColors.text
)
}
Row(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.padding(start = 16.dp, top = 8.dp, bottom = 16.dp),
horizontalArrangement = Arrangement.Start,
verticalAlignment = Alignment.Bottom
) {
TabItem(
text = stringResource(R.string.like),
isSelected = pagerState.currentPage == 0,
onClick = {
scope.launch {
pagerState.animateScrollToPage(0)
}
}
)
TabSpacer()
TabItem(
text = stringResource(R.string.followers_upper),
isSelected = pagerState.currentPage == 1,
onClick = {
scope.launch {
pagerState.animateScrollToPage(1)
}
}
)
TabSpacer()
TabItem(
text = stringResource(R.string.comment).uppercase(),
isSelected = pagerState.currentPage == 2,
onClick = {
scope.launch {
pagerState.animateScrollToPage(2)
}
}
)
}
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.weight(1f)
) { page ->
when (page) {
0 -> LikeNoticeScreen()
1 -> FollowerNoticeScreen()
2 -> CommentNoticeScreen()
}
}
}
}

View File

@@ -799,7 +799,7 @@ fun CommentContent(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) { ) {
Image( Image(
painter = painterResource(id = R.mipmap.invalid_name_3), painter = painterResource(id = R.mipmap.qs_plq_qs_img),
contentDescription = null, contentDescription = null,
modifier = Modifier.size(181.dp) modifier = Modifier.size(181.dp)
) )

View File

@@ -1,19 +1,20 @@
package com.aiosman.ravenow.ui.splash package com.aiosman.ravenow.ui.splash
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
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.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize 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.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width
import androidx.compose.material.Scaffold
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable 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.res.painterResource import androidx.compose.ui.res.painterResource
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
@@ -21,36 +22,38 @@ import com.aiosman.ravenow.R
@Composable @Composable
fun SplashScreen() { fun SplashScreen() {
Box( Scaffold {
modifier = Modifier.fillMaxSize() it
) { Box(
// 居中的图标 modifier = Modifier.fillMaxSize()
Image(
painter = painterResource(id = R.mipmap.invalid_name),
contentDescription = "App Logo",
modifier = Modifier
.align(Alignment.Center)
.size(120.dp)
)
// 底部文字
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Bottom,
modifier = Modifier
.fillMaxSize()
.padding(bottom = 80.dp)
) { ) {
Image( // to bottom
painterResource(id = R.mipmap.kp_p_img), Box(
contentDescription = "", contentAlignment = Alignment.TopCenter,
modifier = Modifier.size(85.dp, 25.dp) modifier = Modifier.padding(top = 211.dp)
) ) {
Spacer(modifier = Modifier.padding(top = 16.dp)) Column(
Text( horizontalAlignment = Alignment.CenterHorizontally,
stringResource(R.string.splash_title), modifier = Modifier.fillMaxWidth()
fontSize = 13.sp ) {
) Image(
painter = painterResource(id = R.mipmap.rider_pro_logo),
contentDescription = "Rave Now",
modifier = Modifier
.width(108.dp)
.height(45.dp)
)
Spacer(modifier = Modifier.height(32.dp))
Text(
"Rave Now".uppercase(),
fontSize = 28.sp,
fontWeight = FontWeight.Bold
)
Text("Your Night Starts Here".uppercase(), fontSize = 20.sp, fontWeight = FontWeight.W700)
}
}
} }
} }
} }

View File

@@ -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)

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 335 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 601 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 365 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 446 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 919 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 852 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 617 B

Some files were not shown because too many files have changed in this diff Show More