Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 85141fde1b | |||
| 234c07142c | |||
| 89227edccf | |||
| 29490d288b | |||
| a99ab30c4e | |||
| 9d3d13a22d | |||
| f0a9704e2d | |||
| 7f2c103ada | |||
| d94e3b5c20 | |||
| 44cc76d2e3 | |||
| fac6f23356 | |||
| 39928abc46 | |||
| 4d0d7004b0 | |||
| 28a6e3fef3 | |||
| b275d88ef7 | |||
| 595ef7f942 | |||
| 1202b55c74 | |||
| 074009d256 | |||
| ad1de9e3f7 | |||
| 359bcfdfd7 | |||
| 1901fddb2e | |||
| b96ae94bdb | |||
| dedd356896 | |||
| bb9fda75ae | |||
| ea911f113b | |||
| 88f379fe5b | |||
| 1a24136c35 | |||
| 742410223c | |||
| bd5aff7564 | |||
| b43c1585c4 | |||
| cb582393f1 | |||
| a200d00587 | |||
| 6d2133545f | |||
| 2aad126010 | |||
| b7b777d2d0 | |||
| e804c8be0c | |||
| 228a74695e | |||
| 41a51b85da | |||
| e74e8615a5 | |||
| 349d39daf2 | |||
| 8154a0ddc4 |
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-09-09T09:51:06.656104400Z">
|
<DropdownSelection timestamp="2025-09-17T06:25:35.585100400Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="Default" identifier="serial=192.168.0.227:45035;connection=094cb92e" />
|
<DeviceId pluginId="Default" identifier="serial=192.168.0.216:5555;connection=698a7727" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
BIN
app/src/main/assets/loading.lottie
Normal file
@@ -45,6 +45,7 @@ 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)
|
||||||
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
|
suspend fun initWithAccount(scope: CoroutineScope, context: Context) {
|
||||||
// 如果是游客模式,使用简化的初始化流程
|
// 如果是游客模式,使用简化的初始化流程
|
||||||
if (AppStore.isGuest) {
|
if (AppStore.isGuest) {
|
||||||
|
|||||||
@@ -43,7 +43,12 @@ 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
|
||||||
@@ -122,11 +127,22 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
var showSplash by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
kotlinx.coroutines.delay(2000)
|
||||||
|
showSplash = false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showSplash) {
|
||||||
|
SplashScreen()
|
||||||
|
} else {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalAppTheme provides AppState.appTheme
|
LocalAppTheme provides AppState.appTheme
|
||||||
) {
|
) {
|
||||||
CheckUpdateDialog()
|
CheckUpdateDialog()
|
||||||
Navigation(startDestination) { navController ->
|
Navigation(startDestination) { navController ->
|
||||||
|
|
||||||
// 处理带有 postId 的通知点击
|
// 处理带有 postId 的通知点击
|
||||||
val postId = intent.getStringExtra("POST_ID")
|
val postId = intent.getStringExtra("POST_ID")
|
||||||
var commentId = intent.getStringExtra("COMMENT_ID")
|
var commentId = intent.getStringExtra("COMMENT_ID")
|
||||||
@@ -179,17 +195,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
NewPostViewModel.asNewPostWithImageUris(imageUris!!.map { it.toString() })
|
NewPostViewModel.asNewPostWithImageUris(imageUris!!.map { it.toString() })
|
||||||
navController.navigate(NavigationRoute.NewPost.route)
|
navController.navigate(NavigationRoute.NewPost.route)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 请求通知权限
|
* 请求通知权限
|
||||||
|
|||||||
@@ -32,28 +32,6 @@ data class Moment(
|
|||||||
val time: String,
|
val time: String,
|
||||||
@SerializedName("isFollowed")
|
@SerializedName("isFollowed")
|
||||||
val isFollowed: Boolean,
|
val isFollowed: Boolean,
|
||||||
@SerializedName("isNews")
|
|
||||||
val isNews: Boolean? = null,
|
|
||||||
@SerializedName("newsTitle")
|
|
||||||
val newsTitle: String? = null,
|
|
||||||
@SerializedName("newsUrl")
|
|
||||||
val newsUrl: String? = null,
|
|
||||||
@SerializedName("newsSource")
|
|
||||||
val newsSource: String? = null,
|
|
||||||
@SerializedName("newsCategory")
|
|
||||||
val newsCategory: String? = null,
|
|
||||||
@SerializedName("newsLanguage")
|
|
||||||
val newsLanguage: String? = null,
|
|
||||||
@SerializedName("newsContent")
|
|
||||||
val newsContent: String? = null,
|
|
||||||
@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(
|
||||||
@@ -82,17 +60,6 @@ data class Moment(
|
|||||||
authorId = user.id.toInt(),
|
authorId = user.id.toInt(),
|
||||||
liked = isLiked,
|
liked = isLiked,
|
||||||
isFavorite = isFavorite,
|
isFavorite = isFavorite,
|
||||||
isNews = isNews,
|
|
||||||
newsTitle = newsTitle,
|
|
||||||
newsUrl = newsUrl,
|
|
||||||
newsSource = newsSource,
|
|
||||||
newsCategory = newsCategory,
|
|
||||||
newsLanguage = newsLanguage,
|
|
||||||
newsContent = newsContent,
|
|
||||||
hasFullText = hasFullText,
|
|
||||||
summary = summary,
|
|
||||||
publishedAt = publishedAt,
|
|
||||||
imageCached = imageCached,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,11 +13,9 @@ 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 {
|
||||||
@@ -25,9 +23,6 @@ fun getSafeOkHttpClient(
|
|||||||
addInterceptor(it)
|
addInterceptor(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.connectTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
|
||||||
.writeTimeout(timeoutSeconds, TimeUnit.SECONDS)
|
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -61,7 +56,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(timeoutSeconds = 30))
|
.client(getSafeOkHttpClient())
|
||||||
.build()
|
.build()
|
||||||
.create(RaveNowAPI::class.java)
|
.create(RaveNowAPI::class.java)
|
||||||
|
|
||||||
@@ -80,10 +75,7 @@ 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(), timeoutSeconds = 30)
|
getSafeOkHttpClient(authInterceptor = AuthInterceptor())
|
||||||
}
|
|
||||||
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()
|
||||||
@@ -92,19 +84,9 @@ 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())
|
||||||
|
|||||||
@@ -42,20 +42,6 @@ data class AgentMomentRequestBody(
|
|||||||
val sessionId: String
|
val sessionId: String
|
||||||
)
|
)
|
||||||
|
|
||||||
data class GenerateAgentInfoRequestBody(
|
|
||||||
@SerializedName("descriptionText")
|
|
||||||
val descriptionText: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class GenerateAgentInfoResponseBody(
|
|
||||||
@SerializedName("title")
|
|
||||||
val title: String,
|
|
||||||
@SerializedName("description")
|
|
||||||
val description: String,
|
|
||||||
@SerializedName("content")
|
|
||||||
val content: String
|
|
||||||
)
|
|
||||||
|
|
||||||
data class SingleChatRequestBody(
|
data class SingleChatRequestBody(
|
||||||
@SerializedName("agentOpenId")
|
@SerializedName("agentOpenId")
|
||||||
val agentOpenId: String? = null,
|
val agentOpenId: String? = null,
|
||||||
@@ -95,7 +81,9 @@ data class CreateGroupChatRequestBody(
|
|||||||
|
|
||||||
data class JoinGroupChatRequestBody(
|
data class JoinGroupChatRequestBody(
|
||||||
@SerializedName("trtcId")
|
@SerializedName("trtcId")
|
||||||
val trtcId: String,
|
val trtcId: String? = null,
|
||||||
|
@SerializedName("roomId")
|
||||||
|
val roomId: Int? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
data class LoginUserRequestBody(
|
data class LoginUserRequestBody(
|
||||||
@@ -285,6 +273,170 @@ data class RemoveAccountRequestBody(
|
|||||||
val password: String,
|
val password: String,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// API 错误响应(用于加入房间等接口的错误处理)
|
||||||
|
data class ApiErrorResponse(
|
||||||
|
@SerializedName("err")
|
||||||
|
val error: String,
|
||||||
|
@SerializedName("success")
|
||||||
|
val success: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
// 群聊中的用户信息
|
||||||
|
data class GroupChatUser(
|
||||||
|
@SerializedName("ID")
|
||||||
|
val id: Int,
|
||||||
|
@SerializedName("CreatedAt")
|
||||||
|
val createdAt: String,
|
||||||
|
@SerializedName("UpdatedAt")
|
||||||
|
val updatedAt: String,
|
||||||
|
@SerializedName("DeletedAt")
|
||||||
|
val deletedAt: String?,
|
||||||
|
@SerializedName("userSessionId")
|
||||||
|
val userSessionId: String,
|
||||||
|
@SerializedName("sessions")
|
||||||
|
val sessions: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("prompts")
|
||||||
|
val prompts: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("isAgent")
|
||||||
|
val isAgent: Boolean
|
||||||
|
)
|
||||||
|
|
||||||
|
// 智能体角色信息
|
||||||
|
data class GroupChatPrompt(
|
||||||
|
@SerializedName("ID")
|
||||||
|
val id: Int,
|
||||||
|
@SerializedName("CreatedAt")
|
||||||
|
val createdAt: String,
|
||||||
|
@SerializedName("UpdatedAt")
|
||||||
|
val updatedAt: String,
|
||||||
|
@SerializedName("DeletedAt")
|
||||||
|
val deletedAt: String?,
|
||||||
|
@SerializedName("Title")
|
||||||
|
val title: String,
|
||||||
|
@SerializedName("Desc")
|
||||||
|
val desc: String,
|
||||||
|
@SerializedName("Value")
|
||||||
|
val value: String,
|
||||||
|
@SerializedName("Enable")
|
||||||
|
val enable: Boolean,
|
||||||
|
@SerializedName("UserSessions")
|
||||||
|
val userSessions: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("Avatar")
|
||||||
|
val avatar: String,
|
||||||
|
@SerializedName("AuthorId")
|
||||||
|
val authorId: Int?,
|
||||||
|
@SerializedName("Author")
|
||||||
|
val author: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("TokenCount")
|
||||||
|
val tokenCount: Int,
|
||||||
|
@SerializedName("OpenId")
|
||||||
|
val openId: String,
|
||||||
|
@SerializedName("Public")
|
||||||
|
val public: Boolean,
|
||||||
|
@SerializedName("BreakMode")
|
||||||
|
val breakMode: Boolean,
|
||||||
|
@SerializedName("DocNamespace")
|
||||||
|
val docNamespace: String,
|
||||||
|
@SerializedName("UseRag")
|
||||||
|
val useRag: Boolean,
|
||||||
|
@SerializedName("RagThreshold")
|
||||||
|
val ragThreshold: Double,
|
||||||
|
@SerializedName("WorkflowId")
|
||||||
|
val workflowId: Int?,
|
||||||
|
@SerializedName("Workflow")
|
||||||
|
val workflow: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("WorkflowInputs")
|
||||||
|
val workflowInputs: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("Source")
|
||||||
|
val source: String,
|
||||||
|
@SerializedName("categories")
|
||||||
|
val categories: Any? // 根据实际需要可以定义具体类型
|
||||||
|
)
|
||||||
|
|
||||||
|
// 群聊详细信息响应
|
||||||
|
data class GroupChatResponse(
|
||||||
|
@SerializedName("ID")
|
||||||
|
val id: Int,
|
||||||
|
@SerializedName("CreatedAt")
|
||||||
|
val createdAt: String,
|
||||||
|
@SerializedName("UpdatedAt")
|
||||||
|
val updatedAt: String,
|
||||||
|
@SerializedName("DeletedAt")
|
||||||
|
val deletedAt: String?,
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerializedName("description")
|
||||||
|
val description: String,
|
||||||
|
@SerializedName("creatorId")
|
||||||
|
val creatorId: Int,
|
||||||
|
@SerializedName("creator")
|
||||||
|
val creator: Any?, // 根据实际需要可以定义具体类型
|
||||||
|
@SerializedName("trtcRoomId")
|
||||||
|
val trtcRoomId: String,
|
||||||
|
@SerializedName("trtcType")
|
||||||
|
val trtcType: String,
|
||||||
|
@SerializedName("cover")
|
||||||
|
val cover: String,
|
||||||
|
@SerializedName("avatar")
|
||||||
|
val avatar: String,
|
||||||
|
@SerializedName("recommendBanner")
|
||||||
|
val recommendBanner: String,
|
||||||
|
@SerializedName("isRecommended")
|
||||||
|
val isRecommended: Boolean,
|
||||||
|
@SerializedName("allowInHot")
|
||||||
|
val allowInHot: Boolean,
|
||||||
|
@SerializedName("users")
|
||||||
|
val users: List<GroupChatUser>,
|
||||||
|
@SerializedName("prompts")
|
||||||
|
val prompts: List<GroupChatPrompt>,
|
||||||
|
@SerializedName("source")
|
||||||
|
val source: String
|
||||||
|
)
|
||||||
|
class CategoryTemplateTranslation(
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerializedName("description")
|
||||||
|
val description: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class CategoryTemplate(
|
||||||
|
@SerializedName("id")
|
||||||
|
val id: Int,
|
||||||
|
@SerializedName("name")
|
||||||
|
val name: String,
|
||||||
|
@SerializedName("description")
|
||||||
|
val description: String,
|
||||||
|
@SerializedName("avatar")
|
||||||
|
val avatar: String,
|
||||||
|
@SerializedName("parentId")
|
||||||
|
val parentId: Int?,
|
||||||
|
@SerializedName("parent")
|
||||||
|
val parent: CategoryTemplate?,
|
||||||
|
@SerializedName("children")
|
||||||
|
val children: List<CategoryTemplate>?,
|
||||||
|
@SerializedName("sort")
|
||||||
|
val sort: Int,
|
||||||
|
@SerializedName("isActive")
|
||||||
|
val isActive: Boolean,
|
||||||
|
@SerializedName("promptCount")
|
||||||
|
val promptCount: Int?,
|
||||||
|
@SerializedName("createdAt")
|
||||||
|
val createdAt: String,
|
||||||
|
@SerializedName("updatedAt")
|
||||||
|
val updatedAt: String,
|
||||||
|
@SerializedName("translations")
|
||||||
|
val translations: Map<String, CategoryTemplateTranslation>?
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* 获取本地化名称,优先使用当前语言的翻译,如果没有则使用默认名称
|
||||||
|
*/
|
||||||
|
fun getLocalizedName(): String {
|
||||||
|
// 这里可以根据需要添加国际化逻辑
|
||||||
|
// 目前直接返回默认名称
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
interface RaveNowAPI {
|
interface RaveNowAPI {
|
||||||
@GET("membership/config")
|
@GET("membership/config")
|
||||||
@retrofit2.http.Headers("X-Requires-Auth: true")
|
@retrofit2.http.Headers("X-Requires-Auth: true")
|
||||||
@@ -316,7 +468,6 @@ interface RaveNowAPI {
|
|||||||
suspend fun getPosts(
|
suspend fun getPosts(
|
||||||
@Query("page") page: Int = 1,
|
@Query("page") page: Int = 1,
|
||||||
@Query("pageSize") pageSize: Int = 20,
|
@Query("pageSize") pageSize: Int = 20,
|
||||||
@Query("id") postId: Int? = null,
|
|
||||||
@Query("timelineId") timelineId: Int? = null,
|
@Query("timelineId") timelineId: Int? = null,
|
||||||
@Query("authorId") authorId: Int? = null,
|
@Query("authorId") authorId: Int? = null,
|
||||||
@Query("contentSearch") contentSearch: String? = null,
|
@Query("contentSearch") contentSearch: String? = null,
|
||||||
@@ -324,15 +475,6 @@ interface RaveNowAPI {
|
|||||||
@Query("trend") trend: String? = null,
|
@Query("trend") trend: String? = null,
|
||||||
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
@Query("favouriteUserId") favouriteUserId: Int? = null,
|
||||||
@Query("explore") explore: String? = null,
|
@Query("explore") explore: String? = null,
|
||||||
@Query("imageTag") imageTag: String? = null,
|
|
||||||
@Query("search") search: String? = null,
|
|
||||||
@Query("advancedSearch") advancedSearch: String? = null,
|
|
||||||
@Query("newsFilter") newsFilter: String? = null,
|
|
||||||
@Query("onlyNews") onlyNews: Boolean? = null,
|
|
||||||
@Query("newsSource") newsSource: String? = null,
|
|
||||||
@Query("newsLanguage") newsLanguage: String? = null,
|
|
||||||
@Query("newsCategory") newsCategory: String? = null,
|
|
||||||
@Query("requireImageCache") requireImageCache: Boolean? = null,
|
|
||||||
): Response<ListContainer<Moment>>
|
): Response<ListContainer<Moment>>
|
||||||
|
|
||||||
@Multipart
|
@Multipart
|
||||||
@@ -574,8 +716,26 @@ interface RaveNowAPI {
|
|||||||
suspend fun getAgent(
|
suspend fun getAgent(
|
||||||
@Query("page") page: Int = 1,
|
@Query("page") page: Int = 1,
|
||||||
@Query("pageSize") pageSize: Int = 20,
|
@Query("pageSize") pageSize: Int = 20,
|
||||||
@Query("withWorkflow") withWorkflow: Int = 1,
|
@Query("order") order: String? = null,
|
||||||
|
@Query("orderKey") orderKey: String? = null,
|
||||||
|
@Query("createdAt") createdAt: String? = null,
|
||||||
|
@Query("updatedAt") updatedAt: String? = null,
|
||||||
|
@Query("createdStart") createdStart: String? = null,
|
||||||
|
@Query("createdEnd") createdEnd: String? = null,
|
||||||
|
@Query("updatedStart") updatedStart: String? = null,
|
||||||
|
@Query("updatedEnd") updatedEnd: String? = null,
|
||||||
|
@Query("title") title: String? = null,
|
||||||
@Query("authorId") authorId: Int? = null,
|
@Query("authorId") authorId: Int? = null,
|
||||||
|
@Query("authorOpenId") authorOpenId: String? = null,
|
||||||
|
@Query("showPrivate") showPrivate: String? = null,
|
||||||
|
@Query("explore") explore: String? = null,
|
||||||
|
@Query("desc") desc: String? = null,
|
||||||
|
@Query("withWorkflow") withWorkflow: String? = null,
|
||||||
|
@Query("hasAvatar") hasAvatar: String? = null,
|
||||||
|
@Query("random") random: String? = null,
|
||||||
|
@Query("categoryName") categoryName: String? = null,
|
||||||
|
@Query("categoryIds") categoryIds: List<Int>? = null,
|
||||||
|
@Query("uncategorized") uncategorized: String? = null,
|
||||||
): Response<DataContainer<ListContainer<Agent>>>
|
): Response<DataContainer<ListContainer<Agent>>>
|
||||||
|
|
||||||
@GET("outside/my/prompts")
|
@GET("outside/my/prompts")
|
||||||
@@ -604,7 +764,10 @@ interface RaveNowAPI {
|
|||||||
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
|
suspend fun agentMoment(@Body body: AgentMomentRequestBody): Response<DataContainer<String>>
|
||||||
|
|
||||||
@GET("outside/rooms/open")
|
@GET("outside/rooms/open")
|
||||||
suspend fun createGroupChatAi(@Query("trtcGroupId") trtcGroupId: String): Response<DataContainer<Unit>>
|
suspend fun createGroupChatAi(
|
||||||
|
@Query("trtcGroupId") trtcGroupId: String? = null,
|
||||||
|
@Query("roomId") roomId: Int? = null
|
||||||
|
): Response<DataContainer<GroupChatResponse>>
|
||||||
|
|
||||||
@POST("outside/rooms/create-single-chat")
|
@POST("outside/rooms/create-single-chat")
|
||||||
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
|
suspend fun createSingleChat(@Body body: SingleChatRequestBody): Response<DataContainer<Unit>>
|
||||||
@@ -619,6 +782,7 @@ interface RaveNowAPI {
|
|||||||
suspend fun getRooms(@Query("page") page: Int = 1,
|
suspend fun getRooms(@Query("page") page: Int = 1,
|
||||||
@Query("pageSize") pageSize: Int = 20,
|
@Query("pageSize") pageSize: Int = 20,
|
||||||
@Query("isRecommended") isRecommended: Int = 1,
|
@Query("isRecommended") isRecommended: Int = 1,
|
||||||
|
@Query("random") random: Int? = null,
|
||||||
): Response<ListContainer<Room>>
|
): Response<ListContainer<Room>>
|
||||||
|
|
||||||
@GET("outside/rooms/detail")
|
@GET("outside/rooms/detail")
|
||||||
@@ -629,8 +793,30 @@ interface RaveNowAPI {
|
|||||||
suspend fun joinRoom(@Body body: JoinGroupChatRequestBody,
|
suspend fun joinRoom(@Body body: JoinGroupChatRequestBody,
|
||||||
): Response<DataContainer<Room>>
|
): Response<DataContainer<Room>>
|
||||||
|
|
||||||
@POST("outside/generate/agent-info")
|
@GET("outside/categories")
|
||||||
suspend fun generateAgentInfo(@Body body: GenerateAgentInfoRequestBody): Response<DataContainer<GenerateAgentInfoResponseBody>>
|
suspend fun getCategories(
|
||||||
|
@Query("page") page: Int? = null,
|
||||||
|
@Query("pageSize") pageSize: Int? = null,
|
||||||
|
@Query("parentId") parentId: Int? = null,
|
||||||
|
@Query("isActive") isActive: Boolean? = null,
|
||||||
|
@Query("name") name: String? = null,
|
||||||
|
@Query("withChildren") withChildren: Boolean? = null,
|
||||||
|
@Query("withParent") withParent: Boolean? = null,
|
||||||
|
@Query("withCount") withCount: Boolean? = null,
|
||||||
|
@Query("hideEmpty") hideEmpty: Boolean? = null
|
||||||
|
): Response<ListContainer<CategoryTemplate>>
|
||||||
|
|
||||||
|
@GET("outside/categories/tree")
|
||||||
|
suspend fun getCategoryTree(
|
||||||
|
@Query("withCount") withCount: Boolean? = null,
|
||||||
|
@Query("hideEmpty") hideEmpty: Boolean? = null
|
||||||
|
): Response<DataContainer<List<CategoryTemplate>>>
|
||||||
|
|
||||||
|
@GET("outside/categories/{id}")
|
||||||
|
suspend fun getCategoryById(
|
||||||
|
@Path("id") id: Int
|
||||||
|
): Response<DataContainer<CategoryTemplate>>
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -299,35 +299,12 @@ data class MomentEntity(
|
|||||||
// 关联动态
|
// 关联动态
|
||||||
var relMoment: MomentEntity? = null,
|
var relMoment: MomentEntity? = null,
|
||||||
// 是否收藏
|
// 是否收藏
|
||||||
var isFavorite: Boolean = false,
|
var isFavorite: Boolean = false
|
||||||
// 是否为新闻
|
|
||||||
val isNews: Boolean? = null,
|
|
||||||
// 新闻标题
|
|
||||||
val newsTitle: String? = null,
|
|
||||||
// 新闻链接
|
|
||||||
val newsUrl: String? = null,
|
|
||||||
// 新闻来源
|
|
||||||
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? = false
|
|
||||||
)
|
)
|
||||||
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
||||||
override suspend fun fetchData(
|
override suspend fun fetchData(
|
||||||
@@ -340,8 +317,7 @@ class MomentLoader : DataLoader<MomentEntity,MomentLoaderExtraArgs>() {
|
|||||||
pageSize = pageSize,
|
pageSize = pageSize,
|
||||||
explore = if (extra.explore == true) "true" else "",
|
explore = if (extra.explore == true) "true" else "",
|
||||||
timelineId = extra.timelineId,
|
timelineId = extra.timelineId,
|
||||||
authorId = extra.authorId,
|
authorId = extra.authorId
|
||||||
newsFilter = if (extra.newsOnly == true) "news_only" else ""
|
|
||||||
)
|
)
|
||||||
val data = result.body()?.let {
|
val data = result.body()?.let {
|
||||||
ListContainer(
|
ListContainer(
|
||||||
|
|||||||
@@ -39,13 +39,13 @@ 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.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.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
|
||||||
@@ -71,6 +71,7 @@ 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,
|
||||||
@@ -116,6 +117,7 @@ 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")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -545,7 +547,7 @@ fun NavigationController(
|
|||||||
composable(
|
composable(
|
||||||
route = NavigationRoute.AddAgent.route,
|
route = NavigationRoute.AddAgent.route,
|
||||||
) {
|
) {
|
||||||
CreateAgentV2Screen()
|
AddAgentScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
composable(
|
composable(
|
||||||
@@ -591,6 +593,13 @@ fun NavigationController(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
composable(route = NavigationRoute.NotificationScreen.route) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalAnimatedContentScope provides this,
|
||||||
|
) {
|
||||||
|
NotificationScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,6 +625,7 @@ fun Navigation(
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = startDestination
|
startDestination = startDestination
|
||||||
)
|
)
|
||||||
|
AgentCreatedSuccessIndicator()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
package com.aiosman.ravenow.ui.account
|
package com.aiosman.ravenow.ui.account
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.Uri
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@@ -47,12 +50,24 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.draw.shadow
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.ConstVars
|
||||||
|
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 编辑用户资料界面
|
* 编辑用户资料界面
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun AccountEditScreen2() {
|
fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,) {
|
||||||
val model = AccountEditViewModel
|
val model = AccountEditViewModel
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
@@ -61,6 +76,19 @@ fun AccountEditScreen2() {
|
|||||||
|
|
||||||
// 防抖导航器
|
// 防抖导航器
|
||||||
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", "")
|
||||||
@@ -152,7 +180,60 @@ fun AccountEditScreen2() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(44.dp))
|
|
||||||
|
|
||||||
|
// 添加横幅图片区域
|
||||||
|
val banner = model.profile?.banner
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(300.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp))
|
||||||
|
) {
|
||||||
|
if (banner != null) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
context = LocalContext.current,
|
||||||
|
imageUrl = banner,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
contentDescription = "Banner",
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(Color.Gray.copy(alpha = 0.1f))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(120.dp)
|
||||||
|
.height(42.dp)
|
||||||
|
.align(Alignment.BottomEnd)
|
||||||
|
.padding(end = 12.dp, bottom = 12.dp)
|
||||||
|
.background(
|
||||||
|
color = Color.Black.copy(alpha = 0.4f),
|
||||||
|
shape = RoundedCornerShape(9.dp)
|
||||||
|
)
|
||||||
|
.noRippleClickable {
|
||||||
|
Intent(Intent.ACTION_PICK).apply {
|
||||||
|
type = "image/*"
|
||||||
|
pickBannerImageLauncher.launch(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
){
|
||||||
|
Text(
|
||||||
|
text = "change",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.W600,
|
||||||
|
color = Color.White,
|
||||||
|
modifier = Modifier.align(Alignment.Center)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
// 显示内容或加载状态
|
// 显示内容或加载状态
|
||||||
Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}")
|
Log.d("AccountEditScreen2", "UI状态 - profile: ${model.profile?.nickName}, isLoading: ${model.isLoading}")
|
||||||
@@ -180,7 +261,15 @@ fun AccountEditScreen2() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(32.dp)
|
.size(32.dp)
|
||||||
.clip(CircleShape)
|
.clip(CircleShape)
|
||||||
.background(appColors.main)
|
.background(
|
||||||
|
brush = Brush.linearGradient(
|
||||||
|
colors = listOf(
|
||||||
|
Color(0xFF7c45ed),
|
||||||
|
Color(0x997c68ef),
|
||||||
|
Color(0xFF7bd8f8)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
.align(Alignment.BottomEnd)
|
.align(Alignment.BottomEnd)
|
||||||
.debouncedClickable(
|
.debouncedClickable(
|
||||||
debounceTime = 800L
|
debounceTime = 800L
|
||||||
@@ -198,7 +287,7 @@ fun AccountEditScreen2() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(58.dp))
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
|
|||||||
@@ -29,10 +29,12 @@ 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
|
||||||
@@ -61,7 +63,28 @@ 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
|
||||||
/**
|
/**
|
||||||
* 添加智能体界面
|
* 添加智能体界面
|
||||||
*/
|
*/
|
||||||
@@ -73,8 +96,12 @@ 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()
|
||||||
@@ -88,15 +115,37 @@ fun AddAgentScreen() {
|
|||||||
fun onDescChange(value: String) {
|
fun onDescChange(value: String) {
|
||||||
model.desc = value.trim()
|
model.desc = value.trim()
|
||||||
agnetDescError = when {
|
agnetDescError = when {
|
||||||
value.length > 100 -> "简介长度不能大于100"
|
value.length > 512 -> "简介长度不能大于512"
|
||||||
|
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 {
|
||||||
@@ -110,7 +159,22 @@ 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
|
||||||
@@ -118,9 +182,12 @@ fun AddAgentScreen() {
|
|||||||
.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 = 24.dp, vertical = 16.dp)
|
modifier = Modifier.padding(horizontal = 14.dp, vertical = 16.dp)
|
||||||
.background(color = appColors.decentBackground)
|
.background(color = appColors.decentBackground)
|
||||||
) {
|
) {
|
||||||
// 自定义header,控制返回按钮行为
|
// 自定义header,控制返回按钮行为
|
||||||
@@ -148,80 +215,298 @@ 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
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.fillMaxWidth()
|
||||||
.noRippleClickable {
|
.height(50.dp)
|
||||||
// 提交创建智能体的逻辑可以在这里实现
|
.padding(horizontal = 20.dp),
|
||||||
},
|
|
||||||
imageVector = Icons.Default.Check,
|
|
||||||
contentDescription = "Add",
|
|
||||||
tint = appColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(44.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.size(88.dp),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
Image(
|
||||||
context,
|
painter = painterResource(id = R.mipmap.group_copy),
|
||||||
model.croppedBitmap,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(88.dp)
|
|
||||||
.clip(
|
|
||||||
RoundedCornerShape(88.dp)
|
|
||||||
),
|
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
contentScale = ContentScale.Crop,
|
modifier = Modifier
|
||||||
placeholderRes = R.mipmap.rider_pro_agent_avatar
|
.size(48.dp)
|
||||||
|
.clip(
|
||||||
|
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(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(32.dp)
|
.fillMaxWidth()
|
||||||
.clip(CircleShape)
|
.height(95.dp)
|
||||||
.background(appColors.main)
|
.shadow(
|
||||||
.align(Alignment.BottomEnd)
|
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 {
|
.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(
|
||||||
|
color = LocalAppTheme.current.text,
|
||||||
|
fontSize = 16.sp
|
||||||
|
),
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedIndicatorColor = Color.Transparent,
|
||||||
|
unfocusedIndicatorColor = Color.Transparent,
|
||||||
|
disabledIndicatorColor = Color.Transparent,
|
||||||
|
focusedContainerColor = appColors.inputBackground2,
|
||||||
|
unfocusedContainerColor = appColors.inputBackground2,
|
||||||
|
),
|
||||||
|
shape = RoundedCornerShape(10.dp),
|
||||||
|
supportingText = null,
|
||||||
|
trailingIcon = null,
|
||||||
|
leadingIcon = null
|
||||||
|
)
|
||||||
|
|
||||||
|
Row(
|
||||||
|
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
|
model.isSelectingAvatar = true
|
||||||
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
navController.navigate(NavigationRoute.AgentImageCrop.route)
|
||||||
},
|
},
|
||||||
contentAlignment = Alignment.Center
|
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(
|
Icon(
|
||||||
Icons.Default.Add,
|
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
||||||
contentDescription = "Add",
|
contentDescription = "Edit",
|
||||||
tint = Color.White,
|
tint = Color.White,
|
||||||
|
modifier = Modifier.size(20.dp),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(58.dp))
|
Spacer(modifier = Modifier.height(18.dp))
|
||||||
Column(
|
// 原版两个输入框
|
||||||
modifier = Modifier
|
Text(
|
||||||
.padding(horizontal = 16.dp)
|
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,
|
||||||
label = stringResource(R.string.agent_name),
|
hint = stringResource(R.string.agent_name_hint_1),
|
||||||
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)
|
||||||
}
|
}
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
Text(
|
||||||
|
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(),
|
||||||
@@ -229,38 +514,243 @@ fun AddAgentScreen() {
|
|||||||
onDescChange(value)
|
onDescChange(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(58.dp))
|
} 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))
|
||||||
|
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.showManualCreation = showManualCreation
|
||||||
|
model.showManualCreationForm = showManualCreationForm
|
||||||
|
model.isCreatingAgent = isCreatingAgent
|
||||||
|
model.showWaveAnimation = showWaveAnimation
|
||||||
|
model.isAutoModeManualForm = false // 标记为手动模式下的手动表单
|
||||||
|
// 设置正在选择头像的标志
|
||||||
|
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))
|
||||||
|
// 原版两个输入框
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.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(
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//手动创造AI界面
|
||||||
|
}
|
||||||
|
|
||||||
// 错误信息显示
|
// 错误信息显示
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
Box(modifier = Modifier.fillMaxWidth()) {
|
||||||
errorMessage?.let { error ->
|
errorMessage?.let { error ->
|
||||||
Text(
|
Text(
|
||||||
text = error,
|
text = error,
|
||||||
color = Color.Red,
|
color = Color.Red,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier
|
||||||
|
.padding(bottom = 20.dp)
|
||||||
|
.align(Alignment.Center),
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
ActionButton(
|
ActionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(345.dp)
|
.width(345.dp)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(bottom = 40.dp)
|
||||||
.background(
|
.background(
|
||||||
brush = Brush.linearGradient(
|
brush = Brush.linearGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color(0xFFEE2A33),
|
Color(0x777c45ed),
|
||||||
Color(0xFFD80264),
|
Color(0x777c68ef),
|
||||||
Color(0xFF8468BC)
|
Color(0x557bd8f8)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
shape = RoundedCornerShape(24.dp)
|
shape = RoundedCornerShape(24.dp)
|
||||||
),
|
),
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
backgroundColor = Color.Transparent,
|
backgroundColor = Color.Transparent,
|
||||||
text = stringResource(R.string.agent_create),
|
text = stringResource(R.string.create_confirm),
|
||||||
isLoading = model.isUpdating,
|
isLoading = model.isUpdating,
|
||||||
loadingText = stringResource(R.string.agent_createing),
|
|
||||||
enabled = !model.isUpdating && validate()
|
enabled = !model.isUpdating && validate()
|
||||||
) {
|
) {
|
||||||
// 验证输入
|
// 验证输入
|
||||||
@@ -268,6 +758,10 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -282,11 +776,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,11 +24,12 @@ 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 hasExitedPage by mutableStateOf(false) // 标记是否已经完全退出页面
|
var showManualCreationForm by mutableStateOf(false)
|
||||||
|
var isCreatingAgent by mutableStateOf(false)
|
||||||
// 保存AI生成的输入文本,避免页面重建时丢失
|
var showWaveAnimation by mutableStateOf(false)
|
||||||
var generateInputText by mutableStateOf("")
|
var showManualCreation by mutableStateOf(false)
|
||||||
|
// 添加一个标志来区分两种手动表单状态
|
||||||
|
var isAutoModeManualForm by mutableStateOf(false)
|
||||||
suspend fun updateAgentAvatar(context: Context) {
|
suspend fun updateAgentAvatar(context: Context) {
|
||||||
croppedBitmap?.let {
|
croppedBitmap?.let {
|
||||||
val file = File(context.cacheDir, "agent_avatar.jpg")
|
val file = File(context.cacheDir, "agent_avatar.jpg")
|
||||||
@@ -74,7 +75,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 > 100 -> "智能体描述长度不能超过100个字符"
|
desc.length > 512 -> "智能体描述长度不能超过512个字符"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -88,7 +89,10 @@ object AddAgentViewModel : ViewModel() {
|
|||||||
croppedBitmap = null
|
croppedBitmap = null
|
||||||
isUpdating = false
|
isUpdating = false
|
||||||
isSelectingAvatar = false
|
isSelectingAvatar = false
|
||||||
hasExitedPage = false
|
showManualCreationForm = false
|
||||||
generateInputText = ""
|
isCreatingAgent = false
|
||||||
|
showWaveAnimation = false
|
||||||
|
showManualCreation = false
|
||||||
|
isAutoModeManualForm = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,648 +0,0 @@
|
|||||||
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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,238 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -283,7 +283,7 @@ fun ChatAiScreen(userId: String) {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(AppColors.decentBackground)
|
.background(Color.White)
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -47,7 +47,8 @@ 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>(
|
||||||
@@ -71,14 +72,47 @@ fun CommentNoticeScreen() {
|
|||||||
modifier = Modifier.fillMaxSize().background(color = AppColors.background)
|
modifier = Modifier.fillMaxSize().background(color = AppColors.background)
|
||||||
) {
|
) {
|
||||||
StatusBarSpacer()
|
StatusBarSpacer()
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
|
||||||
|
if (!isNetworkAvailable) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
.padding(16.dp)
|
.padding(top = 149.dp),
|
||||||
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
NoticeScreenHeader(stringResource(R.string.comment), moreIcon = false)
|
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)
|
||||||
}
|
}
|
||||||
if (comments.itemCount == 0 && comments.loadState.refresh is LoadState.NotLoading) {
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (comments.itemCount == 0 && comments.loadState.refresh is LoadState.NotLoading) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -92,7 +126,7 @@ fun CommentNoticeScreen() {
|
|||||||
androidx.compose.foundation.Image(
|
androidx.compose.foundation.Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.qst_pl_qs_as_img
|
id =if(AppState.darkMode) R.mipmap.qst_pl_qs_as_img
|
||||||
else R.mipmap.qst_pl_qs_img),
|
else R.mipmap.invalid_name_11),
|
||||||
contentDescription = "No Comment",
|
contentDescription = "No Comment",
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -69,72 +69,22 @@ 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
|
||||||
) {
|
) {
|
||||||
Box(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.clip(RoundedCornerShape(20.dp))
|
.clip(RoundedCornerShape(20.dp))
|
||||||
.background(Color.White)
|
.background(Color.Gray.copy(alpha = 0.1f))
|
||||||
.border(1.dp, Color.Black, RoundedCornerShape(20.dp))
|
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.Top
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
value = text,
|
value = text,
|
||||||
@@ -149,30 +99,39 @@ fun EditCommentBottomModal(
|
|||||||
color = Color.Black,
|
color = Color.Black,
|
||||||
fontWeight = FontWeight.Normal
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
minLines = 1
|
decorationBox = { innerTextField ->
|
||||||
|
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 ->
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
Icon(
|
Icon(
|
||||||
painter = painterResource(id = R.mipmap.rider_pro_moment_post),
|
painter = painterResource(id = R.mipmap.btn),
|
||||||
contentDescription = "Send",
|
contentDescription = "Send",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(20.dp)
|
.size(40.dp)
|
||||||
.align(Alignment.Top)
|
.padding(top = 13.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
if (text.isNotEmpty()) {
|
if (text.isNotEmpty()) {
|
||||||
onSend(text)
|
onSend(text)
|
||||||
text = ""
|
text = ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tint = if (isNotEmpty) Color.Unspecified else AppColors.nonActive
|
tint = Color.Unspecified
|
||||||
)
|
)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(navBarHeight))
|
Spacer(modifier = Modifier.height(navBarHeight))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,16 +92,22 @@ 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()
|
||||||
|
if (currentTime - lastClickTime.value > clickDelay) {
|
||||||
|
lastClickTime.value = currentTime
|
||||||
navController.navigateToPost(
|
navController.navigateToPost(
|
||||||
momentEntity.id,
|
momentEntity.id,
|
||||||
highlightCommentId = 0,
|
highlightCommentId = 0,
|
||||||
initImagePagerIndex = imageIndex
|
initImagePagerIndex = imageIndex
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
) {
|
) {
|
||||||
MomentContentGroup(
|
MomentContentGroup(
|
||||||
momentEntity = momentEntity,
|
momentEntity = momentEntity,
|
||||||
@@ -213,7 +219,6 @@ fun MomentPostLocation(location: String) {
|
|||||||
text = location,
|
text = location,
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,6 +243,8 @@ fun MomentTopRowGroup(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
val lastClickTime = remember { mutableStateOf(0L) }
|
||||||
|
val clickDelay = 500L
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
context,
|
context,
|
||||||
momentEntity.avatar,
|
momentEntity.avatar,
|
||||||
@@ -246,12 +253,16 @@ fun MomentTopRowGroup(
|
|||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
.clip(RoundedCornerShape(40.dp))
|
.clip(RoundedCornerShape(40.dp))
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastClickTime.value > clickDelay) {
|
||||||
|
lastClickTime.value = currentTime
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
NavigationRoute.AccountProfile.route.replace(
|
NavigationRoute.AccountProfile.route.replace(
|
||||||
"{id}",
|
"{id}",
|
||||||
momentEntity.authorId.toString()
|
momentEntity.authorId.toString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
@@ -267,7 +278,19 @@ 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))
|
||||||
@@ -416,6 +439,8 @@ 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(
|
||||||
@@ -451,58 +476,14 @@ fun MomentBottomOperateRowGroup(
|
|||||||
.height(56.dp)
|
.height(56.dp)
|
||||||
.padding(start = 16.dp, end = 0.dp)
|
.padding(start = 16.dp, end = 0.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Column(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
|
|
||||||
) {
|
) {
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxHeight(),
|
|
||||||
contentAlignment = Alignment.Center
|
|
||||||
) {
|
|
||||||
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
|
||||||
AnimatedLikeIcon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
liked = momentEntity.liked
|
|
||||||
) {
|
|
||||||
onLikeClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight()
|
|
||||||
,
|
|
||||||
contentAlignment = Alignment.CenterEnd
|
|
||||||
) {
|
|
||||||
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
|
||||||
AnimatedFavouriteIcon(
|
|
||||||
modifier = Modifier.size(24.dp),
|
|
||||||
isFavourite = momentEntity.isFavorite
|
|
||||||
) {
|
|
||||||
onFavoriteClick()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (momentEntity.images.size > 1) {
|
if (momentEntity.images.size > 1) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
@@ -523,8 +504,68 @@ fun MomentBottomOperateRowGroup(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterStart
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(count = momentEntity.favoriteCount.toString()) {
|
||||||
|
AnimatedFavouriteIcon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
isFavourite = momentEntity.isFavorite
|
||||||
|
) {
|
||||||
|
onFavoriteClick()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.fillMaxHeight()
|
||||||
|
.noRippleClickable {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastClickTime.value > clickDelay) {
|
||||||
|
lastClickTime.value = currentTime
|
||||||
|
onCommentClick()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(
|
||||||
|
icon = R.drawable.rider_pro_comment,
|
||||||
|
count = momentEntity.commentCount.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.wrapContentWidth()
|
||||||
|
.fillMaxHeight(),
|
||||||
|
contentAlignment = Alignment.CenterEnd
|
||||||
|
) {
|
||||||
|
MomentOperateBtn(count = momentEntity.likeCount.toString()) {
|
||||||
|
AnimatedLikeIcon(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
liked = momentEntity.liked
|
||||||
|
) {
|
||||||
|
onLikeClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,22 @@ 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
|
||||||
@@ -32,6 +38,7 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 水平布局的输入框
|
* 水平布局的输入框
|
||||||
@@ -47,12 +54,15 @@ 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(16.dp))
|
.clip(RoundedCornerShape(25.dp))
|
||||||
.background(background ?: AppColors.inputBackground)
|
.background(background ?: AppColors.inputBackground)
|
||||||
.let {
|
.let {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
@@ -61,7 +71,11 @@ 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 {
|
||||||
@@ -79,13 +93,27 @@ fun FormTextInput(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(start = 16.dp)
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.align(Alignment.TopStart),
|
||||||
|
tint = AppColors.text.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = hint ?: "",
|
text = hint ?: "",
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
color = AppColors.inputHint
|
color = AppColors.inputHint
|
||||||
)
|
)
|
||||||
@@ -98,15 +126,18 @@ fun FormTextInput(
|
|||||||
onValueChange = {
|
onValueChange = {
|
||||||
onValueChange(it)
|
onValueChange(it)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester),
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
),
|
),
|
||||||
cursorBrush = SolidColor(AppColors.text),
|
cursorBrush = SolidColor(AppColors.text),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -15,16 +15,22 @@ 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
|
||||||
@@ -32,6 +38,7 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 垂直布局的输入框
|
* 垂直布局的输入框
|
||||||
@@ -44,15 +51,19 @@ 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(16.dp))
|
.clip(RoundedCornerShape(25.dp))
|
||||||
.background(background ?: AppColors.inputBackground)
|
.background(background ?: AppColors.inputBackground)
|
||||||
.let {
|
.let {
|
||||||
if (error != null) {
|
if (error != null) {
|
||||||
@@ -61,7 +72,11 @@ fun FormTextInput2(
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(17.dp),
|
.padding(17.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
localFocusRequester.requestFocus()
|
||||||
|
keyboardController?.show()
|
||||||
|
},
|
||||||
|
|
||||||
) {
|
) {
|
||||||
label?.let {
|
label?.let {
|
||||||
@@ -79,13 +94,27 @@ fun FormTextInput2(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(top = 8.dp)
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.mipmap.icons_infor_edit),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(16.dp)
|
||||||
|
.align(Alignment.TopStart),
|
||||||
|
tint = AppColors.text.copy(alpha = 0.4f)
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Spacer(modifier = Modifier.width(24.dp))
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
) {
|
) {
|
||||||
if (value.isEmpty()) {
|
if (value.isEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = hint ?: "",
|
text = hint ?: "",
|
||||||
style = TextStyle(
|
style = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
color = AppColors.inputHint
|
color = AppColors.inputHint
|
||||||
)
|
)
|
||||||
@@ -93,20 +122,23 @@ fun FormTextInput2(
|
|||||||
}
|
}
|
||||||
|
|
||||||
BasicTextField(
|
BasicTextField(
|
||||||
maxLines = 5,
|
maxLines = 6,
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
onValueChange(it)
|
onValueChange(it)
|
||||||
},
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(localFocusRequester),
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.Normal,
|
fontWeight = FontWeight.Normal,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
lineHeight = 20.sp
|
lineHeight = 20.sp
|
||||||
),
|
),
|
||||||
cursorBrush = SolidColor(AppColors.text),
|
cursorBrush = SolidColor(AppColors.text),
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,18 +4,23 @@ 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
|
||||||
@@ -24,7 +29,9 @@ 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
|
||||||
@@ -35,6 +42,8 @@ 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
|
||||||
@@ -71,6 +80,75 @@ fun FavouriteListPage() {
|
|||||||
) {
|
) {
|
||||||
NoticeScreenHeader(stringResource(R.string.favourites_upper), moreIcon = false)
|
NoticeScreenHeader(stringResource(R.string.favourites_upper), moreIcon = false)
|
||||||
}
|
}
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
var moments = dataFlow.collectAsLazyPagingItems()
|
||||||
|
|
||||||
|
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))
|
||||||
|
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(
|
||||||
|
painter = painterResource(
|
||||||
|
id = if (com.aiosman.ravenow.AppState.darkMode) R.mipmap.syss_yh_qs_as_img
|
||||||
|
else R.mipmap.invalid_name_1),
|
||||||
|
contentDescription = "No favourites",
|
||||||
|
modifier = Modifier.size(110.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.size(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.favourites_null),
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}else{
|
||||||
LazyVerticalGrid(
|
LazyVerticalGrid(
|
||||||
columns = GridCells.Fixed(3),
|
columns = GridCells.Fixed(3),
|
||||||
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)
|
modifier = Modifier.fillMaxSize().padding(horizontal = 16.dp)
|
||||||
@@ -111,15 +189,11 @@ fun FavouriteListPage() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
FavouriteListViewModel.isLoading,
|
FavouriteListViewModel.isLoading,
|
||||||
state,
|
state,
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ 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
|
||||||
@@ -19,6 +20,7 @@ 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
|
||||||
@@ -37,6 +39,8 @@ 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
|
||||||
@@ -67,7 +71,47 @@ fun FollowerListScreen(userId: Int) {
|
|||||||
) {
|
) {
|
||||||
NoticeScreenHeader(stringResource(R.string.followers_upper), moreIcon = false)
|
NoticeScreenHeader(stringResource(R.string.followers_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.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()
|
||||||
@@ -81,7 +125,7 @@ fun FollowerListScreen(userId: Int) {
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
||||||
else R.mipmap.qst_fs_qs_img),
|
else R.mipmap.invalid_name_8),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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
|
||||||
@@ -37,7 +38,9 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 关注消息列表
|
* 关注消息列表
|
||||||
@@ -54,19 +57,51 @@ 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()
|
||||||
}
|
}
|
||||||
if (followers.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.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()
|
||||||
@@ -80,7 +115,7 @@ fun FollowerNoticeScreen() {
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
id =if(AppState.darkMode) R.mipmap.qst_fs_qs_as_img
|
||||||
else R.mipmap.qst_fs_qs_img),
|
else R.mipmap.invalid_name_8),
|
||||||
contentDescription = "No Followers",
|
contentDescription = "No Followers",
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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
|
||||||
@@ -37,7 +38,9 @@ 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
|
||||||
@@ -69,7 +72,48 @@ 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()
|
||||||
@@ -83,7 +127,7 @@ fun FollowingListScreen(userId: Int) {
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img_my
|
id =if(AppState.darkMode) R.mipmap.qst_gz_qs_as_img_my
|
||||||
else R.mipmap.qst_gz_qs_img_my),
|
else R.mipmap.invalid_name_9),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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,42 +58,59 @@ 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(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.h_cj_rw_icon),
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(start = 16.dp),
|
||||||
|
colorFilter = androidx.compose.ui.graphics.ColorFilter.tint(appColors.text)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.create_title),
|
text = stringResource(R.string.create_title),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = appColors.text,
|
color = appColors.text,
|
||||||
modifier = Modifier.padding(bottom = 32.dp)
|
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.drawable.ic_create_ai,
|
icon = R.mipmap.icons_circle_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))
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ fun IndexScreen() {
|
|||||||
Text(
|
Text(
|
||||||
text = it.label(),
|
text = it.label(),
|
||||||
fontSize = 10.sp,
|
fontSize = 10.sp,
|
||||||
color = if (isSelected) AppColors.brandColorsColor else AppColors.text,
|
color = if (isSelected) Color.Blue else AppColors.text,
|
||||||
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
fontWeight = if (isSelected) FontWeight.W600 else FontWeight.Normal
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ sealed class NavigationItem(
|
|||||||
|
|
||||||
data object Home : NavigationItem("Home",
|
data object Home : NavigationItem("Home",
|
||||||
icon = { R.drawable.rider_pro_nav_home },
|
icon = { R.drawable.rider_pro_nav_home },
|
||||||
selectedIcon = { R.mipmap.rider_pro_nav_home_hl },
|
selectedIcon = { R.mipmap.bars_x_buttons_home_s },
|
||||||
label = { stringResource(R.string.main_home) }
|
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.dynamic_hl },
|
selectedIcon = { R.mipmap.bars_x_buttons_discover_fill },
|
||||||
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.rider_pro_nav_message_hl },
|
selectedIcon = { R.mipmap.bars_x_buttons_chat_s },
|
||||||
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.rider_pro_nav_profile_hl },
|
selectedIcon = { R.mipmap.bars_x_buttons_user_s },
|
||||||
label = { stringResource(R.string.main_profile) }
|
label = { stringResource(R.string.main_profile) }
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
@@ -36,6 +37,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
@@ -47,6 +49,10 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.window.Dialog
|
||||||
|
import androidx.compose.ui.window.DialogProperties
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import com.aiosman.ravenow.AppStore
|
import com.aiosman.ravenow.AppStore
|
||||||
@@ -69,11 +75,20 @@ import com.aiosman.ravenow.utils.DebounceUtils
|
|||||||
import com.aiosman.ravenow.utils.ResourceCleanupManager
|
import com.aiosman.ravenow.utils.ResourceCleanupManager
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
import androidx.compose.foundation.lazy.LazyRow
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.ui.zIndex
|
||||||
|
|
||||||
|
// 检测是否接近列表底部的扩展函数
|
||||||
|
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
|
||||||
|
val layoutInfo = this.layoutInfo
|
||||||
|
val totalItemsCount = layoutInfo.totalItemsCount
|
||||||
|
val lastVisibleItemIndex = layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||||
|
|
||||||
|
return lastVisibleItemIndex >= (totalItemsCount - buffer)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -89,13 +104,25 @@ fun Agent() {
|
|||||||
var scope = rememberCoroutineScope()
|
var scope = rememberCoroutineScope()
|
||||||
|
|
||||||
val viewModel: AgentViewModel = viewModel()
|
val viewModel: AgentViewModel = viewModel()
|
||||||
val scrollState = rememberScrollState()
|
|
||||||
|
|
||||||
// 确保推荐Agent数据已加载
|
// 确保推荐Agent数据已加载
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
viewModel.ensureDataLoaded()
|
viewModel.ensureDataLoaded()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 列表滚动状态(用于吸顶与加载更多)
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
|
||||||
|
// 监听滚动到底部,加载更多网格数据
|
||||||
|
LaunchedEffect(listState) {
|
||||||
|
snapshotFlow { listState.isScrolledToEnd() }
|
||||||
|
.collect { atEnd ->
|
||||||
|
if (atEnd && !viewModel.isLoading) {
|
||||||
|
viewModel.loadMoreGridAgentData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 防抖状态
|
// 防抖状态
|
||||||
var lastClickTime by remember { mutableStateOf(0L) }
|
var lastClickTime by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
@@ -107,27 +134,25 @@ fun Agent() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Box(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
// 固定顶部搜索条
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
.verticalScroll(scrollState)
|
.background(AppColors.background)
|
||||||
.padding(
|
.zIndex(999.0f)
|
||||||
top = statusBarPaddingValues.calculateTopPadding(),
|
.height(44.dp + statusBarPaddingValues.calculateTopPadding())
|
||||||
bottom = navigationBarPaddings,
|
|
||||||
start = 16.dp,
|
|
||||||
end = 16.dp
|
|
||||||
),
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.wrapContentHeight()
|
.fillMaxWidth()
|
||||||
.height(44.dp)
|
.fillMaxHeight().padding(top = 32.dp, start = 16.dp, end = 16.dp),
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.Text(
|
Text(
|
||||||
text = "Rave AI",
|
text = "Rave AI",
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
fontWeight = FontWeight.W900,
|
fontWeight = FontWeight.W900,
|
||||||
@@ -149,89 +174,30 @@ fun Agent() {
|
|||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(15.dp))
|
}
|
||||||
// // 搜索框
|
|
||||||
// Row(
|
|
||||||
// modifier = Modifier
|
|
||||||
// .height(36.dp)
|
|
||||||
// .weight(1f)
|
|
||||||
// .clip(shape = RoundedCornerShape(8.dp))
|
|
||||||
// .background(AppColors.inputBackground)
|
|
||||||
// .padding(horizontal = 8.dp, vertical = 0.dp)
|
|
||||||
// .noRippleClickable {
|
|
||||||
// // 搜索框点击事件
|
|
||||||
// },
|
|
||||||
// verticalAlignment = Alignment.CenterVertically
|
|
||||||
// ) {
|
|
||||||
// Icon(
|
|
||||||
// painter = painterResource(id = R.drawable.rider_pro_nav_search),
|
|
||||||
// contentDescription = null,
|
|
||||||
// tint = AppColors.inputHint
|
|
||||||
// )
|
|
||||||
// Box {
|
|
||||||
// Text(
|
|
||||||
// text = stringResource(R.string.search),
|
|
||||||
// modifier = Modifier.padding(start = 8.dp),
|
|
||||||
// color = AppColors.inputHint,
|
|
||||||
// fontSize = 17.sp
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
// // 创建智能体
|
|
||||||
// Icon(
|
|
||||||
// modifier = Modifier
|
|
||||||
// .size(36.dp)
|
|
||||||
// .noRippleClickable {
|
|
||||||
// if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
|
||||||
// // 检查游客模式,如果是游客则跳转登录
|
|
||||||
// if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.CREATE_AGENT)) {
|
|
||||||
// navController.navigate(NavigationRoute.Login.route)
|
|
||||||
// } else {
|
|
||||||
// // 导航到添加智能体页面
|
|
||||||
// navController.navigate(
|
|
||||||
// NavigationRoute.AddAgent.route
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }) {
|
|
||||||
// lastClickTime = System.currentTimeMillis()
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
// painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
|
|
||||||
// contentDescription = null,
|
|
||||||
// tint = AppColors.text
|
|
||||||
// )
|
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(
|
||||||
|
top = 44.dp + statusBarPaddingValues.calculateTopPadding() + 15.dp,
|
||||||
|
bottom = navigationBarPaddings,
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
|
||||||
|
// 动态标签页
|
||||||
|
stickyHeader(key = "category_tabs") {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(vertical = 8.dp)
|
.background(AppColors.background)
|
||||||
|
.padding(top = 4.dp, bottom = 8.dp)
|
||||||
) {
|
) {
|
||||||
|
val selectedTabIndex = viewModel.selectedCategoryIndex
|
||||||
// // 标题
|
if (viewModel.categories.isNotEmpty()) {
|
||||||
// Row(
|
|
||||||
// verticalAlignment = Alignment.CenterVertically,
|
|
||||||
// modifier = Modifier.padding(bottom = 12.dp)
|
|
||||||
// ) {
|
|
||||||
// Image(
|
|
||||||
// painter = painterResource(R.mipmap.rider_pro_agent2),
|
|
||||||
// contentDescription = "agent",
|
|
||||||
// modifier = Modifier.size(28.dp),
|
|
||||||
//
|
|
||||||
// )
|
|
||||||
// Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
// androidx.compose.material3.Text(
|
|
||||||
// text = stringResource(R.string.agent_recommend_agent),
|
|
||||||
// fontSize = 16.sp,
|
|
||||||
// fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
|
||||||
// color = AppColors.text
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
|
|
||||||
var selectedTabIndex by remember { mutableStateOf(0) }
|
|
||||||
|
|
||||||
// 标签页
|
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -240,97 +206,96 @@ fun Agent() {
|
|||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.CenterVertically
|
||||||
) {
|
) {
|
||||||
|
viewModel.categories.forEachIndexed { index, category ->
|
||||||
item {
|
item {
|
||||||
CustomTabItem(
|
CustomTabItem(
|
||||||
text = stringResource(R.string.agent_recommend),
|
text = category.getLocalizedName(),
|
||||||
isSelected = selectedTabIndex == 0,
|
isSelected = selectedTabIndex == index,
|
||||||
onClick = {
|
onClick = { viewModel.selectCategory(index) }
|
||||||
selectedTabIndex = 0
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (index < viewModel.categories.size - 1) {
|
||||||
|
item { TabSpacer() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 推荐内容区域
|
||||||
item {
|
item {
|
||||||
TabSpacer()
|
Column(
|
||||||
}
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
item {
|
.padding(vertical = 8.dp)
|
||||||
CustomTabItem(
|
) {
|
||||||
text = "scenes",
|
AgentViewPagerSection(agentItems = viewModel.topAgentItems, viewModel)
|
||||||
isSelected = selectedTabIndex == 1,
|
|
||||||
onClick = {
|
|
||||||
selectedTabIndex = 1
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
TabSpacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
CustomTabItem(
|
|
||||||
text = "voices",
|
|
||||||
isSelected = selectedTabIndex == 2,
|
|
||||||
onClick = {
|
|
||||||
selectedTabIndex = 2
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
TabSpacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
CustomTabItem(
|
|
||||||
text = "anime",
|
|
||||||
isSelected = selectedTabIndex == 3,
|
|
||||||
onClick = {
|
|
||||||
selectedTabIndex = 3
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
TabSpacer()
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
|
||||||
CustomTabItem(
|
|
||||||
text = "assist",
|
|
||||||
isSelected = selectedTabIndex == 4,
|
|
||||||
onClick = {
|
|
||||||
selectedTabIndex = 4
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (selectedTabIndex) {
|
|
||||||
0 -> {
|
|
||||||
AgentViewPagerSection(agentItems = viewModel.agentItems.take(15), viewModel)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
val shuffledAgents = viewModel.agentItems.shuffled().take(15)
|
|
||||||
AgentViewPagerSection(agentItems = shuffledAgents, viewModel)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 热门聊天室
|
||||||
|
stickyHeader(key = "hot_rooms_header") {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.wrapContentHeight(),
|
.background(AppColors.background)
|
||||||
// center the tabs
|
.padding(top = 8.dp, bottom = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.Start,
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.rider_pro_hot_room),
|
||||||
|
contentDescription = "chat room",
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
androidx.compose.material3.Text(
|
||||||
|
text = stringResource(R.string.hot_rooms),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 热门聊天室网格
|
||||||
|
items(viewModel.chatRooms.chunked(2)) { rowRooms ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
rowRooms.forEach { chatRoom ->
|
||||||
|
ChatRoomCard(
|
||||||
|
chatRoom = chatRoom,
|
||||||
|
navController = LocalNavController.current,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (rowRooms.size == 1) {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||||
|
|
||||||
|
// 发现区域
|
||||||
|
stickyHeader(key = "discover_more") {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(AppColors.background)
|
||||||
|
.padding(top = 8.dp, bottom = 12.dp),
|
||||||
horizontalArrangement = Arrangement.Start,
|
horizontalArrangement = Arrangement.Start,
|
||||||
verticalAlignment = Alignment.Bottom
|
verticalAlignment = Alignment.Bottom
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(R.mipmap.rider_pro_agent2),
|
painter = painterResource(R.mipmap.bars_x_buttons_home_n_copy_2),
|
||||||
contentDescription = "agent",
|
contentDescription = "agent",
|
||||||
modifier = Modifier.size(28.dp),
|
modifier = Modifier.size(28.dp)
|
||||||
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
@@ -339,35 +304,127 @@ fun Agent() {
|
|||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||||
color = AppColors.text
|
color = AppColors.text
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(50.dp))
|
}
|
||||||
Column(
|
item { Spacer(modifier = Modifier.height(20.dp)) }
|
||||||
|
|
||||||
|
// Agent 两列网格行
|
||||||
|
items(viewModel.gridAgentItems.chunked(2)) { rowItems ->
|
||||||
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.padding(
|
||||||
|
top = 20.dp,
|
||||||
|
bottom = 20.dp
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
) {
|
) {
|
||||||
val agentItems = viewModel.agentItems.take(15)
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
LazyVerticalGrid(
|
|
||||||
columns = GridCells.Fixed(2),
|
|
||||||
modifier = Modifier.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(50.dp)
|
|
||||||
) {
|
|
||||||
items(agentItems) { agentItem ->
|
|
||||||
AgentCardSquare(
|
AgentCardSquare(
|
||||||
agentItem = agentItem,
|
agentItem = rowItems[0],
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
navController = LocalNavController.current
|
navController = LocalNavController.current
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (rowItems.size > 1) {
|
||||||
|
Box(modifier = Modifier.weight(1f)) {
|
||||||
|
AgentCardSquare(
|
||||||
|
agentItem = rowItems[1],
|
||||||
|
viewModel = viewModel,
|
||||||
|
navController = LocalNavController.current
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载更多指示器
|
||||||
|
if (viewModel.isLoading) {
|
||||||
|
item {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(vertical = 24.dp),
|
||||||
|
horizontalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = AppColors.text,
|
||||||
|
strokeWidth = 2.dp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(12.dp))
|
||||||
|
androidx.compose.material3.Text(
|
||||||
|
text = stringResource(R.string.agent_chat_loading),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun AgentGridLayout(
|
||||||
|
agentItems: List<AgentItem>,
|
||||||
|
viewModel: AgentViewModel,
|
||||||
|
navController: NavHostController
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
// 将agentItems按两列分组
|
||||||
|
agentItems.chunked(2).forEachIndexed { rowIndex, rowItems ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(
|
||||||
|
top = if (rowIndex == 0) 30.dp else 20.dp, // 第一行添加更多顶部间距
|
||||||
|
bottom = 20.dp
|
||||||
|
),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
// 第一列
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
AgentCardSquare(
|
||||||
|
agentItem = rowItems[0],
|
||||||
|
viewModel = viewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 第二列(如果存在)
|
||||||
|
if (rowItems.size > 1) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
) {
|
||||||
|
AgentCardSquare(
|
||||||
|
agentItem = rowItems[1],
|
||||||
|
viewModel = viewModel,
|
||||||
|
navController = navController
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果只有一列,添加空白占位
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressLint("SuspiciousIndentation")
|
@SuppressLint("SuspiciousIndentation")
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navController: NavHostController) {
|
fun AgentCardSquare(
|
||||||
|
agentItem: AgentItem,
|
||||||
|
viewModel: AgentViewModel,
|
||||||
|
navController: NavHostController
|
||||||
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val cardHeight = 180.dp
|
val cardHeight = 180.dp
|
||||||
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
|
val avatarSize = cardHeight / 3 // 头像大小为方块高度的三分之一
|
||||||
@@ -379,7 +436,7 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(cardHeight)
|
.height(cardHeight)
|
||||||
.background(Color(0xFFE0E0E0), RoundedCornerShape(12.dp)) // 灰色背景
|
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
viewModel.goToProfile(agentItem.openId, navController)
|
viewModel.goToProfile(agentItem.openId, navController)
|
||||||
@@ -389,15 +446,19 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
},
|
},
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
// 头像,位于方块上方居中,部分悬于方块外部
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.offset(y = -avatarSize / 2)
|
.offset(y = 4.dp)
|
||||||
.size(avatarSize)
|
.size(avatarSize)
|
||||||
.background(Color.White, RoundedCornerShape(avatarSize / 2))
|
.background(AppColors.background, RoundedCornerShape(avatarSize / 2))
|
||||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.group_copy),
|
||||||
|
contentDescription = "默认头像",
|
||||||
|
modifier = Modifier.size(avatarSize),
|
||||||
|
)
|
||||||
if (agentItem.avatar.isNotEmpty()) {
|
if (agentItem.avatar.isNotEmpty()) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
imageUrl = agentItem.avatar,
|
imageUrl = agentItem.avatar,
|
||||||
@@ -407,13 +468,6 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
.clip(RoundedCornerShape(avatarSize / 2)),
|
.clip(RoundedCornerShape(avatarSize / 2)),
|
||||||
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||||
)
|
)
|
||||||
} else {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(R.mipmap.rider_pro_agent),
|
|
||||||
contentDescription = "默认头像",
|
|
||||||
modifier = Modifier.size(avatarSize / 2),
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -421,12 +475,12 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = avatarSize / 2 + 8.dp, start = 8.dp, end = 8.dp, bottom = 8.dp),
|
.padding(top = 4.dp + avatarSize + 8.dp, start = 8.dp, end = 8.dp, bottom = 48.dp), // 为底部聊天按钮留出空间
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = agentItem.title,
|
text = agentItem.title,
|
||||||
fontSize = 16.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W600,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
@@ -437,23 +491,29 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
|
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = agentItem.desc,
|
text = agentItem.desc,
|
||||||
fontSize = 14.sp,
|
fontSize = 12.sp,
|
||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
maxLines = 2,
|
maxLines = 2,
|
||||||
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
modifier = Modifier.weight(1f, fill = false)
|
modifier = Modifier.weight(1f, fill = false)
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
// 聊天按钮,固定在底部居中,距离底部有一定边距
|
||||||
|
|
||||||
// 聊天按钮,位于底部居中
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.width(80.dp)
|
.align(Alignment.BottomCenter)
|
||||||
|
.padding(bottom = 12.dp) // 距离底部的边距
|
||||||
|
.width(60.dp)
|
||||||
.height(32.dp)
|
.height(32.dp)
|
||||||
.background(
|
.background(
|
||||||
color = Color(0X147c7480),
|
color = AppColors.text,
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(
|
||||||
|
topStart = 10.dp,
|
||||||
|
topEnd = 10.dp,
|
||||||
|
bottomStart = 0.dp,
|
||||||
|
bottomEnd = 10.dp
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
@@ -475,14 +535,14 @@ fun AgentCardSquare(agentItem: AgentItem, viewModel: AgentViewModel, navControll
|
|||||||
) {
|
) {
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = stringResource(R.string.chat),
|
text = stringResource(R.string.chat),
|
||||||
fontSize = 12.sp,
|
fontSize = 15.sp,
|
||||||
color = AppColors.text,
|
color = AppColors.background,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W500
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@OptIn(ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
|
fun AgentViewPagerSection(agentItems: List<AgentItem>, viewModel: AgentViewModel) {
|
||||||
@@ -558,7 +618,13 @@ fun AgentViewPagerSection(agentItems: List<AgentItem>,viewModel: AgentViewModel)
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int, modifier: Modifier = Modifier,navController: NavHostController) {
|
fun AgentPage(
|
||||||
|
viewModel: AgentViewModel,
|
||||||
|
agentItems: List<AgentItem>,
|
||||||
|
page: Int,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
navController: NavHostController
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -566,7 +632,11 @@ fun AgentPage(viewModel: AgentViewModel,agentItems: List<AgentItem>, page: Int,
|
|||||||
) {
|
) {
|
||||||
// 显示3个agent
|
// 显示3个agent
|
||||||
agentItems.forEachIndexed { index, agentItem ->
|
agentItems.forEachIndexed { index, agentItem ->
|
||||||
AgentCard2(agentItem = agentItem, viewModel = viewModel, navController = LocalNavController.current)
|
AgentCard2(
|
||||||
|
agentItem = agentItem,
|
||||||
|
viewModel = viewModel,
|
||||||
|
navController = LocalNavController.current
|
||||||
|
)
|
||||||
if (index < agentItems.size - 1) {
|
if (index < agentItems.size - 1) {
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
}
|
}
|
||||||
@@ -592,7 +662,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(48.dp)
|
.size(48.dp)
|
||||||
.background(Color(0xFFF5F5F5), RoundedCornerShape(24.dp))
|
.background(AppColors.secondaryBackground, RoundedCornerShape(24.dp))
|
||||||
.clickable {
|
.clickable {
|
||||||
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
if (DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
viewModel.goToProfile(agentItem.openId, navController)
|
viewModel.goToProfile(agentItem.openId, navController)
|
||||||
@@ -656,7 +726,7 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(width = 60.dp, height = 32.dp)
|
.size(width = 60.dp, height = 32.dp)
|
||||||
.background(
|
.background(
|
||||||
color = Color(0X147c7480),
|
color = AppColors.inputBackground,
|
||||||
shape = RoundedCornerShape(8.dp)
|
shape = RoundedCornerShape(8.dp)
|
||||||
)
|
)
|
||||||
.clickable {
|
.clickable {
|
||||||
@@ -686,3 +756,180 @@ fun AgentCard2(viewModel: AgentViewModel,agentItem: AgentItem,navController: Nav
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatRoomsSection(
|
||||||
|
chatRooms: List<ChatRoom>,
|
||||||
|
navController: NavHostController
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
// 标题
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.rider_pro_hot_room),
|
||||||
|
contentDescription = "chat room",
|
||||||
|
modifier = Modifier.size(28.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(4.dp))
|
||||||
|
androidx.compose.material3.Text(
|
||||||
|
text = stringResource(R.string.hot_rooms),
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = androidx.compose.ui.text.font.FontWeight.W900,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
chatRooms.chunked(2).forEach { rowRooms ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(bottom = 12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||||
|
) {
|
||||||
|
rowRooms.forEach { chatRoom ->
|
||||||
|
ChatRoomCard(
|
||||||
|
chatRoom = chatRoom,
|
||||||
|
navController = navController,
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ChatRoomCard(
|
||||||
|
chatRoom: ChatRoom,
|
||||||
|
navController: NavHostController,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
val cardSize = 160.dp
|
||||||
|
val viewModel: AgentViewModel = viewModel()
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
// 防抖状态
|
||||||
|
var lastClickTime by remember { mutableStateOf(0L) }
|
||||||
|
|
||||||
|
// Loading 对话框
|
||||||
|
if (viewModel.isJoiningRoom) {
|
||||||
|
Dialog(
|
||||||
|
onDismissRequest = { /* 阻止用户关闭对话框 */ },
|
||||||
|
properties = DialogProperties(
|
||||||
|
dismissOnBackPress = false,
|
||||||
|
dismissOnClickOutside = false
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(120.dp)
|
||||||
|
.background(
|
||||||
|
color = AppColors.background,
|
||||||
|
shape = RoundedCornerShape(12.dp)
|
||||||
|
),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(32.dp),
|
||||||
|
color = AppColors.main
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(12.dp))
|
||||||
|
androidx.compose.material3.Text(
|
||||||
|
text = "加入中...",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
color = AppColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 正方形卡片,文字重叠在底部
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(cardSize)
|
||||||
|
.background(AppColors.secondaryBackground, RoundedCornerShape(12.dp))
|
||||||
|
.clickable(enabled = !viewModel.isJoiningRoom) {
|
||||||
|
if (!viewModel.isJoiningRoom && DebounceUtils.simpleDebounceClick(lastClickTime, 500L) {
|
||||||
|
// 加入群聊房间
|
||||||
|
viewModel.joinRoom(
|
||||||
|
id = chatRoom.id,
|
||||||
|
name = chatRoom.name,
|
||||||
|
avatar = chatRoom.avatar,
|
||||||
|
context = context,
|
||||||
|
navController = navController,
|
||||||
|
onSuccess = {
|
||||||
|
// 成功加入房间
|
||||||
|
},
|
||||||
|
onError = { errorMsg ->
|
||||||
|
// 处理错误,可以显示Toast或其他提示
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}) {
|
||||||
|
lastClickTime = System.currentTimeMillis()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
// 优先显示banner,如果没有banner则显示头像
|
||||||
|
val imageUrl = if (chatRoom.banner.isNotEmpty()) chatRoom.banner else chatRoom.avatar
|
||||||
|
|
||||||
|
if (imageUrl.isNotEmpty()) {
|
||||||
|
CustomAsyncImage(
|
||||||
|
imageUrl = imageUrl,
|
||||||
|
contentDescription = if (chatRoom.banner.isNotEmpty()) "房间banner" else "房间头像",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(cardSize)
|
||||||
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
|
contentScale = androidx.compose.ui.layout.ContentScale.Crop
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 默认房间图标
|
||||||
|
Image(
|
||||||
|
painter = painterResource(R.mipmap.rider_pro_agent),
|
||||||
|
contentDescription = "默认房间图标",
|
||||||
|
modifier = Modifier.size(cardSize * 0.4f),
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.secondaryText)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 房间名称,重叠在底部
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.BottomCenter)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(
|
||||||
|
color = Color.Black.copy(alpha = 0.6f),
|
||||||
|
shape = RoundedCornerShape(bottomStart = 12.dp, bottomEnd = 12.dp)
|
||||||
|
)
|
||||||
|
.padding(horizontal = 8.dp, vertical = 6.dp)
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Text(
|
||||||
|
text = chatRoom.name,
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color.White,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = androidx.compose.ui.text.style.TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = androidx.compose.ui.text.style.TextAlign.Center
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,55 +1,131 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.ai
|
package com.aiosman.ravenow.ui.index.tabs.ai
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import com.aiosman.ravenow.AppState
|
||||||
|
import com.aiosman.ravenow.AppStore
|
||||||
|
import com.aiosman.ravenow.ConstVars
|
||||||
|
import com.aiosman.ravenow.data.Room
|
||||||
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.CreateGroupChatRequestBody
|
||||||
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.data.api.JoinGroupChatRequestBody
|
||||||
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
import com.aiosman.ravenow.ui.index.tabs.ai.tabs.mine.MineAgentViewModel.createGroup2ChatAi
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
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.message.tab.GroupChatListViewModel.createGroupChat
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
import com.aiosman.ravenow.ui.index.tabs.moment.tabs.expolre.AgentItem
|
||||||
|
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
|
||||||
|
|
||||||
|
data class ChatRoom(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val avatar: String = "",
|
||||||
|
val banner: String = "",
|
||||||
|
val memberCount: Int = 0
|
||||||
|
)
|
||||||
|
|
||||||
object AgentViewModel : ViewModel() {
|
object AgentViewModel : ViewModel() {
|
||||||
|
|
||||||
private val apiClient: RaveNowAPI = ApiClient.api
|
private val apiClient: RaveNowAPI = ApiClient.api
|
||||||
|
|
||||||
var agentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
// 顶部Agent列表数据(用于ViewPager)
|
||||||
|
var topAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
// 底部网格布局Agent数据
|
||||||
|
var gridAgentItems by mutableStateOf<List<AgentItem>>(emptyList())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var chatRooms by mutableStateOf<List<ChatRoom>>(emptyList())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var rooms by mutableStateOf<List<Room>>(emptyList())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var categories by mutableStateOf<List<CategoryTemplate>>(emptyList())
|
||||||
|
private set
|
||||||
|
|
||||||
|
var selectedCategoryIndex by mutableStateOf(0)
|
||||||
|
private set
|
||||||
|
|
||||||
var errorMessage by mutableStateOf<String?>(null)
|
var errorMessage by mutableStateOf<String?>(null)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
|
||||||
var isRefreshing by mutableStateOf(false)
|
var isRefreshing by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
var isLoading by mutableStateOf(false)
|
var isLoading by mutableStateOf(false)
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
var isJoiningRoom by mutableStateOf(false)
|
||||||
|
private set
|
||||||
|
|
||||||
|
private var topCurrentPage = 1
|
||||||
|
private var topHasMoreData = true
|
||||||
|
|
||||||
|
private var gridCurrentPage = 1
|
||||||
|
private var gridHasMoreData = true
|
||||||
|
|
||||||
init {
|
init {
|
||||||
loadAgentData()
|
loadTopAgentData()
|
||||||
|
loadGridAgentData()
|
||||||
|
loadChatRooms()
|
||||||
|
loadCategories()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadAgentData() {
|
/**
|
||||||
|
* 加载顶部Agent列表数据(用于ViewPager)
|
||||||
|
*/
|
||||||
|
private fun loadTopAgentData(categoryIndex: Int = 0) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
|
topCurrentPage = 1
|
||||||
|
topHasMoreData = true
|
||||||
try {
|
try {
|
||||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
val selectedCategory =
|
||||||
|
if (categoryIndex < categories.size) categories[categoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (categoryIndex == 0 || selectedCategory == null) {
|
||||||
|
// 推荐分类或无效分类,加载所有 Agent
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = topCurrentPage,
|
||||||
|
pageSize = 15,
|
||||||
|
withWorkflow = "1",
|
||||||
|
random = "1"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 特定分类,使用 categoryName 参数
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = topCurrentPage,
|
||||||
|
pageSize = 15,
|
||||||
|
withWorkflow = "1",
|
||||||
|
categoryName = selectedCategory.name,
|
||||||
|
random = "1"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val agents = response.body()?.data?.list ?: emptyList()
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
agentItems = agents.map { agent ->
|
topAgentItems = agents.map { agent ->
|
||||||
AgentItem.fromAgent(agent)
|
AgentItem.fromAgent(agent)
|
||||||
}
|
}
|
||||||
|
topHasMoreData = agents.size >= 15
|
||||||
} else {
|
} else {
|
||||||
errorMessage = "获取Agent数据失败: ${response.code()}"
|
errorMessage = "获取顶部Agent数据失败: ${response.code()}"
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
errorMessage = "网络请求失败: ${e.message}"
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
@@ -58,14 +134,218 @@ object AgentViewModel: ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载底部网格布局Agent数据
|
||||||
|
*/
|
||||||
|
private fun loadGridAgentData(categoryIndex: Int = 0) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
errorMessage = null
|
||||||
|
gridCurrentPage = 1
|
||||||
|
gridHasMoreData = true
|
||||||
|
try {
|
||||||
|
val selectedCategory =
|
||||||
|
if (categoryIndex < categories.size) categories[categoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (categoryIndex == 0 || selectedCategory == null) {
|
||||||
|
// 推荐分类或无效分类,加载所有 Agent
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = gridCurrentPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// 特定分类,使用 categoryName 参数
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = gridCurrentPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
categoryName = selectedCategory.name,
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
|
gridAgentItems = agents.map { agent ->
|
||||||
|
AgentItem.fromAgent(agent)
|
||||||
|
}
|
||||||
|
gridHasMoreData = agents.size >= 20
|
||||||
|
} else {
|
||||||
|
errorMessage = "获取网格Agent数据失败: ${response.code()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun loadChatRooms() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val response = apiClient.getRooms(
|
||||||
|
page = 1,
|
||||||
|
pageSize = 21,
|
||||||
|
isRecommended = 1,
|
||||||
|
random = 1
|
||||||
|
) // 请求21个,确保是3的倍数
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val allRooms = response.body()?.list ?: emptyList()
|
||||||
|
// 确保房间数量是3的倍数,如果不足则截取,如果超出则取前几个
|
||||||
|
val targetCount = (allRooms.size / 3) * 3 // 向下取整到最近的3的倍数
|
||||||
|
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 {
|
||||||
|
try {
|
||||||
|
val categoriesResponse = apiClient.getCategories(
|
||||||
|
page = 1,
|
||||||
|
pageSize = 100,
|
||||||
|
)
|
||||||
|
if (categoriesResponse.isSuccessful && categoriesResponse.body() != null) {
|
||||||
|
// 添加一个默认的"推荐"分类在第一位
|
||||||
|
val recommendCategory = createRecommendCategory()
|
||||||
|
val categoriesList = categoriesResponse.body()?.list ?: emptyList()
|
||||||
|
categories = listOf(recommendCategory) + categoriesList
|
||||||
|
|
||||||
|
// 分类加载完成后,重新加载当前选中分类的 Agent 数据
|
||||||
|
if (topAgentItems.isEmpty()) {
|
||||||
|
loadTopAgentData(selectedCategoryIndex)
|
||||||
|
}
|
||||||
|
if (gridAgentItems.isEmpty()) {
|
||||||
|
loadGridAgentData(selectedCategoryIndex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 如果请求失败,使用默认分类
|
||||||
|
categories = listOf(createRecommendCategory())
|
||||||
|
errorMessage = "获取分类失败: ${categoriesResponse.code()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 如果网络请求失败,使用默认分类
|
||||||
|
categories = listOf(createRecommendCategory())
|
||||||
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建推荐分类
|
||||||
|
*/
|
||||||
|
private fun createRecommendCategory(): CategoryTemplate {
|
||||||
|
return CategoryTemplate(
|
||||||
|
id = 0,
|
||||||
|
name = "推荐",
|
||||||
|
description = "推荐内容",
|
||||||
|
avatar = "",
|
||||||
|
parentId = null,
|
||||||
|
parent = null,
|
||||||
|
children = null,
|
||||||
|
sort = 0,
|
||||||
|
isActive = true,
|
||||||
|
promptCount = 0,
|
||||||
|
createdAt = "",
|
||||||
|
updatedAt = "",
|
||||||
|
translations = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载更多网格Agent数据
|
||||||
|
*/
|
||||||
|
fun loadMoreGridAgentData() {
|
||||||
|
if (!gridHasMoreData || isLoading) return
|
||||||
|
|
||||||
|
viewModelScope.launch {
|
||||||
|
isLoading = true
|
||||||
|
try {
|
||||||
|
val nextPage = gridCurrentPage + 1
|
||||||
|
val selectedCategory =
|
||||||
|
if (selectedCategoryIndex < categories.size) categories[selectedCategoryIndex] else null
|
||||||
|
|
||||||
|
val response = if (selectedCategoryIndex == 0 || selectedCategory == null) {
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = nextPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
apiClient.getAgent(
|
||||||
|
page = nextPage,
|
||||||
|
pageSize = 20,
|
||||||
|
withWorkflow = "1",
|
||||||
|
categoryName = selectedCategory.name,
|
||||||
|
random = "true"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.isSuccessful) {
|
||||||
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
|
val newAgentItems = agents.map { agent ->
|
||||||
|
AgentItem.fromAgent(agent)
|
||||||
|
}
|
||||||
|
gridAgentItems = gridAgentItems + newAgentItems
|
||||||
|
gridCurrentPage = nextPage
|
||||||
|
gridHasMoreData = agents.size >= 20
|
||||||
|
} else {
|
||||||
|
errorMessage = "加载更多网格Agent数据失败: ${response.code()}"
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
errorMessage = "网络请求失败: ${e.message}"
|
||||||
|
} finally {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 选择分类并加载对应的 Agent 数据
|
||||||
|
*/
|
||||||
|
fun selectCategory(categoryIndex: Int) {
|
||||||
|
if (categoryIndex != selectedCategoryIndex && categoryIndex >= 0 && categoryIndex < categories.size) {
|
||||||
|
selectedCategoryIndex = categoryIndex
|
||||||
|
// 同时加载顶部和网格的数据
|
||||||
|
loadTopAgentData(categoryIndex)
|
||||||
|
loadGridAgentData(categoryIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun createSingleChat(
|
fun createSingleChat(
|
||||||
openId: String,
|
openId: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
val response =
|
||||||
|
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||||
|
Log.d("debug", response.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goToChatAi(
|
fun goToChatAi(
|
||||||
openId: String,
|
openId: String,
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
@@ -99,15 +379,98 @@ object AgentViewModel: ViewModel() {
|
|||||||
* 刷新推荐Agent数据
|
* 刷新推荐Agent数据
|
||||||
*/
|
*/
|
||||||
fun refreshAgentData() {
|
fun refreshAgentData() {
|
||||||
loadAgentData()
|
loadTopAgentData()
|
||||||
|
loadGridAgentData()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查数据是否为空,如果为空则重新加载
|
* 检查数据是否为空,如果为空则重新加载
|
||||||
*/
|
*/
|
||||||
fun ensureDataLoaded() {
|
fun ensureDataLoaded() {
|
||||||
if (agentItems.isEmpty() && !isLoading) {
|
if (topAgentItems.isEmpty() && !isLoading) {
|
||||||
loadAgentData()
|
loadTopAgentData()
|
||||||
|
}
|
||||||
|
if (gridAgentItems.isEmpty() && !isLoading) {
|
||||||
|
loadGridAgentData()
|
||||||
|
}
|
||||||
|
// 同时确保分类数据已加载
|
||||||
|
if (categories.isEmpty() && !isLoading) {
|
||||||
|
loadCategories()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加入房间
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,10 +478,18 @@ object AgentViewModel: ViewModel() {
|
|||||||
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
* 重置ViewModel状态,用于登出或切换账号时清理数据
|
||||||
*/
|
*/
|
||||||
fun ResetModel() {
|
fun ResetModel() {
|
||||||
agentItems = emptyList()
|
topAgentItems = emptyList()
|
||||||
|
gridAgentItems = emptyList()
|
||||||
|
categories = emptyList()
|
||||||
|
selectedCategoryIndex = 0
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
isLoading = false
|
isLoading = false
|
||||||
|
isJoiningRoom = false
|
||||||
|
topCurrentPage = 1
|
||||||
|
topHasMoreData = true
|
||||||
|
gridCurrentPage = 1
|
||||||
|
gridHasMoreData = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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 = { 3 })
|
var pagerState = rememberPagerState (pageCount = { 4 })
|
||||||
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 {
|
||||||
// TODO: 实现通知功能
|
navController.navigate(NavigationRoute.NotificationScreen.route)
|
||||||
},
|
},
|
||||||
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_ai),
|
text = stringResource(R.string.chat_all),
|
||||||
isSelected = pagerState.currentPage == 0,
|
isSelected = pagerState.currentPage == 0,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
@@ -335,6 +335,38 @@ 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(
|
||||||
@@ -353,11 +385,11 @@ fun NotificationsScreen() {
|
|||||||
Box {
|
Box {
|
||||||
TabItem(
|
TabItem(
|
||||||
text = stringResource(R.string.chat_group),
|
text = stringResource(R.string.chat_group),
|
||||||
isSelected = pagerState.currentPage == 1,
|
isSelected = pagerState.currentPage == 2,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
pagerState.animateScrollToPage(1)
|
pagerState.animateScrollToPage(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -378,14 +410,15 @@ fun NotificationsScreen() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
TabSpacer()
|
TabSpacer()
|
||||||
|
|
||||||
Box {
|
Box {
|
||||||
TabItem(
|
TabItem(
|
||||||
text = stringResource(R.string.chat_friend),
|
text = stringResource(R.string.chat_friend),
|
||||||
isSelected = pagerState.currentPage == 2,
|
isSelected = pagerState.currentPage == 3,
|
||||||
onClick = {
|
onClick = {
|
||||||
tabDebouncer {
|
tabDebouncer {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
pagerState.animateScrollToPage(2)
|
pagerState.animateScrollToPage(3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -414,14 +447,17 @@ fun NotificationsScreen() {
|
|||||||
) {
|
) {
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> {
|
0 -> {
|
||||||
|
AllChatListScreen()
|
||||||
|
}
|
||||||
|
1 -> {
|
||||||
AgentChatListScreen()
|
AgentChatListScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
2 -> {
|
||||||
GroupChatListScreen()
|
GroupChatListScreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
2 -> {
|
3 -> {
|
||||||
FriendChatListScreen()
|
FriendChatListScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -91,11 +91,14 @@ fun AgentChatListScreen() {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
|
||||||
) {
|
) {
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
|
||||||
|
|
||||||
|
if (isNetworkAvailable) {
|
||||||
Spacer(modifier = Modifier.height(39.dp))
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.qs_znt_qs_as_img
|
id = if(AppState.darkMode) R.mipmap.qs_znt_qs_as_img
|
||||||
else R.mipmap.qs_znt_qs_img),
|
else R.mipmap.invalid_name_5),
|
||||||
contentDescription = "null data",
|
contentDescription = "null data",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(181.dp)
|
.size(181.dp)
|
||||||
@@ -114,6 +117,35 @@ 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()
|
||||||
|
|||||||
@@ -0,0 +1,390 @@
|
|||||||
|
package com.aiosman.ravenow.ui.index.tabs.message.tab
|
||||||
|
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.*
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
|
import androidx.compose.material3.HorizontalDivider
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.aiosman.ravenow.AppState
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
|
import com.aiosman.ravenow.LocalNavController
|
||||||
|
import com.aiosman.ravenow.R
|
||||||
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
|
import com.aiosman.ravenow.ui.composables.rememberDebouncer
|
||||||
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import com.aiosman.ravenow.utils.NetworkUtils
|
||||||
|
import androidx.compose.material.Button
|
||||||
|
import androidx.compose.material.ButtonDefaults
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
|
||||||
|
data class CombinedConversation(
|
||||||
|
val type: String, // "agent", "group", or "friend"
|
||||||
|
val agentConversation: AgentConversation? = null,
|
||||||
|
val groupConversation: GroupConversation? = null,
|
||||||
|
val friendConversation: FriendConversation? = null
|
||||||
|
) {
|
||||||
|
val id: String
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> "agent_${agentConversation?.id ?: 0}"
|
||||||
|
"group" -> "group_${groupConversation?.id ?: 0}"
|
||||||
|
"friend" -> "friend_${friendConversation?.id ?: 0}"
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val avatar: String
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.avatar ?: ""
|
||||||
|
"group" -> groupConversation?.avatar ?: ""
|
||||||
|
"friend" -> friendConversation?.avatar ?: ""
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val name: String
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.nickname ?: ""
|
||||||
|
"group" -> groupConversation?.groupName ?: ""
|
||||||
|
"friend" -> friendConversation?.nickname ?: ""
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val lastMessageTime: String
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.lastMessageTime ?: ""
|
||||||
|
"group" -> groupConversation?.lastMessageTime ?: ""
|
||||||
|
"friend" -> friendConversation?.lastMessageTime ?: ""
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val displayText: String
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.displayText ?: ""
|
||||||
|
"group" -> groupConversation?.displayText ?: ""
|
||||||
|
"friend" -> friendConversation?.displayText ?: ""
|
||||||
|
else -> ""
|
||||||
|
}
|
||||||
|
|
||||||
|
val unreadCount: Int
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.unreadCount ?: 0
|
||||||
|
"group" -> groupConversation?.unreadCount ?: 0
|
||||||
|
"friend" -> friendConversation?.unreadCount ?: 0
|
||||||
|
else -> 0
|
||||||
|
}
|
||||||
|
|
||||||
|
val isSelf: Boolean
|
||||||
|
get() = when (type) {
|
||||||
|
"agent" -> agentConversation?.isSelf ?: false
|
||||||
|
"group" -> groupConversation?.isSelf ?: false
|
||||||
|
"friend" -> friendConversation?.isSelf ?: false
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun AllChatListScreen() {
|
||||||
|
val context = LocalContext.current
|
||||||
|
val navController = LocalNavController.current
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
|
var allConversations by remember { mutableStateOf<List<CombinedConversation>>(emptyList()) }
|
||||||
|
var refreshing by remember { mutableStateOf(false) }
|
||||||
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
|
var error by remember { mutableStateOf<String?>(null) }
|
||||||
|
|
||||||
|
val state = rememberPullRefreshState(
|
||||||
|
refreshing = refreshing,
|
||||||
|
onRefresh = {
|
||||||
|
refreshing = true
|
||||||
|
refreshAllData(context,
|
||||||
|
onSuccess = { conversations ->
|
||||||
|
allConversations = conversations
|
||||||
|
refreshing = false
|
||||||
|
},
|
||||||
|
onError = { errorMsg ->
|
||||||
|
error = errorMsg
|
||||||
|
refreshing = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
isLoading = true
|
||||||
|
refreshAllData(context,
|
||||||
|
onSuccess = { conversations ->
|
||||||
|
allConversations = conversations
|
||||||
|
isLoading = false
|
||||||
|
},
|
||||||
|
onError = { errorMsg ->
|
||||||
|
error = errorMsg
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state)
|
||||||
|
) {
|
||||||
|
if (allConversations.isEmpty() && !isLoading) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(16.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
) {
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
|
||||||
|
|
||||||
|
if (isNetworkAvailable) {
|
||||||
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
|
Image(
|
||||||
|
painter = painterResource(
|
||||||
|
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
|
||||||
|
else R.mipmap.invalid_name_2),
|
||||||
|
contentDescription = "null data",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(181.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_empty_title),
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_empty_subtitle),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.invalid_name_10),
|
||||||
|
contentDescription = "network error",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(181.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_title),
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ReloadButton(
|
||||||
|
onClick = {
|
||||||
|
isLoading = true
|
||||||
|
refreshAllData(context,
|
||||||
|
onSuccess = { conversations ->
|
||||||
|
allConversations = conversations
|
||||||
|
isLoading = false
|
||||||
|
},
|
||||||
|
onError = { errorMsg ->
|
||||||
|
error = errorMsg
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxSize()
|
||||||
|
) {
|
||||||
|
itemsIndexed(
|
||||||
|
items = allConversations,
|
||||||
|
key = { _, item -> item.id }
|
||||||
|
) { index, item ->
|
||||||
|
when (item.type) {
|
||||||
|
"agent" -> {
|
||||||
|
item.agentConversation?.let { agent ->
|
||||||
|
AgentChatItem(
|
||||||
|
conversation = agent,
|
||||||
|
onUserAvatarClick = { conv ->
|
||||||
|
AgentChatListViewModel.goToUserDetail(conv, navController)
|
||||||
|
},
|
||||||
|
onChatClick = { conv ->
|
||||||
|
if (NetworkUtils.isNetworkAvailable(context)) {
|
||||||
|
AgentChatListViewModel.createSingleChat(conv.trtcUserId)
|
||||||
|
AgentChatListViewModel.goToChatAi(conv.trtcUserId, navController)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"group" -> {
|
||||||
|
item.groupConversation?.let { group ->
|
||||||
|
GroupChatItem(
|
||||||
|
conversation = group,
|
||||||
|
onGroupAvatarClick = { conv ->
|
||||||
|
GroupChatListViewModel.goToGroupDetail(conv, navController)
|
||||||
|
},
|
||||||
|
onChatClick = { conv ->
|
||||||
|
if (NetworkUtils.isNetworkAvailable(context)) {
|
||||||
|
GroupChatListViewModel.goToChat(conv, navController)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"friend" -> {
|
||||||
|
item.friendConversation?.let { friend ->
|
||||||
|
FriendChatItem(
|
||||||
|
conversation = friend,
|
||||||
|
onUserAvatarClick = { conv ->
|
||||||
|
FriendChatListViewModel.goToUserDetail(conv, navController)
|
||||||
|
},
|
||||||
|
onChatClick = { conv ->
|
||||||
|
if (NetworkUtils.isNetworkAvailable(context)) {
|
||||||
|
FriendChatListViewModel.goToChat(conv, navController)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(context, "网络连接异常,请检查网络设置", Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
// if (index < allConversations.size - 1) {
|
||||||
|
// HorizontalDivider(
|
||||||
|
// modifier = Modifier.padding(horizontal = 24.dp),
|
||||||
|
// color = AppColors.divider
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isLoading && allConversations.isNotEmpty()) {
|
||||||
|
item {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
CircularProgressIndicator(
|
||||||
|
modifier = Modifier.size(24.dp),
|
||||||
|
color = AppColors.main
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
refreshing = refreshing,
|
||||||
|
state = state,
|
||||||
|
modifier = Modifier.align(Alignment.TopCenter)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
error?.let { errorMsg ->
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(16.dp),
|
||||||
|
contentAlignment = Alignment.Center
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = errorMsg,
|
||||||
|
color = AppColors.error,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun refreshAllData(
|
||||||
|
context: android.content.Context,
|
||||||
|
onSuccess: (List<CombinedConversation>) -> Unit,
|
||||||
|
onError: (String) -> Unit
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
// 同时刷新所有类型的数据
|
||||||
|
AgentChatListViewModel.refreshPager(context = context)
|
||||||
|
GroupChatListViewModel.refreshPager(context = context)
|
||||||
|
FriendChatListViewModel.refreshPager(context = context)
|
||||||
|
|
||||||
|
val combinedList = mutableListOf<CombinedConversation>()
|
||||||
|
|
||||||
|
AgentChatListViewModel.agentChatList.forEach { agent ->
|
||||||
|
combinedList.add(CombinedConversation(type = "agent", agentConversation = agent))
|
||||||
|
}
|
||||||
|
|
||||||
|
GroupChatListViewModel.groupChatList.forEach { group ->
|
||||||
|
combinedList.add(CombinedConversation(type = "group", groupConversation = group))
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendChatListViewModel.friendChatList.forEach { friend ->
|
||||||
|
val isDuplicate = combinedList.any {//判断重复
|
||||||
|
it.type == "agent" && it.agentConversation?.trtcUserId == friend.trtcUserId
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isDuplicate) {
|
||||||
|
combinedList.add(CombinedConversation(type = "friend", friendConversation = friend))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按最后消息时间排序
|
||||||
|
val sortedList = combinedList.sortedByDescending {
|
||||||
|
it.lastMessageTime
|
||||||
|
}
|
||||||
|
|
||||||
|
onSuccess(sortedList)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
onError("刷新数据失败: ${e.message}")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,8 +33,14 @@ 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
|
||||||
@@ -73,12 +79,14 @@ fun FriendChatListScreen() {
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
//verticalArrangement = Arrangement.Center
|
//verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
|
||||||
|
|
||||||
|
if (isNetworkAvailable) {
|
||||||
Spacer(modifier = Modifier.height(39.dp))
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
|
id = if(AppState.darkMode) R.mipmap.qs_py_qs_as_img
|
||||||
else R.mipmap.qs_py_qs_img),
|
else R.mipmap.invalid_name_2),
|
||||||
contentDescription = "null data",
|
contentDescription = "null data",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(181.dp)
|
.size(181.dp)
|
||||||
@@ -96,6 +104,34 @@ fun FriendChatListScreen() {
|
|||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
|
}else {
|
||||||
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.invalid_name_10),
|
||||||
|
contentDescription = "network error",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(181.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_title),
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ReloadButton(
|
||||||
|
onClick = {
|
||||||
|
FriendChatListViewModel.refreshPager(pullRefresh = true, context = context)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
@@ -266,4 +302,43 @@ 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,11 +71,14 @@ fun GroupChatListScreen() {
|
|||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(context)
|
||||||
|
|
||||||
|
if (isNetworkAvailable) {
|
||||||
Spacer(modifier = Modifier.height(39.dp))
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.qs_ql_qs_as_img
|
id = if(AppState.darkMode) R.mipmap.qs_ql_qs_as_img
|
||||||
else R.mipmap.qs_ql_qs_img),
|
else R.mipmap.invalid_name_12),
|
||||||
contentDescription = "null data",
|
contentDescription = "null data",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(181.dp)
|
.size(181.dp)
|
||||||
@@ -93,6 +96,34 @@ fun GroupChatListScreen() {
|
|||||||
color = AppColors.secondaryText,
|
color = AppColors.secondaryText,
|
||||||
fontSize = 14.sp
|
fontSize = 14.sp
|
||||||
)
|
)
|
||||||
|
}else {
|
||||||
|
Spacer(modifier = Modifier.height(39.dp))
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.mipmap.invalid_name_10),
|
||||||
|
contentDescription = "network error",
|
||||||
|
modifier = Modifier
|
||||||
|
.size(181.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(24.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_title),
|
||||||
|
color = AppColors.text,
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.friend_chat_no_network_subtitle),
|
||||||
|
color = AppColors.secondaryText,
|
||||||
|
fontSize = 14.sp
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
ReloadButton(
|
||||||
|
onClick = {
|
||||||
|
GroupChatListViewModel.refreshPager(context = context)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ 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
|
||||||
@@ -167,10 +168,12 @@ object GroupChatListViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun createGroupChat(
|
fun createGroupChat(
|
||||||
trtcGroupId: String,
|
trtcGroupId: String? = null,
|
||||||
|
roomId: Int? = null
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId)
|
val response = ApiClient.api.createGroupChatAi(trtcGroupId = trtcGroupId,roomId = roomId)
|
||||||
|
Log.d("debug",response.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,13 @@ 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.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
|
||||||
@@ -67,8 +68,8 @@ fun MomentsList() {
|
|||||||
val navigationBarPaddings =
|
val navigationBarPaddings =
|
||||||
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() + 48.dp
|
||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
// 游客模式下显示3个tab:Worldwide、Hot、News,非游客模式显示4个tab:Worldwide、Following、Hot、News
|
// 游客模式下不显示timeline,只显示2个tab:Dynamic、Hot // 游客模式下不显示timeline,只显示3个tab:Explore、Dynamic、Hot
|
||||||
val tabCount = if (AppStore.isGuest) 3 else 4
|
val tabCount = if (AppStore.isGuest) 2 else 3 // val tabCount = if (AppStore.isGuest) 3 else 4
|
||||||
var pagerState = rememberPagerState { tabCount }
|
var pagerState = rememberPagerState { tabCount }
|
||||||
var scope = rememberCoroutineScope()
|
var scope = rememberCoroutineScope()
|
||||||
Column(
|
Column(
|
||||||
@@ -121,6 +122,8 @@ fun MomentsList() {
|
|||||||
//
|
//
|
||||||
// }
|
// }
|
||||||
// Spacer(modifier = Modifier.width(16.dp))
|
// Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
val lastClickTime = remember { mutableStateOf(0L) }
|
||||||
|
val clickDelay = 500L
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.moment),
|
text = stringResource(R.string.moment),
|
||||||
fontSize = 20.sp,
|
fontSize = 20.sp,
|
||||||
@@ -138,7 +141,11 @@ fun MomentsList() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(24.dp)
|
.size(24.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
|
val currentTime = System.currentTimeMillis()
|
||||||
|
if (currentTime - lastClickTime.value > clickDelay) {
|
||||||
|
lastClickTime.value = currentTime
|
||||||
navController.navigate(NavigationRoute.Search.route)
|
navController.navigate(NavigationRoute.Search.route)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
@@ -205,23 +212,6 @@ fun MomentsList() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TabSpacer()
|
|
||||||
|
|
||||||
// 新闻标签
|
|
||||||
Box {
|
|
||||||
CustomTabItem(
|
|
||||||
text = stringResource(R.string.index_news),
|
|
||||||
isSelected = pagerState.currentPage == 3,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(3)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 热门标签 (游客模式)
|
// 热门标签 (游客模式)
|
||||||
Box {
|
Box {
|
||||||
@@ -237,23 +227,6 @@ fun MomentsList() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
TabSpacer()
|
|
||||||
|
|
||||||
// 新闻标签 (游客模式)
|
|
||||||
Box {
|
|
||||||
CustomTabItem(
|
|
||||||
text = stringResource(R.string.index_news),
|
|
||||||
isSelected = pagerState.currentPage == 2,
|
|
||||||
onClick = {
|
|
||||||
tabDebouncer {
|
|
||||||
scope.launch {
|
|
||||||
pagerState.animateScrollToPage(2)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,7 +237,7 @@ fun MomentsList() {
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
if (AppStore.isGuest) {
|
if (AppStore.isGuest) {
|
||||||
// 游客模式:Worldwide(0), Hot(1), News(2)
|
// 游客模式:Dynamic(0), Hot(1)
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> {
|
0 -> {
|
||||||
Dynamic()
|
Dynamic()
|
||||||
@@ -272,12 +245,9 @@ fun MomentsList() {
|
|||||||
1 -> {
|
1 -> {
|
||||||
HotMomentsList()
|
HotMomentsList()
|
||||||
}
|
}
|
||||||
2 -> {
|
|
||||||
News()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 正常用户:Worldwide(0), Following(1), Hot(2), News(3)
|
// 正常用户:Dynamic(0), Timeline(1), Hot(2)
|
||||||
when (it) {
|
when (it) {
|
||||||
0 -> {
|
0 -> {
|
||||||
Dynamic()
|
Dynamic()
|
||||||
@@ -288,9 +258,6 @@ fun MomentsList() {
|
|||||||
2 -> {
|
2 -> {
|
||||||
HotMomentsList()
|
HotMomentsList()
|
||||||
}
|
}
|
||||||
3 -> {
|
|
||||||
News()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -369,6 +369,7 @@ 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()
|
||||||
@@ -636,6 +637,7 @@ 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,6 +17,10 @@ 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() {
|
||||||
@@ -70,7 +74,7 @@ class ExploreViewModel : ViewModel() {
|
|||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
try {
|
try {
|
||||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val agents = response.body()?.data?.list ?: emptyList()
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
agentItems = agents.map { agent ->
|
agentItems = agents.map { agent ->
|
||||||
@@ -114,7 +118,7 @@ class ExploreViewModel : ViewModel() {
|
|||||||
isLoading = true
|
isLoading = true
|
||||||
errorMessage = null
|
errorMessage = null
|
||||||
try {
|
try {
|
||||||
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = 1)
|
val response = apiClient.getAgent(page = 1, pageSize = 20, withWorkflow = "1")
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
val agents = response.body()?.data?.list ?: emptyList()
|
val agents = response.body()?.data?.list ?: emptyList()
|
||||||
agentItems = agents.map { agent ->
|
agentItems = agents.map { agent ->
|
||||||
@@ -130,14 +134,17 @@ class ExploreViewModel : ViewModel() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createSingleChat(
|
fun createSingleChat(
|
||||||
openId: String,
|
openId: String,
|
||||||
) {
|
) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val response = ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
val response =
|
||||||
|
ApiClient.api.createSingleChat(SingleChatRequestBody(agentOpenId = openId))
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun goToChatAi(
|
fun goToChatAi(
|
||||||
openId: String,
|
openId: String,
|
||||||
navController: NavHostController
|
navController: NavHostController
|
||||||
@@ -152,6 +159,7 @@ 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
|
||||||
@@ -164,9 +172,11 @@ class ExploreViewModel : ViewModel() {
|
|||||||
try {
|
try {
|
||||||
createGroupChat(trtcGroupId = trtcId)
|
createGroupChat(trtcGroupId = trtcId)
|
||||||
// 群聊直接使用群ID进行导航
|
// 群聊直接使用群ID进行导航
|
||||||
navController.navigateToGroupChat( id = trtcId,
|
navController.navigateToGroupChat(
|
||||||
|
id = trtcId,
|
||||||
name = name,
|
name = name,
|
||||||
avatar = avatar)
|
avatar = avatar
|
||||||
|
)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
onError("加入房间失败")
|
onError("加入房间失败")
|
||||||
e.printStackTrace()
|
e.printStackTrace()
|
||||||
@@ -175,9 +185,28 @@ class ExploreViewModel : ViewModel() {
|
|||||||
onSuccess()
|
onSuccess()
|
||||||
|
|
||||||
} else {
|
} 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("加入房间失败")
|
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}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,179 +0,0 @@
|
|||||||
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,16 +0,0 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.news
|
|
||||||
|
|
||||||
import com.aiosman.ravenow.entity.MomentLoaderExtraArgs
|
|
||||||
import com.aiosman.ravenow.ui.index.tabs.moment.BaseMomentModel
|
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
|
|
||||||
|
|
||||||
object NewsViewModel : BaseMomentModel() {
|
|
||||||
init {
|
|
||||||
EventBus.getDefault().register(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun extraArgs(): MomentLoaderExtraArgs {
|
|
||||||
return MomentLoaderExtraArgs(explore = true, newsOnly = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -43,6 +43,8 @@ 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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 动态列表
|
* 动态列表
|
||||||
@@ -76,7 +78,49 @@ fun TimelineMomentsList() {
|
|||||||
model.loadMore()
|
model.loadMore()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (moments.isEmpty()) {
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
|
||||||
|
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()
|
||||||
@@ -91,7 +135,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.qst_gz_qs_img),
|
else R.mipmap.invalid_name_4),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(140.dp)
|
modifier = Modifier.size(140.dp)
|
||||||
)
|
)
|
||||||
@@ -191,9 +235,9 @@ fun ExploreButton(
|
|||||||
) {
|
) {
|
||||||
val gradientBrush = Brush.linearGradient(
|
val gradientBrush = Brush.linearGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color(0xFFee2a33),
|
Color(0xFF7c45ed),
|
||||||
Color(0xFFd80264),
|
Color(0xFF7c68ef),
|
||||||
Color(0xFF664c92)
|
Color(0xFF7bd8f8)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ 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.clip
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.draw.shadow
|
import androidx.compose.ui.draw.shadow
|
||||||
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.graphicsLayer
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.graphics.nativeCanvas
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.layout.onGloballyPositioned
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
@@ -96,7 +98,7 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun ProfileV3(
|
fun ProfileV3(
|
||||||
@@ -117,7 +119,6 @@ fun ProfileV3(
|
|||||||
postCount: Int? = null, // 新增参数用于传递帖子总数
|
postCount: Int? = null, // 新增参数用于传递帖子总数
|
||||||
) {
|
) {
|
||||||
val model = MyProfileViewModel
|
val model = MyProfileViewModel
|
||||||
val state = rememberCollapsingToolbarScaffoldState()
|
|
||||||
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
|
val pagerState = rememberPagerState(pageCount = { if (isAiAccount) 1 else 2 })
|
||||||
val enabled by remember { mutableStateOf(true) }
|
val enabled by remember { mutableStateOf(true) }
|
||||||
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
val statusBarPaddingValues = WindowInsets.systemBars.asPaddingValues()
|
||||||
@@ -149,6 +150,15 @@ fun ProfileV3(
|
|||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val gridState = rememberLazyGridState()
|
val gridState = rememberLazyGridState()
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
|
||||||
|
val toolbarAlpha by remember {
|
||||||
|
derivedStateOf {
|
||||||
|
val maxScroll = 500f // 最大滚动距离,可调整
|
||||||
|
val progress = (scrollState.value.coerceAtMost(maxScroll.toInt()) / maxScroll).coerceIn(0f, 1f)
|
||||||
|
progress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// observe list scrolling
|
// observe list scrolling
|
||||||
val reachedListBottom by remember {
|
val reachedListBottom by remember {
|
||||||
@@ -199,8 +209,6 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
fun switchTheme() {
|
fun switchTheme() {
|
||||||
// delay
|
// delay
|
||||||
scope.launch {
|
scope.launch {
|
||||||
@@ -279,43 +287,15 @@ fun ProfileV3(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier.pullRefresh(refreshState)
|
modifier = Modifier.pullRefresh(refreshState)
|
||||||
) {
|
) {
|
||||||
CollapsingToolbarScaffold(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.background(AppColors.profileBackground),
|
.verticalScroll(scrollState)
|
||||||
state = state,
|
|
||||||
scrollStrategy = ScrollStrategy.ExitUntilCollapsed,
|
|
||||||
toolbarScrollable = true,
|
|
||||||
enabled = enabled,
|
|
||||||
toolbar = { toolbarScrollState ->
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(miniToolbarHeight.dp)
|
|
||||||
// 保持在最低高度和当前高度之间
|
|
||||||
.background(AppColors.profileBackground)
|
.background(AppColors.profileBackground)
|
||||||
) {
|
) {
|
||||||
}
|
// Banner
|
||||||
// header
|
val banner = profile?.banner
|
||||||
Box(
|
if (banner != null) {
|
||||||
modifier = Modifier
|
|
||||||
.parallax(0.5f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(if (isAiAccount) 600.dp else 700.dp)
|
|
||||||
.background(AppColors.profileBackground)
|
|
||||||
.verticalScroll(toolbarScrollState)
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.graphicsLayer {
|
|
||||||
alpha = state.toolbarState.progress
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
// banner
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -331,9 +311,7 @@ fun ProfileV3(
|
|||||||
it.noRippleClickable {
|
it.noRippleClickable {
|
||||||
Intent(Intent.ACTION_PICK).apply {
|
Intent(Intent.ACTION_PICK).apply {
|
||||||
type = "image/*"
|
type = "image/*"
|
||||||
pickBannerImageLauncher.launch(
|
pickBannerImageLauncher.launch(this)
|
||||||
this
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -348,77 +326,25 @@ fun ProfileV3(
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
val banner = profile?.banner
|
|
||||||
|
|
||||||
if (banner != null) {
|
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
LocalContext.current,
|
LocalContext.current,
|
||||||
banner,
|
banner,
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize(),
|
||||||
.fillMaxSize(),
|
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
contentScale = ContentScale.Crop
|
contentScale = ContentScale.Crop
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Image(
|
Spacer(modifier = Modifier.height(100.dp))
|
||||||
painter = painterResource(id = R.drawable.rave_now_profile_backgrount_demo_1),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
contentDescription = "",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isSelf&&isMain) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.align(Alignment.TopEnd)
|
|
||||||
.padding(
|
|
||||||
top = statusBarPaddingValues.calculateTopPadding(),
|
|
||||||
start = 8.dp,
|
|
||||||
end = 8.dp
|
|
||||||
)
|
|
||||||
.noRippleClickable {
|
|
||||||
IndexViewModel.openDrawer = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
.clip(RoundedCornerShape(8.dp))
|
|
||||||
.background(
|
|
||||||
AppColors.background.copy(
|
|
||||||
alpha = 0.7f
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
|
||||||
contentDescription = "",
|
|
||||||
|
|
||||||
tint = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
// 用户信息
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(AppColors.profileBackground)
|
.background(AppColors.profileBackground)
|
||||||
) {
|
.padding(horizontal = 16.dp)
|
||||||
// user info
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
|
|
||||||
) {
|
|
||||||
// Spacer(modifier = Modifier.height(16.dp))
|
|
||||||
// 个人信息
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
|
||||||
) {
|
) {
|
||||||
profile?.let {
|
profile?.let {
|
||||||
UserItem(
|
UserItem(
|
||||||
@@ -427,17 +353,20 @@ fun ProfileV3(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(modifier = Modifier.height(20.dp))
|
Spacer(modifier = Modifier.height(20.dp))
|
||||||
|
|
||||||
|
// 操作按钮
|
||||||
profile?.let {
|
profile?.let {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.padding(horizontal = 16.dp)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
if (isSelf) {
|
if (isSelf) {
|
||||||
SelfProfileAction(
|
SelfProfileAction(
|
||||||
onEditProfile = {
|
onEditProfile = {
|
||||||
navController.navigate(
|
navController.navigate(NavigationRoute.AccountEdit.route)
|
||||||
NavigationRoute.AccountEdit.route
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
onPremiumClick = {
|
onPremiumClick = {
|
||||||
navController.navigate(NavigationRoute.VipSelPage.route)
|
navController.navigate(NavigationRoute.VipSelPage.route)
|
||||||
@@ -455,24 +384,20 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加用户智能体行(智能体用户不显示)
|
// 用户智能体行
|
||||||
if (!isAiAccount) {
|
if (!isAiAccount) {
|
||||||
UserAgentsRow(
|
UserAgentsRow(
|
||||||
userId = if (isSelf) null else profile?.id,
|
userId = if (isSelf) null else profile?.id,
|
||||||
modifier = Modifier.padding(top = 16.dp),
|
modifier = Modifier.padding(top = 16.dp),
|
||||||
onMoreClick = {
|
onMoreClick = {
|
||||||
// 导航到智能体列表页面
|
// 导航到智能体列表页面
|
||||||
// TODO: 实现导航逻辑
|
|
||||||
},
|
},
|
||||||
onAgentClick = { agent ->
|
onAgentClick = { agent ->
|
||||||
// 导航到智能体详情页面
|
// 导航到智能体详情页面
|
||||||
// TODO: 实现导航逻辑
|
|
||||||
},
|
},
|
||||||
onAvatarClick = { agent ->
|
onAvatarClick = { agent ->
|
||||||
// 导航到智能体个人主页
|
// 导航到智能体个人主页
|
||||||
@@ -500,100 +425,13 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
}
|
// 内容
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.graphicsLayer {
|
|
||||||
alpha = 1 - state.toolbarState.progress
|
|
||||||
}
|
|
||||||
.background(AppColors.profileBackground)
|
|
||||||
.onGloballyPositioned {
|
|
||||||
miniToolbarHeight = with(density) {
|
|
||||||
it.size.height.toDp().value.toInt()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
StatusBarSpacer()
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(
|
|
||||||
horizontal = 16.dp,
|
|
||||||
vertical = 8.dp,
|
|
||||||
).noRippleClickable {
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
verticalAlignment = Alignment.CenterVertically
|
|
||||||
) {
|
|
||||||
if (!isMain) {
|
|
||||||
Image(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_back_icon), // Replace with your image resource
|
|
||||||
contentDescription = "Back",
|
|
||||||
modifier = Modifier
|
|
||||||
.noRippleClickable {
|
|
||||||
navController.navigateUp()
|
|
||||||
}
|
|
||||||
.size(24.dp),
|
|
||||||
colorFilter = ColorFilter.tint(AppColors.text)
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
|
||||||
CustomAsyncImage(
|
|
||||||
LocalContext.current,
|
|
||||||
profile?.avatar,
|
|
||||||
modifier = Modifier
|
|
||||||
.size(32.dp)
|
|
||||||
.clip(CircleShape),
|
|
||||||
contentDescription = "",
|
|
||||||
contentScale = ContentScale.Crop
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(16.dp))
|
|
||||||
Text(
|
|
||||||
text = profile?.nickName ?: "",
|
|
||||||
fontSize = 16.sp,
|
|
||||||
fontWeight = FontWeight.W600,
|
|
||||||
color = AppColors.text
|
|
||||||
)
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.weight(1f))
|
|
||||||
if (isSelf&&isMain) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.noRippleClickable {
|
|
||||||
IndexViewModel.openDrawer = true
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(16.dp)
|
|
||||||
|
|
||||||
) {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
|
||||||
contentDescription = "",
|
|
||||||
tint = AppColors.text
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.background(AppColors.profileBackground)
|
.background(AppColors.profileBackground)
|
||||||
|
.padding(top = 8.dp)
|
||||||
) {
|
) {
|
||||||
UserContentPageIndicator(
|
UserContentPageIndicator(
|
||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
@@ -602,6 +440,7 @@ fun ProfileV3(
|
|||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
HorizontalPager(
|
HorizontalPager(
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
|
modifier = Modifier.height(500.dp) // 固定滚动高度
|
||||||
) { idx ->
|
) { idx ->
|
||||||
when (idx) {
|
when (idx) {
|
||||||
0 ->
|
0 ->
|
||||||
@@ -631,15 +470,116 @@ fun ProfileV3(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 顶部导航栏
|
||||||
|
TopNavigationBar(
|
||||||
|
isMain = isMain,
|
||||||
|
isSelf = isSelf,
|
||||||
|
profile = profile,
|
||||||
|
navController = navController,
|
||||||
|
alpha = toolbarAlpha
|
||||||
|
)
|
||||||
|
|
||||||
PullRefreshIndicator(
|
PullRefreshIndicator(
|
||||||
model.refreshing,
|
model.refreshing,
|
||||||
refreshState,
|
refreshState,
|
||||||
Modifier.align(Alignment.TopCenter)
|
Modifier.align(Alignment.TopCenter)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//顶部导航栏组件
|
||||||
|
@Composable
|
||||||
|
fun TopNavigationBar(
|
||||||
|
isMain: Boolean,
|
||||||
|
isSelf: Boolean,
|
||||||
|
profile: AccountProfileEntity?,
|
||||||
|
navController: androidx.navigation.NavController,
|
||||||
|
|
||||||
|
alpha: Float
|
||||||
|
) {
|
||||||
|
val appColors = LocalAppTheme.current
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.graphicsLayer { this.alpha = alpha } // 应用透明度
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.background(appColors.profileBackground)
|
||||||
|
) {
|
||||||
|
StatusBarSpacer()
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
},
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
if (!isMain) {
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_back_icon),
|
||||||
|
contentDescription = "Back",
|
||||||
|
modifier = Modifier
|
||||||
|
.noRippleClickable {
|
||||||
|
navController.navigateUp()
|
||||||
|
}
|
||||||
|
.size(24.dp),
|
||||||
|
colorFilter = ColorFilter.tint(appColors.text)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
CustomAsyncImage(
|
||||||
|
LocalContext.current,
|
||||||
|
profile?.avatar,
|
||||||
|
modifier = Modifier
|
||||||
|
.size(32.dp)
|
||||||
|
.clip(CircleShape),
|
||||||
|
contentDescription = "",
|
||||||
|
contentScale = ContentScale.Crop
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = profile?.nickName ?: "",
|
||||||
|
fontSize = 16.sp,
|
||||||
|
fontWeight = FontWeight.W600,
|
||||||
|
color = appColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.weight(1f))
|
||||||
|
if (isSelf && isMain) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.size(24.dp)
|
||||||
|
.padding(16.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
}
|
||||||
|
if (isSelf && isMain) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.TopEnd)
|
||||||
|
.padding(top = 32.dp, end = 16.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
IndexViewModel.openDrawer = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.padding(16.dp)
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.rider_pro_more_horizon),
|
||||||
|
contentDescription = "",
|
||||||
|
tint = appColors.text
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -37,9 +37,16 @@ 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,
|
||||||
@@ -129,8 +136,50 @@ 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 (moments.isEmpty()) {
|
if (!isNetworkAvailable) {
|
||||||
|
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()
|
||||||
@@ -141,7 +190,7 @@ fun GalleryGrid(
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id = if(AppState.darkMode) R.mipmap.qs_dt_qs_as_img
|
id = if(AppState.darkMode) R.mipmap.qs_dt_qs_as_img
|
||||||
else R.mipmap.qs_dt_qs_img),
|
else R.mipmap.invalid_name_7),
|
||||||
contentDescription = "暂无图片",
|
contentDescription = "暂无图片",
|
||||||
modifier = Modifier.size(181.dp),
|
modifier = Modifier.size(181.dp),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ fun OtherProfileAction(
|
|||||||
// 定义渐变色
|
// 定义渐变色
|
||||||
val followGradient = Brush.horizontalGradient(
|
val followGradient = Brush.horizontalGradient(
|
||||||
colors = listOf(
|
colors = listOf(
|
||||||
Color(0xFFE53E3E), // 红色
|
Color(0xFF7c45ed),
|
||||||
Color(0xFF9F7AEA) // 紫色
|
Color(0x777c68ef)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ fun OtherProfileAction(
|
|||||||
Text(
|
Text(
|
||||||
text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper),
|
text = if (profile.isFollowing) "已关注" else stringResource(R.string.follow_upper),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
color = if (profile.isFollowing) {
|
color = if (profile.isFollowing) {
|
||||||
// 已关注状态 - 灰色文字
|
// 已关注状态 - 灰色文字
|
||||||
AppColors.text.copy(alpha = 0.6f)
|
AppColors.text.copy(alpha = 0.6f)
|
||||||
@@ -133,11 +133,37 @@ fun OtherProfileAction(
|
|||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.chat_upper),
|
text = stringResource(R.string.chat_upper),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
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, // 使用主题文字颜色
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -29,11 +29,13 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|||||||
@Composable
|
@Composable
|
||||||
fun SelfProfileAction(
|
fun SelfProfileAction(
|
||||||
onEditProfile: () -> Unit,
|
onEditProfile: () -> Unit,
|
||||||
onPremiumClick: (() -> Unit)? = null
|
onPremiumClick: (() -> Unit),
|
||||||
|
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,
|
||||||
@@ -46,9 +48,9 @@ fun SelfProfileAction(
|
|||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.clip(RoundedCornerShape(8.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(AppColors.nonActive)
|
.background(AppColors.nonActive)
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
.padding(horizontal = 5.dp, vertical = 12.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
editProfileDebouncer {
|
editProfileDebouncer {
|
||||||
onEditProfile()
|
onEditProfile()
|
||||||
@@ -58,39 +60,82 @@ fun SelfProfileAction(
|
|||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.edit_profile),
|
text = stringResource(R.string.edit_profile),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
color = AppColors.text,
|
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(8.dp))
|
.clip(RoundedCornerShape(10.dp))
|
||||||
.background(AppColors.premiumBackground)
|
|
||||||
.padding(horizontal = 16.dp, vertical = 12.dp)
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
.noRippleClickable {
|
.noRippleClickable {
|
||||||
premiumClickDebouncer {
|
|
||||||
onPremiumClick?.invoke()
|
}
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = "",
|
||||||
|
fontSize = 14.sp,
|
||||||
|
fontWeight = FontWeight.W900,
|
||||||
|
color = AppColors.text,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分享按钮
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
|
.background(AppColors.nonActive)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 12.dp)
|
||||||
|
.noRippleClickable {
|
||||||
|
shareDebouncer {
|
||||||
|
// TODO: 添加分享逻辑
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
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(
|
||||||
text = "Rave Premium",
|
text = stringResource(R.string.share),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
fontWeight = FontWeight.W600,
|
fontWeight = FontWeight.W900,
|
||||||
color = AppColors.premiumText,
|
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,
|
||||||
|
// )
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.lazy.items
|
|||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
@@ -29,6 +30,7 @@ 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
|
||||||
@@ -44,7 +46,10 @@ 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(
|
||||||
@@ -196,6 +201,7 @@ fun UserAgentCard(
|
|||||||
@Composable
|
@Composable
|
||||||
fun EmptyAgentsView() {
|
fun EmptyAgentsView() {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -203,10 +209,11 @@ 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.qs_ai_qs_as_img
|
||||||
else R.mipmap.qs_ai_qs_img),
|
else R.mipmap.ai),
|
||||||
contentDescription = "暂无Agent",
|
contentDescription = "暂无Agent",
|
||||||
modifier = Modifier.size(181.dp),
|
modifier = Modifier.size(181.dp),
|
||||||
)
|
)
|
||||||
@@ -228,5 +235,37 @@ fun EmptyAgentsView() {
|
|||||||
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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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
|
||||||
@@ -28,6 +29,8 @@ 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
|
||||||
@@ -44,6 +47,7 @@ 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
|
||||||
@@ -53,6 +57,7 @@ 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
|
||||||
@@ -70,6 +75,7 @@ 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)
|
||||||
@@ -304,6 +310,7 @@ 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()
|
||||||
@@ -317,10 +324,13 @@ 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.syss_yh_qs_img),
|
else R.mipmap.invalid_name_1),
|
||||||
contentDescription = "No Comment",
|
contentDescription = "No Comment",
|
||||||
modifier = Modifier.size(140.dp)
|
modifier = Modifier.size(140.dp)
|
||||||
)
|
)
|
||||||
@@ -337,6 +347,33 @@ 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(
|
||||||
@@ -369,6 +406,7 @@ 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()
|
||||||
) {
|
) {
|
||||||
@@ -380,10 +418,13 @@ 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.syss_yh_qs_img),
|
else R.mipmap.invalid_name_1),
|
||||||
contentDescription = "No Comment",
|
contentDescription = "No Comment",
|
||||||
modifier = Modifier.size(140.dp)
|
modifier = Modifier.size(140.dp)
|
||||||
)
|
)
|
||||||
@@ -400,6 +441,33 @@ 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(
|
||||||
@@ -497,3 +565,42 @@ 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,15 +39,14 @@ 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() {
|
||||||
@@ -72,18 +71,47 @@ fun LikeNoticeScreen() {
|
|||||||
.background(color = AppColors.background)
|
.background(color = AppColors.background)
|
||||||
.padding(horizontal = 16.dp)
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val isNetworkAvailable = NetworkUtils.isNetworkAvailable(LocalContext.current)
|
||||||
|
|
||||||
|
if (!isNetworkAvailable) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxSize()
|
||||||
.fillMaxWidth()
|
.padding(top=149.dp),
|
||||||
.padding(vertical = 16.dp)
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
NoticeScreenHeader(
|
Column(
|
||||||
stringResource(R.string.like_upper),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
moreIcon = false
|
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)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (likes.itemCount == 0) {
|
} else if (likes.itemCount == 0) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
.padding(top=149.dp),
|
.padding(top=149.dp),
|
||||||
@@ -96,7 +124,7 @@ fun LikeNoticeScreen() {
|
|||||||
Image(
|
Image(
|
||||||
painter = painterResource(
|
painter = painterResource(
|
||||||
id =if(AppState.darkMode) R.mipmap.qst_z_qs_as_img
|
id =if(AppState.darkMode) R.mipmap.qst_z_qs_as_img
|
||||||
else R.mipmap.qst_z_qs_img),
|
else R.mipmap.invalid_name_6),
|
||||||
contentDescription = "No Notice",
|
contentDescription = "No Notice",
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -279,6 +279,7 @@ fun LoginPage() {
|
|||||||
contentDescription = "Rave Now",
|
contentDescription = "Rave Now",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(52.dp)
|
.size(52.dp)
|
||||||
|
.clip(RoundedCornerShape(10.dp))
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(8.dp))
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
64
app/src/main/java/com/aiosman/ravenow/ui/network/Reload.kt
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -799,7 +799,7 @@ fun CommentContent(
|
|||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.qs_plq_qs_img),
|
painter = painterResource(id = R.mipmap.invalid_name_3),
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.size(181.dp)
|
modifier = Modifier.size(181.dp)
|
||||||
)
|
)
|
||||||
@@ -917,7 +917,20 @@ fun Header(
|
|||||||
Text(
|
Text(
|
||||||
text = nickname ?: "",
|
text = nickname ?: "",
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier
|
||||||
|
.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
|
||||||
)
|
)
|
||||||
@@ -1196,7 +1209,7 @@ fun PostImageView(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Navigation and Indicator container
|
// 图片导航控件
|
||||||
if (images.size > 1) {
|
if (images.size > 1) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -1347,91 +1360,19 @@ 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
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Column(
|
||||||
Text(
|
horizontalAlignment = Alignment.End
|
||||||
text = commentEntity.date.timeAgo(context),
|
|
||||||
fontSize = 11.sp,
|
|
||||||
color = Color.Gray
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Row (modifier = Modifier.padding(top = 4.dp)){
|
|
||||||
if (isChild) {
|
|
||||||
val annotatedText = buildAnnotatedString {
|
|
||||||
if (commentEntity.replyUserId != null) {
|
|
||||||
pushStringAnnotation(
|
|
||||||
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() },
|
|
||||||
indication = null,
|
|
||||||
onLongClick = {
|
|
||||||
onLongClick(
|
|
||||||
commentEntity
|
|
||||||
)
|
|
||||||
},
|
|
||||||
) {
|
|
||||||
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Row (modifier = Modifier.padding(top = 12.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,){
|
|
||||||
AnimatedLikeIcon(
|
AnimatedLikeIcon(
|
||||||
liked = commentEntity.liked,
|
liked = commentEntity.liked,
|
||||||
onClick = {
|
onClick = {
|
||||||
@@ -1446,29 +1387,54 @@ fun CommentItem(
|
|||||||
},
|
},
|
||||||
modifier = Modifier.size(16.dp)
|
modifier = Modifier.size(16.dp)
|
||||||
)
|
)
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
|
||||||
Text(
|
Text(
|
||||||
text = commentEntity.likes.toString(),
|
text = commentEntity.likes.toString(),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
color = AppColors.text
|
color = AppColors.text,
|
||||||
|
modifier = Modifier.padding(top = 4.dp,end = 4.dp)
|
||||||
)
|
)
|
||||||
Text(
|
}
|
||||||
text = stringResource(R.string.like),
|
}
|
||||||
fontSize = 12.sp,
|
|
||||||
fontWeight = FontWeight.Bold,
|
|
||||||
color = AppColors.nonActiveText,
|
|
||||||
)
|
|
||||||
Spacer(modifier = Modifier.width(27.dp))
|
|
||||||
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.rider_pro_comment),
|
Text(
|
||||||
contentDescription = "",
|
text = commentEntity.comment,
|
||||||
modifier = Modifier.size(16.dp),
|
fontSize = 13.sp,
|
||||||
tint = AppColors.nonActiveText
|
maxLines = Int.MAX_VALUE,
|
||||||
|
softWrap = true,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
color = AppColors.text,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(end = 50.dp)
|
||||||
|
.padding(top = 0.dp)
|
||||||
|
.combinedClickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onLongClick = {
|
||||||
|
onLongClick(
|
||||||
|
commentEntity
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.width(4.dp))
|
},
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Row (
|
||||||
|
modifier = Modifier.padding(top = 12.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
){
|
||||||
|
Text(
|
||||||
|
text = commentEntity.date.timeAgo(context),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
color = Color.Gray
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = stringResource(R.string.reply),
|
text = stringResource(R.string.reply),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
@@ -1541,6 +1507,7 @@ fun CommentItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PostBottomBar(
|
fun PostBottomBar(
|
||||||
onCreateCommentClick: () -> Unit = {},
|
onCreateCommentClick: () -> Unit = {},
|
||||||
@@ -1607,6 +1574,24 @@ 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 = {
|
||||||
@@ -1623,24 +1608,6 @@ 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
|
||||||
|
|||||||
@@ -1,20 +1,19 @@
|
|||||||
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.width
|
import androidx.compose.foundation.layout.size
|
||||||
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
|
||||||
@@ -22,38 +21,36 @@ import com.aiosman.ravenow.R
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SplashScreen() {
|
fun SplashScreen() {
|
||||||
Scaffold {
|
|
||||||
it
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
// to bottom
|
// 居中的图标
|
||||||
Box(
|
Image(
|
||||||
contentAlignment = Alignment.TopCenter,
|
painter = painterResource(id = R.mipmap.invalid_name),
|
||||||
modifier = Modifier.padding(top = 211.dp)
|
contentDescription = "App Logo",
|
||||||
) {
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(120.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 底部文字
|
||||||
Column(
|
Column(
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
modifier = Modifier.fillMaxWidth()
|
verticalArrangement = Arrangement.Bottom,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.padding(bottom = 80.dp)
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
painter = painterResource(id = R.mipmap.rider_pro_logo),
|
painterResource(id = R.mipmap.kp_p_img),
|
||||||
contentDescription = "Rave Now",
|
contentDescription = "",
|
||||||
modifier = Modifier
|
modifier = Modifier.size(85.dp, 25.dp)
|
||||||
.width(108.dp)
|
|
||||||
.height(45.dp)
|
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(32.dp))
|
Spacer(modifier = Modifier.padding(top = 16.dp))
|
||||||
Text(
|
Text(
|
||||||
"Rave Now".uppercase(),
|
stringResource(R.string.splash_title),
|
||||||
fontSize = 28.sp,
|
fontSize = 13.sp
|
||||||
fontWeight = FontWeight.Bold
|
|
||||||
)
|
)
|
||||||
Text("Your Night Starts Here".uppercase(), fontSize = 20.sp, fontWeight = FontWeight.W700)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
<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>
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:aapt="http://schemas.android.com/aapt"
|
|
||||||
android:width="48dp"
|
|
||||||
android:height="48dp"
|
|
||||||
android:viewportWidth="48"
|
|
||||||
android:viewportHeight="48">
|
|
||||||
<path
|
|
||||||
android:pathData="M24,24m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
|
|
||||||
android:fillColor="#5E5CE6"
|
|
||||||
android:fillAlpha="0.1"
|
|
||||||
android:fillType="evenOdd"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M29.132,11.3c0.861,0.075 1.609,0.484 2.124,1.082a3.019,3.019 50,0 1,-0.378 4.325c-0.448,0.365 -0.987,0.6 -1.56,0.68l-0.02,0.002 0.057,0.035c1.649,0.997 2.914,2.401 3.789,4.009l0.041,0.076c1.115,2.087 1.576,4.508 1.374,6.807 -0.237,2.703 -1.392,4.636 -3.188,5.873a3.07,3.07 50,0 1,0.025 1.749c-0.168,0.58 -0.476,0.756 -0.578,0.791 -0.176,0.059 -1.855,-0.393 -2.957,-1.133 -1.289,0.249 -2.726,0.304 -4.271,0.168 -3.173,-0.276 -6.14,-1.296 -8.154,-3.002 -1.84,-1.559 -2.906,-3.682 -2.672,-6.345 0.261,-2.968 1.606,-5.945 3.885,-7.972 1.978,-1.761 4.657,-2.815 7.951,-2.529 0.608,0.053 1.187,0.148 1.738,0.28l0.039,0.009 -0.01,-0.013a3.013,3.013 50,0 1,-0.627 -2.061l0.004,-0.051c0.074,-0.845 0.492,-1.579 1.109,-2.083a3.163,3.163 50,0 1,2.281 -0.698z"
|
|
||||||
android:fillType="nonZero">
|
|
||||||
<aapt:attr name="android:fillColor">
|
|
||||||
<gradient
|
|
||||||
android:startX="23.669"
|
|
||||||
android:startY="11.286"
|
|
||||||
android:endX="23.669"
|
|
||||||
android:endY="36.734"
|
|
||||||
android:type="linear">
|
|
||||||
<item android:offset="0" android:color="#FF7C45ED"/>
|
|
||||||
<item android:offset="0.236" android:color="#FF7C68EF"/>
|
|
||||||
<item android:offset="1" android:color="#FF7BD8F8"/>
|
|
||||||
</gradient>
|
|
||||||
</aapt:attr>
|
|
||||||
</path>
|
|
||||||
<path
|
|
||||||
android:pathData="M17.51,22.904L17.736,22.924A0.912,0.912 50,0 1,18.565 23.912L18.419,25.584A0.912,0.912 50,0 1,17.431 26.413L17.205,26.393A0.912,0.912 50,0 1,16.376 25.405L16.522,23.733A0.912,0.912 50,0 1,17.51 22.904z"
|
|
||||||
android:fillColor="#FFF"
|
|
||||||
android:fillType="nonZero"/>
|
|
||||||
<path
|
|
||||||
android:pathData="M22.163,23.341L22.389,23.361A0.912,0.912 50,0 1,23.218 24.349L23.072,26.021A0.912,0.912 50,0 1,22.083 26.85L21.857,26.83A0.912,0.912 50,0 1,21.028 25.842L21.175,24.17A0.912,0.912 50,0 1,22.163 23.341z"
|
|
||||||
android:fillColor="#FFF"
|
|
||||||
android:fillType="nonZero"/>
|
|
||||||
</vector>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<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.185,2 l2.37,2.341 -6.518,6.44h-2.37V8.438zM2.667,13.707h10.667"
|
|
||||||
android:strokeLineJoin="round"
|
|
||||||
android:strokeWidth="1.185"
|
|
||||||
android:fillColor="#00000000"
|
|
||||||
android:fillType="evenOdd"
|
|
||||||
android:strokeColor="#000"
|
|
||||||
android:strokeLineCap="round"/>
|
|
||||||
</vector>
|
|
||||||
BIN
app/src/main/res/mipmap-hdpi/ai.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
app/src/main/res/mipmap-hdpi/bars_x_buttons_chat_s.png
Normal file
|
After Width: | Height: | Size: 773 B |
BIN
app/src/main/res/mipmap-hdpi/bars_x_buttons_discover_fill.png
Normal file
|
After Width: | Height: | Size: 927 B |
BIN
app/src/main/res/mipmap-hdpi/bars_x_buttons_home_n_copy.png
Normal file
|
After Width: | Height: | Size: 535 B |
BIN
app/src/main/res/mipmap-hdpi/bars_x_buttons_home_n_copy_2.png
Normal file
|
After Width: | Height: | Size: 772 B |
BIN
app/src/main/res/mipmap-hdpi/bars_x_buttons_user_s.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
app/src/main/res/mipmap-hdpi/btn.png
Normal file
|
After Width: | Height: | Size: 975 B |
BIN
app/src/main/res/mipmap-hdpi/group_copy.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/h_cj_rw_icon.png
Normal file
|
After Width: | Height: | Size: 601 B |
BIN
app/src/main/res/mipmap-hdpi/h_cj_x_img.png
Normal file
|
After Width: | Height: | Size: 365 B |
BIN
app/src/main/res/mipmap-hdpi/icons_circle_ai.png
Normal file
|
After Width: | Height: | Size: 836 B |
BIN
app/src/main/res/mipmap-hdpi/icons_circle_camera.png
Normal file
|
After Width: | Height: | Size: 919 B |
BIN
app/src/main/res/mipmap-hdpi/icons_info_magic.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
app/src/main/res/mipmap-hdpi/icons_infor_edit.png
Normal file
|
After Width: | Height: | Size: 294 B |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_1.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_10.png
Normal file
|
After Width: | Height: | Size: 2.1 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_11.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_12.png
Normal file
|
After Width: | Height: | Size: 3.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_2.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_3.png
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_4.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_5.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_6.png
Normal file
|
After Width: | Height: | Size: 3.7 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_7.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_8.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/invalid_name_9.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
app/src/main/res/mipmap-hdpi/kp_bj_img.png
Normal file
|
After Width: | Height: | Size: 96 KiB |
BIN
app/src/main/res/mipmap-hdpi/kp_logo_img.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/kp_p_img.png
Normal file
|
After Width: | Height: | Size: 852 B |
BIN
app/src/main/res/mipmap-mdpi/ai.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/bars_x_buttons_chat_s.png
Normal file
|
After Width: | Height: | Size: 581 B |
BIN
app/src/main/res/mipmap-mdpi/bars_x_buttons_discover_fill.png
Normal file
|
After Width: | Height: | Size: 634 B |
BIN
app/src/main/res/mipmap-mdpi/bars_x_buttons_home_n_copy.png
Normal file
|
After Width: | Height: | Size: 414 B |
BIN
app/src/main/res/mipmap-mdpi/bars_x_buttons_home_n_copy_2.png
Normal file
|
After Width: | Height: | Size: 602 B |
BIN
app/src/main/res/mipmap-mdpi/bars_x_buttons_user_s.png
Normal file
|
After Width: | Height: | Size: 421 B |
BIN
app/src/main/res/mipmap-mdpi/btn.png
Normal file
|
After Width: | Height: | Size: 660 B |
BIN
app/src/main/res/mipmap-mdpi/group_copy.png
Normal file
|
After Width: | Height: | Size: 768 B |
BIN
app/src/main/res/mipmap-mdpi/h_cj_rw_icon.png
Normal file
|
After Width: | Height: | Size: 465 B |
BIN
app/src/main/res/mipmap-mdpi/h_cj_x_img.png
Normal file
|
After Width: | Height: | Size: 327 B |
BIN
app/src/main/res/mipmap-mdpi/icons_circle_ai.png
Normal file
|
After Width: | Height: | Size: 621 B |
BIN
app/src/main/res/mipmap-mdpi/icons_circle_camera.png
Normal file
|
After Width: | Height: | Size: 697 B |
BIN
app/src/main/res/mipmap-mdpi/icons_info_magic.png
Normal file
|
After Width: | Height: | Size: 276 B |
BIN
app/src/main/res/mipmap-mdpi/icons_infor_edit.png
Normal file
|
After Width: | Height: | Size: 260 B |