Compare commits
4 Commits
feat/pr-20
...
add_news
| Author | SHA1 | Date | |
|---|---|---|---|
| a1196715d0 | |||
| 68273ae166 | |||
| 6c7be4ba47 | |||
| cf25540417 |
4
.idea/deploymentTargetSelector.xml
generated
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-11-05T12:24:27.034893100Z">
|
<DropdownSelection timestamp="2025-09-09T09:51:06.656104400Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=c328a150" />
|
<DeviceId pluginId="Default" identifier="serial=192.168.0.227:45035;connection=094cb92e" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
32
.idea/gradle.xml
generated
32
.idea/gradle.xml
generated
@@ -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>
|
||||||
@@ -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)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/Theme.RaveNow"
|
android:theme="@style/Theme.RaveNow"
|
||||||
android:usesCleartextTraffic="true"
|
android:usesCleartextTraffic="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_config"
|
|
||||||
tools:targetApi="31">
|
tools:targetApi="31">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="com.google.android.geo.API_KEY"
|
android:name="com.google.android.geo.API_KEY"
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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) {
|
||||||
|
|||||||
@@ -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环境
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求通知权限
|
* 请求通知权限
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2,16 +2,8 @@ package com.aiosman.ravenow.data
|
|||||||
|
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.AgentRule
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRuleListResponse
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRuleAgent
|
|
||||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.InsufficientBalanceError
|
|
||||||
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.entity.AgentEntity
|
import com.aiosman.ravenow.entity.AgentEntity
|
||||||
import com.aiosman.ravenow.entity.ProfileEntity
|
import com.aiosman.ravenow.entity.ProfileEntity
|
||||||
import com.google.gson.Gson
|
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
data class Agent(
|
data class Agent(
|
||||||
@@ -91,16 +83,9 @@ data class Profile(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/**
|
|
||||||
* 智能体服务
|
|
||||||
*/
|
|
||||||
interface AgentService {
|
interface AgentService {
|
||||||
/**
|
/**
|
||||||
* 获取智能体列表
|
* 获取智能体列表
|
||||||
* @param pageNumber 页码
|
|
||||||
* @param pageSize 每页数量,默认 20
|
|
||||||
* @param authorId 作者ID,可选参数,用于筛选特定作者的智能体
|
|
||||||
* @return 智能体列表容器,包含分页信息和智能体列表,失败时返回 null
|
|
||||||
*/
|
*/
|
||||||
suspend fun getAgent(
|
suspend fun getAgent(
|
||||||
pageNumber: Int,
|
pageNumber: Int,
|
||||||
@@ -111,206 +96,3 @@ interface AgentService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ========== Agent 规则 - 领域实体 ==========
|
|
||||||
|
|
||||||
data class AgentRuleAgentInfo(
|
|
||||||
val id: Int,
|
|
||||||
val title: String,
|
|
||||||
val avatar: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AgentRuleEntity(
|
|
||||||
val id: Int,
|
|
||||||
val rule: String,
|
|
||||||
val creator: String,
|
|
||||||
val creatorType: String,
|
|
||||||
val scope: String,
|
|
||||||
val agent: AgentRuleAgentInfo,
|
|
||||||
val createdAt: String,
|
|
||||||
val updatedAt: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AgentRuleListResult(
|
|
||||||
val page: Int,
|
|
||||||
val pageSize: Int,
|
|
||||||
val total: Int,
|
|
||||||
val list: List<AgentRuleEntity>,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class AgentRuleQuotaEntity(
|
|
||||||
val agentId: Int,
|
|
||||||
val agentTitle: String,
|
|
||||||
val baseMaxCount: Int,
|
|
||||||
val purchasedCount: Int,
|
|
||||||
val totalMaxCount: Int,
|
|
||||||
val currentCount: Int,
|
|
||||||
val remainingCount: Int,
|
|
||||||
val usagePercent: Double,
|
|
||||||
)
|
|
||||||
|
|
||||||
// ========== Agent 规则 - Service 接口 ==========
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Agent 规则服务
|
|
||||||
*/
|
|
||||||
interface AgentRuleService {
|
|
||||||
/**
|
|
||||||
* 根据 OpenId 创建 Agent 规则
|
|
||||||
* @param openId Agent 的 OpenId
|
|
||||||
* @param rule 规则内容,不能为空
|
|
||||||
* @throws ServiceException 创建失败时抛出异常(包括余额不足等情况)
|
|
||||||
*/
|
|
||||||
suspend fun createAgentRuleByOpenId(openId: String, rule: String)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改 Agent 规则
|
|
||||||
* @param id 规则ID
|
|
||||||
* @param rule 新的规则内容,不能为空
|
|
||||||
* @param openId Agent 的 OpenId,可选参数
|
|
||||||
* @throws ServiceException 修改失败时抛出异常(包括余额不足等情况)
|
|
||||||
*/
|
|
||||||
suspend fun updateAgentRule(id: Int, rule: String, openId: String? = null)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除 Agent 规则
|
|
||||||
* @param id 规则ID
|
|
||||||
* @throws ServiceException 删除失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun deleteAgentRule(id: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询 Agent 规则列表
|
|
||||||
* @param openId Agent 的 OpenId
|
|
||||||
* @param keyword 关键词搜索,可选参数
|
|
||||||
* @param page 页码,默认 1
|
|
||||||
* @param pageSize 每页数量,默认 10
|
|
||||||
* @return 规则列表响应,包含分页信息和规则列表
|
|
||||||
* @throws ServiceException 查询失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun getAgentRuleList(
|
|
||||||
openId: String,
|
|
||||||
keyword: String? = null,
|
|
||||||
page: Int = 1,
|
|
||||||
pageSize: Int = 10
|
|
||||||
): AgentRuleListResult
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询 Agent 规则配额使用情况
|
|
||||||
* @param openId Agent 的 OpenId
|
|
||||||
* @return 规则配额信息,包含基础配额、已购买配额、当前使用量等
|
|
||||||
* @throws ServiceException 查询失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun getAgentRuleQuota(openId: String): AgentRuleQuotaEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
class AgentRuleServiceImpl : AgentRuleService {
|
|
||||||
private val gson = Gson()
|
|
||||||
|
|
||||||
override suspend fun createAgentRuleByOpenId(openId: String, rule: String) {
|
|
||||||
val body = CreateAgentRuleRequestBody(
|
|
||||||
rule = rule,
|
|
||||||
promptId = null,
|
|
||||||
openId = openId
|
|
||||||
)
|
|
||||||
val resp = ApiClient.api.createAgentRule(body)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
val errorText = resp.errorBody()?.string()
|
|
||||||
try {
|
|
||||||
val err = gson.fromJson(errorText, InsufficientBalanceError::class.java)
|
|
||||||
if (err != null && err.code == 35600) {
|
|
||||||
throw ServiceException(err.message)
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
// ignore parse error
|
|
||||||
}
|
|
||||||
throw ServiceException("创建 Agent 规则失败: HTTP ${resp.code()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateAgentRule(id: Int, rule: String, openId: String?) {
|
|
||||||
val body = UpdateAgentRuleRequestBody(
|
|
||||||
id = id,
|
|
||||||
rule = rule,
|
|
||||||
promptId = null,
|
|
||||||
openId = openId
|
|
||||||
)
|
|
||||||
val resp = ApiClient.api.updateAgentRule(body)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
val errorText = resp.errorBody()?.string()
|
|
||||||
try {
|
|
||||||
val err = gson.fromJson(errorText, InsufficientBalanceError::class.java)
|
|
||||||
if (err != null && err.code == 35600) {
|
|
||||||
throw ServiceException(err.message)
|
|
||||||
}
|
|
||||||
} catch (_: Exception) {
|
|
||||||
// ignore parse error
|
|
||||||
}
|
|
||||||
throw ServiceException("更新 Agent 规则失败: HTTP ${resp.code()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteAgentRule(id: Int) {
|
|
||||||
val resp = ApiClient.api.deleteAgentRule(id)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
throw ServiceException("删除 Agent 规则失败: HTTP ${resp.code()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAgentRuleList(
|
|
||||||
openId: String,
|
|
||||||
keyword: String?,
|
|
||||||
page: Int,
|
|
||||||
pageSize: Int
|
|
||||||
): AgentRuleListResult {
|
|
||||||
val resp = ApiClient.api.getAgentRuleList(
|
|
||||||
promptId = openId,
|
|
||||||
rule = keyword,
|
|
||||||
page = page,
|
|
||||||
pageSize = pageSize
|
|
||||||
)
|
|
||||||
val data = resp.body()?.data ?: throw ServiceException("获取 Agent 规则列表失败")
|
|
||||||
return data.toEntity()
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getAgentRuleQuota(openId: String): AgentRuleQuotaEntity {
|
|
||||||
val resp = ApiClient.api.getAgentRuleQuota(openId)
|
|
||||||
val data = resp.body()?.data ?: throw ServiceException("获取 Agent 规则配额失败")
|
|
||||||
return data.toEntity()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun AgentRuleListResponse.toEntity(): AgentRuleListResult = AgentRuleListResult(
|
|
||||||
page = page,
|
|
||||||
pageSize = pageSize,
|
|
||||||
total = total,
|
|
||||||
list = list.map { it.toEntity() }
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun AgentRule.toEntity(): AgentRuleEntity = AgentRuleEntity(
|
|
||||||
id = id,
|
|
||||||
rule = rule,
|
|
||||||
creator = creator,
|
|
||||||
creatorType = creatorType,
|
|
||||||
scope = scope,
|
|
||||||
agent = prompt.toEntity(),
|
|
||||||
createdAt = createdAt,
|
|
||||||
updatedAt = updatedAt,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun AgentRuleAgent.toEntity(): AgentRuleAgentInfo = AgentRuleAgentInfo(
|
|
||||||
id = id,
|
|
||||||
title = title,
|
|
||||||
avatar = avatar,
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun AgentRuleQuota.toEntity(): AgentRuleQuotaEntity = AgentRuleQuotaEntity(
|
|
||||||
agentId = promptId,
|
|
||||||
agentTitle = promptTitle,
|
|
||||||
baseMaxCount = baseMaxCount,
|
|
||||||
purchasedCount = purchasedCount,
|
|
||||||
totalMaxCount = totalMaxCount,
|
|
||||||
currentCount = currentCount,
|
|
||||||
remainingCount = remainingCount,
|
|
||||||
usagePercent = usagePercent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.google.gson.annotations.SerializedName
|
|||||||
data class ListContainer<T>(
|
data class ListContainer<T>(
|
||||||
// 总数
|
// 总数
|
||||||
@SerializedName("total")
|
@SerializedName("total")
|
||||||
val total: Long,
|
val total: Int,
|
||||||
// 当前页
|
// 当前页
|
||||||
@SerializedName("page")
|
@SerializedName("page")
|
||||||
val page: Int,
|
val page: Int,
|
||||||
|
|||||||
@@ -32,9 +32,8 @@ data class Moment(
|
|||||||
val time: String,
|
val time: String,
|
||||||
@SerializedName("isFollowed")
|
@SerializedName("isFollowed")
|
||||||
val isFollowed: Boolean,
|
val isFollowed: Boolean,
|
||||||
// 新闻相关字段
|
|
||||||
@SerializedName("isNews")
|
@SerializedName("isNews")
|
||||||
val isNews: Boolean = false,
|
val isNews: Boolean? = null,
|
||||||
@SerializedName("newsTitle")
|
@SerializedName("newsTitle")
|
||||||
val newsTitle: String? = null,
|
val newsTitle: String? = null,
|
||||||
@SerializedName("newsUrl")
|
@SerializedName("newsUrl")
|
||||||
@@ -47,6 +46,14 @@ data class Moment(
|
|||||||
val newsLanguage: String? = null,
|
val newsLanguage: String? = null,
|
||||||
@SerializedName("newsContent")
|
@SerializedName("newsContent")
|
||||||
val newsContent: String? = null,
|
val newsContent: String? = null,
|
||||||
|
@SerializedName("hasFullText")
|
||||||
|
val hasFullText: Boolean? = null,
|
||||||
|
@SerializedName("summary")
|
||||||
|
val summary: String? = null,
|
||||||
|
@SerializedName("publishedAt")
|
||||||
|
val publishedAt: String? = null,
|
||||||
|
@SerializedName("imageCached")
|
||||||
|
val imageCached: Boolean? = null,
|
||||||
) {
|
) {
|
||||||
fun toMomentItem(): MomentEntity {
|
fun toMomentItem(): MomentEntity {
|
||||||
return MomentEntity(
|
return MomentEntity(
|
||||||
@@ -75,14 +82,17 @@ data class Moment(
|
|||||||
authorId = user.id.toInt(),
|
authorId = user.id.toInt(),
|
||||||
liked = isLiked,
|
liked = isLiked,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
// 新闻相关字段
|
|
||||||
isNews = isNews,
|
isNews = isNews,
|
||||||
newsTitle = newsTitle ?: "",
|
newsTitle = newsTitle,
|
||||||
newsUrl = newsUrl ?: "",
|
newsUrl = newsUrl,
|
||||||
newsSource = newsSource ?: "",
|
newsSource = newsSource,
|
||||||
newsCategory = newsCategory ?: "",
|
newsCategory = newsCategory,
|
||||||
newsLanguage = newsLanguage ?: "",
|
newsLanguage = newsLanguage,
|
||||||
newsContent = newsContent ?: ""
|
newsContent = newsContent,
|
||||||
|
hasFullText = hasFullText,
|
||||||
|
summary = summary,
|
||||||
|
publishedAt = publishedAt,
|
||||||
|
imageCached = imageCached,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
package com.aiosman.ravenow.data
|
package com.aiosman.ravenow.data
|
||||||
|
|
||||||
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.CreateRoomRuleRequestBody
|
import com.aiosman.ravenow.entity.AgentEntity
|
||||||
import com.aiosman.ravenow.data.api.UpdateRoomRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.RoomRuleQuota
|
|
||||||
import com.aiosman.ravenow.data.api.RoomRule
|
|
||||||
import com.aiosman.ravenow.data.api.RoomRuleCreator
|
|
||||||
import com.aiosman.ravenow.entity.CreatorEntity
|
import com.aiosman.ravenow.entity.CreatorEntity
|
||||||
|
import com.aiosman.ravenow.entity.ProfileEntity
|
||||||
import com.aiosman.ravenow.entity.RoomEntity
|
import com.aiosman.ravenow.entity.RoomEntity
|
||||||
import com.aiosman.ravenow.entity.RoomRuleEntity
|
|
||||||
import com.aiosman.ravenow.entity.RoomRuleCreatorEntity
|
|
||||||
import com.aiosman.ravenow.entity.RoomRuleQuotaEntity
|
|
||||||
import com.aiosman.ravenow.entity.UsersEntity
|
import com.aiosman.ravenow.entity.UsersEntity
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
|
|
||||||
@@ -111,190 +106,6 @@ data class Users(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 房间规则相关服务
|
|
||||||
*/
|
|
||||||
interface RoomService {
|
|
||||||
/**
|
|
||||||
* 创建房间规则
|
|
||||||
* @param rule 规则内容,不能为空
|
|
||||||
* @param roomId 房间ID,与 trtcId 二选一
|
|
||||||
* @param trtcId TRTC房间ID,与 roomId 二选一
|
|
||||||
* @throws ServiceException 创建失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun createRoomRule(
|
|
||||||
rule: String,
|
|
||||||
roomId: Int? = null,
|
|
||||||
trtcId: String? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改房间规则
|
|
||||||
* @param id 规则ID
|
|
||||||
* @param rule 新的规则内容,不能为空
|
|
||||||
* @throws ServiceException 修改失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun updateRoomRule(
|
|
||||||
id: Int,
|
|
||||||
rule: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除房间规则
|
|
||||||
* @param id 规则ID
|
|
||||||
* @throws ServiceException 删除失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun deleteRoomRule(id: Int)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询房间规则列表
|
|
||||||
* @param roomId 房间ID,与 trtcId 二选一
|
|
||||||
* @param trtcId TRTC房间ID,与 roomId 二选一
|
|
||||||
* @param page 页码,默认 1
|
|
||||||
* @param pageSize 每页数量,默认 10
|
|
||||||
* @return 规则列表响应,包含分页信息和规则列表
|
|
||||||
* @throws ServiceException 查询失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun getRoomRuleList(
|
|
||||||
roomId: Int? = null,
|
|
||||||
trtcId: String? = null,
|
|
||||||
page: Int = 1,
|
|
||||||
pageSize: Int = 10
|
|
||||||
): ListContainer<RoomRuleEntity>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询规则配额使用情况
|
|
||||||
* @param roomId 房间ID,与 trtcId 二选一
|
|
||||||
* @param trtcId TRTC房间ID,与 roomId 二选一
|
|
||||||
* @return 规则配额信息
|
|
||||||
* @throws ServiceException 查询失败时抛出异常
|
|
||||||
*/
|
|
||||||
suspend fun getRoomRuleQuota(
|
|
||||||
roomId: Int? = null,
|
|
||||||
trtcId: String? = null
|
|
||||||
): RoomRuleQuotaEntity
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 房间规则服务实现类
|
|
||||||
*/
|
|
||||||
class RoomServiceImpl : RoomService {
|
|
||||||
override suspend fun createRoomRule(
|
|
||||||
rule: String,
|
|
||||||
roomId: Int?,
|
|
||||||
trtcId: String?
|
|
||||||
) {
|
|
||||||
val resp = ApiClient.api.createRoomRule(
|
|
||||||
CreateRoomRuleRequestBody(
|
|
||||||
rule = rule,
|
|
||||||
roomId = roomId,
|
|
||||||
trtcId = trtcId
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
throw ServiceException("创建房间规则失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun updateRoomRule(
|
|
||||||
id: Int,
|
|
||||||
rule: String
|
|
||||||
) {
|
|
||||||
val resp = ApiClient.api.updateRoomRule(
|
|
||||||
UpdateRoomRuleRequestBody(
|
|
||||||
id = id,
|
|
||||||
rule = rule
|
|
||||||
)
|
|
||||||
)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
throw ServiceException("修改房间规则失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun deleteRoomRule(id: Int) {
|
|
||||||
val resp = ApiClient.api.deleteRoomRule(id)
|
|
||||||
if (!resp.isSuccessful) {
|
|
||||||
throw ServiceException("删除房间规则失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getRoomRuleList(
|
|
||||||
roomId: Int?,
|
|
||||||
trtcId: String?,
|
|
||||||
page: Int,
|
|
||||||
pageSize: Int
|
|
||||||
): ListContainer<RoomRuleEntity> {
|
|
||||||
val resp = ApiClient.api.getRoomRuleList(
|
|
||||||
roomId = roomId,
|
|
||||||
trtcId = trtcId,
|
|
||||||
page = page,
|
|
||||||
pageSize = pageSize
|
|
||||||
)
|
|
||||||
val body = resp.body() ?: throw ServiceException("获取房间规则列表失败")
|
|
||||||
|
|
||||||
return ListContainer(
|
|
||||||
list = body.list.map { it.toRoomRuleEntity() },
|
|
||||||
page = body.page,
|
|
||||||
total = body.total,
|
|
||||||
pageSize = body.pageSize
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun getRoomRuleQuota(
|
|
||||||
roomId: Int?,
|
|
||||||
trtcId: String?
|
|
||||||
): RoomRuleQuotaEntity {
|
|
||||||
val resp = ApiClient.api.getRoomRuleQuota(
|
|
||||||
roomId = roomId,
|
|
||||||
trtcId = trtcId
|
|
||||||
)
|
|
||||||
val body = resp.body() ?: throw ServiceException("获取规则配额信息失败")
|
|
||||||
val data = body.data ?: throw ServiceException("规则配额数据为空")
|
|
||||||
|
|
||||||
return data.toRoomRuleQuotaEntity()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RoomRule 扩展函数,转换为 RoomRuleEntity
|
|
||||||
*/
|
|
||||||
fun RoomRule.toRoomRuleEntity(): RoomRuleEntity {
|
|
||||||
return RoomRuleEntity(
|
|
||||||
id = id,
|
|
||||||
rule = rule,
|
|
||||||
creator = creator?.toRoomRuleCreatorEntity(),
|
|
||||||
creatorType = creatorType,
|
|
||||||
roomId = roomId,
|
|
||||||
createdAt = createdAt,
|
|
||||||
updatedAt = updatedAt
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RoomRuleCreator 扩展函数,转换为 RoomRuleCreatorEntity
|
|
||||||
*/
|
|
||||||
fun RoomRuleCreator.toRoomRuleCreatorEntity(): RoomRuleCreatorEntity {
|
|
||||||
return RoomRuleCreatorEntity(
|
|
||||||
id = id,
|
|
||||||
nickname = nickname,
|
|
||||||
avatar = avatar
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RoomRuleQuota 扩展函数,转换为 RoomRuleQuotaEntity
|
|
||||||
*/
|
|
||||||
fun RoomRuleQuota.toRoomRuleQuotaEntity(): RoomRuleQuotaEntity {
|
|
||||||
return RoomRuleQuotaEntity(
|
|
||||||
baseMaxCount = baseMaxCount,
|
|
||||||
purchasedCount = purchasedCount,
|
|
||||||
totalMaxCount = totalMaxCount,
|
|
||||||
currentCount = currentCount,
|
|
||||||
remainingCount = remainingCount,
|
|
||||||
usagePercent = usagePercent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -97,8 +97,7 @@ class UserServiceImpl : UserService {
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
search = nickname,
|
search = nickname,
|
||||||
followerId = followerId,
|
followerId = followerId,
|
||||||
followingId = followingId,
|
followingId = followingId
|
||||||
includeAI = true
|
|
||||||
)
|
)
|
||||||
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
val body = resp.body() ?: throw ServiceException("Failed to get account")
|
||||||
return ListContainer<AccountProfileEntity>(
|
return ListContainer<AccountProfileEntity>(
|
||||||
|
|||||||
@@ -13,9 +13,11 @@ import retrofit2.Retrofit
|
|||||||
import retrofit2.converter.gson.GsonConverterFactory
|
import retrofit2.converter.gson.GsonConverterFactory
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
fun getSafeOkHttpClient(
|
fun getSafeOkHttpClient(
|
||||||
authInterceptor: AuthInterceptor? = null
|
authInterceptor: AuthInterceptor? = null,
|
||||||
|
timeoutSeconds: Long = 30
|
||||||
): OkHttpClient {
|
): OkHttpClient {
|
||||||
return OkHttpClient.Builder()
|
return OkHttpClient.Builder()
|
||||||
.apply {
|
.apply {
|
||||||
@@ -23,6 +25,9 @@ fun getSafeOkHttpClient(
|
|||||||
addInterceptor(it)
|
addInterceptor(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||||
|
.writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +61,7 @@ class AuthInterceptor() : Interceptor {
|
|||||||
val client = Retrofit.Builder()
|
val client = Retrofit.Builder()
|
||||||
.baseUrl(ApiClient.RETROFIT_URL)
|
.baseUrl(ApiClient.RETROFIT_URL)
|
||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.client(getSafeOkHttpClient())
|
.client(getSafeOkHttpClient(timeoutSeconds = 30))
|
||||||
.build()
|
.build()
|
||||||
.create(RaveNowAPI::class.java)
|
.create(RaveNowAPI::class.java)
|
||||||
|
|
||||||
@@ -75,7 +80,10 @@ object ApiClient {
|
|||||||
val RETROFIT_URL = "${BASE_API_URL}/"
|
val RETROFIT_URL = "${BASE_API_URL}/"
|
||||||
const val TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"
|
const val TIME_FORMAT = "yyyy-MM-dd HH:mm:ss"
|
||||||
private val okHttpClient: OkHttpClient by lazy {
|
private val okHttpClient: OkHttpClient by lazy {
|
||||||
getSafeOkHttpClient(authInterceptor = AuthInterceptor())
|
getSafeOkHttpClient(authInterceptor = AuthInterceptor(), timeoutSeconds = 30)
|
||||||
|
}
|
||||||
|
private val longTimeoutOkHttpClient: OkHttpClient by lazy {
|
||||||
|
getSafeOkHttpClient(authInterceptor = AuthInterceptor(), timeoutSeconds = 120)
|
||||||
}
|
}
|
||||||
private val retrofit: Retrofit by lazy {
|
private val retrofit: Retrofit by lazy {
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
@@ -84,9 +92,19 @@ object ApiClient {
|
|||||||
.addConverterFactory(GsonConverterFactory.create())
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
private val longTimeoutRetrofit: Retrofit by lazy {
|
||||||
|
Retrofit.Builder()
|
||||||
|
.baseUrl(RETROFIT_URL)
|
||||||
|
.client(longTimeoutOkHttpClient)
|
||||||
|
.addConverterFactory(GsonConverterFactory.create())
|
||||||
|
.build()
|
||||||
|
}
|
||||||
val api: RaveNowAPI by lazy {
|
val api: RaveNowAPI by lazy {
|
||||||
retrofit.create(RaveNowAPI::class.java)
|
retrofit.create(RaveNowAPI::class.java)
|
||||||
}
|
}
|
||||||
|
val longTimeoutApi: RaveNowAPI by lazy {
|
||||||
|
longTimeoutRetrofit.create(RaveNowAPI::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
fun formatTime(date: Date): String {
|
fun formatTime(date: Date): String {
|
||||||
val dateFormat = SimpleDateFormat(TIME_FORMAT, Locale.getDefault())
|
val dateFormat = SimpleDateFormat(TIME_FORMAT, Locale.getDefault())
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -7,13 +7,18 @@ import com.aiosman.ravenow.data.Agent
|
|||||||
import com.aiosman.ravenow.data.ListContainer
|
import com.aiosman.ravenow.data.ListContainer
|
||||||
import com.aiosman.ravenow.data.AgentService
|
import com.aiosman.ravenow.data.AgentService
|
||||||
import com.aiosman.ravenow.data.DataContainer
|
import com.aiosman.ravenow.data.DataContainer
|
||||||
|
import com.aiosman.ravenow.data.MomentService
|
||||||
import com.aiosman.ravenow.data.ServiceException
|
import com.aiosman.ravenow.data.ServiceException
|
||||||
import com.aiosman.ravenow.data.UploadImage
|
import com.aiosman.ravenow.data.UploadImage
|
||||||
|
import com.aiosman.ravenow.data.UserService
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
|
import okhttp3.MediaType
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.MultipartBody
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.asRequestBody
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
import retrofit2.http.Part
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
@@ -155,7 +160,7 @@ class AgentBackend {
|
|||||||
return if (authorId != null) {
|
return if (authorId != null) {
|
||||||
// getAgent 返回 DataContainer<ListContainer<Agent>>
|
// getAgent 返回 DataContainer<ListContainer<Agent>>
|
||||||
val dataContainer =
|
val dataContainer =
|
||||||
body as DataContainer<ListContainer<Agent>>
|
body as com.aiosman.ravenow.data.DataContainer<com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>>
|
||||||
val listContainer = dataContainer.data
|
val listContainer = dataContainer.data
|
||||||
ListContainer(
|
ListContainer(
|
||||||
total = listContainer.total,
|
total = listContainer.total,
|
||||||
@@ -166,7 +171,7 @@ class AgentBackend {
|
|||||||
} else {
|
} else {
|
||||||
// getMyAgent 返回 ListContainer<Agent>
|
// getMyAgent 返回 ListContainer<Agent>
|
||||||
val listContainer =
|
val listContainer =
|
||||||
body as ListContainer<Agent>
|
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
|
||||||
ListContainer(
|
ListContainer(
|
||||||
total = listContainer.total,
|
total = listContainer.total,
|
||||||
page = pageNumber,
|
page = pageNumber,
|
||||||
@@ -246,7 +251,7 @@ class AgentLoader : DataLoader<AgentEntity, AgentLoaderExtraArgs>() {
|
|||||||
} else {
|
} else {
|
||||||
// getMyAgent 返回 ListContainer<Agent>
|
// getMyAgent 返回 ListContainer<Agent>
|
||||||
val listContainer =
|
val listContainer =
|
||||||
body as ListContainer<Agent>
|
body as com.aiosman.ravenow.data.ListContainer<com.aiosman.ravenow.data.Agent>
|
||||||
ListContainer(
|
ListContainer(
|
||||||
list = listContainer.list.map { it.toAgentEntity() },
|
list = listContainer.list.map { it.toAgentEntity() },
|
||||||
total = listContainer.total,
|
total = listContainer.total,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import com.aiosman.ravenow.data.ListContainer
|
|||||||
abstract class DataLoader<T,ET> {
|
abstract class DataLoader<T,ET> {
|
||||||
var list: MutableList<T> = mutableListOf()
|
var list: MutableList<T> = mutableListOf()
|
||||||
var page by mutableStateOf(1)
|
var page by mutableStateOf(1)
|
||||||
var total by mutableStateOf(0L)
|
var total by mutableStateOf(0)
|
||||||
var pageSize by mutableStateOf(10)
|
var pageSize by mutableStateOf(10)
|
||||||
var hasNext by mutableStateOf(true)
|
var hasNext by mutableStateOf(true)
|
||||||
var isLoading by mutableStateOf(false)
|
var isLoading by mutableStateOf(false)
|
||||||
|
|||||||
@@ -300,20 +300,34 @@ data class MomentEntity(
|
|||||||
var relMoment: MomentEntity? = null,
|
var relMoment: MomentEntity? = null,
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
var isFavorite: Boolean = false,
|
var isFavorite: Boolean = false,
|
||||||
// 新闻相关字段
|
// 是否为新闻
|
||||||
val isNews: Boolean = false,
|
val isNews: Boolean? = null,
|
||||||
val newsTitle: String = "",
|
// 新闻标题
|
||||||
val newsUrl: String = "",
|
val newsTitle: String? = null,
|
||||||
val newsSource: String = "",
|
// 新闻链接
|
||||||
val newsCategory: String = "",
|
val newsUrl: String? = null,
|
||||||
val newsLanguage: String = "",
|
// 新闻来源
|
||||||
val newsContent: String = ""
|
val newsSource: String? = null,
|
||||||
|
// 新闻分类
|
||||||
|
val newsCategory: String? = null,
|
||||||
|
// 新闻语言
|
||||||
|
val newsLanguage: String? = null,
|
||||||
|
// 新闻内容
|
||||||
|
val newsContent: String? = null,
|
||||||
|
// 是否有完整文本
|
||||||
|
val hasFullText: Boolean? = null,
|
||||||
|
// 摘要
|
||||||
|
val summary: String? = null,
|
||||||
|
// 发布时间
|
||||||
|
val publishedAt: String? = null,
|
||||||
|
// 图片是否已缓存
|
||||||
|
val imageCached: Boolean? = null
|
||||||
)
|
)
|
||||||
class MomentLoaderExtraArgs(
|
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
|
val newsOnly: Boolean? = false
|
||||||
)
|
)
|
||||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||||
override suspend fun fetchData(
|
override suspend fun fetchData(
|
||||||
@@ -365,18 +379,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)
|
||||||
|
|||||||
@@ -3,6 +3,10 @@ package com.aiosman.ravenow.entity
|
|||||||
import com.aiosman.ravenow.data.ListContainer
|
import com.aiosman.ravenow.data.ListContainer
|
||||||
import com.aiosman.ravenow.data.ServiceException
|
import com.aiosman.ravenow.data.ServiceException
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
|
import okhttp3.MultipartBody
|
||||||
|
import okhttp3.RequestBody.Companion.asRequestBody
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 群聊房间
|
* 群聊房间
|
||||||
@@ -52,40 +56,6 @@ data class ProfileEntity(
|
|||||||
val aiAccount: Boolean,
|
val aiAccount: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
|
||||||
* 房间规则创建者信息
|
|
||||||
*/
|
|
||||||
data class RoomRuleCreatorEntity(
|
|
||||||
val id: Int,
|
|
||||||
val nickname: String,
|
|
||||||
val avatar: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 房间规则详情
|
|
||||||
*/
|
|
||||||
data class RoomRuleEntity(
|
|
||||||
val id: Int,
|
|
||||||
val rule: String,
|
|
||||||
val creator: RoomRuleCreatorEntity?,
|
|
||||||
val creatorType: String,
|
|
||||||
val roomId: Int,
|
|
||||||
val createdAt: String,
|
|
||||||
val updatedAt: String
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 房间规则配额信息
|
|
||||||
*/
|
|
||||||
data class RoomRuleQuotaEntity(
|
|
||||||
val baseMaxCount: Int,
|
|
||||||
val purchasedCount: Int,
|
|
||||||
val totalMaxCount: Int,
|
|
||||||
val currentCount: Int,
|
|
||||||
val remainingCount: Int,
|
|
||||||
val usagePercent: Double
|
|
||||||
)
|
|
||||||
|
|
||||||
class RoomLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
|
class RoomLoader : DataLoader<AgentEntity,AgentLoaderExtraArgs>() {
|
||||||
override suspend fun fetchData(
|
override suspend fun fetchData(
|
||||||
page: Int,
|
page: Int,
|
||||||
|
|||||||
@@ -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,40 +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
|
|
||||||
}
|
|
||||||
|
|
||||||
// ===================== 用户本地扩展信息 =====================
|
|
||||||
// 后端暂未提供 MBTI 与星座字段,使用本地持久化按用户维度进行存储
|
|
||||||
private fun mbtiKey(userId: Int) = "mbti_user_$userId"
|
|
||||||
private fun zodiacKey(userId: Int) = "zodiac_user_$userId"
|
|
||||||
|
|
||||||
fun getUserMbti(userId: Int): String? {
|
|
||||||
return sharedPreferences.getString(mbtiKey(userId), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUserMbti(userId: Int, mbti: String?) {
|
|
||||||
sharedPreferences.edit().apply {
|
|
||||||
if (mbti.isNullOrEmpty()) remove(mbtiKey(userId)) else putString(mbtiKey(userId), mbti)
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getUserZodiac(userId: Int): String? {
|
|
||||||
return sharedPreferences.getString(zodiacKey(userId), null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setUserZodiac(userId: Int, zodiac: String?) {
|
|
||||||
sharedPreferences.edit().apply {
|
|
||||||
if (zodiac.isNullOrEmpty()) remove(zodiacKey(userId)) else putString(zodiacKey(userId), zodiac)
|
|
||||||
}.apply()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -35,20 +35,17 @@ import com.aiosman.ravenow.LocalSharedTransitionScope
|
|||||||
import com.aiosman.ravenow.ui.about.AboutScreen
|
import com.aiosman.ravenow.ui.about.AboutScreen
|
||||||
import com.aiosman.ravenow.ui.account.AccountEditScreen2
|
import com.aiosman.ravenow.ui.account.AccountEditScreen2
|
||||||
import com.aiosman.ravenow.ui.account.AccountSetting
|
import com.aiosman.ravenow.ui.account.AccountSetting
|
||||||
import com.aiosman.ravenow.ui.account.MbtiSelectScreen
|
|
||||||
import com.aiosman.ravenow.ui.account.RemoveAccountScreen
|
import com.aiosman.ravenow.ui.account.RemoveAccountScreen
|
||||||
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
import com.aiosman.ravenow.ui.account.ResetPasswordScreen
|
||||||
import com.aiosman.ravenow.ui.account.ZodiacSelectScreen
|
|
||||||
import com.aiosman.ravenow.ui.agent.AddAgentScreen
|
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.agent.CreateAgentV2Screen
|
||||||
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
|
||||||
@@ -74,7 +71,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,
|
||||||
@@ -109,7 +105,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")
|
||||||
@@ -121,9 +116,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")
|
|
||||||
data object MbtiSelect : NavigationRoute("MbtiSelect")
|
|
||||||
data object ZodiacSelect : NavigationRoute("ZodiacSelect")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -425,12 +417,6 @@ fun NavigationController(
|
|||||||
composable(route = NavigationRoute.RemoveAccountScreen.route) {
|
composable(route = NavigationRoute.RemoveAccountScreen.route) {
|
||||||
RemoveAccountScreen()
|
RemoveAccountScreen()
|
||||||
}
|
}
|
||||||
composable(route = NavigationRoute.MbtiSelect.route) {
|
|
||||||
MbtiSelectScreen()
|
|
||||||
}
|
|
||||||
composable(route = NavigationRoute.ZodiacSelect.route) {
|
|
||||||
ZodiacSelectScreen()
|
|
||||||
}
|
|
||||||
composable(route = NavigationRoute.VipSelPage.route) {
|
composable(route = NavigationRoute.VipSelPage.route) {
|
||||||
VipSelPage()
|
VipSelPage()
|
||||||
}
|
}
|
||||||
@@ -503,14 +489,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 },
|
||||||
@@ -567,7 +545,7 @@ fun NavigationController(
|
|||||||
composable(
|
composable(
|
||||||
route = NavigationRoute.AddAgent.route,
|
route = NavigationRoute.AddAgent.route,
|
||||||
) {
|
) {
|
||||||
AddAgentScreen()
|
CreateAgentV2Screen()
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(
|
composable(
|
||||||
@@ -613,13 +591,6 @@ fun NavigationController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(route = NavigationRoute.NotificationScreen.route) {
|
|
||||||
CompositionLocalProvider(
|
|
||||||
LocalAnimatedContentScope provides this,
|
|
||||||
) {
|
|
||||||
NotificationScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -645,7 +616,6 @@ fun Navigation(
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = startDestination
|
startDestination = startDestination
|
||||||
)
|
)
|
||||||
AgentCreatedSuccessIndicator()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,6 @@ object AccountEditViewModel : ViewModel() {
|
|||||||
var croppedBitmap by mutableStateOf<Bitmap?>(null)
|
var croppedBitmap by mutableStateOf<Bitmap?>(null)
|
||||||
var isUpdating by mutableStateOf(false)
|
var isUpdating by mutableStateOf(false)
|
||||||
var isLoading by mutableStateOf(false)
|
var isLoading by mutableStateOf(false)
|
||||||
// 本地扩展字段
|
|
||||||
var mbti by mutableStateOf<String?>(null)
|
|
||||||
var zodiac by mutableStateOf<String?>(null)
|
|
||||||
suspend fun reloadProfile(updateTrtcProfile:Boolean = false) {
|
suspend fun reloadProfile(updateTrtcProfile:Boolean = false) {
|
||||||
Log.d("AccountEditViewModel", "reloadProfile: 开始加载用户资料")
|
Log.d("AccountEditViewModel", "reloadProfile: 开始加载用户资料")
|
||||||
isLoading = true
|
isLoading = true
|
||||||
@@ -41,12 +38,6 @@ object AccountEditViewModel : ViewModel() {
|
|||||||
bio = it.bio
|
bio = it.bio
|
||||||
// 清除之前裁剪的图片
|
// 清除之前裁剪的图片
|
||||||
croppedBitmap = null
|
croppedBitmap = null
|
||||||
// 读取本地扩展字段
|
|
||||||
try {
|
|
||||||
val uid = it.id // 使用 profile 的 id,确保非空
|
|
||||||
mbti = com.aiosman.ravenow.AppStore.getUserMbti(uid)
|
|
||||||
zodiac = com.aiosman.ravenow.AppStore.getUserZodiac(uid)
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
if (updateTrtcProfile) {
|
if (updateTrtcProfile) {
|
||||||
TrtcHelper.updateTrtcProfile(
|
TrtcHelper.updateTrtcProfile(
|
||||||
it.nickName,
|
it.nickName,
|
||||||
@@ -93,13 +84,6 @@ object AccountEditViewModel : ViewModel() {
|
|||||||
nickName = newName,
|
nickName = newName,
|
||||||
bio = cleanBio
|
bio = cleanBio
|
||||||
)
|
)
|
||||||
// 保存本地扩展字段
|
|
||||||
try {
|
|
||||||
profile?.id?.let { uid ->
|
|
||||||
com.aiosman.ravenow.AppStore.setUserMbti(uid, mbti)
|
|
||||||
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiac)
|
|
||||||
}
|
|
||||||
} catch (_: Exception) { }
|
|
||||||
// 刷新用户资料
|
// 刷新用户资料
|
||||||
reloadProfile()
|
reloadProfile()
|
||||||
// 刷新个人资料页面的用户资料
|
// 刷新个人资料页面的用户资料
|
||||||
|
|||||||
@@ -1,177 +1,117 @@
|
|||||||
package com.aiosman.ravenow.ui.account
|
package com.aiosman.ravenow.ui.account
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
import android.widget.Toast
|
||||||
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.Spacer
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
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
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
import androidx.compose.ui.platform.LocalContext
|
||||||
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.AppState
|
||||||
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
|
import com.aiosman.ravenow.Messaging
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
|
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||||
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.ActionButton
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.ravenow.ui.index.NavItem
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
private object AccountSettingConstants {
|
|
||||||
const val BACK_BUTTON_SIZE = 36
|
|
||||||
const val BACK_BUTTON_ICON_SIZE = 24
|
|
||||||
const val BACK_BUTTON_START_PADDING = 19
|
|
||||||
const val OPTION_ITEM_HEIGHT = 56
|
|
||||||
const val OPTION_ITEM_ICON_SIZE = 24
|
|
||||||
const val OPTION_ITEM_HORIZONTAL_PADDING = 16
|
|
||||||
const val OPTION_ITEM_ICON_TEXT_SPACING = 12
|
|
||||||
const val OPTION_ITEM_TEXT_SIZE = 17
|
|
||||||
const val HEADER_VERTICAL_PADDING = 16
|
|
||||||
const val TITLE_OFFSET_X = 19
|
|
||||||
const val CARD_CORNER_RADIUS = 16
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun CircularBackButton(
|
|
||||||
onClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
Box(
|
|
||||||
modifier = modifier
|
|
||||||
.size(AccountSettingConstants.BACK_BUTTON_SIZE.dp)
|
|
||||||
.background(
|
|
||||||
color = appColors.secondaryBackground,
|
|
||||||
shape = CircleShape
|
|
||||||
)
|
|
||||||
.noRippleClickable { onClick() },
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
|
||||||
contentDescription = "返回",
|
|
||||||
modifier = Modifier.size(AccountSettingConstants.BACK_BUTTON_ICON_SIZE.dp),
|
|
||||||
colorFilter = ColorFilter.tint(appColors.text)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SecurityOptionItem(
|
|
||||||
iconRes: Int,
|
|
||||||
label: String,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
applyColorFilter: Boolean = true
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(AccountSettingConstants.OPTION_ITEM_HEIGHT.dp)
|
|
||||||
.padding(horizontal = AccountSettingConstants.OPTION_ITEM_HORIZONTAL_PADDING.dp)
|
|
||||||
.noRippleClickable { onClick() },
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = iconRes),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp),
|
|
||||||
colorFilter = if (applyColorFilter) ColorFilter.tint(appColors.text) else null
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(start = AccountSettingConstants.OPTION_ITEM_ICON_TEXT_SPACING.dp)
|
|
||||||
.weight(1f),
|
|
||||||
color = appColors.text,
|
|
||||||
fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rave_now_nav_right),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(AccountSettingConstants.OPTION_ITEM_ICON_SIZE.dp),
|
|
||||||
colorFilter = ColorFilter.tint(appColors.secondaryText)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountSetting() {
|
fun AccountSetting() {
|
||||||
val appColors = LocalAppTheme.current
|
val appColors = LocalAppTheme.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val context = LocalContext.current
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(appColors.background),
|
.background(appColors.background),
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
|
|
||||||
// 顶部标题栏
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = AccountSettingConstants.HEADER_VERTICAL_PADDING.dp)
|
|
||||||
) {
|
) {
|
||||||
CircularBackButton(
|
NoticeScreenHeader(
|
||||||
onClick = { navController.navigateUp() },
|
title = stringResource(R.string.account_and_security),
|
||||||
modifier = Modifier.padding(start = AccountSettingConstants.BACK_BUTTON_START_PADDING.dp)
|
moreIcon = false
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.account_and_security),
|
|
||||||
fontWeight = FontWeight.W800,
|
|
||||||
fontSize = AccountSettingConstants.OPTION_ITEM_TEXT_SIZE.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Center)
|
|
||||||
.offset(x = AccountSettingConstants.TITLE_OFFSET_X.dp)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 安全选项卡片
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(start = 24.dp)
|
||||||
.fillMaxWidth()
|
|
||||||
.background(
|
|
||||||
color = appColors.background,
|
|
||||||
shape = RoundedCornerShape(AccountSettingConstants.CARD_CORNER_RADIUS.dp)
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
SecurityOptionItem(
|
Box(
|
||||||
iconRes = R.mipmap.icons_padlock,
|
modifier = Modifier
|
||||||
label = stringResource(R.string.change_password),
|
.fillMaxWidth()
|
||||||
onClick = { navController.navigate(NavigationRoute.ChangePasswordScreen.route) }
|
.padding(vertical = 8.dp)
|
||||||
)
|
) {
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.mipmap.rider_pro_change_password,
|
||||||
|
label = stringResource(R.string.change_password),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
navController.navigate(NavigationRoute.ChangePasswordScreen.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
SecurityOptionItem(
|
// 分割线
|
||||||
iconRes = R.mipmap.icons_block,
|
Box(
|
||||||
label = stringResource(R.string.blocked_users),
|
modifier = Modifier
|
||||||
onClick = {
|
.fillMaxWidth()
|
||||||
// TODO: 导航到屏蔽用户页面
|
.height(1.dp)
|
||||||
}
|
.background(appColors.divider)
|
||||||
)
|
)
|
||||||
|
Box(
|
||||||
SecurityOptionItem(
|
modifier = Modifier
|
||||||
iconRes = R.mipmap.icons_remove,
|
.fillMaxWidth()
|
||||||
label = stringResource(R.string.remove_account),
|
.padding(vertical = 8.dp)
|
||||||
onClick = { navController.navigate(NavigationRoute.RemoveAccountScreen.route) },
|
) {
|
||||||
applyColorFilter = false
|
NavItem(
|
||||||
|
iconRes = R.drawable.rider_pro_moment_delete,
|
||||||
|
label = stringResource(R.string.remove_account),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
navController.navigate(NavigationRoute.RemoveAccountScreen.route)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(1.dp)
|
||||||
|
.background(appColors.divider)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,141 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.account
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
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.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
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.runtime.remember
|
|
||||||
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.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Check
|
|
||||||
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.comment.NoticeScreenHeader
|
|
||||||
|
|
||||||
// MBTI类型列表
|
|
||||||
val MBTI_TYPES = listOf(
|
|
||||||
"INTJ", "INTP", "ENTJ", "ENTP",
|
|
||||||
"INFJ", "INFP", "ENFJ", "ENFP",
|
|
||||||
"ISTJ", "ISFJ", "ESTJ", "ESFJ",
|
|
||||||
"ISTP", "ISFP", "ESTP", "ESFP"
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MbtiSelectScreen() {
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val model = AccountEditViewModel
|
|
||||||
val currentMbti = model.mbti
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(appColors.profileBackground)
|
|
||||||
) {
|
|
||||||
// 头部
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
NoticeScreenHeader(
|
|
||||||
title = stringResource(R.string.choose_mbti),
|
|
||||||
moreIcon = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
items(MBTI_TYPES) { mbti ->
|
|
||||||
MBTIItem(
|
|
||||||
mbti = mbti,
|
|
||||||
isSelected = mbti == currentMbti,
|
|
||||||
onClick = {
|
|
||||||
model.mbti = mbti
|
|
||||||
// 立即保存到本地存储,确保选择后立即生效
|
|
||||||
AppState.UserId?.let { uid ->
|
|
||||||
com.aiosman.ravenow.AppStore.setUserMbti(uid, mbti)
|
|
||||||
}
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MBTIItem(
|
|
||||||
mbti: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(if (isSelected) appColors.main.copy(alpha = 0.1f) else Color.White)
|
|
||||||
.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
onClick()
|
|
||||||
}
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = mbti,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = if (isSelected) appColors.main else appColors.text,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Check,
|
|
||||||
contentDescription = "Selected",
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = appColors.main
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.account
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
||||||
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.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
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.remember
|
|
||||||
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.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Check
|
|
||||||
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.comment.NoticeScreenHeader
|
|
||||||
|
|
||||||
// 星座资源ID列表
|
|
||||||
val ZODIAC_SIGN_RES_IDS = listOf(
|
|
||||||
R.string.zodiac_aries,
|
|
||||||
R.string.zodiac_taurus,
|
|
||||||
R.string.zodiac_gemini,
|
|
||||||
R.string.zodiac_cancer,
|
|
||||||
R.string.zodiac_leo,
|
|
||||||
R.string.zodiac_virgo,
|
|
||||||
R.string.zodiac_libra,
|
|
||||||
R.string.zodiac_scorpio,
|
|
||||||
R.string.zodiac_sagittarius,
|
|
||||||
R.string.zodiac_capricorn,
|
|
||||||
R.string.zodiac_aquarius,
|
|
||||||
R.string.zodiac_pisces
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据存储的星座字符串(可能是任何语言)找到对应的资源ID
|
|
||||||
* 如果找不到,返回null
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun findZodiacResId(storedZodiac: String?): Int? {
|
|
||||||
if (storedZodiac.isNullOrEmpty()) return null
|
|
||||||
|
|
||||||
// 尝试在所有语言的资源中查找匹配
|
|
||||||
ZODIAC_SIGN_RES_IDS.forEachIndexed { index, resId ->
|
|
||||||
val zodiacText = stringResource(resId)
|
|
||||||
if (zodiacText == storedZodiac) {
|
|
||||||
return resId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果找不到精确匹配,尝试通过资源ID索引查找(兼容旧数据)
|
|
||||||
// 这里可以根据需要添加更多兼容逻辑
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ZodiacSelectScreen() {
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val model = AccountEditViewModel
|
|
||||||
val currentZodiacResId = findZodiacResId(model.zodiac)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(appColors.profileBackground)
|
|
||||||
) {
|
|
||||||
// 头部
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
NoticeScreenHeader(
|
|
||||||
title = stringResource(R.string.choose_zodiac),
|
|
||||||
moreIcon = false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 列表
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
|
||||||
) {
|
|
||||||
items(ZODIAC_SIGN_RES_IDS.size) { index ->
|
|
||||||
val zodiacResId = ZODIAC_SIGN_RES_IDS[index]
|
|
||||||
val zodiacText = stringResource(zodiacResId)
|
|
||||||
ZodiacItem(
|
|
||||||
zodiac = zodiacText,
|
|
||||||
zodiacResId = zodiacResId,
|
|
||||||
isSelected = zodiacResId == currentZodiacResId,
|
|
||||||
onClick = {
|
|
||||||
// 保存当前语言的星座文本
|
|
||||||
model.zodiac = zodiacText
|
|
||||||
// 立即保存到本地存储,确保选择后立即生效
|
|
||||||
AppState.UserId?.let { uid ->
|
|
||||||
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiacText)
|
|
||||||
}
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun ZodiacItem(
|
|
||||||
zodiac: String,
|
|
||||||
zodiacResId: Int,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(if (isSelected) appColors.main.copy(alpha = 0.1f) else Color.White)
|
|
||||||
.clickable(
|
|
||||||
indication = null,
|
|
||||||
interactionSource = remember { MutableInteractionSource() }
|
|
||||||
) {
|
|
||||||
onClick()
|
|
||||||
}
|
|
||||||
.padding(16.dp)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = zodiac,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = if (isSelected) appColors.main else appColors.text,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (isSelected) {
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Check,
|
|
||||||
contentDescription = "Selected",
|
|
||||||
modifier = Modifier.size(20.dp),
|
|
||||||
tint = appColors.main
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,29 +1,19 @@
|
|||||||
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.border
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.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.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.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
import androidx.compose.material.icons.filled.ArrowForward
|
|
||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.Edit as EditIcon
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@@ -35,14 +25,10 @@ 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.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
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.platform.LocalContext
|
||||||
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.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
@@ -50,35 +36,23 @@ import com.aiosman.ravenow.LocalAppTheme
|
|||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
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.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.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.ravenow.ui.composables.form.FormTextInput
|
||||||
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
||||||
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
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.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
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.graphics.SolidColor
|
|
||||||
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
|
||||||
@@ -87,27 +61,14 @@ 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", "")
|
||||||
model.name = cleanValue
|
model.name = cleanValue
|
||||||
usernameError = when {
|
usernameError = when {
|
||||||
cleanValue.trim().isEmpty() -> context.getString(R.string.error_nickname_empty)
|
cleanValue.trim().isEmpty() -> "昵称不能为空"
|
||||||
cleanValue.length < 3 -> context.getString(R.string.error_nickname_too_short)
|
cleanValue.length < 3 -> "昵称长度不能小于3"
|
||||||
cleanValue.length > 20 -> context.getString(R.string.error_nickname_too_long)
|
cleanValue.length > 20 -> "昵称长度不能大于20"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -119,7 +80,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
|||||||
val cleanValue = value.replace("\n", "").replace("\r", "")
|
val cleanValue = value.replace("\n", "").replace("\r", "")
|
||||||
model.bio = cleanValue
|
model.bio = cleanValue
|
||||||
bioError = when {
|
bioError = when {
|
||||||
cleanValue.length > 100 -> context.getString(R.string.error_bio_too_long)
|
cleanValue.length > 100 -> "个人简介长度不能大于100"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -145,28 +106,131 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
|||||||
// 确保显示的是当前登录用户的信息,而不是之前用户的缓存数据
|
// 确保显示的是当前登录用户的信息,而不是之前用户的缓存数据
|
||||||
model.reloadProfile()
|
model.reloadProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置状态栏为透明,使用浅色图标(因为顶部背景是深色图片)
|
|
||||||
val systemUiController = rememberSystemUiController()
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
systemUiController.setStatusBarColor(Color.Transparent, darkIcons = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
StatusBarMaskLayout(
|
StatusBarMaskLayout(
|
||||||
modifier = Modifier.background(Color(0xFFFAF9FB)),
|
modifier = Modifier.background(color = appColors.background).padding(horizontal = 16.dp),
|
||||||
darkIcons = false, // 浅色图标(白色),因为顶部背景是深色
|
darkIcons = !AppState.darkMode,
|
||||||
maskBoxBackgroundColor = Color.Transparent
|
maskBoxBackgroundColor = appColors.background
|
||||||
) {
|
) {
|
||||||
Box(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(Color(0xFFFAF9FB))
|
.background(color = appColors.background),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
|
//StatusBarSpacer()
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(horizontal = 0.dp, vertical = 16.dp)
|
||||||
|
) {
|
||||||
|
NoticeScreenHeader(
|
||||||
|
title = stringResource(R.string.edit_profile),
|
||||||
|
moreIcon = false
|
||||||
|
) {
|
||||||
|
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.debouncedClickable(
|
||||||
|
enabled = validate() && !model.isUpdating,
|
||||||
|
debounceTime = 1000L
|
||||||
|
) {
|
||||||
|
if (validate() && !model.isUpdating) {
|
||||||
|
model.viewModelScope.launch {
|
||||||
|
model.isUpdating = true
|
||||||
|
model.updateUserProfile(context)
|
||||||
|
model.viewModelScope.launch(Dispatchers.Main) {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
|
model.isUpdating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = "保存",
|
||||||
|
tint = if (validate() && !model.isUpdating) appColors.text else appColors.nonActiveText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(44.dp))
|
||||||
|
|
||||||
|
// 显示内容或加载状态
|
||||||
|
Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}")
|
||||||
when {
|
when {
|
||||||
|
model.profile != null -> {
|
||||||
|
Log.d("AccountEditScreen2", "显示用户资料内容")
|
||||||
|
// 有数据时显示内容
|
||||||
|
val it = model.profile!!
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(88.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context,
|
||||||
|
model.croppedBitmap ?: it.avatar,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(88.dp)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(88.dp)
|
||||||
|
),
|
||||||
|
contentDescription = "",
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(appColors.main)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.debouncedClickable(
|
||||||
|
debounceTime = 800L
|
||||||
|
) {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.ImageCrop.route)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Add,
|
||||||
|
contentDescription = "Add",
|
||||||
|
tint = Color.White,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(58.dp))
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
FormTextInput(
|
||||||
|
value = model.name,
|
||||||
|
label = stringResource(R.string.nickname),
|
||||||
|
hint = "Input nickname",
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
error = usernameError
|
||||||
|
) { value ->
|
||||||
|
onNicknameChange(value)
|
||||||
|
}
|
||||||
|
FormTextInput(
|
||||||
|
value = model.bio,
|
||||||
|
label = stringResource(R.string.bio),
|
||||||
|
hint = "Input bio",
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
error = bioError
|
||||||
|
) { value ->
|
||||||
|
onBioChange(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
model.isLoading -> {
|
model.isLoading -> {
|
||||||
|
Log.d("AccountEditScreen2", "显示加载指示器")
|
||||||
// 加载中状态
|
// 加载中状态
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.CircularProgressIndicator(
|
androidx.compose.material3.CircularProgressIndicator(
|
||||||
@@ -174,465 +238,24 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model.profile != null -> {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
// 顶部背景区域(圆角在底部,覆盖状态栏)
|
|
||||||
val banner = model.profile?.banner
|
|
||||||
val statusBarPadding = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.offset(y = -statusBarPadding)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(402.dp)
|
|
||||||
.height(206.dp)
|
|
||||||
.align(Alignment.TopCenter)
|
|
||||||
.clip(RoundedCornerShape(bottomStart = 32.dp, bottomEnd = 32.dp))
|
|
||||||
) {
|
|
||||||
if (banner != null) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
context = context,
|
|
||||||
imageUrl = banner,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentDescription = "Banner",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(Color.Gray.copy(alpha = 0.2f))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更换封面按钮(位于壁纸右下方)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
.padding(end = 16.dp, bottom = 20.dp)
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(Color(0x5C7D7C80)) // RGB(125, 120, 128, 0.36)
|
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
Intent(Intent.ACTION_PICK).apply {
|
|
||||||
type = "image/*"
|
|
||||||
pickBannerImageLauncher.launch(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
// 更换封面图标
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(
|
|
||||||
id = if (AppState.darkMode) {
|
|
||||||
// TODO: 添加更换封面暗色模式图标
|
|
||||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
|
||||||
} else {
|
|
||||||
// TODO: 添加更换封面亮色模式图标
|
|
||||||
R.mipmap.fengm // 临时占位,需替换为实际图标
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.change_cover),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 状态栏区域(时间、信号、电池)
|
|
||||||
// 这里使用系统状态栏,不单独实现
|
|
||||||
|
|
||||||
// 导航栏区域
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.height(statusBarPadding + 12.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(bottom = 12.dp)
|
|
||||||
) {
|
|
||||||
// 返回按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(44.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(Color.White.copy(alpha = 0.3f))
|
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
.align(Alignment.CenterStart),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
|
||||||
contentDescription = "Back",
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color.White)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 标题
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.edit_profile_info),
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = Color.White,
|
|
||||||
modifier = Modifier.align(Alignment.Center)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 用户头像区域(距离顶部-50dp,包含状态栏高度,左右居中)
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.offset(y = (-50).dp - statusBarPadding),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
val it = model.profile!!
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.size(96.dp),
|
|
||||||
contentAlignment = Alignment.BottomEnd
|
|
||||||
) {
|
|
||||||
// 头像
|
|
||||||
CustomAsyncImage(
|
|
||||||
context,
|
|
||||||
model.croppedBitmap ?: it.avatar,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(96.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.border(2.4.dp, Color(0xFFFAF9FB), CircleShape),
|
|
||||||
contentDescription = "",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
|
|
||||||
// 编辑头像按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.size(28.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(Color(0xFF110C13))
|
|
||||||
.border(2.dp, Color.White, CircleShape)
|
|
||||||
.debouncedClickable(debounceTime = 800L) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.ImageCrop.route)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(
|
|
||||||
id = if (AppState.darkMode) {
|
|
||||||
// TODO: 添加编辑头像暗色模式图标
|
|
||||||
R.mipmap.frame_4 // 临时占位,需替换为实际图标
|
|
||||||
} else {
|
|
||||||
// TODO: 添加编辑头像亮色模式图标
|
|
||||||
R.mipmap.bi // 临时占位,需替换为实际图标
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = "Edit Avatar",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.offset(y = (-74).dp)//
|
|
||||||
) {
|
|
||||||
// 昵称输入框
|
|
||||||
ProfileInfoCard(
|
|
||||||
label = stringResource(R.string.nickname),
|
|
||||||
value = model.name,
|
|
||||||
placeholder = "Value",
|
|
||||||
onValueChange = { onNicknameChange(it) },
|
|
||||||
isMultiline = false
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 个人简介输入框
|
|
||||||
ProfileInfoCard(
|
|
||||||
label = stringResource(R.string.personal_intro),
|
|
||||||
value = model.bio,
|
|
||||||
placeholder = "Welcome to my fantiac word i will show you something about magic",
|
|
||||||
onValueChange = { onBioChange(it) },
|
|
||||||
isMultiline = true
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// MBTI 类型和星座
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
) {
|
|
||||||
// MBTI 类型
|
|
||||||
ProfileSelectItem(
|
|
||||||
label = stringResource(R.string.mbti_type),
|
|
||||||
value = model.mbti ?: "ENFP",
|
|
||||||
iconColor = Color(0xFF7C45ED),
|
|
||||||
iconResDark = null, // TODO: 添加MBTI暗色模式图标
|
|
||||||
iconResLight = null, // TODO: 添加MBTI亮色模式图标
|
|
||||||
onClick = {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.MbtiSelect.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 分隔线
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(0.3.dp)
|
|
||||||
.background(Color(0x41413C43).copy(alpha = 0.2f))
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 星座(使用当前图标)
|
|
||||||
ProfileSelectItem(
|
|
||||||
label = stringResource(R.string.zodiac),
|
|
||||||
value = model.zodiac?.let { storedZodiac ->
|
|
||||||
// 尝试找到对应的资源ID并显示当前语言的文本
|
|
||||||
findZodiacResId(storedZodiac)?.let { resId ->
|
|
||||||
stringResource(resId)
|
|
||||||
} ?: storedZodiac // 如果找不到,显示原始存储的值
|
|
||||||
} ?: stringResource(R.string.zodiac_aries),
|
|
||||||
iconColor = Color(0xFFFFCC00),
|
|
||||||
iconResDark = R.mipmap.frame_4, // 星座暗色模式图标
|
|
||||||
iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标
|
|
||||||
onClick = {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.ZodiacSelect.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
|
|
||||||
// 保存按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(50.dp)
|
|
||||||
.clip(RoundedCornerShape(1000.dp))
|
|
||||||
.background(
|
|
||||||
brush = Brush.horizontalGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color(0xFF7C45ED), // RGB(124, 69, 237) - 左侧
|
|
||||||
Color(0xFF7C57EE), // RGB(124, 87, 238) - 中间
|
|
||||||
Color(0xFF7BD8F8) // RGB(123, 216, 248) - 右侧
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.debouncedClickable(
|
|
||||||
enabled = validate() && !model.isUpdating,
|
|
||||||
debounceTime = 1000L
|
|
||||||
) {
|
|
||||||
if (validate() && !model.isUpdating) {
|
|
||||||
model.viewModelScope.launch {
|
|
||||||
model.isUpdating = true
|
|
||||||
model.updateUserProfile(context)
|
|
||||||
model.viewModelScope.launch(Dispatchers.Main) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
model.isUpdating = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.save),
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {
|
else -> {
|
||||||
|
Log.d("AccountEditScreen2", "显示错误信息 - 没有数据且不在加载中")
|
||||||
// 没有数据且不在加载中,显示错误信息
|
// 没有数据且不在加载中,显示错误信息
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
androidx.compose.material3.Text(
|
||||||
text = stringResource(R.string.error_load_profile_failed),
|
text = "加载用户资料失败,请重试",
|
||||||
color = appColors.text
|
color = appColors.text
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 信息输入卡片组件
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun ProfileInfoCard(
|
|
||||||
label: String,
|
|
||||||
value: String,
|
|
||||||
placeholder: String,
|
|
||||||
onValueChange: (String) -> Unit,
|
|
||||||
isMultiline: Boolean = false
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp,个人简介66dp
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(Color.White),
|
|
||||||
contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.padding(vertical = if (isMultiline) 11.dp else 0.dp),
|
|
||||||
verticalAlignment = if (isMultiline) Alignment.Top else Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 标签
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color.Black,
|
|
||||||
modifier = Modifier.width(100.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
|
|
||||||
// 输入框
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = placeholder,
|
|
||||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color(0x993C3C43),
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicTextField(
|
|
||||||
value = value,
|
|
||||||
onValueChange = onValueChange,
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textStyle = androidx.compose.ui.text.TextStyle(
|
|
||||||
fontSize = if (isMultiline) 15.sp else 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color.Black
|
|
||||||
),
|
|
||||||
cursorBrush = SolidColor(Color.Black),
|
|
||||||
maxLines = if (isMultiline) Int.MAX_VALUE else 1,
|
|
||||||
singleLine = !isMultiline
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选择项组件(MBTI、星座)
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun ProfileSelectItem(
|
|
||||||
label: String,
|
|
||||||
value: String,
|
|
||||||
iconColor: Color,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
iconResDark: Int? = null,
|
|
||||||
iconResLight: Int? = null
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp)
|
|
||||||
.clickable(onClick = onClick)
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
// 自定义图标
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(
|
|
||||||
id = if (AppState.darkMode) {
|
|
||||||
iconResDark ?: R.mipmap.frame_4 // 使用传入的暗色模式图标,或默认占位
|
|
||||||
} else {
|
|
||||||
iconResLight ?: R.mipmap.naoz // 使用传入的亮色模式图标,或默认占位
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
tint = iconColor
|
|
||||||
)
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = label,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color.Black
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = value,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = Color(0x993C3C43)
|
|
||||||
)
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.ArrowForward,
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(8.dp),
|
|
||||||
tint = Color(0x4D3C3C43)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -29,12 +29,10 @@ import androidx.activity.compose.BackHandler
|
|||||||
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.graphics.Brush
|
import androidx.compose.ui.graphics.Brush
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
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.platform.LocalSoftwareKeyboardController
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
@@ -63,28 +61,7 @@ import com.aiosman.ravenow.ui.composables.form.FormTextInput2
|
|||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.foundation.border
|
|
||||||
import androidx.compose.ui.draw.shadow
|
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
||||||
import androidx.compose.animation.core.RepeatMode
|
|
||||||
import androidx.compose.animation.core.StartOffset
|
|
||||||
import androidx.compose.animation.core.animateFloat
|
|
||||||
import androidx.compose.animation.core.infiniteRepeatable
|
|
||||||
import androidx.compose.animation.core.rememberInfiniteTransition
|
|
||||||
import androidx.compose.animation.core.tween
|
|
||||||
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
|
|
||||||
/**
|
/**
|
||||||
* 添加智能体界面
|
* 添加智能体界面
|
||||||
*/
|
*/
|
||||||
@@ -96,12 +73,8 @@ fun AddAgentScreen() {
|
|||||||
var agnetNameError by remember { mutableStateOf<String?>(null) }
|
var agnetNameError by remember { mutableStateOf<String?>(null) }
|
||||||
var agnetDescError by remember { mutableStateOf<String?>(null) }
|
var agnetDescError by remember { mutableStateOf<String?>(null) }
|
||||||
var errorMessage by remember { mutableStateOf<String?>(null) }
|
var errorMessage by remember { mutableStateOf<String?>(null) }
|
||||||
var isProcessing 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 +88,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,35 +110,17 @@ 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 {
|
|
||||||
mutableStateOf(model.showManualCreation)
|
|
||||||
}
|
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp)
|
modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)
|
||||||
.background(color = appColors.decentBackground)
|
.background(color = appColors.decentBackground)
|
||||||
) {
|
) {
|
||||||
// 自定义header,控制返回按钮行为
|
// 自定义header,控制返回按钮行为
|
||||||
@@ -215,500 +148,80 @@ fun AddAgentScreen() {
|
|||||||
stringResource(R.string.agent_add),
|
stringResource(R.string.agent_add),
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W600,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
fontSize = 17.sp,
|
fontSize = 17.sp,
|
||||||
color = appColors.text
|
color = appColors.text
|
||||||
)
|
)
|
||||||
}
|
Spacer(modifier = Modifier.size(12.dp))
|
||||||
}
|
Icon(
|
||||||
Spacer(modifier = Modifier.height(1.dp))
|
|
||||||
|
|
||||||
if (!isCreatingAgent) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(50.dp)
|
|
||||||
.padding(horizontal = 20.dp),
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.group_copy),
|
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(24.dp)
|
||||||
.clip(
|
.noRippleClickable {
|
||||||
RoundedCornerShape(48.dp)
|
// 提交创建智能体的逻辑可以在这里实现
|
||||||
),
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
.padding(start = 20.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${AppState.profile?.nickName ?: "User"} ${stringResource(R.string.welcome_1)}",
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
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))
|
|
||||||
|
|
||||||
if (!showManualCreation) {
|
|
||||||
//自动创造AI界面
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 20.dp)
|
|
||||||
) {
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(95.dp)
|
|
||||||
.shadow(
|
|
||||||
elevation = 10.dp,
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
spotColor = Color(0x33F563FF),
|
|
||||||
ambientColor = Color(0x99F563FF),
|
|
||||||
clip = false
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
brush = Brush.linearGradient(
|
|
||||||
listOf(
|
|
||||||
Color(0xFF6246FF),
|
|
||||||
Color(0xFF7C45ED)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(10.dp)
|
|
||||||
)
|
|
||||||
.padding(0.5.dp)
|
|
||||||
.background(
|
|
||||||
color = appColors.inputBackground2,
|
|
||||||
shape = RoundedCornerShape(10.dp)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
val focusRequester = remember { FocusRequester() }
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.noRippleClickable {
|
|
||||||
model.viewModelScope.launch {
|
|
||||||
focusRequester.requestFocus()
|
|
||||||
keyboardController?.show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
TextField(
|
|
||||||
value = tempDesc,
|
|
||||||
onValueChange = { value -> onTempDescChange(value) },
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(95.dp)
|
|
||||||
.focusRequester(focusRequester),
|
|
||||||
placeholder = {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.agent_desc_hint_auto),
|
|
||||||
color = Color.Gray
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
textStyle = TextStyle(
|
imageVector = Icons.Default.Check,
|
||||||
color = LocalAppTheme.current.text,
|
contentDescription = "Add",
|
||||||
fontSize = 16.sp
|
tint = appColors.text
|
||||||
),
|
|
||||||
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(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.BottomEnd)
|
|
||||||
.padding(end = 12.dp, bottom = 12.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
// 只有在有内容且未处理中且未创建中时才可点击
|
|
||||||
if (tempDesc.isNotEmpty() && !isProcessing && !isCreatingAgent) {
|
|
||||||
isProcessing = true
|
|
||||||
showWaveAnimation = true // 显示构思动画
|
|
||||||
keyboardController?.hide()
|
|
||||||
model.viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
val optimizedText = optimizeTextWithAI(tempDesc)
|
|
||||||
if (optimizedText != null) {
|
|
||||||
onTempDescChange(optimizedText)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
} finally {
|
|
||||||
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
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.icons_info_magic),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(5.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.agent_text_beautify),
|
|
||||||
color = Color(0xFF6246FF),
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
if ((isCreatingAgent && showWaveAnimation) || (isProcessing && showWaveAnimation)) {
|
|
||||||
// 显示构思动画
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Start)
|
|
||||||
.padding(start = 20.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.size(32.dp)
|
|
||||||
) {
|
|
||||||
LottieAnimation(
|
|
||||||
composition = rememberLottieComposition(LottieCompositionSpec.Asset("loading.lottie")).value,
|
|
||||||
iterations = LottieConstants.IterateForever,
|
|
||||||
modifier = Modifier.matchParentSize()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.ideaing),
|
|
||||||
color = appColors.text.copy(alpha = 0.6f),
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (isCreatingAgent && !showWaveAnimation && showManualCreationForm) {
|
|
||||||
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(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Start)
|
|
||||||
.padding(start = 20.dp)
|
|
||||||
.width(136.dp)
|
|
||||||
.height(40.dp)
|
|
||||||
.border(
|
|
||||||
width = 1.dp,
|
|
||||||
color = Color(0x33858B98),
|
|
||||||
shape = RoundedCornerShape(12.dp)
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
color = appColors.background,
|
|
||||||
shape = RoundedCornerShape(12.dp),
|
|
||||||
)
|
|
||||||
.noRippleClickable {
|
|
||||||
showManualCreation = true
|
|
||||||
tempDesc = ""
|
|
||||||
agnetDescError = null
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 18.dp, vertical = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = appColors.text,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.create_agent_hand),
|
|
||||||
color = appColors.text,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}else {
|
|
||||||
//手动创造AI界面
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.align(Alignment.Start)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.Start)
|
|
||||||
.width(140.dp)
|
|
||||||
.height(40.dp)
|
|
||||||
.shadow(
|
|
||||||
elevation = 10.dp,
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
spotColor = Color(0x33F563FF),
|
|
||||||
ambientColor = Color(0x99F563FF),
|
|
||||||
clip = false
|
|
||||||
)
|
|
||||||
.background(
|
|
||||||
brush = Brush.linearGradient(
|
|
||||||
listOf(
|
|
||||||
Color(0xFF6246FF),
|
|
||||||
Color(0xFF7C45ED)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
shape = RoundedCornerShape(10.dp)
|
|
||||||
)
|
|
||||||
.padding(0.5.dp)
|
|
||||||
.background(
|
|
||||||
color = appColors.background,
|
|
||||||
shape = RoundedCornerShape(10.dp),
|
|
||||||
)
|
|
||||||
.noRippleClickable {
|
|
||||||
showManualCreation = false
|
|
||||||
model.name = ""
|
|
||||||
model.desc = ""
|
|
||||||
model.croppedBitmap = null
|
|
||||||
isCreatingAgent = false
|
|
||||||
showManualCreationForm = false
|
|
||||||
showWaveAnimation = false
|
|
||||||
isProcessing = false
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.icons_info_magic),
|
|
||||||
contentDescription = null,
|
|
||||||
tint = appColors.text,
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.create_agent_auto),
|
|
||||||
color = appColors.text,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.avatar),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
fontWeight = FontWeight.W600
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(44.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.size(88.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context,
|
||||||
|
model.croppedBitmap,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(88.dp)
|
||||||
|
.clip(
|
||||||
|
RoundedCornerShape(88.dp)
|
||||||
|
),
|
||||||
|
contentDescription = "",
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
placeholderRes = R.mipmap.rider_pro_agent_avatar
|
||||||
|
)
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(72.dp)
|
.size(32.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(
|
.background(appColors.main)
|
||||||
brush = Brush.linearGradient(
|
.align(Alignment.BottomEnd)
|
||||||
colors = listOf(
|
|
||||||
Color(0x777c45ed),
|
|
||||||
Color(0x777c68ef),
|
|
||||||
Color(0x557bd8f8)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.align(Alignment.Start)
|
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
// 保存当前状态
|
|
||||||
model.showManualCreation = showManualCreation
|
|
||||||
model.showManualCreationForm = showManualCreationForm
|
|
||||||
model.isCreatingAgent = isCreatingAgent
|
|
||||||
model.showWaveAnimation = showWaveAnimation
|
|
||||||
model.isAutoModeManualForm = false // 标记为手动模式下的手动表单
|
|
||||||
// 设置正在选择头像的标志
|
// 设置正在选择头像的标志
|
||||||
model.isSelectingAvatar = true
|
model.isSelectingAvatar = true
|
||||||
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
// 如果已有裁剪后的头像,则显示头像,否则显示编辑图标
|
Icon(
|
||||||
if (model.croppedBitmap != null) {
|
Icons.Default.Add,
|
||||||
Image(
|
contentDescription = "Add",
|
||||||
bitmap = model.croppedBitmap!!.asImageBitmap(),
|
tint = Color.White,
|
||||||
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(58.dp))
|
||||||
// 原版两个输入框
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.agent_name),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
fontWeight = FontWeight.W600
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
FormTextInput(
|
FormTextInput(
|
||||||
value = model.name,
|
value = model.name,
|
||||||
hint = stringResource(R.string.agent_name_hint_1),
|
label = stringResource(R.string.agent_name),
|
||||||
|
hint = stringResource(R.string.agent_name_hint),
|
||||||
background = appColors.inputBackground2,
|
background = appColors.inputBackground2,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
) { value ->
|
) { value ->
|
||||||
onNameChange(value)
|
onNameChange(value)
|
||||||
}
|
}
|
||||||
Text(
|
// Spacer(modifier = Modifier.height(16.dp))
|
||||||
text = stringResource(R.string.agent_desc),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = appColors.text,
|
|
||||||
fontWeight = FontWeight.W600
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
FormTextInput2(
|
FormTextInput2(
|
||||||
value = model.desc,
|
value = model.desc,
|
||||||
|
label = stringResource(R.string.agent_desc),
|
||||||
hint = stringResource(R.string.agent_desc_hint),
|
hint = stringResource(R.string.agent_desc_hint),
|
||||||
background = appColors.inputBackground2,
|
background = appColors.inputBackground2,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -716,41 +229,38 @@ fun AddAgentScreen() {
|
|||||||
onDescChange(value)
|
onDescChange(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//手动创造AI界面
|
Spacer(modifier = Modifier.height(58.dp))
|
||||||
}
|
|
||||||
|
|
||||||
// 错误信息显示
|
// 错误信息显示
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
errorMessage?.let { error ->
|
||||||
Box(modifier = Modifier.fillMaxWidth()) {
|
Text(
|
||||||
errorMessage?.let { error ->
|
text = error,
|
||||||
Text(
|
color = Color.Red,
|
||||||
text = error,
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
color = Color.Red,
|
fontSize = 14.sp
|
||||||
modifier = Modifier
|
)
|
||||||
.padding(bottom = 20.dp)
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
.align(Alignment.Center),
|
|
||||||
fontSize = 14.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(345.dp)
|
.width(345.dp)
|
||||||
.padding(bottom = 40.dp)
|
.padding(horizontal = 16.dp)
|
||||||
.background(
|
.background(
|
||||||
brush = Brush.linearGradient(
|
brush = Brush.linearGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color(0x777c45ed),
|
Color(0xFFEE2A33),
|
||||||
Color(0x777c68ef),
|
Color(0xFFD80264),
|
||||||
Color(0x557bd8f8)
|
Color(0xFF8468BC)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(24.dp)
|
shape = RoundedCornerShape(24.dp)
|
||||||
),
|
),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
backgroundColor = Color.Transparent,
|
backgroundColor = Color.Transparent,
|
||||||
text = stringResource(R.string.create_confirm),
|
text = stringResource(R.string.agent_create),
|
||||||
isLoading = model.isUpdating,
|
isLoading = model.isUpdating,
|
||||||
|
loadingText = stringResource(R.string.agent_createing),
|
||||||
enabled = !model.isUpdating && validate()
|
enabled = !model.isUpdating && validate()
|
||||||
) {
|
) {
|
||||||
// 验证输入
|
// 验证输入
|
||||||
@@ -758,10 +268,6 @@ fun AddAgentScreen() {
|
|||||||
if (validationError != null) {
|
if (validationError != null) {
|
||||||
// 显示验证错误
|
// 显示验证错误
|
||||||
errorMessage = validationError
|
errorMessage = validationError
|
||||||
model.viewModelScope.launch {
|
|
||||||
kotlinx.coroutines.delay(3000)
|
|
||||||
errorMessage = null
|
|
||||||
}
|
|
||||||
return@ActionButton
|
return@ActionButton
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -776,19 +282,15 @@ fun AddAgentScreen() {
|
|||||||
// 创建成功,清空数据并关闭页面
|
// 创建成功,清空数据并关闭页面
|
||||||
model.clearData()
|
model.clearData()
|
||||||
navController.popBackStack()
|
navController.popBackStack()
|
||||||
AppState.agentCreatedSuccess = true
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
// 显示错误信息
|
// 显示错误信息
|
||||||
errorMessage = "创建智能体失败: ${e.message}"
|
errorMessage = "创建智能体失败: ${e.message}"
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
// 3秒后清除错误信息
|
|
||||||
kotlinx.coroutines.delay(3000)
|
|
||||||
errorMessage = null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -24,12 +24,11 @@ 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 hasExitedPage by mutableStateOf(false) // 标记是否已经完全退出页面
|
||||||
var isCreatingAgent by mutableStateOf(false)
|
|
||||||
var showWaveAnimation by mutableStateOf(false)
|
// 保存AI生成的输入文本,避免页面重建时丢失
|
||||||
var showManualCreation by mutableStateOf(false)
|
var generateInputText by mutableStateOf("")
|
||||||
// 添加一个标志来区分两种手动表单状态
|
|
||||||
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")
|
||||||
@@ -75,7 +74,7 @@ object AddAgentViewModel : ViewModel() {
|
|||||||
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 +88,7 @@ object AddAgentViewModel : ViewModel() {
|
|||||||
croppedBitmap = null
|
croppedBitmap = null
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
isSelectingAvatar = false
|
isSelectingAvatar = false
|
||||||
showManualCreationForm = false
|
hasExitedPage = false
|
||||||
isCreatingAgent = false
|
generateInputText = ""
|
||||||
showWaveAnimation = false
|
|
||||||
showManualCreation = false
|
|
||||||
isAutoModeManualForm = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
648
app/src/main/java/com/aiosman/ravenow/ui/agent/CreateAgentV2.kt
Normal file
648
app/src/main/java/com/aiosman/ravenow/ui/agent/CreateAgentV2.kt
Normal file
@@ -0,0 +1,648 @@
|
|||||||
|
package com.aiosman.ravenow.ui.agent
|
||||||
|
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.border
|
||||||
|
import androidx.compose.foundation.BorderStroke
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.layout.offset
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.Edit
|
||||||
|
import androidx.compose.material3.*
|
||||||
|
import androidx.compose.runtime.*
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
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.painterResource
|
||||||
|
import androidx.compose.animation.core.*
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import kotlin.math.cos
|
||||||
|
import kotlin.math.sin
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
|
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.AppState
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.LocalNavController
|
||||||
|
import com.aiosman.ravenow.R
|
||||||
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
|
import com.aiosman.ravenow.ui.composables.ActionButton
|
||||||
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun LoadingDots(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
dotColor: Color = Color.Gray
|
||||||
|
) {
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "loading_dots")
|
||||||
|
|
||||||
|
val animationValues = (0..2).map { index ->
|
||||||
|
infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 1f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(
|
||||||
|
durationMillis = 600,
|
||||||
|
easing = EaseInOut,
|
||||||
|
delayMillis = index * 200
|
||||||
|
),
|
||||||
|
repeatMode = RepeatMode.Reverse
|
||||||
|
),
|
||||||
|
label = "dot_$index"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
animationValues.forEach { animValue ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(6.dp)
|
||||||
|
.offset(y = (-8 * animValue.value).dp)
|
||||||
|
.background(
|
||||||
|
color = dotColor.copy(alpha = 0.5f + 0.5f * animValue.value),
|
||||||
|
shape = CircleShape
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun CreateAgentV2Screen(
|
||||||
|
viewModel: CreateAgentV2ViewModel = remember { CreateAgentV2ViewModel() }
|
||||||
|
) {
|
||||||
|
// 页面进入时的状态管理
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
// 总是先同步状态
|
||||||
|
viewModel.syncStateOnResume()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面退出时的处理
|
||||||
|
DisposableEffect(Unit) {
|
||||||
|
onDispose {
|
||||||
|
// 页面退出时,标记为已退出(除非是在选择头像)
|
||||||
|
if (!viewModel.isSelectingAvatar) {
|
||||||
|
viewModel.markPageExited()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val appColors = LocalAppTheme.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// 获取当前用户名,如果没有则使用默认值
|
||||||
|
val userName = AppState.profile?.nickName ?: "用户"
|
||||||
|
|
||||||
|
// 渐变边框旋转动画
|
||||||
|
val infiniteTransition = rememberInfiniteTransition(label = "gradient_rotation")
|
||||||
|
val rotationAngle by infiniteTransition.animateFloat(
|
||||||
|
initialValue = 0f,
|
||||||
|
targetValue = 360f,
|
||||||
|
animationSpec = infiniteRepeatable(
|
||||||
|
animation = tween(
|
||||||
|
durationMillis = 16000,
|
||||||
|
easing = LinearEasing
|
||||||
|
),
|
||||||
|
repeatMode = RepeatMode.Restart
|
||||||
|
),
|
||||||
|
label = "rotation_angle"
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(appColors.background)
|
||||||
|
) {
|
||||||
|
// 状态栏占位
|
||||||
|
StatusBarSpacer()
|
||||||
|
|
||||||
|
// 顶部标题栏
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
// 返回按钮
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||||
|
contentDescription = "返回",
|
||||||
|
colorFilter = ColorFilter.tint(appColors.text),
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
|
||||||
|
// 标题 - 左对齐
|
||||||
|
Text(
|
||||||
|
text = "创建AI",
|
||||||
|
fontSize = 18.sp,
|
||||||
|
fontWeight = FontWeight.W700,
|
||||||
|
color = appColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 主要内容区域 - 可滚动
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
|
.verticalScroll(rememberScrollState())
|
||||||
|
.padding(horizontal = 24.dp),
|
||||||
|
horizontalAlignment = Alignment.Start
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
|
|
||||||
|
// AI头像图标
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(48.dp)
|
||||||
|
.background(
|
||||||
|
color = appColors.inputBackground,
|
||||||
|
shape = CircleShape
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_create_head_logo),
|
||||||
|
contentDescription = "AI头像",
|
||||||
|
modifier = Modifier.size(48.dp),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 问候语
|
||||||
|
Text(
|
||||||
|
text = "$userName 你好呀!今天想创建什么?",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = appColors.text,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 描述性文字
|
||||||
|
Text(
|
||||||
|
text = "只需要一句话,你的专属AI在这里诞生",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = appColors.secondaryText,
|
||||||
|
textAlign = TextAlign.Start
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(40.dp))
|
||||||
|
|
||||||
|
// 根据模式显示不同的UI
|
||||||
|
if (!viewModel.isManualMode) {
|
||||||
|
// AI生成模式 - 渐变边框输入框
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.shadow(
|
||||||
|
elevation = 8.dp,
|
||||||
|
shape = RoundedCornerShape(16.dp),
|
||||||
|
ambientColor = Color(0xFF6246ff).copy(alpha = 0.4f),
|
||||||
|
spotColor = Color(0xFFd80264).copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// 渐变边框
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFF6246ff), // 紫色
|
||||||
|
Color(0xFFd80264), // 红色
|
||||||
|
Color(0xFF6246ff), // 紫色
|
||||||
|
Color(0xFFd80264) // 红色
|
||||||
|
),
|
||||||
|
start = Offset(
|
||||||
|
x = cos(Math.toRadians(rotationAngle.toDouble())).toFloat() * 1000f,
|
||||||
|
y = sin(Math.toRadians(rotationAngle.toDouble())).toFloat() * 1000f
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = cos(Math.toRadians(rotationAngle.toDouble() + 180)).toFloat() * 1000f,
|
||||||
|
y = sin(Math.toRadians(rotationAngle.toDouble() + 180)).toFloat() * 1000f
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(16.dp)
|
||||||
|
)
|
||||||
|
.padding(1.5.dp) // 边框宽度
|
||||||
|
) {
|
||||||
|
// 内部输入框
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = appColors.background,
|
||||||
|
shape = RoundedCornerShape(15.dp)
|
||||||
|
)
|
||||||
|
.padding(8.dp)
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
TextField(
|
||||||
|
value = viewModel.inputText,
|
||||||
|
onValueChange = {
|
||||||
|
if (!viewModel.isGenerating) {
|
||||||
|
viewModel.updateInputText(it)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
placeholder = {
|
||||||
|
Text(
|
||||||
|
text = "一个会写诗的AI,一个会懂你笑点的AI",
|
||||||
|
color = appColors.inputHint,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
},
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent,
|
||||||
|
disabledContainerColor = Color.Transparent,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
disabledIndicatorColor = Color.Transparent,
|
||||||
|
focusedTextColor = appColors.text,
|
||||||
|
unfocusedTextColor = appColors.text,
|
||||||
|
disabledTextColor = appColors.inputHint,
|
||||||
|
cursorColor = if (viewModel.isGenerating) Color.Transparent else appColors.main,
|
||||||
|
focusedPlaceholderColor = appColors.inputHint,
|
||||||
|
unfocusedPlaceholderColor = appColors.inputHint,
|
||||||
|
disabledPlaceholderColor = appColors.inputHint.copy(alpha = 0.5f)
|
||||||
|
),
|
||||||
|
enabled = !viewModel.isGenerating,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 100.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// AI美化按钮
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.End
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.isGenerating) {
|
||||||
|
viewModel.generateAgentInfo()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = viewModel.canGenerate() && !viewModel.isGenerating,
|
||||||
|
colors = ButtonDefaults.textButtonColors(
|
||||||
|
contentColor = if (viewModel.isGenerating) appColors.inputHint else Color(0xFF7c46ed),
|
||||||
|
disabledContentColor = appColors.inputHint
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.drawable.ic_create_agent_generate),
|
||||||
|
contentDescription = "AI美化图标",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
colorFilter = ColorFilter.tint(Color(0xFF7c46ed))
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = if (viewModel.isGenerating) "生成中..." else "AI美化",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// AI生成中的loading状态
|
||||||
|
if (viewModel.isGenerating) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
LoadingDots(
|
||||||
|
dotColor = appColors.main
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "正在为你构思",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = appColors.secondaryText,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 手动创造AI按钮 - 只在非生成状态下显示
|
||||||
|
if (!viewModel.isGenerating) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
OutlinedButton(
|
||||||
|
onClick = {
|
||||||
|
if (!viewModel.isGenerating) {
|
||||||
|
viewModel.enableManualMode()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !viewModel.isGenerating,
|
||||||
|
modifier = Modifier.height(40.dp),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
border = BorderStroke(1.dp, appColors.inputHint.copy(alpha = 0.3f)),
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
contentColor = appColors.secondaryText,
|
||||||
|
disabledContentColor = appColors.inputHint
|
||||||
|
),
|
||||||
|
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Edit,
|
||||||
|
contentDescription = "编辑图标",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = appColors.secondaryText
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "手动创造AI",
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 手动模式 - "一句话创造AI"按钮
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
horizontalArrangement = Arrangement.Start
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.shadow(
|
||||||
|
elevation = 6.dp,
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
ambientColor = Color(0xFF6246ff).copy(alpha = 0.3f),
|
||||||
|
spotColor = Color(0xFFd80264).copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
// 渐变边框
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFF6246ff), // 紫色
|
||||||
|
Color(0xFFd80264), // 红色
|
||||||
|
Color(0xFF6246ff), // 紫色
|
||||||
|
Color(0xFFd80264) // 红色
|
||||||
|
),
|
||||||
|
start = Offset(
|
||||||
|
x = cos(Math.toRadians(rotationAngle.toDouble())).toFloat() * 1000f,
|
||||||
|
y = sin(Math.toRadians(rotationAngle.toDouble())).toFloat() * 1000f
|
||||||
|
),
|
||||||
|
end = Offset(
|
||||||
|
x = cos(Math.toRadians(rotationAngle.toDouble() + 180)).toFloat() * 1000f,
|
||||||
|
y = sin(Math.toRadians(rotationAngle.toDouble() + 180)).toFloat() * 1000f
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
)
|
||||||
|
.padding(1.dp) // 边框宽度
|
||||||
|
) {
|
||||||
|
// 内部按钮
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.background(
|
||||||
|
color = appColors.background,
|
||||||
|
shape = RoundedCornerShape(11.dp)
|
||||||
|
)
|
||||||
|
.noRippleClickable {
|
||||||
|
viewModel.disableManualMode()
|
||||||
|
}
|
||||||
|
.padding(horizontal = 12.dp, vertical = 8.dp)
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(6.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Edit,
|
||||||
|
contentDescription = "编辑图标",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = appColors.secondaryText
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
text = "一句话创造AI",
|
||||||
|
fontSize = 13.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = appColors.secondaryText
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成结果显示区域
|
||||||
|
if (viewModel.hasGeneratedResult()) {
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 头像选择组件
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(72.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
viewModel.setSelectingAvatar(true)
|
||||||
|
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
if (viewModel.croppedBitmap != null) {
|
||||||
|
// 有头像时显示头像
|
||||||
|
CustomAsyncImage(
|
||||||
|
context,
|
||||||
|
viewModel.croppedBitmap,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(72.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
contentDescription = "AI头像",
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
placeholderRes = R.mipmap.rider_pro_agent_avatar,
|
||||||
|
showShimmer = false
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 没有头像时显示渐变背景和编辑图标
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(72.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
0f to Color(0xFF7c45ed),
|
||||||
|
0.24f to Color(0xFF7c68ef),
|
||||||
|
1f to Color(0xFF7bd8f8)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Edit,
|
||||||
|
contentDescription = "选择头像",
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 标题输入框
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "名称",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = appColors.text,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = viewModel.agentTitle,
|
||||||
|
onValueChange = { viewModel.updateAgentTitle(it) },
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = appColors.inputBackground,
|
||||||
|
unfocusedContainerColor = appColors.inputBackground,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
focusedTextColor = appColors.text,
|
||||||
|
unfocusedTextColor = appColors.text
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
// 描述输入框
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "描述",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.Medium,
|
||||||
|
color = appColors.text,
|
||||||
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
TextField(
|
||||||
|
value = viewModel.agentDescription,
|
||||||
|
onValueChange = { viewModel.updateAgentDescription(it) },
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = appColors.inputBackground,
|
||||||
|
unfocusedContainerColor = appColors.inputBackground,
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
focusedTextColor = appColors.text,
|
||||||
|
unfocusedTextColor = appColors.text
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(12.dp),
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 120.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
|
// 错误信息显示
|
||||||
|
viewModel.errorMessage?.let { error ->
|
||||||
|
Text(
|
||||||
|
text = error,
|
||||||
|
color = Color.Red,
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建AI按钮
|
||||||
|
ActionButton(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFF7bd8f8),
|
||||||
|
Color(0xFF7c68ef),
|
||||||
|
Color(0xFF7c45ed)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(24.dp)
|
||||||
|
),
|
||||||
|
color = Color.White,
|
||||||
|
backgroundColor = Color.Transparent,
|
||||||
|
text = "好的,就它了",
|
||||||
|
isLoading = viewModel.isCreating,
|
||||||
|
loadingText = "创建中...",
|
||||||
|
enabled = viewModel.canCreate()
|
||||||
|
) {
|
||||||
|
viewModel.createAgent(context) {
|
||||||
|
navController.popBackStack()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,238 @@
|
|||||||
|
package com.aiosman.ravenow.ui.agent
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
|
import com.aiosman.ravenow.data.api.GenerateAgentInfoRequestBody
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class CreateAgentV2ViewModel : ViewModel() {
|
||||||
|
// UI状态
|
||||||
|
var inputText by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
var agentTitle by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
var agentDescription by mutableStateOf("")
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isGenerating by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var errorMessage by mutableStateOf<String?>(null)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isCreating by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
var isManualMode by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
// 临时保存的生成结果,用于在生成过程中暂时隐藏当前结果
|
||||||
|
private var tempAgentTitle by mutableStateOf("")
|
||||||
|
private var tempAgentDescription by mutableStateOf("")
|
||||||
|
|
||||||
|
// AddAgentViewModel实例,用于头像和创建逻辑
|
||||||
|
private val addAgentViewModel = AddAgentViewModel
|
||||||
|
|
||||||
|
// 获取头像相关状态
|
||||||
|
val croppedBitmap get() = addAgentViewModel.croppedBitmap
|
||||||
|
val isSelectingAvatar get() = addAgentViewModel.isSelectingAvatar
|
||||||
|
|
||||||
|
init {
|
||||||
|
// 初始化时检查是否需要恢复状态
|
||||||
|
if (addAgentViewModel.hasExitedPage) {
|
||||||
|
// 如果之前已经完全退出页面,清空所有数据
|
||||||
|
addAgentViewModel.clearData()
|
||||||
|
} else {
|
||||||
|
// 否则恢复已有状态(包括从头像选择回来的情况)
|
||||||
|
if (addAgentViewModel.name.isNotEmpty()) {
|
||||||
|
agentTitle = addAgentViewModel.name
|
||||||
|
}
|
||||||
|
if (addAgentViewModel.desc.isNotEmpty()) {
|
||||||
|
agentDescription = addAgentViewModel.desc
|
||||||
|
}
|
||||||
|
// 恢复输入文本
|
||||||
|
if (addAgentViewModel.generateInputText.isNotEmpty()) {
|
||||||
|
inputText = addAgentViewModel.generateInputText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateInputText(text: String) {
|
||||||
|
inputText = text
|
||||||
|
addAgentViewModel.generateInputText = text // 同时保存到AddAgentViewModel
|
||||||
|
clearError()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAgentTitle(title: String) {
|
||||||
|
agentTitle = title
|
||||||
|
syncToAddAgentViewModel()
|
||||||
|
clearError()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateAgentDescription(description: String) {
|
||||||
|
agentDescription = description
|
||||||
|
syncToAddAgentViewModel()
|
||||||
|
clearError()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun clearError() {
|
||||||
|
errorMessage = null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun syncToAddAgentViewModel() {
|
||||||
|
addAgentViewModel.name = agentTitle
|
||||||
|
addAgentViewModel.desc = agentDescription
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSelectingAvatar(isSelecting: Boolean) {
|
||||||
|
addAgentViewModel.isSelectingAvatar = isSelecting
|
||||||
|
}
|
||||||
|
|
||||||
|
fun markPageExited() {
|
||||||
|
addAgentViewModel.hasExitedPage = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun syncStateOnResume() {
|
||||||
|
// 如果之前在选择头像,现在回来了,重置选择状态
|
||||||
|
if (addAgentViewModel.isSelectingAvatar) {
|
||||||
|
addAgentViewModel.isSelectingAvatar = false
|
||||||
|
// 从头像选择页面回来,恢复文本状态
|
||||||
|
if (addAgentViewModel.name.isNotEmpty()) {
|
||||||
|
agentTitle = addAgentViewModel.name
|
||||||
|
}
|
||||||
|
if (addAgentViewModel.desc.isNotEmpty()) {
|
||||||
|
agentDescription = addAgentViewModel.desc
|
||||||
|
}
|
||||||
|
if (addAgentViewModel.generateInputText.isNotEmpty()) {
|
||||||
|
inputText = addAgentViewModel.generateInputText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun enableManualMode() {
|
||||||
|
isManualMode = true
|
||||||
|
// 手动模式下,如果没有现有内容,初始化为空
|
||||||
|
if (agentTitle.isEmpty() && agentDescription.isEmpty()) {
|
||||||
|
agentTitle = ""
|
||||||
|
agentDescription = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun disableManualMode() {
|
||||||
|
isManualMode = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun generateAgentInfo() {
|
||||||
|
if (inputText.isBlank() || isGenerating) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isGenerating = true
|
||||||
|
clearError()
|
||||||
|
|
||||||
|
// 开始生成时,暂存当前结果并清空显示
|
||||||
|
tempAgentTitle = agentTitle
|
||||||
|
tempAgentDescription = agentDescription
|
||||||
|
agentTitle = ""
|
||||||
|
agentDescription = ""
|
||||||
|
|
||||||
|
val response = ApiClient.longTimeoutApi.generateAgentInfo(
|
||||||
|
GenerateAgentInfoRequestBody(inputText)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val data = response.body()?.data
|
||||||
|
data?.let {
|
||||||
|
// 成功时,使用新结果
|
||||||
|
agentTitle = it.title
|
||||||
|
agentDescription = it.description
|
||||||
|
syncToAddAgentViewModel()
|
||||||
|
// 清空临时保存
|
||||||
|
tempAgentTitle = ""
|
||||||
|
tempAgentDescription = ""
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 失败时,恢复之前的结果
|
||||||
|
agentTitle = tempAgentTitle
|
||||||
|
agentDescription = tempAgentDescription
|
||||||
|
tempAgentTitle = ""
|
||||||
|
tempAgentDescription = ""
|
||||||
|
errorMessage = "生成失败,请重试"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 异常时,恢复之前的结果
|
||||||
|
agentTitle = tempAgentTitle
|
||||||
|
agentDescription = tempAgentDescription
|
||||||
|
tempAgentTitle = ""
|
||||||
|
tempAgentDescription = ""
|
||||||
|
errorMessage = "网络错误: ${e.message}"
|
||||||
|
} finally {
|
||||||
|
isGenerating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createAgent(context: Context, onSuccess: () -> Unit) {
|
||||||
|
if (isCreating) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
isCreating = true
|
||||||
|
clearError()
|
||||||
|
|
||||||
|
// 验证输入
|
||||||
|
val validationError = addAgentViewModel.validate()
|
||||||
|
if (validationError != null) {
|
||||||
|
errorMessage = validationError
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用创建智能体API
|
||||||
|
val result = addAgentViewModel.createAgent(context)
|
||||||
|
if (result != null) {
|
||||||
|
// 创建成功,清空数据
|
||||||
|
clearData()
|
||||||
|
onSuccess()
|
||||||
|
} else {
|
||||||
|
errorMessage = "创建失败,请重试"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "创建智能体失败: ${e.message}"
|
||||||
|
} finally {
|
||||||
|
isCreating = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearData() {
|
||||||
|
inputText = ""
|
||||||
|
agentTitle = ""
|
||||||
|
agentDescription = ""
|
||||||
|
errorMessage = null
|
||||||
|
isGenerating = false
|
||||||
|
isCreating = false
|
||||||
|
addAgentViewModel.clearData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否可以创建
|
||||||
|
fun canCreate(): Boolean {
|
||||||
|
return !isCreating && agentTitle.isNotBlank() && agentDescription.isNotBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否可以生成
|
||||||
|
fun canGenerate(): Boolean {
|
||||||
|
return !isGenerating && inputText.isNotBlank()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否有生成结果或处于手动模式
|
||||||
|
fun hasGeneratedResult(): Boolean {
|
||||||
|
return agentTitle.isNotEmpty() || agentDescription.isNotEmpty() || isManualMode
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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(AppColors.decentBackground)
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -334,7 +346,8 @@ fun ChatAiScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -558,8 +571,7 @@ fun ChatAiInput(
|
|||||||
}
|
}
|
||||||
Box( modifier = Modifier
|
Box( modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color.Transparent)
|
.padding(start = 16.dp, end = 16.dp, bottom = 12.dp),){
|
||||||
.padding(start = 16.dp, end = 16.dp, bottom = 45.dp),){
|
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
|||||||
@@ -64,7 +64,6 @@ import androidx.compose.ui.platform.LocalFocusManager
|
|||||||
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.platform.SoftwareKeyboardController
|
import androidx.compose.ui.platform.SoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
@@ -75,12 +74,10 @@ import androidx.lifecycle.ViewModelProvider
|
|||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.entity.ChatItem
|
import com.aiosman.ravenow.entity.ChatItem
|
||||||
import com.aiosman.ravenow.exp.formatChatTime
|
import com.aiosman.ravenow.exp.formatChatTime
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.ravenow.ui.composables.DropdownMenu
|
import com.aiosman.ravenow.ui.composables.DropdownMenu
|
||||||
import com.aiosman.ravenow.ui.composables.MenuItem
|
import com.aiosman.ravenow.ui.composables.MenuItem
|
||||||
@@ -94,6 +91,7 @@ import java.util.UUID
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ChatScreen(userId: String) {
|
fun ChatScreen(userId: String) {
|
||||||
|
var isMenuExpanded by remember { mutableStateOf(false) }
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalNavController.current.context
|
val context = LocalNavController.current.context
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
@@ -160,37 +158,14 @@ fun ChatScreen(userId: String) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Box(
|
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
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@@ -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()
|
||||||
@@ -125,13 +91,12 @@ fun CommentNoticeScreen() {
|
|||||||
) {
|
) {
|
||||||
androidx.compose.foundation.Image(
|
androidx.compose.foundation.Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.tietie_dark
|
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
|
modifier = Modifier.size(181.dp)
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "等一位旅人~",
|
text = "等一位旅人~",
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -56,14 +56,15 @@ fun AnimatedFavouriteIcon(
|
|||||||
}) {
|
}) {
|
||||||
Image(
|
Image(
|
||||||
painter = if (isFavourite) {
|
painter = if (isFavourite) {
|
||||||
painterResource(id = R.mipmap.icon_variant_2)
|
painterResource(id = R.drawable.rider_pro_favourited)
|
||||||
} else {
|
} else {
|
||||||
painterResource(id = R.mipmap.icon_collect)
|
painterResource(id = R.drawable.rider_pro_favourite)
|
||||||
},
|
},
|
||||||
contentDescription = "Favourite",
|
contentDescription = "Favourite",
|
||||||
modifier = modifier.graphicsLayer {
|
modifier = modifier.graphicsLayer {
|
||||||
rotationZ = animatableRotation.value
|
rotationZ = animatableRotation.value
|
||||||
},
|
},
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -51,8 +51,7 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|||||||
@Composable
|
@Composable
|
||||||
fun EditCommentBottomModal(
|
fun EditCommentBottomModal(
|
||||||
replyComment: CommentEntity? = null,
|
replyComment: CommentEntity? = null,
|
||||||
autoFocus: Boolean = false,
|
onSend: (String) -> Unit = {}
|
||||||
onSend: (String) -> Unit = {},
|
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
var text by remember { mutableStateOf("") }
|
var text by remember { mutableStateOf("") }
|
||||||
@@ -60,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(
|
||||||
@@ -72,22 +69,72 @@ fun EditCommentBottomModal(
|
|||||||
.background(AppColors.background)
|
.background(AppColors.background)
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
if (replyComment == null) "Comment" else "Reply",
|
||||||
|
fontWeight = FontWeight.W600,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
if (replyComment != null) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context,
|
||||||
|
replyComment.avatar,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentDescription = "Avatar",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Text(
|
||||||
|
replyComment.name,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
Text(
|
||||||
|
replyComment.comment,
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(start = 32.dp),
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
}
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) {
|
) {
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.clip(RoundedCornerShape(20.dp))
|
.clip(RoundedCornerShape(20.dp))
|
||||||
.background(Color.Gray.copy(alpha = 0.1f))
|
.background(Color.White)
|
||||||
|
.border(1.dp, Color.Black, RoundedCornerShape(20.dp))
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
verticalAlignment = Alignment.Top
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = text,
|
value = text,
|
||||||
@@ -102,40 +149,31 @@ fun EditCommentBottomModal(
|
|||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
fontWeight = FontWeight.Normal
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
decorationBox = { innerTextField ->
|
minLines = 1
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
contentAlignment = Alignment.CenterStart
|
|
||||||
) {
|
|
||||||
innerTextField()
|
|
||||||
if (text.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = if (replyComment == null) "快来互动吧..." else "回复@${replyComment.name}",
|
|
||||||
color = AppColors.text.copy(alpha = 0.3f), // 30%透明度
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
Crossfade(
|
||||||
|
targetState = text.isNotEmpty(), animationSpec = tween(500),
|
||||||
|
label = ""
|
||||||
|
) { isNotEmpty ->
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.mipmap.rider_pro_moment_post),
|
||||||
|
contentDescription = "Send",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(20.dp)
|
||||||
|
.align(Alignment.Top)
|
||||||
|
.noRippleClickable {
|
||||||
|
if (text.isNotEmpty()) {
|
||||||
|
onSend(text)
|
||||||
|
text = ""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tint = if (isNotEmpty) Color.Unspecified else AppColors.nonActive
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(12.dp))
|
}
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.mipmap.btn),
|
|
||||||
contentDescription = "Send",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(40.dp)
|
|
||||||
.padding(top = 13.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
if (text.isNotEmpty()) {
|
|
||||||
onSend(text)
|
|
||||||
text = ""
|
|
||||||
}
|
|
||||||
},
|
|
||||||
tint = Color.Unspecified
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(navBarHeight))
|
Spacer(modifier = Modifier.height(navBarHeight))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ 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.graphics.Color
|
|
||||||
import androidx.compose.ui.res.stringResource
|
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
|
||||||
@@ -33,7 +32,7 @@ fun FollowButton(
|
|||||||
.wrapContentWidth()
|
.wrapContentWidth()
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(8.dp))
|
||||||
.background(
|
.background(
|
||||||
color = if (isFollowing) AppColors.nonActive else AppColors.nonActive
|
color = if (isFollowing) AppColors.main else AppColors.nonActive
|
||||||
)
|
)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
@@ -42,9 +41,11 @@ fun FollowButton(
|
|||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (isFollowing) stringResource(R.string.follow_upper_had) else stringResource(R.string.follow_upper),
|
text = if (isFollowing) stringResource(R.string.following_upper) else stringResource(
|
||||||
|
R.string.follow_upper
|
||||||
|
),
|
||||||
fontSize = fontSize,
|
fontSize = fontSize,
|
||||||
color = if (isFollowing) AppColors.text else AppColors.text,
|
color = if (isFollowing) AppColors.mainText else AppColors.text,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold)
|
style = TextStyle(fontWeight = FontWeight.Bold)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,21 +92,15 @@ fun MomentCard(
|
|||||||
showFollowButton = showFollowButton
|
showFollowButton = showFollowButton
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val lastClickTime = remember { mutableStateOf(0L) }
|
|
||||||
val clickDelay = 500L
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
val currentTime = System.currentTimeMillis()
|
navController.navigateToPost(
|
||||||
if (currentTime - lastClickTime.value > clickDelay) {
|
momentEntity.id,
|
||||||
lastClickTime.value = currentTime
|
highlightCommentId = 0,
|
||||||
navController.navigateToPost(
|
initImagePagerIndex = imageIndex
|
||||||
momentEntity.id,
|
)
|
||||||
highlightCommentId = 0,
|
|
||||||
initImagePagerIndex = imageIndex
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
MomentContentGroup(
|
MomentContentGroup(
|
||||||
@@ -219,7 +213,8 @@ fun MomentPostLocation(location: String) {
|
|||||||
text = location,
|
text = location,
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
)
|
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -243,8 +238,6 @@ fun MomentTopRowGroup(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val lastClickTime = remember { mutableStateOf(0L) }
|
|
||||||
val clickDelay = 500L
|
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
momentEntity.avatar,
|
momentEntity.avatar,
|
||||||
@@ -253,16 +246,12 @@ fun MomentTopRowGroup(
|
|||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
.clip(RoundedCornerShape(40.dp))
|
.clip(RoundedCornerShape(40.dp))
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
val currentTime = System.currentTimeMillis()
|
navController.navigate(
|
||||||
if (currentTime - lastClickTime.value > clickDelay) {
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
lastClickTime.value = currentTime
|
"{id}",
|
||||||
navController.navigate(
|
momentEntity.authorId.toString()
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
|
||||||
"{id}",
|
|
||||||
momentEntity.authorId.toString()
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
},
|
},
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
@@ -278,19 +267,7 @@ fun MomentTopRowGroup(
|
|||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
MomentName(
|
MomentName(
|
||||||
modifier = Modifier.weight(1f)
|
modifier = Modifier.weight(1f),
|
||||||
.noRippleClickable {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastClickTime.value > clickDelay) {
|
|
||||||
lastClickTime.value = currentTime
|
|
||||||
navController.navigate(
|
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
|
||||||
"{id}",
|
|
||||||
momentEntity.authorId.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
name = momentEntity.nickname
|
name = momentEntity.nickname
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
@@ -364,6 +341,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 +366,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 +378,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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -441,8 +416,6 @@ fun MomentBottomOperateRowGroup(
|
|||||||
momentEntity: MomentEntity,
|
momentEntity: MomentEntity,
|
||||||
imageIndex: Int = 0
|
imageIndex: Int = 0
|
||||||
) {
|
) {
|
||||||
val lastClickTime = remember { mutableStateOf(0L) }
|
|
||||||
val clickDelay = 500L
|
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
var showCommentModal by remember { mutableStateOf(false) }
|
||||||
if (showCommentModal) {
|
if (showCommentModal) {
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
@@ -478,84 +451,45 @@ fun MomentBottomOperateRowGroup(
|
|||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.padding(start = 16.dp, end = 0.dp)
|
.padding(start = 16.dp, end = 0.dp)
|
||||||
) {
|
) {
|
||||||
Column(
|
Row(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
||||||
) {
|
) {
|
||||||
if (momentEntity.images.size > 1) {
|
Box(
|
||||||
Row(
|
modifier = Modifier.fillMaxHeight(),
|
||||||
modifier = Modifier
|
contentAlignment = Alignment.Center
|
||||||
.fillMaxWidth()
|
) {
|
||||||
.weight(1f),
|
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
||||||
horizontalArrangement = Arrangement.Center,
|
AnimatedLikeIcon(
|
||||||
verticalAlignment = Alignment.CenterVertically
|
modifier = Modifier.size(24.dp),
|
||||||
) {
|
liked = momentEntity.liked
|
||||||
momentEntity.images.forEachIndexed { index, _ ->
|
) {
|
||||||
Box(
|
onLikeClick()
|
||||||
modifier = Modifier
|
|
||||||
.size(4.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(
|
|
||||||
if (imageIndex == index) Color.Red else Color.Gray.copy(
|
|
||||||
alpha = 0.5f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.padding(1.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.noRippleClickable {
|
||||||
|
onCommentClick()
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(
|
||||||
|
icon = R.drawable.rider_pro_comment,
|
||||||
|
count = momentEntity.commentCount.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.fillMaxHeight()
|
||||||
verticalAlignment = Alignment.CenterVertically
|
,
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
) {
|
) {
|
||||||
Row(
|
|
||||||
modifier = Modifier.weight(1f).fillMaxHeight(),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 点赞按钮
|
|
||||||
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
|
||||||
AnimatedLikeIcon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
liked = momentEntity.liked
|
|
||||||
) {
|
|
||||||
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()) {
|
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
||||||
AnimatedFavouriteIcon(
|
AnimatedFavouriteIcon(
|
||||||
modifier = Modifier.size(24.dp),
|
modifier = Modifier.size(24.dp),
|
||||||
@@ -566,18 +500,41 @@ fun MomentBottomOperateRowGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
if (momentEntity.images.size > 1) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
momentEntity.images.forEachIndexed { index, _ ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(4.dp)
|
||||||
|
.clip(CircleShape)
|
||||||
|
.background(
|
||||||
|
if (imageIndex == index) Color.Red else Color.Gray.copy(
|
||||||
|
alpha = 0.5f
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.padding(1.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun MomentListLoading() {
|
fun MomentListLoading() {
|
||||||
CircularProgressIndicator(
|
CircularProgressIndicator(
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentWidth(Alignment.CenterHorizontally),
|
.wrapContentWidth(Alignment.CenterHorizontally),
|
||||||
color = Color.Red
|
color = Color.Red
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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",
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,10 +43,6 @@ import com.aiosman.ravenow.LocalAppTheme
|
|||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
|
||||||
private val LabelTextColor = Color(red = 60f / 255f, green = 60f / 255f, blue = 67f / 255f, alpha = 0.6f)
|
|
||||||
private val HintTextColor = Color(red = 60f / 255f, green = 60f / 255f, blue = 67f / 255f, alpha = 0.3f)
|
|
||||||
private val PasswordIconColor = Color(red = 17f / 255f, green = 12f / 255f, blue = 19f / 255f)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun TextInputField(
|
fun TextInputField(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
@@ -56,96 +52,69 @@ fun TextInputField(
|
|||||||
label: String? = null,
|
label: String? = null,
|
||||||
hint: String? = null,
|
hint: String? = null,
|
||||||
error: String? = null,
|
error: String? = null,
|
||||||
enabled: Boolean = true,
|
enabled: Boolean = true
|
||||||
leadingIcon: @Composable (() -> Unit)? = null,
|
|
||||||
customBackgroundColor: Color? = null,
|
|
||||||
customCornerRadius: Float = 24f
|
|
||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
var showPassword by remember { mutableStateOf(!password) }
|
var showPassword by remember { mutableStateOf(!password) }
|
||||||
var isFocused by remember { mutableStateOf(false) }
|
var isFocused by remember { mutableStateOf(false) }
|
||||||
val backgroundColor = customBackgroundColor ?: AppColors.inputBackground
|
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
label?.let {
|
label?.let {
|
||||||
Text(
|
Text(it, color = AppColors.secondaryText)
|
||||||
text = it,
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
color = LabelTextColor,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
modifier = Modifier.padding(start = 8.dp, top = 8.dp, bottom = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
Box(
|
Box(
|
||||||
contentAlignment = Alignment.CenterStart,
|
contentAlignment = Alignment.CenterStart,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(RoundedCornerShape(customCornerRadius.dp))
|
.clip(RoundedCornerShape(24.dp))
|
||||||
.background(backgroundColor)
|
.background(AppColors.inputBackground)
|
||||||
.border(
|
.border(
|
||||||
width = 2.dp,
|
width = 2.dp,
|
||||||
color = if (error == null) Color.Transparent else AppColors.error,
|
color = if (error == null) Color.Transparent else AppColors.error,
|
||||||
shape = RoundedCornerShape(customCornerRadius.dp)
|
shape = RoundedCornerShape(24.dp)
|
||||||
)
|
)
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(verticalAlignment = Alignment.CenterVertically){
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
BasicTextField(
|
||||||
modifier = Modifier.fillMaxWidth()
|
value = text,
|
||||||
){
|
onValueChange = onValueChange,
|
||||||
leadingIcon?.let {
|
modifier = Modifier
|
||||||
Box(modifier = Modifier.size(24.dp)) {
|
.weight(1f)
|
||||||
it()
|
.onFocusChanged { focusState ->
|
||||||
}
|
isFocused = focusState.isFocused
|
||||||
Spacer(modifier = Modifier.size(12.dp))
|
},
|
||||||
}
|
textStyle = TextStyle(
|
||||||
Box(modifier = Modifier.weight(1f)) {
|
fontSize = 16.sp,
|
||||||
BasicTextField(
|
fontWeight = FontWeight.W500,
|
||||||
value = text,
|
color = AppColors.text
|
||||||
onValueChange = onValueChange,
|
),
|
||||||
modifier = Modifier
|
keyboardOptions = KeyboardOptions(
|
||||||
.fillMaxWidth()
|
keyboardType = if (password) KeyboardType.Password else KeyboardType.Text
|
||||||
.onFocusChanged { focusState ->
|
),
|
||||||
isFocused = focusState.isFocused
|
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
||||||
},
|
singleLine = true,
|
||||||
textStyle = TextStyle(
|
enabled = enabled,
|
||||||
fontSize = 16.sp,
|
cursorBrush = SolidColor(AppColors.text),
|
||||||
fontWeight = FontWeight.W400,
|
)
|
||||||
color = AppColors.text
|
|
||||||
),
|
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = if (password) KeyboardType.Password else KeyboardType.Email
|
|
||||||
),
|
|
||||||
visualTransformation = if (showPassword) VisualTransformation.None else PasswordVisualTransformation(),
|
|
||||||
singleLine = true,
|
|
||||||
enabled = enabled,
|
|
||||||
cursorBrush = SolidColor(AppColors.text),
|
|
||||||
)
|
|
||||||
if (text.isEmpty() && hint != null) {
|
|
||||||
Text(
|
|
||||||
text = hint,
|
|
||||||
color = HintTextColor,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.W400
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (password) {
|
if (password) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(id = R.drawable.rider_pro_eye),
|
||||||
id = if (showPassword) {
|
|
||||||
R.drawable.rider_pro_eye
|
|
||||||
} else {
|
|
||||||
R.mipmap.icon_eyes_closed_light
|
|
||||||
}
|
|
||||||
),
|
|
||||||
contentDescription = "Password",
|
contentDescription = "Password",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(18.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
showPassword = !showPassword
|
showPassword = !showPassword
|
||||||
},
|
},
|
||||||
colorFilter = ColorFilter.tint(PasswordIconColor)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
hint?.let {
|
||||||
|
Text(it, modifier = Modifier.padding(start = 5.dp), color = AppColors.inputHint, fontWeight = FontWeight.W600)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Row(
|
Row(
|
||||||
|
|||||||
@@ -15,22 +15,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
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.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
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 +32,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,15 +47,12 @@ 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
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(25.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.background(background ?: AppColors.inputBackground)
|
.background(background ?: AppColors.inputBackground)
|
||||||
.let {
|
.let {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
@@ -71,11 +61,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 {
|
||||||
@@ -93,51 +79,34 @@ fun FormTextInput(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
.padding(start = 16.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (value.isEmpty()) {
|
||||||
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
Text(
|
||||||
contentDescription = null,
|
text = hint ?: "",
|
||||||
modifier = Modifier
|
style = TextStyle(
|
||||||
.size(16.dp)
|
fontSize = 16.sp,
|
||||||
.align(Alignment.TopStart),
|
fontWeight = FontWeight.Normal,
|
||||||
tint = AppColors.text.copy(alpha = 0.4f)
|
color = AppColors.inputHint
|
||||||
)
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = hint ?: "",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = AppColors.inputHint
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicTextField(
|
|
||||||
maxLines = 1,
|
|
||||||
value = value,
|
|
||||||
onValueChange = {
|
|
||||||
onValueChange(it)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.focusRequester(focusRequester),
|
|
||||||
singleLine = true,
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = AppColors.text
|
|
||||||
),
|
|
||||||
cursorBrush = SolidColor(AppColors.text),
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
maxLines = 1,
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it)
|
||||||
|
},
|
||||||
|
singleLine = true,
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = AppColors.text
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(AppColors.text),
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,22 +15,16 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.height
|
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.widthIn
|
import androidx.compose.foundation.layout.widthIn
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
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 +32,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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 垂直布局的输入框
|
* 垂直布局的输入框
|
||||||
@@ -51,19 +44,15 @@ fun FormTextInput2(
|
|||||||
error: String? = null,
|
error: String? = null,
|
||||||
hint: String? = null,
|
hint: String? = null,
|
||||||
background: Color? = null,
|
background: Color? = null,
|
||||||
focusRequester: FocusRequester? = null,
|
|
||||||
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)
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(25.dp))
|
.clip(RoundedCornerShape(16.dp))
|
||||||
.background(background ?: AppColors.inputBackground)
|
.background(background ?: AppColors.inputBackground)
|
||||||
.let {
|
.let {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
@@ -72,13 +61,9 @@ fun FormTextInput2(
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(17.dp)
|
.padding(17.dp),
|
||||||
.noRippleClickable {
|
|
||||||
localFocusRequester.requestFocus()
|
|
||||||
keyboardController?.show()
|
|
||||||
},
|
|
||||||
|
|
||||||
) {
|
) {
|
||||||
label?.let {
|
label?.let {
|
||||||
Text(
|
Text(
|
||||||
text = it,
|
text = it,
|
||||||
@@ -94,51 +79,34 @@ fun FormTextInput2(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
.padding(top = 8.dp)
|
||||||
) {
|
) {
|
||||||
Icon(
|
if (value.isEmpty()) {
|
||||||
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
Text(
|
||||||
contentDescription = null,
|
text = hint ?: "",
|
||||||
modifier = Modifier
|
style = TextStyle(
|
||||||
.size(16.dp)
|
fontSize = 16.sp,
|
||||||
.align(Alignment.TopStart),
|
fontWeight = FontWeight.Normal,
|
||||||
tint = AppColors.text.copy(alpha = 0.4f)
|
color = AppColors.inputHint
|
||||||
)
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Spacer(modifier = Modifier.width(24.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
if (value.isEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = hint ?: "",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = AppColors.inputHint
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
BasicTextField(
|
|
||||||
maxLines = 6,
|
|
||||||
value = value,
|
|
||||||
onValueChange = {
|
|
||||||
onValueChange(it)
|
|
||||||
},
|
|
||||||
modifier = Modifier
|
|
||||||
.focusRequester(localFocusRequester),
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 14.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp
|
|
||||||
),
|
|
||||||
cursorBrush = SolidColor(AppColors.text),
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
maxLines = 5,
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
onValueChange(it)
|
||||||
|
},
|
||||||
|
textStyle = TextStyle(
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.Normal,
|
||||||
|
color = AppColors.text,
|
||||||
|
lineHeight = 20.sp
|
||||||
|
),
|
||||||
|
cursorBrush = SolidColor(AppColors.text),
|
||||||
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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(181.dp, 153.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
|
||||||
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,
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -124,21 +80,21 @@ fun FollowerListScreen(userId: Int) {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.frame_4
|
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, 153.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "还没有人关注你呢",
|
text = "还没有人关注哦",
|
||||||
color = appColors.text,
|
color = appColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.size(8.dp))
|
Spacer(modifier = Modifier.size(8.dp))
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "试着发信号出来,某人就会被吸引啦~",
|
text = "去发布动态,吸引更多粉丝~",
|
||||||
color = appColors.text,
|
color = appColors.text,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W400
|
fontWeight = FontWeight.W400
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -114,22 +79,21 @@ fun FollowerNoticeScreen() {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.frame_4
|
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
|
modifier = Modifier.size(181.dp)
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "还没有人关注你呢",
|
text = "还没有人关注哦",
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.size(8.dp))
|
Spacer(modifier = Modifier.size(8.dp))
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "试着发信号出来,某人就会被吸引啦~",
|
text = "去发布动态,吸引更多粉丝~",
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W400
|
fontWeight = FontWeight.W400
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -126,14 +82,14 @@ fun FollowingListScreen(userId: Int) {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.frame_3
|
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, 153.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.size(9.dp)) // 调整间距为9dp
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
androidx.compose.material.Text(
|
androidx.compose.material.Text(
|
||||||
text = "还没有关注任何灵魂",
|
text = "没有关注任何灵魂",
|
||||||
color = appColors.text,
|
color = appColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,5 @@
|
|||||||
package com.aiosman.ravenow.ui.group
|
package com.aiosman.ravenow.ui.group
|
||||||
|
|
||||||
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
|
||||||
@@ -9,14 +8,6 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.ChatState
|
import com.aiosman.ravenow.ChatState
|
||||||
import com.aiosman.ravenow.data.api.ApiClient
|
import com.aiosman.ravenow.data.api.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.AgentRule
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
|
||||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.UpdateAgentRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.CreateAgentRuleRequestBody
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRule
|
|
||||||
import com.aiosman.ravenow.data.api.AgentRuleQuota
|
|
||||||
import com.aiosman.ravenow.data.parseErrorResponse
|
|
||||||
import com.aiosman.ravenow.entity.ChatNotification
|
import com.aiosman.ravenow.entity.ChatNotification
|
||||||
import com.aiosman.ravenow.entity.GroupInfo
|
import com.aiosman.ravenow.entity.GroupInfo
|
||||||
import com.aiosman.ravenow.entity.GroupMember
|
import com.aiosman.ravenow.entity.GroupMember
|
||||||
@@ -31,36 +22,9 @@ class GroupChatInfoViewModel(
|
|||||||
var isLoading by mutableStateOf(false)
|
var isLoading by mutableStateOf(false)
|
||||||
var error by mutableStateOf<String?>(null)
|
var error by mutableStateOf<String?>(null)
|
||||||
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
var chatNotification by mutableStateOf<ChatNotification?>(null)
|
||||||
var isAddingMemory by mutableStateOf(false)
|
|
||||||
var addMemoryError by mutableStateOf<String?>(null)
|
|
||||||
var addMemorySuccess by mutableStateOf(false)
|
|
||||||
val notificationStrategy get() = chatNotification?.strategy ?: "default"
|
val notificationStrategy get() = chatNotification?.strategy ?: "default"
|
||||||
|
|
||||||
// 记忆管理相关状态
|
|
||||||
var memoryQuota by mutableStateOf<AgentRuleQuota?>(null)
|
|
||||||
var memoryList by mutableStateOf<List<AgentRule>>(emptyList())
|
|
||||||
var isLoadingMemory by mutableStateOf(false)
|
|
||||||
var memoryError by mutableStateOf<String?>(null)
|
|
||||||
var promptOpenId by mutableStateOf<String?>(null)
|
|
||||||
init {
|
init {
|
||||||
loadGroupInfo()
|
loadGroupInfo()
|
||||||
loadPromptOpenId()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取群聊中智能体的 OpenID
|
|
||||||
*/
|
|
||||||
private fun loadPromptOpenId() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
|
||||||
val groupChatResponse = response.body()?.data
|
|
||||||
val prompts = groupChatResponse?.prompts
|
|
||||||
promptOpenId = prompts?.firstOrNull()?.openId
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("GroupChatInfoViewModel", "获取智能体OpenID失败: ${e.message}", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
suspend fun updateNotificationStrategy(strategy: String) {
|
suspend fun updateNotificationStrategy(strategy: String) {
|
||||||
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
|
val result = ChatState.updateChatNotification(groupId.hashCode(), strategy)
|
||||||
@@ -107,218 +71,4 @@ class GroupChatInfoViewModel(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 添加群记忆
|
|
||||||
* @param memoryText 记忆内容
|
|
||||||
* @param promptOpenId 智能体的 OpenID(可选),如果不提供则从群聊信息中获取
|
|
||||||
*/
|
|
||||||
fun addGroupMemory(memoryText: String, promptOpenId: String? = null) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isAddingMemory = true
|
|
||||||
addMemoryError = null
|
|
||||||
addMemorySuccess = false
|
|
||||||
|
|
||||||
// 如果没有提供 promptOpenId,需要先获取群聊的智能体信息
|
|
||||||
val openId = promptOpenId ?: run {
|
|
||||||
// 通过 createGroupChatAi 接口获取群聊详细信息(包含 prompts)
|
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
|
||||||
val groupChatResponse = response.body()?.data
|
|
||||||
val prompts = groupChatResponse?.prompts
|
|
||||||
|
|
||||||
if (prompts.isNullOrEmpty()) {
|
|
||||||
throw Exception("群聊中没有找到智能体,无法添加记忆")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用第一个智能体的 openId
|
|
||||||
prompts.firstOrNull()?.openId
|
|
||||||
?: throw Exception("无法获取智能体信息")
|
|
||||||
}
|
|
||||||
|
|
||||||
if (openId.isBlank()) {
|
|
||||||
throw Exception("智能体ID不能为空")
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建智能体规则(群记忆)
|
|
||||||
val requestBody = CreateAgentRuleRequestBody(
|
|
||||||
rule = memoryText,
|
|
||||||
openId = openId
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = ApiClient.api.createAgentRule(requestBody)
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
addMemorySuccess = true
|
|
||||||
Log.d("GroupChatInfoViewModel", "群记忆添加成功")
|
|
||||||
// 刷新记忆列表和配额
|
|
||||||
loadMemoryQuota(openId)
|
|
||||||
loadMemoryList(openId)
|
|
||||||
} else {
|
|
||||||
val errorResponse = parseErrorResponse(response.errorBody())
|
|
||||||
val errorMessage = errorResponse?.toServiceException()?.message
|
|
||||||
?: "添加群记忆失败: ${response.code()}"
|
|
||||||
throw Exception(errorMessage)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
addMemoryError = e.message ?: "添加群记忆失败"
|
|
||||||
Log.e("GroupChatInfoViewModel", "添加群记忆失败: ${e.message}", e)
|
|
||||||
} finally {
|
|
||||||
isAddingMemory = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取记忆配额信息
|
|
||||||
*/
|
|
||||||
fun loadMemoryQuota(openId: String? = null) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isLoadingMemory = true
|
|
||||||
memoryError = null
|
|
||||||
|
|
||||||
val targetOpenId = openId ?: promptOpenId
|
|
||||||
if (targetOpenId.isNullOrBlank()) {
|
|
||||||
// 如果还没有获取到 openId,先获取
|
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
|
||||||
val groupChatResponse = response.body()?.data
|
|
||||||
val prompts = groupChatResponse?.prompts
|
|
||||||
val fetchedOpenId = prompts?.firstOrNull()?.openId
|
|
||||||
?: throw Exception("无法获取智能体信息")
|
|
||||||
|
|
||||||
promptOpenId = fetchedOpenId
|
|
||||||
val quotaResponse = ApiClient.api.getAgentRuleQuota(fetchedOpenId)
|
|
||||||
if (quotaResponse.isSuccessful) {
|
|
||||||
memoryQuota = quotaResponse.body()?.data
|
|
||||||
} else {
|
|
||||||
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val quotaResponse = ApiClient.api.getAgentRuleQuota(targetOpenId)
|
|
||||||
if (quotaResponse.isSuccessful) {
|
|
||||||
memoryQuota = quotaResponse.body()?.data
|
|
||||||
} else {
|
|
||||||
throw Exception("获取配额信息失败: ${quotaResponse.code()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
memoryError = e.message ?: "获取配额信息失败"
|
|
||||||
Log.e("GroupChatInfoViewModel", "获取配额信息失败: ${e.message}", e)
|
|
||||||
} finally {
|
|
||||||
isLoadingMemory = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取记忆列表
|
|
||||||
*/
|
|
||||||
fun loadMemoryList(openId: String? = null, page: Int = 1, pageSize: Int = 20) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isLoadingMemory = true
|
|
||||||
memoryError = null
|
|
||||||
|
|
||||||
val targetOpenId = openId ?: promptOpenId
|
|
||||||
if (targetOpenId.isNullOrBlank()) {
|
|
||||||
// 如果还没有获取到 openId,先获取
|
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = groupId)
|
|
||||||
val groupChatResponse = response.body()?.data
|
|
||||||
val prompts = groupChatResponse?.prompts
|
|
||||||
val fetchedOpenId = prompts?.firstOrNull()?.openId
|
|
||||||
?: throw Exception("无法获取智能体信息")
|
|
||||||
|
|
||||||
promptOpenId = fetchedOpenId
|
|
||||||
val listResponse = ApiClient.api.getAgentRuleList(fetchedOpenId, page = page, pageSize = pageSize)
|
|
||||||
if (listResponse.isSuccessful) {
|
|
||||||
memoryList = listResponse.body()?.data?.list ?: emptyList()
|
|
||||||
} else {
|
|
||||||
throw Exception("获取记忆列表失败: ${listResponse.code()}")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val listResponse = ApiClient.api.getAgentRuleList(targetOpenId, page = page, pageSize = pageSize)
|
|
||||||
if (listResponse.isSuccessful) {
|
|
||||||
memoryList = listResponse.body()?.data?.list ?: emptyList()
|
|
||||||
} else {
|
|
||||||
throw Exception("获取记忆列表失败: ${listResponse.code()}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
memoryError = e.message ?: "获取记忆列表失败"
|
|
||||||
Log.e("GroupChatInfoViewModel", "获取记忆列表失败: ${e.message}", e)
|
|
||||||
} finally {
|
|
||||||
isLoadingMemory = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除记忆
|
|
||||||
*/
|
|
||||||
fun deleteMemory(ruleId: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isLoadingMemory = true
|
|
||||||
memoryError = null
|
|
||||||
|
|
||||||
val response = ApiClient.api.deleteAgentRule(ruleId)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
// 刷新记忆列表和配额
|
|
||||||
promptOpenId?.let { openId ->
|
|
||||||
loadMemoryQuota(openId)
|
|
||||||
loadMemoryList(openId)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val errorResponse = parseErrorResponse(response.errorBody())
|
|
||||||
val errorMessage = errorResponse?.toServiceException()?.message
|
|
||||||
?: "删除记忆失败: ${response.code()}"
|
|
||||||
throw Exception(errorMessage)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
memoryError = e.message ?: "删除记忆失败"
|
|
||||||
Log.e("GroupChatInfoViewModel", "删除记忆失败: ${e.message}", e)
|
|
||||||
} finally {
|
|
||||||
isLoadingMemory = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新记忆
|
|
||||||
*/
|
|
||||||
fun updateMemory(ruleId: Int, newRuleText: String, targetOpenId: String? = null) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isLoadingMemory = true
|
|
||||||
memoryError = null
|
|
||||||
|
|
||||||
val openId = targetOpenId ?: promptOpenId
|
|
||||||
?: throw Exception("无法获取智能体ID")
|
|
||||||
|
|
||||||
val requestBody = UpdateAgentRuleRequestBody(
|
|
||||||
id = ruleId,
|
|
||||||
rule = newRuleText,
|
|
||||||
openId = openId
|
|
||||||
)
|
|
||||||
|
|
||||||
val response = ApiClient.api.updateAgentRule(requestBody)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
// 刷新记忆列表和配额
|
|
||||||
loadMemoryQuota(openId)
|
|
||||||
loadMemoryList(openId)
|
|
||||||
} else {
|
|
||||||
val errorResponse = parseErrorResponse(response.errorBody())
|
|
||||||
val errorMessage = errorResponse?.toServiceException()?.message
|
|
||||||
?: "更新记忆失败: ${response.code()}"
|
|
||||||
throw Exception(errorMessage)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
memoryError = e.message ?: "更新记忆失败"
|
|
||||||
Log.e("GroupChatInfoViewModel", "更新记忆失败: ${e.message}", e)
|
|
||||||
} finally {
|
|
||||||
isLoadingMemory = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,625 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.group
|
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.*
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.lazy.items
|
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
|
||||||
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.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
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.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.text.BasicTextField
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.compose.ui.graphics.Brush
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GroupMemoryManageContent(
|
|
||||||
groupId: String,
|
|
||||||
viewModel: GroupChatInfoViewModel,
|
|
||||||
onAddMemoryClick: () -> Unit = {},
|
|
||||||
onDismiss: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
|
||||||
val sheetHeight = screenHeight * 0.95f
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
// 编辑记忆的状态 - 存储正在编辑的记忆ID
|
|
||||||
var editingMemoryId by remember { mutableStateOf<Int?>(null) }
|
|
||||||
|
|
||||||
// 加载配额和列表数据
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
viewModel.loadMemoryQuota()
|
|
||||||
viewModel.loadMemoryList()
|
|
||||||
}
|
|
||||||
|
|
||||||
val quota = viewModel.memoryQuota
|
|
||||||
val memoryList = viewModel.memoryList
|
|
||||||
val isLoading = viewModel.isLoadingMemory
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight)
|
|
||||||
.background(Color(0xFFFAF9FB))
|
|
||||||
) {
|
|
||||||
// 顶部栏:返回按钮 + 标题 + 加号按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(44.dp)
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
// 中间标题 - 绝对居中,不受其他组件影响
|
|
||||||
Text(
|
|
||||||
text = "记忆管理",
|
|
||||||
style = TextStyle(
|
|
||||||
color = Color.Black,
|
|
||||||
fontSize = 17.sp,
|
|
||||||
fontWeight = FontWeight.SemiBold
|
|
||||||
),
|
|
||||||
modifier = Modifier.align(Alignment.Center),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
|
||||||
|
|
||||||
// 左侧返回按钮
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterStart)
|
|
||||||
.clip(RoundedCornerShape(296.dp))
|
|
||||||
.background(Color.White) // 浅灰色背景
|
|
||||||
.noRippleClickable { onDismiss() }
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.drawable.rider_pro_back_icon),
|
|
||||||
contentDescription = "返回",
|
|
||||||
modifier = Modifier.size(16.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = "返回",
|
|
||||||
style = TextStyle(
|
|
||||||
color = Color.Black,
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Normal
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 右侧圆形加号按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.CenterEnd)
|
|
||||||
.size(32.dp)
|
|
||||||
.clip(CircleShape)
|
|
||||||
.background(Color.White)
|
|
||||||
.noRippleClickable { onAddMemoryClick() },
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "+",
|
|
||||||
style = TextStyle(
|
|
||||||
color = Color.Black,
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Medium
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 浅黄色提示栏 - 显示真实的配额数据
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(Color(0xFFFBF8EF))
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("已付费:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
|
||||||
Spacer(Modifier.width(3.dp))
|
|
||||||
Text(
|
|
||||||
"${quota?.purchasedCount ?: 0}",
|
|
||||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("已使用:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
|
||||||
Spacer(Modifier.width(3.dp))
|
|
||||||
Text(
|
|
||||||
"${quota?.currentCount ?: 0}",
|
|
||||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
|
||||||
Text("可用上限:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
|
|
||||||
Spacer(Modifier.width(3.dp))
|
|
||||||
Text(
|
|
||||||
"50",
|
|
||||||
style = TextStyle(color = Color(0xFFFF8D28), fontSize = 13.sp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 记忆列表或空状态
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
if (isLoading) {
|
|
||||||
// 加载中状态
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
CircularProgressIndicator(
|
|
||||||
modifier = Modifier.size(40.dp),
|
|
||||||
color = Color(0xFFFF8D28)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else if (memoryList.isNotEmpty()) {
|
|
||||||
// 显示记忆列表
|
|
||||||
LazyColumn(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentPadding = PaddingValues(horizontal = 16.dp, vertical = 12.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
items(memoryList) { memory ->
|
|
||||||
MemoryItem(
|
|
||||||
memory = memory,
|
|
||||||
isEditing = editingMemoryId == memory.id,
|
|
||||||
onEdit = {
|
|
||||||
editingMemoryId = memory.id
|
|
||||||
},
|
|
||||||
onCancel = {
|
|
||||||
editingMemoryId = null
|
|
||||||
},
|
|
||||||
onSave = { newText ->
|
|
||||||
viewModel.updateMemory(memory.id, newText)
|
|
||||||
editingMemoryId = null
|
|
||||||
},
|
|
||||||
onDelete = {
|
|
||||||
viewModel.deleteMemory(memory.id)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(vertical = 60.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.group),
|
|
||||||
contentDescription = "暂无记忆",
|
|
||||||
modifier = Modifier
|
|
||||||
.height(150.dp).width(180.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(Modifier.height(10.dp))
|
|
||||||
Text(
|
|
||||||
text = "暂无记忆",
|
|
||||||
style = TextStyle(color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
|
|
||||||
)
|
|
||||||
Spacer(Modifier.height(6.dp))
|
|
||||||
Text(
|
|
||||||
text = "点击上方按钮添加群记忆",
|
|
||||||
style = TextStyle(color = Color.Black, fontSize = 14.sp, fontWeight = FontWeight.Normal)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 编辑记忆对话框
|
|
||||||
*/
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun EditGroupMemoryDialog(
|
|
||||||
memory: com.aiosman.ravenow.data.api.AgentRule,
|
|
||||||
viewModel: GroupChatInfoViewModel,
|
|
||||||
onDismiss: () -> Unit,
|
|
||||||
onUpdateMemory: (String) -> Unit
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
var memoryText by remember { mutableStateOf(memory.rule) }
|
|
||||||
val maxLength = 500
|
|
||||||
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
|
|
||||||
|
|
||||||
val gradientColors = listOf(
|
|
||||||
Color(0xFF7C45ED),
|
|
||||||
Color(0xFF7C57EE),
|
|
||||||
Color(0xFF7BD8F8)
|
|
||||||
)
|
|
||||||
val gradientBrush = Brush.horizontalGradient(colors = gradientColors)
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = onDismiss,
|
|
||||||
sheetState = sheetState,
|
|
||||||
containerColor = Color(0xFFFAF9FB),
|
|
||||||
dragHandle = {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(36.dp)
|
|
||||||
.height(5.dp)
|
|
||||||
.padding(top = 5.dp)
|
|
||||||
.background(
|
|
||||||
Color(0xFFCCCCCC),
|
|
||||||
RoundedCornerShape(100.dp)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp)
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 12.dp, vertical = 16.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(88.dp)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(brush = gradientBrush)
|
|
||||||
)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(1.dp)
|
|
||||||
.clip(RoundedCornerShape(15.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(12.dp),
|
|
||||||
contentAlignment = Alignment.TopStart
|
|
||||||
) {
|
|
||||||
BasicTextField(
|
|
||||||
value = memoryText,
|
|
||||||
onValueChange = { newText ->
|
|
||||||
if (newText.length <= maxLength) {
|
|
||||||
memoryText = newText
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cursorBrush = SolidColor(Color.Black),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 13.sp,
|
|
||||||
color = Color.Black,
|
|
||||||
lineHeight = 18.sp
|
|
||||||
),
|
|
||||||
decorationBox = { innerTextField ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
contentAlignment = Alignment.TopStart
|
|
||||||
) {
|
|
||||||
innerTextField()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
// 取消按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(34.dp)
|
|
||||||
.clip(RoundedCornerShape(653.8.dp))
|
|
||||||
.background(Color(0x147C7480))
|
|
||||||
.noRippleClickable { onDismiss() },
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "取消",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 15.sp,
|
|
||||||
color = Color.Black
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(34.dp)
|
|
||||||
.clip(RoundedCornerShape(653.8.dp))
|
|
||||||
.background(Color(0xFF110C13))
|
|
||||||
.noRippleClickable {
|
|
||||||
if (memoryText.isNotBlank() && memoryText != memory.rule) {
|
|
||||||
onUpdateMemory(memoryText)
|
|
||||||
Toast.makeText(context, "记忆更新成功", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "保存",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 15.sp,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 记忆项组件
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun MemoryItem(
|
|
||||||
memory: com.aiosman.ravenow.data.api.AgentRule,
|
|
||||||
isEditing: Boolean = false,
|
|
||||||
onEdit: () -> Unit = {},
|
|
||||||
onCancel: () -> Unit = {},
|
|
||||||
onSave: (String) -> Unit = {},
|
|
||||||
onDelete: () -> Unit
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
var memoryText by remember { mutableStateOf(memory.rule) }
|
|
||||||
val maxLength = 500
|
|
||||||
|
|
||||||
// 渐变边框颜色
|
|
||||||
val gradientColors = listOf(
|
|
||||||
Color(0xFF7C45ED),
|
|
||||||
Color(0xFF7C57EE),
|
|
||||||
Color(0xFF7BD8F8)
|
|
||||||
)
|
|
||||||
val gradientBrush = Brush.horizontalGradient(colors = gradientColors)
|
|
||||||
|
|
||||||
// 当进入编辑模式时,重置文本
|
|
||||||
LaunchedEffect(isEditing) {
|
|
||||||
if (isEditing) {
|
|
||||||
memoryText = memory.rule
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isEditing) {
|
|
||||||
// 编辑模式:显示编辑界面
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
// 文本输入框 - 带渐变边框
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(88.dp)
|
|
||||||
) {
|
|
||||||
// 渐变边框层
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(brush = gradientBrush)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 内容层 - 白色背景,通过padding形成边框效果
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(1.dp)
|
|
||||||
.clip(RoundedCornerShape(15.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(12.dp),
|
|
||||||
contentAlignment = Alignment.TopStart
|
|
||||||
) {
|
|
||||||
BasicTextField(
|
|
||||||
value = memoryText,
|
|
||||||
onValueChange = { newText ->
|
|
||||||
if (newText.length <= maxLength) {
|
|
||||||
memoryText = newText
|
|
||||||
}
|
|
||||||
},
|
|
||||||
cursorBrush = SolidColor(Color.Black),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
textStyle = TextStyle(
|
|
||||||
fontSize = 13.sp,
|
|
||||||
color = Color.Black,
|
|
||||||
lineHeight = 18.sp
|
|
||||||
),
|
|
||||||
decorationBox = { innerTextField ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
contentAlignment = Alignment.TopStart
|
|
||||||
) {
|
|
||||||
innerTextField()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 按钮行:取消和保存
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
|
||||||
) {
|
|
||||||
// 取消按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(34.dp)
|
|
||||||
.clip(RoundedCornerShape(653.8.dp))
|
|
||||||
.background(Color(0x147C7480))
|
|
||||||
.noRippleClickable { onCancel() },
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "取消",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 15.sp,
|
|
||||||
color = Color.Black
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存按钮
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.height(34.dp)
|
|
||||||
.clip(RoundedCornerShape(653.8.dp))
|
|
||||||
.background(Color(0xFF110C13))
|
|
||||||
.noRippleClickable {
|
|
||||||
if (memoryText.isNotBlank() && memoryText != memory.rule) {
|
|
||||||
onSave(memoryText)
|
|
||||||
Toast.makeText(context, "记忆更新成功", Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "保存",
|
|
||||||
style = TextStyle(
|
|
||||||
fontSize = 15.sp,
|
|
||||||
color = Color.White
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 显示模式:显示记忆内容
|
|
||||||
// 格式化日期:从 "2025-10-20T10:30:00Z" 格式转换为 "2025年10月20日"
|
|
||||||
val formattedDate = try {
|
|
||||||
if (memory.createdAt.length >= 10) {
|
|
||||||
val dateStr = memory.createdAt.substring(0, 10)
|
|
||||||
val parts = dateStr.split("-")
|
|
||||||
if (parts.size == 3) {
|
|
||||||
"${parts[0]}年${parts[1].toInt()}月${parts[2].toInt()}日"
|
|
||||||
} else {
|
|
||||||
dateStr
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
memory.createdAt
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
if (memory.createdAt.length >= 10) memory.createdAt.substring(0, 10) else memory.createdAt
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(16.dp))
|
|
||||||
.background(Color.White)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 16.dp)
|
|
||||||
) {
|
|
||||||
// 主文本 - 顶部
|
|
||||||
Text(
|
|
||||||
text = memory.rule,
|
|
||||||
style = TextStyle(
|
|
||||||
color = Color.Black,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
lineHeight = 18.sp
|
|
||||||
),
|
|
||||||
maxLines = 3,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 底部行:日期 + 编辑删除按钮
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.Bottom
|
|
||||||
) {
|
|
||||||
// 日期文本 - 左侧
|
|
||||||
Text(
|
|
||||||
text = formattedDate,
|
|
||||||
style = TextStyle(
|
|
||||||
color = Color(0x993C3C43),
|
|
||||||
fontSize = 11.sp
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 编辑和删除图标 - 右侧
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 编辑图标
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.mipmap.icons_infor_edit),
|
|
||||||
contentDescription = "编辑",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(20.dp)
|
|
||||||
.noRippleClickable { onEdit() },
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.mipmap.iconsdelete),
|
|
||||||
contentDescription = "删除",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(20.dp)
|
|
||||||
.noRippleClickable { onDelete() },
|
|
||||||
colorFilter = ColorFilter.tint(Color(0xFFEE2A33))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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))
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
package com.aiosman.ravenow.ui.index
|
package com.aiosman.ravenow.ui.index
|
||||||
|
|
||||||
import androidx.compose.animation.animateColorAsState
|
import androidx.compose.animation.animateColorAsState
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
|
||||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
|
||||||
import androidx.compose.animation.core.LinearOutSlowInEasing
|
|
||||||
import androidx.compose.animation.core.updateTransition
|
|
||||||
import androidx.compose.animation.core.animateFloat
|
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||||
import androidx.compose.foundation.Image
|
import androidx.compose.foundation.Image
|
||||||
@@ -17,21 +11,15 @@ import androidx.compose.foundation.layout.Column
|
|||||||
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
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.systemBars
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
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
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
import androidx.compose.foundation.layout.requiredHeight
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
import androidx.compose.foundation.layout.requiredWidth
|
import androidx.compose.foundation.layout.requiredWidth
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.pager.HorizontalPager
|
import androidx.compose.foundation.pager.HorizontalPager
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
@@ -54,10 +42,8 @@ import androidx.compose.runtime.CompositionLocalProvider
|
|||||||
import androidx.compose.runtime.DisposableEffect
|
import androidx.compose.runtime.DisposableEffect
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
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.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
@@ -73,11 +59,7 @@ import androidx.compose.ui.text.font.FontWeight
|
|||||||
import androidx.compose.ui.unit.LayoutDirection
|
import androidx.compose.ui.unit.LayoutDirection
|
||||||
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.zIndex
|
|
||||||
import com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||||
@@ -141,16 +123,151 @@ fun IndexScreen() {
|
|||||||
gesturesEnabled = drawerState.isOpen,
|
gesturesEnabled = drawerState.isOpen,
|
||||||
drawerContent = {
|
drawerContent = {
|
||||||
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
|
||||||
SideMenuContent(
|
Column(
|
||||||
onClose = {
|
modifier = Modifier
|
||||||
coroutineScope.launch {
|
.requiredWidth(250.dp)
|
||||||
drawerState.close()
|
.fillMaxHeight()
|
||||||
|
.background(
|
||||||
|
AppColors.background
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.height(88.dp))
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_account,
|
||||||
|
label = stringResource(R.string.account_and_security),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(NavigationRoute.AccountSetting.route)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
navController = navController,
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
context = context,
|
NavItem(
|
||||||
isDrawerOpen = drawerState.isOpen
|
iconRes = R.drawable.rider_pro_favourited,
|
||||||
)
|
label = stringResource(R.string.favourites),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(NavigationRoute.FavouriteList.route)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_night,
|
||||||
|
label = stringResource(R.string.dark_mode),
|
||||||
|
rightContent = {
|
||||||
|
Switch(
|
||||||
|
checked = AppState.darkMode,
|
||||||
|
onCheckedChange = {
|
||||||
|
AppState.switchTheme()
|
||||||
|
},
|
||||||
|
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = Color.White,
|
||||||
|
checkedTrackColor = AppColors.main,
|
||||||
|
uncheckedThumbColor = Color.White,
|
||||||
|
uncheckedTrackColor = AppColors.main.copy(alpha = 0.5f),
|
||||||
|
uncheckedBorderColor = Color.Transparent
|
||||||
|
),
|
||||||
|
modifier = Modifier.scale(0.8f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// divider
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(1.dp)
|
||||||
|
.background(AppColors.divider)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_about,
|
||||||
|
label = stringResource(R.string.blocked),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_about,
|
||||||
|
label = stringResource(R.string.feedback),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_about,
|
||||||
|
label = stringResource(R.string.about_rave_now),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
navController.navigate(NavigationRoute.AboutScreen.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
// divider
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp, end = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(1.dp)
|
||||||
|
.background(AppColors.divider)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NavItem(
|
||||||
|
// iconRes = R.drawable.rave_now_nav_switch,
|
||||||
|
// label = "Switch Account"
|
||||||
|
// )
|
||||||
|
// Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
NavItem(
|
||||||
|
iconRes = R.drawable.rave_now_nav_logout,
|
||||||
|
label = stringResource(R.string.logout),
|
||||||
|
modifier = Modifier.noRippleClickable {
|
||||||
|
coroutineScope.launch {
|
||||||
|
drawerState.close()
|
||||||
|
// 只有非游客用户才需要取消注册推送设备
|
||||||
|
if (!AppStore.isGuest) {
|
||||||
|
Messaging.unregisterDevice(context)
|
||||||
|
}
|
||||||
|
AppStore.apply {
|
||||||
|
token = null
|
||||||
|
rememberMe = false
|
||||||
|
isGuest = false // 清除游客状态
|
||||||
|
saveData()
|
||||||
|
}
|
||||||
|
// 删除推送渠道
|
||||||
|
|
||||||
|
navController.navigate(NavigationRoute.Login.route) {
|
||||||
|
popUpTo(NavigationRoute.Login.route) {
|
||||||
|
inclusive = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AppState.ReloadAppState(context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
@@ -159,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 = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -229,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
|
||||||
@@ -248,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) AppColors.brandColorsColor else AppColors.text,
|
||||||
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -330,6 +444,7 @@ fun IndexScreen() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@@ -506,343 +621,3 @@ fun NavItem(
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun SideMenuContent(
|
|
||||||
onClose: () -> Unit,
|
|
||||||
navController: androidx.navigation.NavController,
|
|
||||||
context: android.content.Context,
|
|
||||||
isDrawerOpen: Boolean
|
|
||||||
) {
|
|
||||||
val appColors = LocalAppTheme.current
|
|
||||||
val coroutineScope = rememberCoroutineScope()
|
|
||||||
var messageNotificationEnabled by remember { mutableStateOf(true) }
|
|
||||||
var darkModeEnabled by remember { mutableStateOf(AppState.darkMode) }
|
|
||||||
|
|
||||||
// 菜单背景色 #FAF9FB
|
|
||||||
val menuBackgroundColor = Color(0xFFFAF9FB)
|
|
||||||
// 遮罩颜色 黑色透明度0.6
|
|
||||||
val overlayColor = Color.Black.copy(alpha = 0.6f)
|
|
||||||
// 卡片背景色 白色
|
|
||||||
val cardBackgroundColor = Color.White
|
|
||||||
// 跟随系统文字颜色 #979499
|
|
||||||
val followSystemTextColor = Color(0xFF979499)
|
|
||||||
// 开关开启颜色 #7C45ED
|
|
||||||
val switchActiveColor = Color(0xFF7C45ED)
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
) {
|
|
||||||
// 左侧半透明遮罩(平滑淡入淡出)
|
|
||||||
val overlayTransition = updateTransition(targetState = isDrawerOpen, label = "overlay")
|
|
||||||
val overlayAlpha by overlayTransition.animateFloat(
|
|
||||||
transitionSpec = {
|
|
||||||
if (targetState) {
|
|
||||||
tween(durationMillis = 400, easing = LinearOutSlowInEasing)
|
|
||||||
} else {
|
|
||||||
tween(durationMillis = 300, easing = FastOutLinearInEasing)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
label = "overlayAlpha"
|
|
||||||
) { open -> if (open) 0.6f else 0f }
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(Color.Black.copy(alpha = overlayAlpha))
|
|
||||||
)
|
|
||||||
|
|
||||||
// 右侧菜单面板
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.requiredWidth(302.dp)
|
|
||||||
.requiredHeight(874.dp)
|
|
||||||
.align(Alignment.CenterEnd)
|
|
||||||
.background(menuBackgroundColor)
|
|
||||||
) {
|
|
||||||
// 顶部状态栏间距
|
|
||||||
val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
|
|
||||||
|
|
||||||
// 扫一扫功能入口 - 右边距离右边66pt
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
.offset(x = (-112).dp, y = 88.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
// TODO: 实现扫一扫功能
|
|
||||||
},
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 扫一扫图标(使用现有图标或占位)
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.sao),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// 绝对定位的"扫一扫"文字:上方71.5dp,右侧66dp
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.scan_qr),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = Color.Black,
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
.offset(x = (-66).dp, y = 91.5.dp)
|
|
||||||
)
|
|
||||||
// QR码图标 - 右边距离右边112dp,上边距离上边68pt
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.qr_code_icon),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
.offset(x = (-26).dp, y = 88.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
// TODO: 实现QR码功能
|
|
||||||
},
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 菜单选项卡片组 - 第一组卡片上方距离上方108pt(绝对定位)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.offset(y = 128.dp) // 直接距离顶部128dp(整体下移20dp)
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
// 第一组卡片:编辑资料、账号安全、收藏
|
|
||||||
MenuCard(
|
|
||||||
backgroundColor = cardBackgroundColor,
|
|
||||||
width = 270.dp,
|
|
||||||
height = 164.dp,
|
|
||||||
items = listOf(
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.icons_edited_data,
|
|
||||||
label = stringResource(R.string.edit_profile_info),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
navController.navigate(NavigationRoute.AccountEdit.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.icons_account_and_security,
|
|
||||||
label = stringResource(R.string.account_and_security),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
navController.navigate(NavigationRoute.AccountSetting.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.collect,
|
|
||||||
label = stringResource(R.string.favourites),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
navController.navigate(NavigationRoute.FavouriteList.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 第二组卡片:暗色模式、消息通知
|
|
||||||
MenuCard(
|
|
||||||
backgroundColor = cardBackgroundColor,
|
|
||||||
width = 270.dp,
|
|
||||||
height = 112.dp, // 根据设计图,第二组卡片高度为112dp
|
|
||||||
items = listOf(
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.icons_dark_mode,
|
|
||||||
label = stringResource(R.string.dark_mode),
|
|
||||||
rightContent = {
|
|
||||||
Switch(
|
|
||||||
checked = darkModeEnabled,
|
|
||||||
onCheckedChange = {
|
|
||||||
darkModeEnabled = it
|
|
||||||
AppState.darkMode = it
|
|
||||||
AppState.appTheme = if (it) {
|
|
||||||
com.aiosman.ravenow.DarkThemeColors()
|
|
||||||
} else {
|
|
||||||
com.aiosman.ravenow.LightThemeColors()
|
|
||||||
}
|
|
||||||
AppStore.saveDarkMode(it)
|
|
||||||
},
|
|
||||||
colors = SwitchDefaults.colors(
|
|
||||||
checkedThumbColor = Color.White,
|
|
||||||
checkedTrackColor = switchActiveColor,
|
|
||||||
uncheckedThumbColor = Color.White,
|
|
||||||
uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f),
|
|
||||||
uncheckedBorderColor = Color.Transparent
|
|
||||||
),
|
|
||||||
modifier = Modifier.size(width = 64.dp, height = 28.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.icons_bell,
|
|
||||||
label = stringResource(R.string.message_notification),
|
|
||||||
rightContent = {
|
|
||||||
Switch(
|
|
||||||
checked = messageNotificationEnabled,
|
|
||||||
onCheckedChange = { messageNotificationEnabled = it },
|
|
||||||
colors = SwitchDefaults.colors(
|
|
||||||
checkedThumbColor = Color.White,
|
|
||||||
checkedTrackColor = switchActiveColor,
|
|
||||||
uncheckedThumbColor = Color.White,
|
|
||||||
uncheckedTrackColor = switchActiveColor.copy(alpha = 0.5f),
|
|
||||||
uncheckedBorderColor = Color.Transparent
|
|
||||||
),
|
|
||||||
modifier = Modifier.size(width = 64.dp, height = 28.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
// 第三组卡片:关于派派、反馈、退出登录
|
|
||||||
MenuCard(
|
|
||||||
backgroundColor = cardBackgroundColor,
|
|
||||||
width = 270.dp,
|
|
||||||
height = 164.dp,
|
|
||||||
items = listOf(
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.icons_about,
|
|
||||||
label = stringResource(R.string.about_paipai),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.feedback_icon,
|
|
||||||
label = stringResource(R.string.feedback),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
navController.navigate(NavigationRoute.AboutScreen.route)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
),
|
|
||||||
MenuItem(
|
|
||||||
icon = R.mipmap.log_out_icon,
|
|
||||||
label = stringResource(R.string.logout_confirm),
|
|
||||||
onClick = {
|
|
||||||
coroutineScope.launch {
|
|
||||||
onClose()
|
|
||||||
// 只有非游客用户才需要取消注册推送设备
|
|
||||||
if (!AppStore.isGuest) {
|
|
||||||
Messaging.unregisterDevice(context)
|
|
||||||
}
|
|
||||||
AppStore.apply {
|
|
||||||
token = null
|
|
||||||
rememberMe = false
|
|
||||||
isGuest = false
|
|
||||||
saveData()
|
|
||||||
}
|
|
||||||
navController.navigate(NavigationRoute.Login.route) {
|
|
||||||
popUpTo(NavigationRoute.Login.route) {
|
|
||||||
inclusive = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
AppState.ReloadAppState(context)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
showRightArrow = false
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class MenuItem(
|
|
||||||
val icon: Int,
|
|
||||||
val label: String,
|
|
||||||
val onClick: (() -> Unit)? = null,
|
|
||||||
val rightContent: @Composable (() -> Unit)? = null,
|
|
||||||
val showRightArrow: Boolean = true
|
|
||||||
)
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MenuCard(
|
|
||||||
backgroundColor: Color,
|
|
||||||
items: List<MenuItem>,
|
|
||||||
width: androidx.compose.ui.unit.Dp? = null,
|
|
||||||
height: androidx.compose.ui.unit.Dp? = null
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.then(if (width != null) Modifier.requiredWidth(width) else Modifier.fillMaxWidth())
|
|
||||||
.then(if (height != null) Modifier.requiredHeight(height) else Modifier)
|
|
||||||
.background(backgroundColor, RoundedCornerShape(16.dp))
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
verticalArrangement = if (height != null) Arrangement.SpaceEvenly else Arrangement.spacedBy(8.dp) // 固定高度时均匀分布
|
|
||||||
) {
|
|
||||||
items.forEachIndexed { index, item ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.then(if (height != null) Modifier.weight(1f) else Modifier),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
MenuItemRow(item = item, compact = height != null) // 传递compact参数
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun MenuItemRow(item: MenuItem, compact: Boolean = false) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.then(
|
|
||||||
if (item.onClick != null) {
|
|
||||||
Modifier.noRippleClickable { item.onClick?.invoke() }
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.padding(vertical = if (compact) 4.dp else 8.dp), // 紧凑模式下减少垂直padding
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.weight(1f)
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = item.icon),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = item.label,
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = Color.Black
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item.rightContent != null) {
|
|
||||||
item.rightContent?.invoke()
|
|
||||||
} else if (item.showRightArrow) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rave_now_nav_right),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color(0xFF111213))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,14 +16,14 @@ 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.rider_pro_nav_home_hl },
|
||||||
label = { stringResource(R.string.main_ai) }
|
label = { stringResource(R.string.main_home) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Ai : NavigationItem("Ai",
|
data object Ai : NavigationItem("Ai",
|
||||||
icon = { R.mipmap.bars_x_buttons_discover_bold},
|
icon = { R.mipmap.bars_x_buttons_discover_bold},
|
||||||
selectedIcon = { R.mipmap.bars_x_buttons_discover_fill },
|
selectedIcon = { R.mipmap.dynamic_hl },
|
||||||
label = { stringResource(R.string.index_dynamic) }
|
label = { stringResource(R.string.index_dynamic) }
|
||||||
)
|
)
|
||||||
// data object Ai : NavigationItem("Ai",
|
// data object Ai : NavigationItem("Ai",
|
||||||
@@ -40,13 +40,13 @@ sealed class NavigationItem(
|
|||||||
|
|
||||||
data object Notification : NavigationItem("Notification",
|
data object Notification : NavigationItem("Notification",
|
||||||
icon = { R.drawable.rider_pro_nav_notification },
|
icon = { R.drawable.rider_pro_nav_notification },
|
||||||
selectedIcon = { R.mipmap.bars_x_buttons_chat_s },
|
selectedIcon = { R.mipmap.rider_pro_nav_message_hl },
|
||||||
label = { stringResource(R.string.main_message) }
|
label = { stringResource(R.string.main_message) }
|
||||||
)
|
)
|
||||||
|
|
||||||
data object Profile : NavigationItem("Profile",
|
data object Profile : NavigationItem("Profile",
|
||||||
icon = { R.drawable.rider_pro_nav_profile },
|
icon = { R.drawable.rider_pro_nav_profile },
|
||||||
selectedIcon = { R.mipmap.bars_x_buttons_user_s },
|
selectedIcon = { R.mipmap.rider_pro_nav_profile_hl },
|
||||||
label = { stringResource(R.string.main_profile) }
|
label = { stringResource(R.string.main_profile) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -6,10 +6,7 @@ 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.ApiClient
|
||||||
import com.aiosman.ravenow.data.api.CategoryTemplate
|
|
||||||
import com.aiosman.ravenow.data.api.RaveNowAPI
|
import com.aiosman.ravenow.data.api.RaveNowAPI
|
||||||
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
import com.aiosman.ravenow.data.api.SingleChatRequestBody
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
||||||
@@ -17,37 +14,6 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
|||||||
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userService
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import android.util.Log
|
|
||||||
import com.aiosman.ravenow.data.Room
|
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import com.aiosman.ravenow.AppStore
|
|
||||||
import com.aiosman.ravenow.ConstVars
|
|
||||||
import com.aiosman.ravenow.data.api.CreateGroupChatRequestBody
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel.createGroupChat
|
|
||||||
import com.aiosman.ravenow.ui.navigateToGroupChat
|
|
||||||
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import android.content.Context
|
|
||||||
import android.widget.Toast
|
|
||||||
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(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val avatar: String = "",
|
|
||||||
val banner: String = "",
|
|
||||||
val memberCount: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
object AgentViewModel: ViewModel() {
|
object AgentViewModel: ViewModel() {
|
||||||
|
|
||||||
@@ -56,17 +22,10 @@ object AgentViewModel: ViewModel() {
|
|||||||
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var categories by mutableStateOf<List<CategoryItem>>(emptyList())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var errorMessage by mutableStateOf<String?>(null)
|
var errorMessage by mutableStateOf<String?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var rooms by mutableStateOf<List<Room>>(emptyList())
|
|
||||||
private set
|
|
||||||
|
|
||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
@@ -74,242 +33,31 @@ object AgentViewModel: ViewModel() {
|
|||||||
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)
|
|
||||||
private set
|
|
||||||
|
|
||||||
private val pageSize = 20
|
|
||||||
private var currentCategoryId: Int? = null
|
|
||||||
|
|
||||||
// 缓存:使用分类ID作为key,null表示推荐列表
|
|
||||||
private val agentCache = mutableMapOf<Int?, AgentCacheData>()
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadAgentData()
|
loadAgentData()
|
||||||
loadCategories()
|
|
||||||
loadChatRooms()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAgentData(categoryId: Int? = null, page: Int = 1, isLoadMore: Boolean = false, forceRefresh: Boolean = false) {
|
private fun loadAgentData() {
|
||||||
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
|
||||||
try {
|
try {
|
||||||
val response = if (categoryId != null) {
|
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
||||||
// 根据分类ID获取智能体
|
|
||||||
apiClient.getAgent(
|
|
||||||
page = page,
|
|
||||||
pageSize = pageSize,
|
|
||||||
withWorkflow = 1,
|
|
||||||
categoryIds = listOf(categoryId),
|
|
||||||
random = 1
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 获取推荐智能体,使用random=1
|
|
||||||
apiClient.getAgent(
|
|
||||||
page = page,
|
|
||||||
pageSize = pageSize,
|
|
||||||
withWorkflow = 1,
|
|
||||||
categoryIds = null,
|
|
||||||
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>()
|
agentItems = agents.map { agent ->
|
||||||
val newAgentItems = agents.map { agent ->
|
|
||||||
AgentItem.fromAgent(agent)
|
AgentItem.fromAgent(agent)
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
|
||||||
} else {
|
|
||||||
isLoading = false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadChatRooms() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
val response = apiClient.getRooms(
|
|
||||||
page = 1,
|
|
||||||
pageSize = 20,
|
|
||||||
isRecommended = 1,
|
|
||||||
random = 1
|
|
||||||
)
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
val allRooms = response.body()?.list ?: emptyList()
|
|
||||||
val targetCount = (allRooms.size / 2) * 2
|
|
||||||
rooms = allRooms.take(targetCount)
|
|
||||||
|
|
||||||
// 转换为ChatRoom格式用于兼容现有UI
|
|
||||||
chatRooms = rooms.map { room ->
|
|
||||||
ChatRoom(
|
|
||||||
id = room.id,
|
|
||||||
name = room.name,
|
|
||||||
avatar = room.avatar,
|
|
||||||
banner = ConstVars.BASE_SERVER + "/api/v1/outside/" + room.recommendBanner + "?token=${AppStore.token}",
|
|
||||||
memberCount = room.userCount
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
// 如果网络请求失败,使用默认数据
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadCategories() {
|
|
||||||
viewModelScope.launch {
|
|
||||||
// 如果分类已经加载,不重复请求
|
|
||||||
if (categories.isNotEmpty()) {
|
|
||||||
println("使用已缓存的分类数据,数量: ${categories.size}")
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取完整的语言标记(如 "zh-CN")
|
|
||||||
val fullLangTag = com.aiosman.ravenow.utils.Utils.getPreferredLanguageTag()
|
|
||||||
// 转换为后端支持的语言代码(仅支持 zh、cn、ja)
|
|
||||||
val sysLang = convertToSupportedLangCode(fullLangTag)
|
|
||||||
val response = apiClient.getCategories(
|
|
||||||
page = 1,
|
|
||||||
pageSize = 100,
|
|
||||||
isActive = true,
|
|
||||||
// withChildren = false,
|
|
||||||
// withParent = false,
|
|
||||||
// withCount = true,
|
|
||||||
// hideEmpty = true,
|
|
||||||
lang = sysLang
|
|
||||||
)
|
|
||||||
println("分类数据请求完成,响应成功: ${response.isSuccessful}, 原始语言标记: $fullLangTag, 转换后: $sysLang")
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
val categoryList = response.body()?.list ?: emptyList()
|
|
||||||
println("获取到 ${categoryList.size} 个分类")
|
|
||||||
// 使用转换后的语言代码获取翻译后的分类名称
|
|
||||||
categories = categoryList.map { category ->
|
|
||||||
CategoryItem.fromCategoryTemplate(category, sysLang)
|
|
||||||
}
|
|
||||||
println("成功处理并映射了 ${categories.size} 个分类")
|
|
||||||
} else {
|
|
||||||
errorMessage = "获取分类数据失败: ${response.code()}"
|
|
||||||
println("获取分类数据失败: ${response.code()}")
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
errorMessage = "获取分类数据失败: ${e.message}"
|
|
||||||
println("获取分类数据异常: ${e.message}")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 将完整的语言标记转换为后端支持的语言代码
|
|
||||||
* 后端仅支持: zh, cn, ja
|
|
||||||
*
|
|
||||||
* @param langTag 完整的语言标记,如 "zh-CN", "zh-TW", "ja-JP", "en-US" 等
|
|
||||||
* @return 后端支持的语言代码,默认返回 "zh"
|
|
||||||
*/
|
|
||||||
private fun convertToSupportedLangCode(langTag: String): String {
|
|
||||||
return when {
|
|
||||||
langTag.startsWith("zh", ignoreCase = true) -> "zh"
|
|
||||||
langTag.startsWith("ja", ignoreCase = true) -> "ja"
|
|
||||||
// 如果是中文相关的其他标记,也返回 zh
|
|
||||||
langTag.equals("cn", ignoreCase = true) -> "cn"
|
|
||||||
// 默认返回中文
|
|
||||||
else -> "zh"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadAgentsByCategory(categoryId: Int) {
|
|
||||||
loadAgentData(categoryId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun loadAllAgents() {
|
|
||||||
loadAgentData()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载更多Agent数据
|
|
||||||
*/
|
|
||||||
fun loadMoreAgents() {
|
|
||||||
// 检查是否正在加载或没有更多数据
|
|
||||||
if (isLoadingMore || !hasMoreData) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextPage = currentPage + 1
|
|
||||||
loadAgentData(
|
|
||||||
categoryId = currentCategoryId,
|
|
||||||
page = nextPage,
|
|
||||||
isLoadMore = true
|
|
||||||
)
|
|
||||||
}
|
|
||||||
fun createSingleChat(
|
fun createSingleChat(
|
||||||
openId: String,
|
openId: String,
|
||||||
) {
|
) {
|
||||||
@@ -348,12 +96,10 @@ object AgentViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 刷新当前分类的Agent数据(强制刷新,清除缓存)
|
* 刷新推荐Agent数据
|
||||||
*/
|
*/
|
||||||
fun refreshAgentData() {
|
fun refreshAgentData() {
|
||||||
// 清除当前分类的缓存
|
loadAgentData()
|
||||||
agentCache.remove(currentCategoryId)
|
|
||||||
loadAgentData(categoryId = currentCategoryId, forceRefresh = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -365,115 +111,14 @@ object AgentViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加入房间
|
|
||||||
*/
|
|
||||||
fun joinRoom(
|
|
||||||
id: Int,
|
|
||||||
name: String,
|
|
||||||
avatar: String,
|
|
||||||
context: Context,
|
|
||||||
navController: NavHostController,
|
|
||||||
onSuccess: () -> Unit,
|
|
||||||
onError: (String) -> Unit
|
|
||||||
) {
|
|
||||||
// 防止重复点击
|
|
||||||
if (isJoiningRoom) return
|
|
||||||
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
isJoiningRoom = true
|
|
||||||
val response = apiClient.joinRoom(JoinGroupChatRequestBody(roomId = id))
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
// 打开房间
|
|
||||||
val openRoomResponse = apiClient.createGroupChatAi(
|
|
||||||
roomId = id
|
|
||||||
)
|
|
||||||
|
|
||||||
if (openRoomResponse.isSuccessful){
|
|
||||||
val respData = openRoomResponse.body()
|
|
||||||
respData?.let {
|
|
||||||
viewModelScope.launch {
|
|
||||||
try {
|
|
||||||
// 群聊直接使用群ID进行导航
|
|
||||||
navController.navigateToGroupChat(
|
|
||||||
id = respData.data.trtcRoomId,
|
|
||||||
name = name,
|
|
||||||
avatar = avatar
|
|
||||||
)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
onError("加入房间失败")
|
|
||||||
e.printStackTrace()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onSuccess()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// 处理错误响应
|
|
||||||
try {
|
|
||||||
val errorBody = response.errorBody()?.string()
|
|
||||||
if (errorBody != null) {
|
|
||||||
val gson = Gson()
|
|
||||||
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
|
|
||||||
|
|
||||||
// 在主线程显示 Toast
|
|
||||||
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
|
|
||||||
onError(errorResponse.error)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("加入房间失败")
|
|
||||||
}
|
|
||||||
} catch (parseException: Exception) {
|
|
||||||
// 如果解析错误响应失败,显示默认错误信息
|
|
||||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("加入房间失败")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("网络请求失败:${e.message}")
|
|
||||||
} finally {
|
|
||||||
isJoiningRoom = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
||||||
*/
|
*/
|
||||||
fun ResetModel() {
|
fun ResetModel() {
|
||||||
agentItems = emptyList()
|
agentItems = emptyList()
|
||||||
categories = emptyList()
|
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
isLoading = false
|
isLoading = false
|
||||||
isLoadingMore = false
|
|
||||||
currentPage = 1
|
|
||||||
hasMoreData = true
|
|
||||||
currentCategoryId = null
|
|
||||||
// 清空缓存
|
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,25 +91,22 @@ 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.juhao_dark
|
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(width = 181.dp, height = 153.dp)
|
.size(181.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.agent_chat_empty_title),
|
text = stringResource(R.string.agent_chat_empty_title),
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.agent_chat_empty_subtitle),
|
text = stringResource(R.string.agent_chat_empty_subtitle),
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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.piao_dark
|
|
||||||
else R.mipmap.invalid_name_2),
|
|
||||||
contentDescription = "null data",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 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}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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,59 +73,29 @@ 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.piao_dark
|
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(width = 181.dp, height = 153.dp)
|
.size(181.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.friend_chat_empty_title),
|
text = stringResource(R.string.friend_chat_empty_title),
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.friend_chat_empty_subtitle),
|
text = stringResource(R.string.friend_chat_empty_subtitle),
|
||||||
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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -71,59 +71,28 @@ 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.fei_dark
|
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(width = 181.dp, height = 153.dp)
|
.size(181.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.group_chat_empty),
|
text = stringResource(R.string.group_chat_empty),
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.group_chat_empty_join),
|
text = stringResource(R.string.group_chat_empty_join),
|
||||||
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(
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.aiosman.ravenow.ui.index.tabs.message.tab
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.icu.util.Calendar
|
import android.icu.util.Calendar
|
||||||
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
|
||||||
@@ -168,12 +167,10 @@ object GroupChatListViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createGroupChat(
|
fun createGroupChat(
|
||||||
trtcGroupId: String? = null,
|
trtcGroupId: String,
|
||||||
roomId: Int? = null
|
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId,roomId = roomId)
|
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId)
|
||||||
Log.d("debug",response.toString())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,6 @@ import com.aiosman.ravenow.event.MomentAddEvent
|
|||||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||||
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
||||||
import com.aiosman.ravenow.event.MomentRemoveEvent
|
import com.aiosman.ravenow.event.MomentRemoveEvent
|
||||||
import com.aiosman.ravenow.ui.follower.FollowerNoticeViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.greenrobot.eventbus.Subscribe
|
import org.greenrobot.eventbus.Subscribe
|
||||||
@@ -73,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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -87,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)
|
||||||
@@ -123,11 +118,10 @@ open class BaseMomentModel :ViewModel(){
|
|||||||
userService.unFollowUser(moment.authorId.toString())
|
userService.unFollowUser(moment.authorId.toString())
|
||||||
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, false))
|
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, false))
|
||||||
} else {
|
} else {
|
||||||
// 调用 FollowerNoticeViewModel.followUser() 实现与 FollowerNotice.kt 相同的效果
|
userService.followUser(moment.authorId.toString())
|
||||||
// 该方法内部会调用 userService.followUser() 并发布 FollowChangeEvent 事件
|
EventBus.getDefault().post(FollowChangeEvent(moment.authorId, true))
|
||||||
FollowerNoticeViewModel.followUser(moment.authorId)
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -46,18 +44,16 @@ import com.aiosman.ravenow.ui.NavigationRoute
|
|||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic.Dynamic
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.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.news.News
|
||||||
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
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
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 +67,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:推荐、短视频、新闻、探索、关注、热门
|
// 游客模式下显示3个tab:Worldwide、Hot、News,非游客模式显示4个tab:Worldwide、Following、Hot、News
|
||||||
val tabCount = 6
|
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,28 +80,86 @@ 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
|
||||||
|
// .noRippleClickable {
|
||||||
|
// scope.launch {
|
||||||
|
// pagerState.animateScrollToPage(0)
|
||||||
|
// }
|
||||||
|
// }.padding(start = 16.dp),
|
||||||
|
// verticalArrangement = Arrangement.Center,
|
||||||
|
// horizontalAlignment = Alignment.CenterHorizontally
|
||||||
|
//
|
||||||
|
// ) {
|
||||||
|
// Text(
|
||||||
|
// text = stringResource(R.string.index_worldwide),
|
||||||
|
// fontSize = if (pagerState.currentPage == 0)18.sp else 16.sp,
|
||||||
|
// color = if (pagerState.currentPage == 0) AppColors.text else AppColors.nonActiveText,
|
||||||
|
// fontWeight = FontWeight.W600)
|
||||||
|
// 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",
|
||||||
|
// modifier = Modifier
|
||||||
|
// .width(34.dp)
|
||||||
|
// .height(4.dp)
|
||||||
|
// )
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
// Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.moment),
|
||||||
|
fontSize = 20.sp,
|
||||||
|
fontWeight = FontWeight.W900,
|
||||||
|
color = AppColors.text,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.align(Alignment.CenterVertically)
|
||||||
.horizontalScroll(rememberScrollState()),
|
)
|
||||||
horizontalArrangement = Arrangement.Start,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
val tabDebouncer = rememberDebouncer()
|
|
||||||
|
|
||||||
// 推荐标签
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.tab_recommend),
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
||||||
|
contentDescription = "search",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.navigate(NavigationRoute.Search.route)
|
||||||
|
},
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(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,
|
isSelected = pagerState.currentPage == 0,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
@@ -115,40 +169,49 @@ fun MomentsList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabSpacer()
|
||||||
|
|
||||||
// 短视频标签
|
// 只有非游客用户才显示"关注"tab
|
||||||
UnderlineTabItem(
|
if (!AppStore.isGuest) {
|
||||||
text = stringResource(R.string.tab_short_video),
|
Box {
|
||||||
isSelected = pagerState.currentPage == 1,
|
CustomTabItem(
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// 动态标签
|
|
||||||
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),
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
TabSpacer()
|
||||||
|
|
||||||
|
// 新闻标签
|
||||||
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
|
text = stringResource(R.string.index_news),
|
||||||
isSelected = pagerState.currentPage == 3,
|
isSelected = pagerState.currentPage == 3,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
@@ -158,67 +221,40 @@ fun MomentsList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// 热门标签
|
// 热门标签 (游客模式)
|
||||||
UnderlineTabItem(
|
Box {
|
||||||
|
CustomTabItem(
|
||||||
text = stringResource(R.string.index_hot),
|
text = stringResource(R.string.index_hot),
|
||||||
isSelected = pagerState.currentPage == 4,
|
isSelected = pagerState.currentPage == 1,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
pagerState.animateScrollToPage(4)
|
pagerState.animateScrollToPage(1)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 热门标签 (游客模式)
|
|
||||||
UnderlineTabItem(
|
|
||||||
text = stringResource(R.string.index_hot),
|
|
||||||
isSelected = pagerState.currentPage == 4,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(4)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TabSpacer()
|
||||||
|
|
||||||
// 新闻标签
|
// 新闻标签 (游客模式)
|
||||||
UnderlineTabItem(
|
Box {
|
||||||
text = stringResource(R.string.tab_news),
|
CustomTabItem(
|
||||||
isSelected = pagerState.currentPage == 5,
|
text = stringResource(R.string.index_news),
|
||||||
onClick = {
|
isSelected = pagerState.currentPage == 2,
|
||||||
tabDebouncer {
|
onClick = {
|
||||||
scope.launch {
|
tabDebouncer {
|
||||||
pagerState.animateScrollToPage(5)
|
scope.launch {
|
||||||
|
pagerState.animateScrollToPage(2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索按钮
|
|
||||||
val lastClickTime = remember { mutableStateOf(0L) }
|
|
||||||
val clickDelay = 500L
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
|
||||||
contentDescription = "search",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastClickTime.value > clickDelay) {
|
|
||||||
lastClickTime.value = currentTime
|
|
||||||
navController.navigate(NavigationRoute.Search.route)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
@@ -227,33 +263,34 @@ fun MomentsList() {
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
when (it) {
|
if (AppStore.isGuest) {
|
||||||
0 -> {
|
// 游客模式:Worldwide(0), Hot(1), News(2)
|
||||||
// 推荐页面
|
when (it) {
|
||||||
NewsScreen()
|
0 -> {
|
||||||
}
|
Dynamic()
|
||||||
1 -> {
|
}
|
||||||
// 短视频页面
|
1 -> {
|
||||||
}
|
|
||||||
2 -> {
|
|
||||||
// 动态页面 - 暂时显示时间线内容
|
|
||||||
Dynamic()
|
|
||||||
}
|
|
||||||
3 -> {
|
|
||||||
// 关注页面 (仅非游客用户) 或 热门页面 (游客用户)
|
|
||||||
if (AppStore.isGuest) {
|
|
||||||
HotMomentsList()
|
HotMomentsList()
|
||||||
} else {
|
}
|
||||||
TimelineMomentsList()
|
2 -> {
|
||||||
|
News()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
4 -> {
|
} else {
|
||||||
// 热门页面 (仅非游客用户)
|
// 正常用户:Worldwide(0), Following(1), Hot(2), News(3)
|
||||||
HotMomentsList()
|
when (it) {
|
||||||
}
|
0 -> {
|
||||||
5 -> {
|
Dynamic()
|
||||||
// 新闻页面
|
}
|
||||||
NewsScreen()
|
1 -> {
|
||||||
|
TimelineMomentsList()
|
||||||
|
}
|
||||||
|
2 -> {
|
||||||
|
HotMomentsList()
|
||||||
|
}
|
||||||
|
3 -> {
|
||||||
|
News()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ fun Dynamic() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
PullRefreshIndicator(model.refreshing, state, Modifier.align(Alignment.TopCenter))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,7 +369,6 @@ fun Explore() {
|
|||||||
trtcId = roomItem.trtcId.toString(),
|
trtcId = roomItem.trtcId.toString(),
|
||||||
name = roomItem.title,
|
name = roomItem.title,
|
||||||
avatar = roomItem.avatar,
|
avatar = roomItem.avatar,
|
||||||
context = context,
|
|
||||||
navController = navController,
|
navController = navController,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
||||||
@@ -524,7 +523,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 +535,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
|
||||||
)
|
)*/
|
||||||
}
|
}
|
||||||
|
|
||||||
// 底部:标题和描述
|
// 底部:标题和描述
|
||||||
@@ -637,7 +636,6 @@ fun Explore() {
|
|||||||
trtcId = bannerItem.trtcId.toString(),
|
trtcId = bannerItem.trtcId.toString(),
|
||||||
name = bannerItem.title,
|
name = bannerItem.title,
|
||||||
avatar = bannerItem.avatar,
|
avatar = bannerItem.avatar,
|
||||||
context = context,
|
|
||||||
navController = navController,
|
navController = navController,
|
||||||
onSuccess = {
|
onSuccess = {
|
||||||
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, enterSuccessText, Toast.LENGTH_SHORT).show()
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ import com.aiosman.ravenow.ui.index.tabs.message.MessageListViewModel.userServic
|
|||||||
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
|
import com.aiosman.ravenow.ui.index.tabs.message.tab.GroupChatListViewModel
|
||||||
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.navigateToGroupChat
|
import com.aiosman.ravenow.ui.navigateToGroupChat
|
||||||
import com.aiosman.ravenow.data.api.ApiErrorResponse
|
|
||||||
import com.google.gson.Gson
|
|
||||||
import android.content.Context
|
|
||||||
import android.widget.Toast
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class ExploreViewModel : ViewModel() {
|
class ExploreViewModel : ViewModel() {
|
||||||
@@ -134,24 +130,21 @@ class ExploreViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSingleChat(
|
fun createSingleChat(
|
||||||
openId: String,
|
openId: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response =
|
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||||
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +152,6 @@ class ExploreViewModel : ViewModel() {
|
|||||||
trtcId: String,
|
trtcId: String,
|
||||||
name: String,
|
name: String,
|
||||||
avatar: String,
|
avatar: String,
|
||||||
context: Context,
|
|
||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
onSuccess: () -> Unit,
|
onSuccess: () -> Unit,
|
||||||
onError: (String) -> Unit
|
onError: (String) -> Unit
|
||||||
@@ -168,45 +160,24 @@ class ExploreViewModel : ViewModel() {
|
|||||||
try {
|
try {
|
||||||
val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId))
|
val response = apiClient.joinRoom(JoinGroupChatRequestBody(trtcId = trtcId))
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
try {
|
try {
|
||||||
createGroupChat(trtcGroupId = trtcId)
|
createGroupChat(trtcGroupId = trtcId)
|
||||||
// 群聊直接使用群ID进行导航
|
// 群聊直接使用群ID进行导航
|
||||||
navController.navigateToGroupChat(
|
navController.navigateToGroupChat( id = trtcId,
|
||||||
id = trtcId,
|
name = name,
|
||||||
name = name,
|
avatar = avatar)
|
||||||
avatar = avatar
|
} catch (e: Exception) {
|
||||||
)
|
onError("加入房间失败")
|
||||||
} catch (e: Exception) {
|
e.printStackTrace()
|
||||||
onError("加入房间失败")
|
}
|
||||||
e.printStackTrace()
|
}
|
||||||
}
|
onSuccess()
|
||||||
}
|
|
||||||
onSuccess()
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// 处理错误响应
|
onError("加入房间失败")
|
||||||
try {
|
|
||||||
val errorBody = response.errorBody()?.string()
|
|
||||||
if (errorBody != null) {
|
|
||||||
val gson = Gson()
|
|
||||||
val errorResponse = gson.fromJson(errorBody, ApiErrorResponse::class.java)
|
|
||||||
|
|
||||||
// 在主线程显示 Toast
|
|
||||||
Toast.makeText(context, errorResponse.error, Toast.LENGTH_LONG).show()
|
|
||||||
onError(errorResponse.error)
|
|
||||||
} else {
|
|
||||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("加入房间失败")
|
|
||||||
}
|
|
||||||
} catch (parseException: Exception) {
|
|
||||||
// 如果解析错误响应失败,显示默认错误信息
|
|
||||||
Toast.makeText(context, "加入房间失败", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("加入房间失败")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Toast.makeText(context, "网络请求失败:${e.message}", Toast.LENGTH_SHORT).show()
|
|
||||||
onError("网络请求失败:${e.message}")
|
onError("网络请求失败:${e.message}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
|
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||||
|
import com.aiosman.ravenow.LocalNavController
|
||||||
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
|
import com.aiosman.ravenow.ui.composables.MomentCard
|
||||||
|
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 新闻动态列表
|
||||||
|
*/
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun News() {
|
||||||
|
val model = NewsViewModel
|
||||||
|
val moments = model.moments
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||||
|
model.refreshPager(
|
||||||
|
pullRefresh = true
|
||||||
|
)
|
||||||
|
})
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
// 用于跟踪是否已经触发过加载更多
|
||||||
|
var hasTriggeredLoadMore by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
// observe list scrolling with simplified logic
|
||||||
|
val reachedBottom by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
val layoutInfo = listState.layoutInfo
|
||||||
|
val lastVisibleItem = layoutInfo.visibleItemsInfo.lastOrNull()
|
||||||
|
val totalItems = layoutInfo.totalItemsCount
|
||||||
|
|
||||||
|
if (lastVisibleItem == null || totalItems == 0) {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
val isLastItemVisible = lastVisibleItem.index >= totalItems - 2
|
||||||
|
|
||||||
|
// 简化逻辑:只要滑动到底部且还没有触发过,就触发加载
|
||||||
|
isLastItemVisible && !hasTriggeredLoadMore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// load more if scrolled to bottom
|
||||||
|
LaunchedEffect(reachedBottom) {
|
||||||
|
if (reachedBottom) {
|
||||||
|
hasTriggeredLoadMore = true
|
||||||
|
model.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
model.refreshPager()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听数据变化,重置加载状态
|
||||||
|
LaunchedEffect(moments.size) {
|
||||||
|
if (moments.size > 0) {
|
||||||
|
// 延迟重置,确保数据已经稳定
|
||||||
|
kotlinx.coroutines.delay(500)
|
||||||
|
hasTriggeredLoadMore = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
|
||||||
|
) {
|
||||||
|
Box(Modifier.pullRefresh(state)) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
state = listState
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
moments.size,
|
||||||
|
key = { idx -> idx }
|
||||||
|
) { idx ->
|
||||||
|
//处理下标越界
|
||||||
|
if (idx < 0 || idx >= moments.size) return@items
|
||||||
|
val momentItem = moments[idx] ?: return@items
|
||||||
|
|
||||||
|
val commentDebouncer = rememberDebouncer()
|
||||||
|
val likeDebouncer = rememberDebouncer()
|
||||||
|
val favoriteDebouncer = rememberDebouncer()
|
||||||
|
val followDebouncer = rememberDebouncer()
|
||||||
|
|
||||||
|
MomentCard(
|
||||||
|
momentEntity = momentItem,
|
||||||
|
onAddComment = {
|
||||||
|
commentDebouncer {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
model.onAddComment(momentItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLikeClick = {
|
||||||
|
likeDebouncer {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
if (momentItem.liked) {
|
||||||
|
model.dislikeMoment(momentItem.id)
|
||||||
|
} else {
|
||||||
|
model.likeMoment(momentItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFavoriteClick = {
|
||||||
|
favoriteDebouncer {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
} else {
|
||||||
|
scope.launch {
|
||||||
|
if (momentItem.isFavorite) {
|
||||||
|
model.unfavoriteMoment(momentItem.id)
|
||||||
|
} else {
|
||||||
|
model.favoriteMoment(momentItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFollowClick = {
|
||||||
|
followDebouncer {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.FOLLOW_USER)) {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
} else {
|
||||||
|
model.followAction(momentItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
showFollowButton = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = model.refreshing,
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,306 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.navigationBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.Close
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.HorizontalDivider
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.ViewModelProvider
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
||||||
import com.aiosman.ravenow.AppState
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.data.CommentService
|
|
||||||
import com.aiosman.ravenow.data.CommentServiceImpl
|
|
||||||
import com.aiosman.ravenow.entity.CommentEntity
|
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
|
|
||||||
import com.aiosman.ravenow.ui.composables.debouncedClickable
|
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncedNavigation
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentContent
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentMenuModal
|
|
||||||
import com.aiosman.ravenow.ui.post.CommentsViewModel
|
|
||||||
import com.aiosman.ravenow.ui.post.OrderSelectionComponent
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
|
|
||||||
class NewsCommentModalViewModel(
|
|
||||||
val postId: Int?
|
|
||||||
) : ViewModel() {
|
|
||||||
var commentsViewModel: CommentsViewModel = CommentsViewModel(postId.toString())
|
|
||||||
var commentService: CommentService = CommentServiceImpl()
|
|
||||||
|
|
||||||
init {
|
|
||||||
commentsViewModel.preTransit()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun likeComment(commentId: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.likeComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun unlikeComment(commentId: Int) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.unlikeComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createComment(
|
|
||||||
content: String,
|
|
||||||
parentCommentId: Int? = null,
|
|
||||||
replyUserId: Int? = null,
|
|
||||||
replyCommentId: Int? = null
|
|
||||||
) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
commentsViewModel.createComment(
|
|
||||||
content = content,
|
|
||||||
parentCommentId = parentCommentId,
|
|
||||||
replyUserId = replyUserId,
|
|
||||||
replyCommentId = replyCommentId
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun deleteComment(commentId: Int) {
|
|
||||||
commentsViewModel.deleteComment(commentId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新闻评论弹窗
|
|
||||||
// @param postId 新闻帖子ID
|
|
||||||
// @param commentCount 评论数量
|
|
||||||
// @param onDismiss 关闭回调
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@Composable
|
|
||||||
fun NewsCommentModal(
|
|
||||||
postId: Int? = null,
|
|
||||||
commentCount: Int = 0,
|
|
||||||
onDismiss: () -> Unit = {},
|
|
||||||
onCommentAdded: () -> Unit = {},
|
|
||||||
onCommentDeleted: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val debouncedNavigation = rememberDebouncedNavigation()
|
|
||||||
|
|
||||||
// 实时评论数状态
|
|
||||||
var currentCommentCount by remember { mutableStateOf(commentCount) }
|
|
||||||
|
|
||||||
val model = viewModel<NewsCommentModalViewModel>(
|
|
||||||
key = "NewsCommentModalViewModel_$postId",
|
|
||||||
factory = object : ViewModelProvider.Factory {
|
|
||||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
|
||||||
return NewsCommentModalViewModel(postId) as T
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
val commentViewModel = model.commentsViewModel
|
|
||||||
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
|
||||||
|
|
||||||
var showCommentMenu by remember { mutableStateOf(false) }
|
|
||||||
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
|
|
||||||
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
|
||||||
|
|
||||||
// 菜单弹窗
|
|
||||||
if (showCommentMenu) {
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = {
|
|
||||||
showCommentMenu = false
|
|
||||||
},
|
|
||||||
containerColor = AppColors.background,
|
|
||||||
sheetState = rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
dragHandle = {},
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
|
||||||
windowInsets = WindowInsets(0)
|
|
||||||
) {
|
|
||||||
CommentMenuModal(
|
|
||||||
onDeleteClick = {
|
|
||||||
showCommentMenu = false
|
|
||||||
contextComment?.let {
|
|
||||||
model.deleteComment(it.id)
|
|
||||||
onCommentDeleted()
|
|
||||||
currentCommentCount = (currentCommentCount - 1).coerceAtLeast(0)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
commentEntity = contextComment,
|
|
||||||
onCloseClick = {
|
|
||||||
showCommentMenu = false
|
|
||||||
},
|
|
||||||
isSelf = AppState.UserId?.toLong() == contextComment?.author,
|
|
||||||
onLikeClick = {
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showCommentMenu = false
|
|
||||||
contextComment?.let {
|
|
||||||
if (it.liked) {
|
|
||||||
model.unlikeComment(it.id)
|
|
||||||
} else {
|
|
||||||
model.likeComment(it.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onReplyClick = {
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
showCommentMenu = false
|
|
||||||
replyComment = contextComment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "${currentCommentCount}条评论",
|
|
||||||
fontSize = 15.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
|
|
||||||
// 排序选择
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.End
|
|
||||||
) {
|
|
||||||
OrderSelectionComponent {
|
|
||||||
commentViewModel.order = it
|
|
||||||
commentViewModel.reloadComment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 评论列表
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.weight(1f)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
LazyColumn {
|
|
||||||
item {
|
|
||||||
CommentContent(
|
|
||||||
viewModel = commentViewModel,
|
|
||||||
onLongClick = { comment ->
|
|
||||||
showCommentMenu = true
|
|
||||||
contextComment = comment
|
|
||||||
},
|
|
||||||
onReply = { parentComment, _, _, _ ->
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
replyComment = parentComment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 底部输入栏
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
HorizontalDivider(color = AppColors.inputBackground)
|
|
||||||
|
|
||||||
EditCommentBottomModal(
|
|
||||||
replyComment = replyComment,
|
|
||||||
autoFocus = false
|
|
||||||
) {
|
|
||||||
if (replyComment != null) {
|
|
||||||
if (replyComment?.parentCommentId != null) {
|
|
||||||
// 第三级评论
|
|
||||||
model.createComment(
|
|
||||||
content = it,
|
|
||||||
parentCommentId = replyComment?.parentCommentId,
|
|
||||||
replyUserId = replyComment?.author?.toInt(),
|
|
||||||
replyCommentId = replyComment?.id
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
// 子级评论
|
|
||||||
model.createComment(
|
|
||||||
content = it,
|
|
||||||
parentCommentId = replyComment?.id,
|
|
||||||
replyCommentId = replyComment?.id
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 顶级评论
|
|
||||||
model.createComment(content = it)
|
|
||||||
}
|
|
||||||
replyComment = null
|
|
||||||
onCommentAdded()
|
|
||||||
currentCommentCount++
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(navBarHeight))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,436 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.statusBars
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.pager.VerticalPager
|
|
||||||
import androidx.compose.foundation.pager.rememberPagerState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.material3.rememberModalBottomSheetState
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.runtime.setValue
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.graphics.Color
|
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
|
||||||
import com.aiosman.ravenow.LocalNavController
|
|
||||||
import com.aiosman.ravenow.R
|
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
|
||||||
import com.aiosman.ravenow.exp.timeAgo
|
|
||||||
import com.aiosman.ravenow.exp.formatPostTime2
|
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
|
||||||
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.news.NewsViewModel
|
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
|
|
||||||
@Composable
|
|
||||||
fun NewsScreen() {
|
|
||||||
val model = NewsViewModel
|
|
||||||
val moments = model.moments
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
val navController = LocalNavController.current
|
|
||||||
val scope = rememberCoroutineScope()
|
|
||||||
|
|
||||||
// 评论弹窗状态
|
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
|
||||||
var selectedMoment by remember { mutableStateOf<MomentEntity?>(null) }
|
|
||||||
|
|
||||||
// 查看全文弹窗状态
|
|
||||||
var showFullArticleModal by remember { mutableStateOf(false) }
|
|
||||||
var selectedArticleMoment by remember { mutableStateOf<MomentEntity?>(null) }
|
|
||||||
// 垂直翻页状态
|
|
||||||
val pagerState = rememberPagerState(pageCount = { moments.size })
|
|
||||||
|
|
||||||
// 防抖器
|
|
||||||
val likeDebouncer = rememberDebouncer()
|
|
||||||
val favoriteDebouncer = rememberDebouncer()
|
|
||||||
|
|
||||||
// 初始化加载数据
|
|
||||||
LaunchedEffect(Unit) {
|
|
||||||
model.refreshPager()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听数据变化,重置加载状态
|
|
||||||
LaunchedEffect(moments.size) {
|
|
||||||
// 当数据增加时,如果接近列表末尾,Pager会自动更新页数
|
|
||||||
}
|
|
||||||
|
|
||||||
// 当翻页接近末尾时加载更多
|
|
||||||
LaunchedEffect(pagerState.currentPage, moments.size) {
|
|
||||||
if (moments.isNotEmpty() && pagerState.currentPage >= moments.size - 2) {
|
|
||||||
model.loadMore()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.background)
|
|
||||||
) {
|
|
||||||
if (moments.isEmpty()) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(text = "暂无新闻内容", color = AppColors.text, fontSize = 16.sp)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
VerticalPager(
|
|
||||||
state = pagerState,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) { page ->
|
|
||||||
val momentItem = moments.getOrNull(page) ?: return@VerticalPager
|
|
||||||
NewsItem(
|
|
||||||
moment = momentItem,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
onCommentClick = {
|
|
||||||
selectedMoment = momentItem
|
|
||||||
showCommentModal = true
|
|
||||||
},
|
|
||||||
onReadFullClick = {
|
|
||||||
selectedArticleMoment = momentItem
|
|
||||||
showFullArticleModal = true
|
|
||||||
},
|
|
||||||
onLikeClick = {
|
|
||||||
likeDebouncer {
|
|
||||||
// 检查游客模式
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
} else {
|
|
||||||
scope.launch {
|
|
||||||
if (momentItem.liked) {
|
|
||||||
model.dislikeMoment(momentItem.id)
|
|
||||||
} else {
|
|
||||||
model.likeMoment(momentItem.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFavoriteClick = {
|
|
||||||
favoriteDebouncer {
|
|
||||||
// 检查游客模式
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
} else {
|
|
||||||
scope.launch {
|
|
||||||
if (momentItem.isFavorite) {
|
|
||||||
model.unfavoriteMoment(momentItem.id)
|
|
||||||
} else {
|
|
||||||
model.favoriteMoment(momentItem.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查看全文弹窗
|
|
||||||
if (showFullArticleModal && selectedArticleMoment != null) {
|
|
||||||
FullArticleModal(
|
|
||||||
moment = selectedArticleMoment!!,
|
|
||||||
onDismiss = {
|
|
||||||
showFullArticleModal = false
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 评论弹窗
|
|
||||||
if (showCommentModal && selectedMoment != null) {
|
|
||||||
val configuration = LocalConfiguration.current
|
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
|
||||||
val sheetHeight = screenHeight * 0.67f // 三分之二高度
|
|
||||||
|
|
||||||
ModalBottomSheet(
|
|
||||||
onDismissRequest = {
|
|
||||||
showCommentModal = false
|
|
||||||
},
|
|
||||||
sheetState = rememberModalBottomSheetState(
|
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
containerColor = AppColors.background,
|
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
|
||||||
windowInsets = androidx.compose.foundation.layout.WindowInsets(0)
|
|
||||||
) {
|
|
||||||
NewsCommentModal(
|
|
||||||
postId = selectedMoment?.id,
|
|
||||||
commentCount = selectedMoment?.commentCount ?: 0,
|
|
||||||
onDismiss = {
|
|
||||||
showCommentModal = false
|
|
||||||
},
|
|
||||||
onCommentAdded = {
|
|
||||||
selectedMoment?.id?.let { model.onAddComment(it) }
|
|
||||||
},
|
|
||||||
onCommentDeleted = {
|
|
||||||
selectedMoment?.id?.let { model.onDeleteComment(it) }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//单个新闻项
|
|
||||||
@Composable
|
|
||||||
fun NewsItem(
|
|
||||||
moment: MomentEntity,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
onCommentClick: () -> Unit = {},
|
|
||||||
onReadFullClick: () -> Unit = {},
|
|
||||||
onLikeClick: () -> Unit = {},
|
|
||||||
onFavoriteClick: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val context = LocalContext.current
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.background)
|
|
||||||
.padding(vertical = 8.dp),
|
|
||||||
verticalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(bottom = 30.dp)
|
|
||||||
) {
|
|
||||||
// 新闻图片
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(300.dp)
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
if (moment.images.isNotEmpty()) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
context = context,
|
|
||||||
imageUrl = moment.images[0].thumbnail,
|
|
||||||
contentDescription = "新闻图片",
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
blurHash = moment.images[0].blurHash,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = R.drawable.default_moment_img),
|
|
||||||
contentDescription = "默认图片",
|
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻标题
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsTitle.isNotEmpty()) moment.newsTitle else moment.nickname,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text,
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻内容(超出使用省略号)
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else moment.momentTextContent,
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
lineHeight = 20.sp,
|
|
||||||
maxLines = 6,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 新闻信息
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 来源和时间(显示月份与具体时间)
|
|
||||||
Text(
|
|
||||||
text = if (moment.newsSource.isNotEmpty()) "${moment.newsSource} • ${moment.time.formatPostTime2()}" else "${moment.nickname} • ${moment.time.formatPostTime2()}",
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.secondaryText,
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.padding(end = 8.dp),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
|
|
||||||
// 查看全文
|
|
||||||
Row(
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
modifier = Modifier.noRippleClickable { onReadFullClick() }
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.read_full_article),
|
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
color = Color(0xFF7c45ed)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = R.mipmap.arrow),
|
|
||||||
contentDescription = "箭头",
|
|
||||||
modifier = Modifier.size(18.dp),
|
|
||||||
colorFilter = ColorFilter.tint(Color(0xFF7c45ed))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 互动栏
|
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.padding(bottom = 25.dp),
|
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
|
||||||
) {
|
|
||||||
// 点赞
|
|
||||||
NewsActionButton(
|
|
||||||
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
|
||||||
count = moment.likeCount.toString(),
|
|
||||||
isActive = moment.liked,
|
|
||||||
modifier = Modifier.noRippleClickable { onLikeClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 评论
|
|
||||||
NewsActionButton(
|
|
||||||
icon = R.mipmap.icon_comment,
|
|
||||||
count = moment.commentCount.toString(),
|
|
||||||
isActive = false,
|
|
||||||
modifier = Modifier.noRippleClickable { onCommentClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 收藏
|
|
||||||
NewsActionButton(
|
|
||||||
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
|
||||||
count = moment.favoriteCount.toString(),
|
|
||||||
isActive = moment.isFavorite,
|
|
||||||
modifier = Modifier.noRippleClickable { onFavoriteClick() }
|
|
||||||
)
|
|
||||||
|
|
||||||
// 分享
|
|
||||||
NewsActionButton(
|
|
||||||
icon = R.mipmap.icon_share,
|
|
||||||
count = "",
|
|
||||||
isActive = false,
|
|
||||||
text = stringResource(R.string.share),
|
|
||||||
textSize = 8.sp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 互动栏按钮
|
|
||||||
@Composable
|
|
||||||
fun NewsActionButton(
|
|
||||||
icon: Int,
|
|
||||||
count: String,
|
|
||||||
isActive: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
text: String? = null,
|
|
||||||
textSize: androidx.compose.ui.unit.TextUnit = 12.sp
|
|
||||||
) {
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.width(60.dp)
|
|
||||||
.background(
|
|
||||||
color = AppColors.secondaryBackground,
|
|
||||||
shape = RoundedCornerShape(16.dp)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 12.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = androidx.compose.ui.res.painterResource(id = icon),
|
|
||||||
contentDescription = "操作图标",
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (count.isNotEmpty()) {
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
|
||||||
text = count,
|
|
||||||
fontSize = 12.sp,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (text != null) {
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = textSize,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,15 @@ package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|||||||
|
|
||||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
|
||||||
|
|
||||||
object NewsViewModel : BaseMomentModel() {
|
object NewsViewModel : BaseMomentModel() {
|
||||||
|
init {
|
||||||
|
EventBus.getDefault().register(this)
|
||||||
|
}
|
||||||
|
|
||||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
override fun extraArgs(): MomentLoaderExtraArgs {
|
||||||
// 只拉取新闻
|
return MomentLoaderExtraArgs(explore = true, newsOnly = true)
|
||||||
return MomentLoaderExtraArgs(
|
|
||||||
explore = false,
|
|
||||||
timelineId = null,
|
|
||||||
authorId = null,
|
|
||||||
newsOnly = true
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -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()
|
||||||
@@ -189,17 +140,16 @@ fun GalleryGrid(
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.shuihu_dark
|
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
|
modifier = Modifier.size(181.dp),
|
||||||
.size(width = 181.dp, height = 153.dp),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.your_story_not_started),
|
text = "故事还没开始",
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontWeight = FontWeight.W600
|
fontWeight = FontWeight.W600
|
||||||
@@ -208,7 +158,7 @@ fun GalleryGrid(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.publish_moment_greeting),
|
text = "发布一条动态,和世界打个招呼吧",
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontWeight = FontWeight.W400
|
fontWeight = FontWeight.W400
|
||||||
|
|||||||
@@ -1,168 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.profile.composable
|
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.layout.size
|
|
||||||
import androidx.compose.foundation.layout.width
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
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.layout.ContentScale
|
|
||||||
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.R
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun GroupChatEmptyContent() {
|
|
||||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
// 分割线(紧贴上方栏)
|
|
||||||
Divider(
|
|
||||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
|
||||||
thickness = 1.dp,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 分段控制器
|
|
||||||
SegmentedControl(
|
|
||||||
selectedIndex = selectedSegment,
|
|
||||||
onSegmentSelected = { selectedSegment = it },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 空状态内容(居中)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
// 空状态插图
|
|
||||||
EmptyStateIllustration()
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(9.dp))
|
|
||||||
|
|
||||||
// 空状态文本
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.empty_nothing),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.SemiBold,
|
|
||||||
color = Color(0xFF000000)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SegmentedControl(
|
|
||||||
selectedIndex: Int,
|
|
||||||
onSegmentSelected: (Int) -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.height(32.dp),
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 全部
|
|
||||||
SegmentButton(
|
|
||||||
text = stringResource(R.string.chat_all),
|
|
||||||
isSelected = selectedIndex == 0,
|
|
||||||
onClick = { onSegmentSelected(0) },
|
|
||||||
width = 54.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
// 公开
|
|
||||||
SegmentButton(
|
|
||||||
text = stringResource(R.string.public_label),
|
|
||||||
isSelected = selectedIndex == 1,
|
|
||||||
onClick = { onSegmentSelected(1) },
|
|
||||||
width = 59.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
// 私有
|
|
||||||
SegmentButton(
|
|
||||||
text = stringResource(R.string.private_label),
|
|
||||||
isSelected = selectedIndex == 2,
|
|
||||||
onClick = { onSegmentSelected(2) },
|
|
||||||
width = 54.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun SegmentButton(
|
|
||||||
text: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
width: androidx.compose.ui.unit.Dp
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(width)
|
|
||||||
.height(32.dp)
|
|
||||||
.background(
|
|
||||||
color = if (isSelected) {
|
|
||||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
|
||||||
} else {
|
|
||||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(1000.dp)
|
|
||||||
)
|
|
||||||
.clickable(onClick = onClick),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun EmptyStateIllustration() {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.l_empty_img),
|
|
||||||
contentDescription = "空状态",
|
|
||||||
modifier = Modifier
|
|
||||||
.width(181.dp)
|
|
||||||
.height(153.dp),
|
|
||||||
contentScale = ContentScale.Fit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -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) // 紫色
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -98,9 +98,9 @@ fun OtherProfileAction(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = if (profile.isFollowing) stringResource(R.string.follow_upper_had) 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, // 使用主题文字颜色
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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,
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,8 @@ import androidx.compose.foundation.layout.width
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Divider
|
|
||||||
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
|
||||||
@@ -31,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
|
||||||
@@ -47,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(
|
||||||
@@ -61,14 +55,15 @@ fun UserAgentsList(
|
|||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
if (agents.isEmpty()) {
|
LazyColumn(
|
||||||
// 使用带分段控制器的空状态布局
|
modifier = modifier.fillMaxSize(),
|
||||||
AgentEmptyContentWithSegments()
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
} else {
|
) {
|
||||||
LazyColumn(
|
if (agents.isEmpty()) {
|
||||||
modifier = modifier.fillMaxSize(),
|
item {
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
EmptyAgentsView()
|
||||||
) {
|
}
|
||||||
|
} else {
|
||||||
items(agents) { agent ->
|
items(agents) { agent ->
|
||||||
UserAgentCard(
|
UserAgentCard(
|
||||||
agent = agent,
|
agent = agent,
|
||||||
@@ -76,11 +71,11 @@ fun UserAgentsList(
|
|||||||
onAvatarClick = onAvatarClick
|
onAvatarClick = onAvatarClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 底部间距
|
// 底部间距
|
||||||
item {
|
item {
|
||||||
Spacer(modifier = Modifier.height(120.dp))
|
Spacer(modifier = Modifier.height(120.dp))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -198,182 +193,9 @@ fun UserAgentCard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AgentEmptyContentWithSegments() {
|
|
||||||
var selectedSegment by remember { mutableStateOf(0) } // 0: 全部, 1: 公开, 2: 私有
|
|
||||||
val AppColors = LocalAppTheme.current
|
|
||||||
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
|
||||||
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
) {
|
|
||||||
// 分割线(紧贴上方栏)
|
|
||||||
Divider(
|
|
||||||
color = Color(0xFFF0F0F0), // 更浅的灰色
|
|
||||||
thickness = 1.dp,
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 分段控制器
|
|
||||||
AgentSegmentedControl(
|
|
||||||
selectedIndex = selectedSegment,
|
|
||||||
onSegmentSelected = { selectedSegment = it },
|
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
// 空状态内容(使用智能体原本的图标和文字)
|
|
||||||
Column(
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
|
||||||
) {
|
|
||||||
if (isNetworkAvailable) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(
|
|
||||||
id = if(AppState.darkMode) R.mipmap.ai_dark
|
|
||||||
else R.mipmap.ai),
|
|
||||||
contentDescription = "暂无Agent",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
.align(Alignment.CenterHorizontally),
|
|
||||||
)
|
|
||||||
|
|
||||||
// 根据是否为深色模式调整间距
|
|
||||||
Spacer(modifier = Modifier.height(if(AppState.darkMode) 9.dp else 24.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.exclusive_ai_waiting),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
fontWeight = FontWeight.W600
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.ai_companion_not_tool),
|
|
||||||
fontSize = 14.sp,
|
|
||||||
color = AppColors.secondaryText,
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AgentSegmentedControl(
|
|
||||||
selectedIndex: Int,
|
|
||||||
onSegmentSelected: (Int) -> Unit,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
.height(32.dp),
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// 全部
|
|
||||||
AgentSegmentButton(
|
|
||||||
text = stringResource(R.string.chat_all),
|
|
||||||
isSelected = selectedIndex == 0,
|
|
||||||
onClick = { onSegmentSelected(0) },
|
|
||||||
width = 54.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
// 公开
|
|
||||||
AgentSegmentButton(
|
|
||||||
text = stringResource(R.string.public_label),
|
|
||||||
isSelected = selectedIndex == 1,
|
|
||||||
onClick = { onSegmentSelected(1) },
|
|
||||||
width = 59.dp
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
|
|
||||||
// 私有
|
|
||||||
AgentSegmentButton(
|
|
||||||
text = stringResource(R.string.private_label),
|
|
||||||
isSelected = selectedIndex == 2,
|
|
||||||
onClick = { onSegmentSelected(2) },
|
|
||||||
width = 54.dp
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun AgentSegmentButton(
|
|
||||||
text: String,
|
|
||||||
isSelected: Boolean,
|
|
||||||
onClick: () -> Unit,
|
|
||||||
width: androidx.compose.ui.unit.Dp
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(width)
|
|
||||||
.height(32.dp)
|
|
||||||
.background(
|
|
||||||
color = if (isSelected) {
|
|
||||||
Color(0xFF110C13) // RGB(17, 12, 19)
|
|
||||||
} else {
|
|
||||||
Color(0x147C7480) // RGB(124, 116, 128, alpha 0.08)
|
|
||||||
},
|
|
||||||
shape = RoundedCornerShape(1000.dp)
|
|
||||||
)
|
|
||||||
.clickable(onClick = onClick),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
color = if (isSelected) Color(0xFFFFFFFF) else Color(0xFF000000)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@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
|
||||||
@@ -381,66 +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(if(AppState.darkMode) 9.dp else 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)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -10,14 +10,21 @@ 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.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.pager.PagerState
|
import androidx.compose.foundation.pager.PagerState
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
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.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.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
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.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@@ -45,7 +52,7 @@ fun UserContentPageIndicator(
|
|||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
horizontalArrangement = Arrangement.SpaceEvenly
|
horizontalArrangement = Arrangement.SpaceEvenly
|
||||||
) {
|
) {
|
||||||
// 动态 Tab
|
// 图片/相册 Tab
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -57,24 +64,15 @@ fun UserContentPageIndicator(
|
|||||||
}
|
}
|
||||||
.padding(vertical = 12.dp)
|
.padding(vertical = 12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = stringResource(R.string.index_dynamic),
|
painter = painterResource(id = R.drawable.rider_pro_images),
|
||||||
fontSize = 16.sp,
|
contentDescription = "Gallery",
|
||||||
fontWeight = if (pagerState.currentPage == 0) FontWeight.SemiBold else FontWeight.Medium,
|
tint = if (pagerState.currentPage == 0) AppColors.text else AppColors.text.copy(alpha = 0.6f),
|
||||||
color = if (pagerState.currentPage == 0) Color(0xFF000000) else Color(0x993C3C43)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 0) 6.dp else 4.dp))
|
|
||||||
if (pagerState.currentPage == 0) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(20.dp)
|
|
||||||
.height(2.dp)
|
|
||||||
.background(Color(0xFF000000))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 智能体 Tab (只在非智能体用户时显示)
|
// Agent Tab (只在非智能体用户时显示)
|
||||||
if (showAgentTab) {
|
if (showAgentTab) {
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
@@ -87,53 +85,39 @@ fun UserContentPageIndicator(
|
|||||||
}
|
}
|
||||||
.padding(vertical = 12.dp)
|
.padding(vertical = 12.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Icon(
|
||||||
text = stringResource(R.string.chat_ai),
|
painter = painterResource(id = R.drawable.rider_pro_nav_ai),
|
||||||
fontSize = 16.sp,
|
contentDescription = "Agents",
|
||||||
fontWeight = if (pagerState.currentPage == 1) FontWeight.SemiBold else FontWeight.Medium,
|
tint = if (pagerState.currentPage == 1) AppColors.text else AppColors.text.copy(alpha = 0.6f),
|
||||||
color = if (pagerState.currentPage == 1) Color(0xFF000000) else Color(0x993C3C43)
|
modifier = Modifier.size(24.dp)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 1) 6.dp else 4.dp))
|
|
||||||
if (pagerState.currentPage == 1) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(20.dp)
|
|
||||||
.height(2.dp)
|
|
||||||
.background(Color(0xFF000000))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 群聊 Tab (只在非智能体用户时显示)
|
// 下划线指示器
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.height(2.dp)
|
||||||
|
.background(
|
||||||
|
if (pagerState.currentPage == 0) AppColors.text else Color.Transparent
|
||||||
|
)
|
||||||
|
)
|
||||||
if (showAgentTab) {
|
if (showAgentTab) {
|
||||||
Column(
|
Box(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.noRippleClickable {
|
.height(2.dp)
|
||||||
scope.launch {
|
.background(
|
||||||
pagerState.scrollToPage(2)
|
if (pagerState.currentPage == 1) AppColors.text else Color.Transparent
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(vertical = 12.dp)
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.chat_group),
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = if (pagerState.currentPage == 2) FontWeight.SemiBold else FontWeight.Medium,
|
|
||||||
color = if (pagerState.currentPage == 2) Color(0xFF000000) else Color(0x993C3C43)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.height(if (pagerState.currentPage == 2) 6.dp else 4.dp))
|
|
||||||
if (pagerState.currentPage == 2) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.width(20.dp)
|
|
||||||
.height(2.dp)
|
|
||||||
.background(Color(0xFF000000))
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,26 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.profile.composable
|
package com.aiosman.ravenow.ui.index.tabs.profile.composable
|
||||||
|
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
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.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.height
|
||||||
import androidx.compose.foundation.layout.offset
|
|
||||||
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
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
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.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.graphics.Color
|
|
||||||
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.platform.LocalContext
|
||||||
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.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
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.AppStore
|
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
@@ -42,52 +29,23 @@ 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.rememberDebouncer
|
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
import java.text.NumberFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserItem(
|
fun UserItem(
|
||||||
accountProfileEntity: AccountProfileEntity,
|
accountProfileEntity: AccountProfileEntity,
|
||||||
postCount: Long = 0,
|
postCount: Int = 0
|
||||||
isSelf: Boolean = false,
|
|
||||||
onEditClick: () -> Unit = {}
|
|
||||||
) {
|
) {
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val followerDebouncer = rememberDebouncer()
|
val followerDebouncer = rememberDebouncer()
|
||||||
val followingDebouncer = rememberDebouncer()
|
val followingDebouncer = rememberDebouncer()
|
||||||
|
|
||||||
// 获取 MBTI 和星座信息
|
|
||||||
val mbti = remember(accountProfileEntity.id) {
|
|
||||||
AppStore.getUserMbti(accountProfileEntity.id)
|
|
||||||
}
|
|
||||||
val zodiac = remember(accountProfileEntity.id) {
|
|
||||||
AppStore.getUserZodiac(accountProfileEntity.id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 格式化粉丝数
|
|
||||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
|
||||||
val formattedFollowerCount = remember(accountProfileEntity.followerCount) {
|
|
||||||
if (accountProfileEntity.followerCount >= 10000) {
|
|
||||||
val wan = accountProfileEntity.followerCount / 10000.0
|
|
||||||
if (wan >= 100) {
|
|
||||||
"${wan.toInt()}万"
|
|
||||||
} else {
|
|
||||||
String.format("%.1f万", wan)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
numberFormat.format(accountProfileEntity.followerCount)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
// 顶部:头像和统计数据
|
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
|
||||||
) {
|
) {
|
||||||
// 头像
|
// 头像
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
@@ -95,47 +53,39 @@ fun UserItem(
|
|||||||
accountProfileEntity.avatar,
|
accountProfileEntity.avatar,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.size(96.dp),
|
.size(48.dp),
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.width(32.dp))
|
||||||
// 统计数据
|
//个人统计
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.weight(1f),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(0.dp),
|
modifier = Modifier.weight(1f)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
// 帖子数
|
// 帖子数
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
|
||||||
.width(80.dp)
|
|
||||||
.height(40.dp),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.Center
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = postCount.toString(),
|
text = postCount.toString(),
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.W600,
|
||||||
fontSize = 15.sp,
|
fontSize = 16.sp,
|
||||||
color = Color(0xFF000000),
|
color = AppColors.text
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.posts),
|
text = "帖子",
|
||||||
fontWeight = FontWeight.Normal,
|
color = AppColors.text
|
||||||
fontSize = 11.sp,
|
|
||||||
color = Color(0xFF000000),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 粉丝数
|
// 粉丝数
|
||||||
Column(
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(80.dp)
|
.weight(1f)
|
||||||
.height(40.dp)
|
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
followerDebouncer {
|
followerDebouncer {
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
@@ -145,33 +95,26 @@ fun UserItem(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = formattedFollowerCount,
|
text = accountProfileEntity.followerCount.toString(),
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.W600,
|
||||||
fontSize = 15.sp,
|
fontSize = 16.sp,
|
||||||
color = Color(0xFF000000),
|
color = AppColors.text
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.followers_upper),
|
text = "粉丝",
|
||||||
fontWeight = FontWeight.Normal,
|
color = AppColors.text
|
||||||
fontSize = 11.sp,
|
|
||||||
color = Color(0xFF000000),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 关注数
|
// 关注数
|
||||||
Column(
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(80.dp)
|
.weight(1f)
|
||||||
.height(40.dp)
|
|
||||||
.offset(x = 6.dp)
|
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
followingDebouncer {
|
followingDebouncer {
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
@@ -181,161 +124,49 @@ fun UserItem(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
verticalArrangement = Arrangement.Center
|
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = accountProfileEntity.followingCount.toString(),
|
text = accountProfileEntity.followingCount.toString(),
|
||||||
fontWeight = FontWeight.Medium,
|
fontWeight = FontWeight.W600,
|
||||||
fontSize = 15.sp,
|
fontSize = 16.sp,
|
||||||
color = Color(0xFF000000),
|
color = AppColors.text
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(2.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.following_upper),
|
text = "关注",
|
||||||
fontWeight = FontWeight.Normal,
|
color = AppColors.text
|
||||||
fontSize = 11.sp,
|
|
||||||
color = Color(0xFF000000),
|
|
||||||
textAlign = TextAlign.Center
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
// 昵称
|
||||||
// 中间:昵称、简介、创建者信息
|
Text(
|
||||||
Column(
|
text = accountProfileEntity.nickName,
|
||||||
modifier = Modifier
|
fontWeight = FontWeight.W600,
|
||||||
.fillMaxWidth()
|
fontSize = 16.sp,
|
||||||
) {
|
color = AppColors.text
|
||||||
// 昵称
|
)
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
// 个人简介
|
||||||
|
if (accountProfileEntity.bio.isNotEmpty()){
|
||||||
Text(
|
Text(
|
||||||
text = accountProfileEntity.nickName,
|
text = accountProfileEntity.bio,
|
||||||
fontWeight = FontWeight.Bold,
|
fontSize = 14.sp,
|
||||||
fontSize = 22.sp,
|
color = AppColors.secondaryText,
|
||||||
letterSpacing = (-0.3).sp,
|
maxLines = 1,
|
||||||
color = Color(0xFF000000)
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
|
}else{
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
|
|
||||||
// 个人简介
|
|
||||||
if (accountProfileEntity.bio.isNotEmpty()) {
|
|
||||||
Text(
|
|
||||||
text = accountProfileEntity.bio,
|
|
||||||
fontSize = 13.sp,
|
|
||||||
color = Color(0x993C3C43), // 60/255, 60/255, 67/255, alpha 0.6
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
Text(
|
|
||||||
text = "Welcome to my fantiac word i will show you something about magic",
|
|
||||||
fontSize = 13.sp,
|
|
||||||
color = Color(0x993C3C43),
|
|
||||||
maxLines = 2,
|
|
||||||
overflow = TextOverflow.Ellipsis
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建者信息(如果是 AI 账户,可以显示创建者)
|
|
||||||
// 注意:当前 AccountProfileEntity 没有创建者字段,这里暂时留空
|
|
||||||
// 如果需要显示,需要从其他地方获取创建者信息
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(12.dp))
|
|
||||||
|
|
||||||
// 底部:标签按钮
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
// MBTI 标签
|
|
||||||
if (!mbti.isNullOrEmpty()) {
|
|
||||||
ProfileTag(
|
|
||||||
text = mbti,
|
|
||||||
backgroundColor = Color(0x33FF8D28), // 255/255, 141/255, 40/255, alpha 0.2
|
|
||||||
textColor = Color(0xFF000000)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 星座标签
|
|
||||||
if (!zodiac.isNullOrEmpty()) {
|
|
||||||
ProfileTag(
|
|
||||||
text = zodiac,
|
|
||||||
backgroundColor = Color(0x33FFCC00), // 255/255, 204/255, 0/255, alpha 0.2
|
|
||||||
textColor = Color(0xFF000000)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 编辑标签(仅自己可见)
|
|
||||||
if (isSelf) {
|
|
||||||
ProfileTag(
|
|
||||||
text = stringResource(R.string.edit_profile),
|
|
||||||
backgroundColor = Color(0x14947A80), // 124/255, 116/255, 128/255, alpha 0.08
|
|
||||||
textColor = Color(0xFF9284BD), // 146/255, 132/255, 189/255
|
|
||||||
leadingIcon = {
|
|
||||||
EditIcon(
|
|
||||||
color = Color(0xFF9284BD),
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onClick = onEditClick
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun ProfileTag(
|
|
||||||
text: String,
|
|
||||||
backgroundColor: Color,
|
|
||||||
textColor: Color,
|
|
||||||
leadingIcon: (@Composable () -> Unit)? = null,
|
|
||||||
onClick: (() -> Unit)? = null
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.height(25.dp)
|
|
||||||
.clip(RoundedCornerShape(12.dp))
|
|
||||||
.background(backgroundColor)
|
|
||||||
.then(
|
|
||||||
if (onClick != null) {
|
|
||||||
Modifier.clickable(onClick = onClick)
|
|
||||||
} else {
|
|
||||||
Modifier
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
leadingIcon?.invoke()
|
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = "No bio here.",
|
||||||
fontSize = 12.sp,
|
fontSize = 14.sp,
|
||||||
color = textColor
|
color = AppColors.secondaryText,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
private fun EditIcon(
|
|
||||||
color: Color,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.bi),
|
|
||||||
contentDescription = stringResource(R.string.edit_profile),
|
|
||||||
modifier = modifier,
|
|
||||||
colorFilter = ColorFilter.tint(color)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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),
|
||||||
@@ -123,15 +95,14 @@ fun LikeNoticeScreen() {
|
|||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.sanqiu_dark
|
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
|
modifier = Modifier.size(181.dp)
|
||||||
.size(width = 181.dp, height = 153.dp)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(if (AppState.darkMode) 9.dp else 24.dp))
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
Text(
|
Text(
|
||||||
text = "你的赞在赶来的路上",
|
text = "你的赞在路上~",
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W600,
|
||||||
|
|||||||
@@ -1,19 +1,15 @@
|
|||||||
package com.aiosman.ravenow.ui.login
|
package com.aiosman.ravenow.ui.login
|
||||||
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
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.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.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.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@@ -23,13 +19,9 @@ 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.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.ColorFilter
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
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 com.aiosman.ravenow.AppState
|
import com.aiosman.ravenow.AppState
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
import com.aiosman.ravenow.LocalAppTheme
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
@@ -41,28 +33,25 @@ import com.aiosman.ravenow.data.ServiceException
|
|||||||
import com.aiosman.ravenow.data.AccountServiceImpl
|
import com.aiosman.ravenow.data.AccountServiceImpl
|
||||||
import com.aiosman.ravenow.data.api.getErrorMessageCode
|
import com.aiosman.ravenow.data.api.getErrorMessageCode
|
||||||
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.ActionButton
|
import com.aiosman.ravenow.ui.composables.ActionButton
|
||||||
import com.aiosman.ravenow.ui.composables.CheckboxWithLabel
|
import com.aiosman.ravenow.ui.composables.CheckboxWithLabel
|
||||||
import com.aiosman.ravenow.ui.composables.PolicyCheckbox
|
import com.aiosman.ravenow.ui.composables.PolicyCheckbox
|
||||||
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
|
||||||
import com.aiosman.ravenow.ui.composables.TextInputField
|
import com.aiosman.ravenow.ui.composables.TextInputField
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|
||||||
import com.aiosman.ravenow.utils.PasswordValidator
|
import com.aiosman.ravenow.utils.PasswordValidator
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
private val LightGrayBackground = Color(red = 250f / 255f, green = 249f / 255f, blue = 251f / 255f)
|
|
||||||
private val IconGray = Color(red = 151f / 255f, green = 148f / 255f, blue = 153f / 255f)
|
|
||||||
private val PurpleButton = Color(0xFF7C45ED)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun EmailSignupScreen() {
|
fun EmailSignupScreen() {
|
||||||
val appColor = LocalAppTheme.current
|
var appColor = LocalAppTheme.current
|
||||||
var email by remember { mutableStateOf("") }
|
var email by remember { mutableStateOf("") }
|
||||||
var password by remember { mutableStateOf("") }
|
var password by remember { mutableStateOf("") }
|
||||||
var confirmPassword by remember { mutableStateOf("") }
|
var confirmPassword by remember { mutableStateOf("") }
|
||||||
var rememberMe by remember { mutableStateOf(false) }
|
var rememberMe by remember { mutableStateOf(false) }
|
||||||
var acceptTerms by remember { mutableStateOf(false) }
|
var acceptTerms by remember { mutableStateOf(false) }
|
||||||
|
var acceptPromotions by remember { mutableStateOf(false) }
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -71,13 +60,14 @@ fun EmailSignupScreen() {
|
|||||||
var passwordError by remember { mutableStateOf<String?>(null) }
|
var passwordError by remember { mutableStateOf<String?>(null) }
|
||||||
var confirmPasswordError by remember { mutableStateOf<String?>(null) }
|
var confirmPasswordError by remember { mutableStateOf<String?>(null) }
|
||||||
var termsError by remember { mutableStateOf<Boolean>(false) }
|
var termsError by remember { mutableStateOf<Boolean>(false) }
|
||||||
|
var promotionsError by remember { mutableStateOf<Boolean>(false) }
|
||||||
fun validateForm(): Boolean {
|
fun validateForm(): Boolean {
|
||||||
emailError = when {
|
emailError = when {
|
||||||
// 非空
|
// 非空
|
||||||
email.isEmpty() -> context.getString(R.string.text_error_email_required)
|
email.isEmpty() -> context.getString(R.string.text_error_email_required)
|
||||||
// 邮箱格式
|
// 邮箱格式
|
||||||
!android.util.Patterns.EMAIL_ADDRESS.matcher(email)
|
!android.util.Patterns.EMAIL_ADDRESS.matcher(email)
|
||||||
.matches() -> context.getString(R.string.text_error_email_format_1)
|
.matches() -> context.getString(R.string.text_error_email_format)
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
@@ -98,8 +88,22 @@ fun EmailSignupScreen() {
|
|||||||
}
|
}
|
||||||
termsError = true
|
termsError = true
|
||||||
return false
|
return false
|
||||||
|
} else {
|
||||||
|
termsError = false
|
||||||
|
}
|
||||||
|
if (!acceptPromotions) {
|
||||||
|
scope.launch(Dispatchers.Main) {
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getString(R.string.error_not_accept_recive_notice),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
promotionsError = true
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
promotionsError = false
|
||||||
}
|
}
|
||||||
termsError = false
|
|
||||||
|
|
||||||
return emailError == null && passwordError == null && confirmPasswordError == null
|
return emailError == null && passwordError == null && confirmPasswordError == null
|
||||||
}
|
}
|
||||||
@@ -154,127 +158,63 @@ fun EmailSignupScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(appColor.background)
|
.background(appColor.background)
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
// 顶部导航栏:返回箭头 + "注册"标题,左对齐
|
Box(
|
||||||
Row(
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 15.dp, start = 16.dp, bottom = 15.dp, end = 16.dp),
|
.padding(top = 16.dp, start = 16.dp, end = 16.dp)
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
) {
|
||||||
Image(
|
NoticeScreenHeader(stringResource(R.string.sign_up_upper), moreIcon = false)
|
||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
|
||||||
contentDescription = "Back",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigateUp()
|
|
||||||
},
|
|
||||||
colorFilter = ColorFilter.tint(Color.Black)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
text = stringResource(R.string.sign_up_upper),
|
|
||||||
fontSize = 20.sp,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
color = Color.Black
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.padding(32.dp))
|
||||||
// 输入区域
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(horizontal = 0.dp)
|
.padding(horizontal = 24.dp)
|
||||||
) {
|
) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 邮箱输入框
|
|
||||||
TextInputField(
|
TextInputField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
text = email,
|
text = email,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
email = it
|
email = it
|
||||||
},
|
},
|
||||||
label = stringResource(R.string.login_email_label),
|
hint = stringResource(R.string.text_hint_email),
|
||||||
hint = "输入电子邮件",
|
error = emailError
|
||||||
error = emailError,
|
|
||||||
leadingIcon = {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.icon_email_light),
|
|
||||||
contentDescription = "Email",
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(IconGray)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
customBackgroundColor = LightGrayBackground,
|
|
||||||
customCornerRadius = 16f
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(4.dp))
|
||||||
// 密码输入框
|
|
||||||
TextInputField(
|
TextInputField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
text = password,
|
text = password,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
password = it
|
password = it
|
||||||
},
|
},
|
||||||
password = true,
|
password = true,
|
||||||
label = stringResource(R.string.text_hint_password).replace("输入", ""),
|
|
||||||
hint = stringResource(R.string.text_hint_password),
|
hint = stringResource(R.string.text_hint_password),
|
||||||
error = passwordError,
|
error = passwordError
|
||||||
leadingIcon = {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.icon_lock_light),
|
|
||||||
contentDescription = "Lock",
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(IconGray)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
customBackgroundColor = LightGrayBackground,
|
|
||||||
customCornerRadius = 16f
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.padding(4.dp))
|
||||||
// 确认密码输入框
|
|
||||||
TextInputField(
|
TextInputField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth(),
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
text = confirmPassword,
|
text = confirmPassword,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
confirmPassword = it
|
confirmPassword = it
|
||||||
},
|
},
|
||||||
password = true,
|
password = true,
|
||||||
label = stringResource(R.string.confirm_password_label),
|
hint = stringResource(R.string.text_hint_confirm_password),
|
||||||
hint = stringResource(R.string.text_hint_password),
|
error = confirmPasswordError
|
||||||
error = confirmPasswordError,
|
|
||||||
leadingIcon = {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.icon_lock_light),
|
|
||||||
contentDescription = "Lock",
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(IconGray)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
customBackgroundColor = LightGrayBackground,
|
|
||||||
customCornerRadius = 16f
|
|
||||||
)
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
|
|
||||||
// 功能选项区域
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
horizontalAlignment = Alignment.Start,
|
horizontalAlignment = Alignment.Start,
|
||||||
) {
|
) {
|
||||||
CheckboxWithLabel(
|
CheckboxWithLabel(
|
||||||
@@ -296,31 +236,42 @@ fun EmailSignupScreen() {
|
|||||||
termsError = false
|
termsError = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
CheckboxWithLabel(
|
||||||
|
checked = acceptPromotions,
|
||||||
|
checkSize = 16,
|
||||||
|
fontSize = 12,
|
||||||
|
label = stringResource(R.string.agree_promotion),
|
||||||
|
error = promotionsError
|
||||||
|
) {
|
||||||
|
acceptPromotions = it
|
||||||
|
// 当用户勾选时,立即清除错误状态
|
||||||
|
if (it) {
|
||||||
|
promotionsError = false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(76.dp))
|
Spacer(modifier = Modifier.height(32.dp))
|
||||||
|
|
||||||
// 底部注册按钮
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 24.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
text = stringResource(R.string.sign_up_upper),
|
.width(345.dp),
|
||||||
backgroundColor = PurpleButton,
|
text = stringResource(R.string.lets_ride_upper),
|
||||||
color = Color.White,
|
backgroundColor = Color(0xffda3832),
|
||||||
fontSize = 17.sp,
|
color = Color.White
|
||||||
fontWeight = FontWeight.W600
|
|
||||||
) {
|
) {
|
||||||
scope.launch(Dispatchers.IO) {
|
scope.launch(Dispatchers.IO) {
|
||||||
registerUser()
|
registerUser()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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,77 @@ 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)
|
)
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()
|
||||||
@@ -72,17 +70,8 @@ fun UserAuthScreen() {
|
|||||||
var passwordError by remember { mutableStateOf<String?>(null) }
|
var passwordError by remember { mutableStateOf<String?>(null) }
|
||||||
var captchaInfo by remember { mutableStateOf<CaptchaInfo?>(null) }
|
var captchaInfo by remember { mutableStateOf<CaptchaInfo?>(null) }
|
||||||
fun validateForm(): Boolean {
|
fun validateForm(): Boolean {
|
||||||
// 如果密码为空,先检查邮箱格式
|
emailError =
|
||||||
if (password.isEmpty()) {
|
if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
|
||||||
emailError = when {
|
|
||||||
email.isEmpty() -> context.getString(R.string.text_error_email_required)
|
|
||||||
!android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() ->
|
|
||||||
context.getString(R.string.text_error_email_format)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
emailError = if (email.isEmpty()) context.getString(R.string.text_error_email_required) else null
|
|
||||||
}
|
|
||||||
|
|
||||||
// 使用通用密码校验器
|
// 使用通用密码校验器
|
||||||
val passwordValidation = PasswordValidator.validateCurrentPassword(password, context)
|
val passwordValidation = PasswordValidator.validateCurrentPassword(password, context)
|
||||||
@@ -310,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()
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -52,8 +52,6 @@ import androidx.compose.ui.draw.rotate
|
|||||||
import androidx.compose.ui.draw.scale
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
import androidx.compose.ui.geometry.CornerRadius
|
import androidx.compose.ui.geometry.CornerRadius
|
||||||
import androidx.compose.ui.geometry.Offset
|
|
||||||
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.ColorFilter
|
||||||
import androidx.compose.ui.graphics.PathEffect
|
import androidx.compose.ui.graphics.PathEffect
|
||||||
@@ -153,42 +151,71 @@ fun NewPostScreen() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(1.dp)
|
.height(1.dp)
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.background(AppColors.divider)
|
.background(AppColors.divider)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(24.dp))
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 16.dp)
|
.fillMaxWidth()
|
||||||
.width(100.dp)
|
.padding(top = 8.dp, start = 16.dp, end = 16.dp, bottom = 8.dp),
|
||||||
.height(40.dp)
|
verticalAlignment = Alignment.CenterVertically
|
||||||
.clip(RoundedCornerShape(20.dp))
|
|
||||||
.background(
|
|
||||||
brush = Brush.linearGradient(
|
|
||||||
colors = listOf(
|
|
||||||
Color(0xFF8CDDFF),
|
|
||||||
Color(0xFF9887FF),
|
|
||||||
Color(0xFFFF8D28)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.padding(horizontal = 14.dp, vertical = 8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
horizontalArrangement = Arrangement.Center
|
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.icon_ai),
|
painter = painterResource(id = R.mipmap.rider_pro_moment_ai),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(16.dp)
|
.size(24.dp)
|
||||||
|
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.moment_ai_co),
|
text = stringResource(R.string.moment_ai_co),
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 13.sp,
|
fontSize = 15.sp,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 2.dp),
|
.padding(start = 8.dp)
|
||||||
color = Color.White,
|
.weight(1f),
|
||||||
|
color = AppColors.text,
|
||||||
|
)
|
||||||
|
Switch(
|
||||||
|
checked = isAiEnabled,
|
||||||
|
onCheckedChange = {
|
||||||
|
isChecked ->
|
||||||
|
isAiEnabled = isChecked
|
||||||
|
if (isChecked) {
|
||||||
|
// 收起键盘
|
||||||
|
keyboardController?.hide()
|
||||||
|
isRequesting = true
|
||||||
|
isRotating = true
|
||||||
|
model.viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
model.agentMoment(model.textContent)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}finally {
|
||||||
|
isRequesting = false
|
||||||
|
isRotating = false
|
||||||
|
isAiEnabled = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled = !isRequesting && model.textContent.isNotEmpty(),
|
||||||
|
colors = SwitchDefaults.colors(
|
||||||
|
checkedThumbColor = Color.White,
|
||||||
|
checkedTrackColor = AppColors.brandColorsColor,
|
||||||
|
uncheckedThumbColor = Color.White,
|
||||||
|
uncheckedTrackColor = AppColors.nonActive,
|
||||||
|
uncheckedBorderColor = Color.White,
|
||||||
|
disabledCheckedTrackColor = AppColors.brandColorsColor.copy(alpha = 0.8f),
|
||||||
|
disabledCheckedThumbColor= Color.White,
|
||||||
|
disabledUncheckedTrackColor = AppColors.nonActive,
|
||||||
|
disabledUncheckedThumbColor= Color.White
|
||||||
|
|
||||||
|
),
|
||||||
|
modifier = Modifier.scale(0.8f)
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -325,7 +352,7 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
|||||||
modifier = Modifier.align(Alignment.CenterStart),
|
modifier = Modifier.align(Alignment.CenterStart),
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
painter = painterResource(id = R.drawable.rider_pro_close),
|
||||||
contentDescription = "Back",
|
contentDescription = "Back",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
@@ -339,31 +366,9 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
|||||||
},
|
},
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
Text(
|
|
||||||
modifier = Modifier.align(Alignment.CenterVertically),
|
|
||||||
text = stringResource(R.string.publish_dynamic),
|
|
||||||
fontSize = 17.sp,
|
|
||||||
color = AppColors.text,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.icon_draft_box_light),
|
painter = painterResource(id = R.mipmap.rider_pro_moment_post),
|
||||||
contentDescription = "",
|
|
||||||
modifier = Modifier
|
|
||||||
.size(24.dp)
|
|
||||||
.noRippleClickable {
|
|
||||||
// 添加防抖逻辑
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastSendClickTime > debounceTime) {
|
|
||||||
lastSendClickTime = currentTime
|
|
||||||
}
|
|
||||||
},
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(20.dp))
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.mipmap.icon_released_light),
|
|
||||||
contentDescription = "Send",
|
contentDescription = "Send",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
@@ -386,8 +391,11 @@ fun NewPostTopBar(onSendClick: () -> Unit = {}) {
|
|||||||
}finally {
|
}finally {
|
||||||
uploading = false
|
uploading = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -480,24 +488,72 @@ fun AddImageGrid() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val addImageDebouncer = rememberDebouncer()
|
val addImageDebouncer = rememberDebouncer()
|
||||||
|
val takePhotoDebouncer = rememberDebouncer()
|
||||||
|
|
||||||
|
val stroke = Stroke(
|
||||||
|
width = 2f,
|
||||||
|
pathEffect = PathEffect.dashPathEffect(floatArrayOf(10f, 10f), 0f)
|
||||||
|
)
|
||||||
|
DraggableGrid(
|
||||||
|
items = NewPostViewModel.imageList,
|
||||||
|
onMove = { from, to ->
|
||||||
|
NewPostViewModel.imageList = NewPostViewModel.imageList.toMutableList().apply {
|
||||||
|
add(to, removeAt(from))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
lockedIndices = listOf(
|
||||||
|
|
||||||
|
),
|
||||||
|
onDragModeEnd = {},
|
||||||
|
onDragModeStart = {},
|
||||||
|
additionalItems = listOf(
|
||||||
|
|
||||||
|
),
|
||||||
|
getItemId = { it.id }
|
||||||
|
) { item, isDrag ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
LocalContext.current,
|
||||||
|
item.bitmap,
|
||||||
|
contentDescription = "Image",
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(1f)
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.navigate(NavigationRoute.NewPostImageGrid.route)
|
||||||
|
},
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
if (isDrag) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Black.copy(alpha = 0.4f))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
val canAddMoreImages = model.imageList.size < 9
|
val canAddMoreImages = model.imageList.size < 9
|
||||||
|
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(5),
|
columns = GridCells.Fixed(5),
|
||||||
contentPadding = PaddingValues(horizontal = 19.dp, vertical = 4.dp),
|
contentPadding = PaddingValues(8.dp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
// 添加按钮
|
|
||||||
if (canAddMoreImages) {
|
if (canAddMoreImages) {
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(24.dp))
|
.clip(RoundedCornerShape(16.dp)) // 设置圆角
|
||||||
.background(Color(0xFFFAF9FB))
|
.background(AppColors.basicMain) // 设置背景色
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
addImageDebouncer {
|
addImageDebouncer {
|
||||||
if (model.imageList.size < 9) {
|
if (model.imageList.size < 9) {
|
||||||
@@ -516,21 +572,20 @@ fun AddImageGrid() {
|
|||||||
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
|
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
|
||||||
contentDescription = "Add Image",
|
contentDescription = "Add Image",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(23.3.dp)
|
.size(24.dp)
|
||||||
.align(Alignment.Center),
|
.align(Alignment.Center),
|
||||||
tint = AppColors.nonActiveText
|
tint = AppColors.nonActiveText
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 相机按钮
|
|
||||||
item {
|
item {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.aspectRatio(1f)
|
.aspectRatio(1f)
|
||||||
.clip(RoundedCornerShape(24.dp))
|
.clip(RoundedCornerShape(16.dp)) // 设置圆角
|
||||||
.background(Color(0xFFFAF9FB))
|
.background(AppColors.basicMain) // 设置背景色
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
if (model.imageList.size < 9) {
|
if (model.imageList.size < 9) {
|
||||||
val photoFile = File(context.cacheDir, "photo.jpg")
|
val photoFile = File(context.cacheDir, "photo.jpg")
|
||||||
@@ -550,60 +605,13 @@ fun AddImageGrid() {
|
|||||||
painter = painterResource(id = R.drawable.rider_pro_camera),
|
painter = painterResource(id = R.drawable.rider_pro_camera),
|
||||||
contentDescription = "Take Photo",
|
contentDescription = "Take Photo",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(23.3.dp)
|
.size(24.dp)
|
||||||
.align(Alignment.Center),
|
.align(Alignment.Center),
|
||||||
tint = AppColors.nonActiveText
|
tint = AppColors.nonActiveText
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 已添加的图片,显示在相机按钮后面
|
|
||||||
items(model.imageList.size) { index ->
|
|
||||||
val item = model.imageList[index]
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.aspectRatio(1f)
|
|
||||||
.clip(RoundedCornerShape(24.dp))
|
|
||||||
.background(Color(0xFFFAF9FB))
|
|
||||||
) {
|
|
||||||
CustomAsyncImage(
|
|
||||||
context,
|
|
||||||
item.bitmap,
|
|
||||||
contentDescription = "Image",
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigate(NavigationRoute.NewPostImageGrid.route)
|
|
||||||
},
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
|
|
||||||
// // 删除按钮 - 右上角
|
|
||||||
// Box(
|
|
||||||
// modifier = Modifier
|
|
||||||
// .align(Alignment.TopEnd)
|
|
||||||
// .padding(4.dp)
|
|
||||||
// .size(20.dp)
|
|
||||||
// .clip(RoundedCornerShape(10.dp))
|
|
||||||
// .background(Color.Black.copy(alpha = 0.6f))
|
|
||||||
// .noRippleClickable {
|
|
||||||
// model.imageList = model.imageList.toMutableList().apply {
|
|
||||||
// removeAt(index)
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// contentAlignment = Alignment.Center
|
|
||||||
// ) {
|
|
||||||
// Icon(
|
|
||||||
// painter = painterResource(id = R.drawable.rider_pro_close),
|
|
||||||
// contentDescription = "Delete",
|
|
||||||
// modifier = Modifier.size(12.dp),
|
|
||||||
// tint = Color.White
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
)
|
)
|
||||||
@@ -917,20 +917,7 @@ fun Header(
|
|||||||
Text(
|
Text(
|
||||||
text = nickname ?: "",
|
text = nickname ?: "",
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.weight(1f)
|
|
||||||
.debouncedClickable(debounceTime = 1000L) {
|
|
||||||
userId?.let {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(
|
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
|
||||||
"{id}",
|
|
||||||
userId.toString()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
fontSize = 17.sp
|
fontSize = 17.sp
|
||||||
)
|
)
|
||||||
@@ -1209,7 +1196,7 @@ fun PostImageView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片导航控件
|
// Navigation and Indicator container
|
||||||
if (images.size > 1) {
|
if (images.size > 1) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -1360,56 +1347,76 @@ fun CommentItem(
|
|||||||
}
|
}
|
||||||
) {}
|
) {}
|
||||||
) {
|
) {
|
||||||
Row(
|
Row {
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.SpaceBetween
|
|
||||||
) {
|
|
||||||
Text(
|
Text(
|
||||||
text = commentEntity.name,
|
text = commentEntity.name,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
fontSize = 11.sp,
|
fontSize = 11.sp,
|
||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
Column(
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
horizontalAlignment = Alignment.End
|
|
||||||
) {
|
|
||||||
AnimatedLikeIcon(
|
|
||||||
liked = commentEntity.liked,
|
|
||||||
onClick = {
|
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onLike(commentEntity)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(16.dp)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
text = commentEntity.likes.toString(),
|
|
||||||
fontSize = 12.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.text,
|
|
||||||
modifier = Modifier.padding(top = 4.dp,end = 4.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = commentEntity.comment,
|
text = commentEntity.date.timeAgo(context),
|
||||||
fontSize = 13.sp,
|
fontSize = 11.sp,
|
||||||
maxLines = Int.MAX_VALUE,
|
color = Color.Gray
|
||||||
softWrap = true,
|
)
|
||||||
lineHeight = 20.sp,
|
}
|
||||||
color = AppColors.text,
|
Row (modifier = Modifier.padding(top = 4.dp)){
|
||||||
modifier = Modifier
|
if (isChild) {
|
||||||
.fillMaxWidth()
|
val annotatedText = buildAnnotatedString {
|
||||||
.padding(end = 50.dp)
|
if (commentEntity.replyUserId != null) {
|
||||||
.padding(top = 0.dp)
|
pushStringAnnotation(
|
||||||
.combinedClickable(
|
tag = "replyUser",
|
||||||
|
annotation = commentEntity.replyUserId.toString()
|
||||||
|
)
|
||||||
|
withStyle(
|
||||||
|
style = SpanStyle(
|
||||||
|
fontWeight = FontWeight.W600,
|
||||||
|
color = Color(0xFF6F94AE)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
append("@${commentEntity.replyUserNickname}")
|
||||||
|
}
|
||||||
|
pop()
|
||||||
|
}
|
||||||
|
append(" ${commentEntity.comment}")
|
||||||
|
|
||||||
|
}
|
||||||
|
Box {
|
||||||
|
CustomClickableText(
|
||||||
|
text = annotatedText,
|
||||||
|
onClick = { offset ->
|
||||||
|
annotatedText.getStringAnnotations(
|
||||||
|
tag = "replyUser",
|
||||||
|
start = offset,
|
||||||
|
end = offset
|
||||||
|
).firstOrNull()?.let {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(
|
||||||
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
|
"{id}",
|
||||||
|
it.item
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
style = TextStyle(fontSize = 14.sp, color = AppColors.text),
|
||||||
|
onLongPress = {
|
||||||
|
onLongClick(commentEntity)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
Text(
|
||||||
|
text = commentEntity.comment,
|
||||||
|
fontSize = 13.sp,
|
||||||
|
maxLines = Int.MAX_VALUE,
|
||||||
|
softWrap = true,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier.combinedClickable(
|
||||||
interactionSource = remember { MutableInteractionSource() },
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
indication = null,
|
indication = null,
|
||||||
onLongClick = {
|
onLongClick = {
|
||||||
@@ -1420,21 +1427,48 @@ fun CommentItem(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row (modifier = Modifier.padding(top = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,){
|
||||||
|
AnimatedLikeIcon(
|
||||||
|
liked = commentEntity.liked,
|
||||||
|
onClick = {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onLike(commentEntity)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Row (
|
|
||||||
modifier = Modifier.padding(top = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
|
||||||
){
|
|
||||||
Text(
|
Text(
|
||||||
text = commentEntity.date.timeAgo(context),
|
text = commentEntity.likes.toString(),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = Color.Gray
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.like),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = AppColors.nonActiveText,
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(27.dp))
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_comment),
|
||||||
|
contentDescription = "",
|
||||||
|
modifier = Modifier.size(16.dp),
|
||||||
|
tint = AppColors.nonActiveText
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.reply),
|
text = stringResource(R.string.reply),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
@@ -1507,7 +1541,6 @@ fun CommentItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PostBottomBar(
|
fun PostBottomBar(
|
||||||
onCreateCommentClick: () -> Unit = {},
|
onCreateCommentClick: () -> Unit = {},
|
||||||
@@ -1574,24 +1607,6 @@ fun PostBottomBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
AnimatedFavouriteIcon(
|
|
||||||
isFavourite = momentEntity?.isFavorite == true,
|
|
||||||
onClick = {
|
|
||||||
// 检查游客模式,如果是游客则跳转登录
|
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
onFavoriteClick()
|
|
||||||
}
|
|
||||||
},
|
|
||||||
modifier = Modifier.size(24.dp)
|
|
||||||
)
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(text = momentEntity?.favoriteCount.toString(), color = AppColors.text)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
AnimatedLikeIcon(
|
AnimatedLikeIcon(
|
||||||
liked = momentEntity?.liked == true,
|
liked = momentEntity?.liked == true,
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -1608,6 +1623,24 @@ fun PostBottomBar(
|
|||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
Text(text = momentEntity?.likeCount.toString(), color = AppColors.text)
|
Text(text = momentEntity?.likeCount.toString(), color = AppColors.text)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
AnimatedFavouriteIcon(
|
||||||
|
isFavourite = momentEntity?.isFavorite == true,
|
||||||
|
onClick = {
|
||||||
|
// 检查游客模式,如果是游客则跳转登录
|
||||||
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.LIKE_MOMENT)) {
|
||||||
|
debouncedNavigation {
|
||||||
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
onFavoriteClick()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
modifier = Modifier.size(24.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
Text(text = momentEntity?.favoriteCount.toString(), color = AppColors.text)
|
||||||
|
|
||||||
}
|
}
|
||||||
BottomNavigationPlaceholder(
|
BottomNavigationPlaceholder(
|
||||||
color = AppColors.background
|
color = AppColors.background
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import com.aiosman.ravenow.data.UserServiceImpl
|
|||||||
import com.aiosman.ravenow.entity.AccountProfileEntity
|
import com.aiosman.ravenow.entity.AccountProfileEntity
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
import com.aiosman.ravenow.entity.MomentEntity
|
||||||
import com.aiosman.ravenow.entity.MomentServiceImpl
|
import com.aiosman.ravenow.entity.MomentServiceImpl
|
||||||
import com.aiosman.ravenow.event.FollowChangeEvent
|
|
||||||
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
import com.aiosman.ravenow.event.MomentFavouriteChangeEvent
|
||||||
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
import com.aiosman.ravenow.event.MomentLikeChangeEvent
|
||||||
import com.aiosman.ravenow.event.MomentRemoveEvent
|
import com.aiosman.ravenow.event.MomentRemoveEvent
|
||||||
@@ -167,8 +166,7 @@ class PostViewModel(
|
|||||||
moment?.let {
|
moment?.let {
|
||||||
userService.followUser(it.authorId.toString())
|
userService.followUser(it.authorId.toString())
|
||||||
moment = moment?.copy(followStatus = true)
|
moment = moment?.copy(followStatus = true)
|
||||||
// 发送关注事件,通知动态列表更新关注状态
|
// 更新我的关注页面的关注数
|
||||||
EventBus.getDefault().post(FollowChangeEvent(it.authorId, true))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,8 +174,7 @@ class PostViewModel(
|
|||||||
moment?.let {
|
moment?.let {
|
||||||
userService.unFollowUser(it.authorId.toString())
|
userService.unFollowUser(it.authorId.toString())
|
||||||
moment = moment?.copy(followStatus = false)
|
moment = moment?.copy(followStatus = false)
|
||||||
// 发送取消关注事件,通知动态列表更新关注状态
|
// 更新我的关注页面的关注数
|
||||||
EventBus.getDefault().post(FollowChangeEvent(it.authorId, false))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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.kp_logo_img),
|
|
||||||
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
18
app/src/main/res/drawable/ic_create_agent_generate.xml
Normal file
18
app/src/main/res/drawable/ic_create_agent_generate.xml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="16dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="16"
|
||||||
|
android:viewportHeight="16">
|
||||||
|
<path
|
||||||
|
android:pathData="m9.098,4.33 l1.475,-1.475a0.643,0.643 0,0 1,0.909 0l1.663,1.663a0.643,0.643 0,0 1,0 0.91L11.67,6.901M9.098,4.33l-6.243,6.242a0.643,0.643 0,0 0,-0.188 0.455v1.663c0,0.355 0.288,0.643 0.643,0.643h1.663c0.17,0 0.334,-0.067 0.455,-0.188l6.242,-6.243M9.098,4.33l2.572,2.572"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.333"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="#7C45ED"
|
||||||
|
android:fillType="evenOdd"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
<path
|
||||||
|
android:pathData="M3.608,6.333c0.13,0 0.202,-0.07 0.215,-0.208 0.056,-0.434 0.114,-0.775 0.172,-1.022 0.06,-0.247 0.152,-0.434 0.28,-0.56 0.128,-0.126 0.318,-0.221 0.57,-0.286 0.252,-0.066 0.599,-0.133 1.042,-0.202 0.152,-0.026 0.228,-0.1 0.228,-0.222 0,-0.06 -0.02,-0.11 -0.059,-0.146a0.304,0.304 0,0 0,-0.143 -0.075,15.533 15.533,0 0,1 -1.048,-0.225c-0.256,-0.067 -0.45,-0.161 -0.583,-0.283 -0.132,-0.121 -0.228,-0.304 -0.287,-0.547a10.18,10.18 0,0 1,-0.172 -1.015c-0.013,-0.14 -0.085,-0.209 -0.215,-0.209a0.221,0.221 0,0 0,-0.153 0.056,0.208 0.208,0 0,0 -0.068,0.146 9.948,9.948 0,0 1,-0.176 1.039c-0.06,0.25 -0.155,0.438 -0.283,0.566 -0.128,0.128 -0.32,0.224 -0.576,0.286 -0.256,0.063 -0.608,0.125 -1.055,0.186a0.251,0.251 0,0 0,-0.143 0.072,0.203 0.203,0 0,0 -0.059,0.15c0,0.06 0.02,0.109 0.059,0.146 0.039,0.037 0.086,0.062 0.143,0.075 0.447,0.082 0.799,0.158 1.055,0.228 0.256,0.069 0.448,0.166 0.576,0.29 0.128,0.123 0.221,0.306 0.28,0.55 0.058,0.242 0.118,0.581 0.179,1.015a0.202,0.202 0,0 0,0.068 0.14,0.221 0.221,0 0,0 0.153,0.055zM12.516,14.702c0.086,0 0.139,-0.05 0.156,-0.15 0.056,-0.308 0.108,-0.552 0.156,-0.732a0.963,0.963 0,0 1,0.208 -0.417,0.879 0.879,0 0,1 0.41,-0.225c0.183,-0.052 0.439,-0.106 0.769,-0.162 0.1,-0.018 0.15,-0.072 0.15,-0.163 0,-0.091 -0.05,-0.146 -0.15,-0.163a6.959,6.959 0,0 1,-0.768 -0.166,0.919 0.919,0 0,1 -0.41,-0.224 0.937,0.937 0,0 1,-0.209 -0.414c-0.048,-0.18 -0.1,-0.426 -0.156,-0.739 -0.017,-0.095 -0.07,-0.143 -0.156,-0.143 -0.091,0 -0.146,0.048 -0.163,0.143a9.897,9.897 0,0 1,-0.156 0.74,0.937 0.937,0 0,1 -0.209,0.413 0.919,0.919 0,0 1,-0.41 0.224c-0.182,0.054 -0.436,0.11 -0.762,0.166 -0.1,0.017 -0.15,0.072 -0.15,0.163 0,0.091 0.05,0.145 0.15,0.163 0.326,0.056 0.58,0.11 0.762,0.162a0.879,0.879 0,0 1,0.41 0.225c0.091,0.098 0.16,0.237 0.209,0.417 0.047,0.18 0.1,0.424 0.156,0.732 0.009,0.044 0.026,0.08 0.052,0.108a0.143,0.143 0,0 0,0.11 0.042z"
|
||||||
|
android:fillColor="#7C45ED"
|
||||||
|
android:fillType="evenOdd"/>
|
||||||
|
</vector>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user