新增聊天设置页面;自定义背景UI

This commit is contained in:
2025-10-31 11:57:49 +08:00
parent 00933dadb8
commit d7f87c7c55
8 changed files with 407 additions and 61 deletions

View File

@@ -41,6 +41,7 @@ import com.aiosman.ravenow.ui.agent.AddAgentScreen
import com.aiosman.ravenow.ui.agent.AgentImageCropScreen import com.aiosman.ravenow.ui.agent.AgentImageCropScreen
import com.aiosman.ravenow.ui.group.CreateGroupChatScreen import com.aiosman.ravenow.ui.group.CreateGroupChatScreen
import com.aiosman.ravenow.ui.chat.ChatAiScreen import com.aiosman.ravenow.ui.chat.ChatAiScreen
import com.aiosman.ravenow.ui.chat.ChatSettingScreen
import com.aiosman.ravenow.ui.chat.ChatScreen import com.aiosman.ravenow.ui.chat.ChatScreen
import com.aiosman.ravenow.ui.chat.GroupChatScreen import com.aiosman.ravenow.ui.chat.GroupChatScreen
import com.aiosman.ravenow.ui.comment.CommentsScreen import com.aiosman.ravenow.ui.comment.CommentsScreen
@@ -106,6 +107,7 @@ sealed class NavigationRoute(
data object FavouriteList : NavigationRoute("FavouriteList") data object FavouriteList : NavigationRoute("FavouriteList")
data object Chat : NavigationRoute("Chat/{id}") data object Chat : NavigationRoute("Chat/{id}")
data object ChatAi : NavigationRoute("ChatAi/{id}") data object ChatAi : NavigationRoute("ChatAi/{id}")
data object ChatSetting : NavigationRoute("ChatSetting")
data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}") data object ChatGroup : NavigationRoute("ChatGroup/{id}/{name}/{avatar}")
data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen") data object CommentNoticeScreen : NavigationRoute("CommentNoticeScreen")
data object ImageCrop : NavigationRoute("ImageCrop") data object ImageCrop : NavigationRoute("ImageCrop")
@@ -491,6 +493,14 @@ fun NavigationController(
} }
} }
composable(route = NavigationRoute.ChatSetting.route) {
CompositionLocalProvider(
LocalAnimatedContentScope provides this,
) {
ChatSettingScreen()
}
}
composable( composable(
route = NavigationRoute.ChatGroup.route, route = NavigationRoute.ChatGroup.route,
arguments = listOf(navArgument("id") { type = NavType.StringType }, arguments = listOf(navArgument("id") { type = NavType.StringType },

View File

@@ -79,11 +79,10 @@ import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.ChatItem import com.aiosman.ravenow.entity.ChatItem
import com.aiosman.ravenow.exp.formatChatTime import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.DropdownMenu
import com.aiosman.ravenow.ui.composables.MenuItem
import com.aiosman.ravenow.ui.composables.StatusBarSpacer import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils import com.aiosman.ravenow.utils.NetworkUtils
import com.aiosman.ravenow.ui.NavigationRoute
import io.openim.android.sdk.enums.MessageType import io.openim.android.sdk.enums.MessageType
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.UUID import java.util.UUID
@@ -91,7 +90,6 @@ import java.util.UUID
@Composable @Composable
fun ChatAiScreen(userId: String) { fun ChatAiScreen(userId: String) {
var isMenuExpanded by remember { mutableStateOf(false) }
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalNavController.current.context val context = LocalNavController.current.context
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -213,39 +211,12 @@ fun ChatAiScreen(userId: String) {
modifier = Modifier modifier = Modifier
.size(28.dp) .size(28.dp)
.noRippleClickable { .noRippleClickable {
isMenuExpanded = true navController.navigate(NavigationRoute.ChatSetting.route)
}, },
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
DropdownMenu(
expanded = isMenuExpanded,
onDismissRequest = {
isMenuExpanded = false
},
menuItems = listOf(
MenuItem(
title = if (viewModel.notificationStrategy == "mute") "Unmute" else "Mute",
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
) {
isMenuExpanded = false
if (NetworkUtils.isNetworkAvailable(context)) {
viewModel.viewModelScope.launch {
if (viewModel.notificationStrategy == "mute") {
viewModel.updateNotificationStrategy("active")
} else {
viewModel.updateNotificationStrategy("mute")
}
}
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
}
),
)
} }
} }
} }

View File

@@ -78,6 +78,7 @@ import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R import com.aiosman.ravenow.R
import com.aiosman.ravenow.entity.ChatItem import com.aiosman.ravenow.entity.ChatItem
import com.aiosman.ravenow.exp.formatChatTime import com.aiosman.ravenow.exp.formatChatTime
import com.aiosman.ravenow.ui.NavigationRoute
import com.aiosman.ravenow.ui.composables.CustomAsyncImage import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import com.aiosman.ravenow.ui.composables.DropdownMenu import com.aiosman.ravenow.ui.composables.DropdownMenu
import com.aiosman.ravenow.ui.composables.MenuItem import com.aiosman.ravenow.ui.composables.MenuItem
@@ -91,7 +92,6 @@ import java.util.UUID
@Composable @Composable
fun ChatScreen(userId: String) { fun ChatScreen(userId: String) {
var isMenuExpanded by remember { mutableStateOf(false) }
val navController = LocalNavController.current val navController = LocalNavController.current
val context = LocalNavController.current.context val context = LocalNavController.current.context
val AppColors = LocalAppTheme.current val AppColors = LocalAppTheme.current
@@ -214,39 +214,12 @@ fun ChatScreen(userId: String) {
modifier = Modifier modifier = Modifier
.size(28.dp) .size(28.dp)
.noRippleClickable { .noRippleClickable {
isMenuExpanded = true navController.navigate(NavigationRoute.ChatSetting.route)
}, },
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint( colorFilter = ColorFilter.tint(
AppColors.text) AppColors.text)
) )
DropdownMenu(
expanded = isMenuExpanded,
onDismissRequest = {
isMenuExpanded = false
},
menuItems = listOf(
MenuItem(
title = if (viewModel.notificationStrategy == "mute") "取消静音" else "静音",
icon = if (viewModel.notificationStrategy == "mute") R.drawable.rider_pro_notice_mute else R.drawable.rider_pro_notice_active,
) {
isMenuExpanded = false
if (NetworkUtils.isNetworkAvailable(context)) {
viewModel.viewModelScope.launch {
if (viewModel.notificationStrategy == "mute") {
viewModel.updateNotificationStrategy("active")
} else {
viewModel.updateNotificationStrategy("mute")
}
}
} else {
android.widget.Toast.makeText(context, "网络连接异常,请检查网络设置", android.widget.Toast.LENGTH_SHORT).show()
}
}
),
)
} }
} }
} }

View File

@@ -0,0 +1,356 @@
package com.aiosman.ravenow.ui.chat
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.composables.StatusBarSpacer
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.IconButton
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.text.style.TextAlign
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
@Composable
fun ChatSettingScreen() {
val appColors = LocalAppTheme.current
val navController = LocalNavController.current
var showThemeSheet by remember { mutableStateOf(false) }
Column(
modifier = Modifier
.fillMaxSize()
.background(appColors.secondaryBackground)
) {
StatusBarSpacer()
Box(modifier = Modifier.padding(horizontal = 24.dp, vertical = 16.dp)) {
NoticeScreenHeader(title = stringResource(R.string.chat_settings), moreIcon = false)
}
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
SettingCard(
title = stringResource(R.string.chat_theme_settings),
onClick = { showThemeSheet = true }
)
Spacer(modifier = Modifier.height(12.dp))
SettingCard(
title = stringResource(R.string.report),
onClick = { /* TODO: 跳转举报 */ }
)
}
}
if (showThemeSheet) {
ThemePickerSheet(onClose = { showThemeSheet = false })
}
}
@Composable
private fun SettingCard(title: String, onClick: () -> Unit) {
val appColors = LocalAppTheme.current
Box(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(appColors.background)
.clickable { onClick() }
.padding(horizontal = 12.dp, vertical = 14.dp)
) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.fillMaxWidth()) {
Text(
text = title,
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W500,
modifier = Modifier.weight(1f)
)
Icon(
painter = painterResource(id = R.drawable.rave_now_nav_right),
contentDescription = null,
tint = appColors.text,
modifier = Modifier.size(20.dp)
)
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun ThemePickerSheet(onClose: () -> Unit) {
val appColors = LocalAppTheme.current
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val sheetHeight = screenHeight * 0.9f
var previewUrl by remember { mutableStateOf<String?>(null) }
ModalBottomSheet(
onDismissRequest = onClose,
sheetState = sheetState,
containerColor = appColors.secondaryBackground,
dragHandle = null,
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight),
) {
Column(modifier = Modifier.padding(horizontal = 16.dp)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 4.dp, bottom = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Spacer(modifier = Modifier.size(24.dp))
Text(
text = stringResource(R.string.custom_background),
color = appColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600,
modifier = Modifier.weight(1f).padding(start = 100.dp),
)
IconButton(onClick = onClose) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_close),
contentDescription = "close",
tint = appColors.text
)
}
}
// 从相册选择
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(appColors.background)
.clickable { /* TODO: 打开相册选择 */ }
.padding(horizontal = 16.dp, vertical = 14.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(text = stringResource(R.string.select_from_gallery), color = appColors.text, fontSize = 15.sp, modifier = Modifier.weight(1f))
Icon(
painter = painterResource(id = R.drawable.group_info_edit),
contentDescription = null,
tint = appColors.text,
modifier = Modifier.size(20.dp)
)
}
Spacer(modifier = Modifier.height(12.dp))
Text(text = stringResource(R.string.featured_backgrounds), color = appColors.text, fontSize = 12.sp)
Spacer(modifier = Modifier.height(8.dp))
val presets = remember {
listOf(
"https://picsum.photos/seed/ai1/400/600",
"https://picsum.photos/seed/ai2/400/600",
"https://picsum.photos/seed/ai3/400/600",
"https://picsum.photos/seed/ai4/400/600",
"https://picsum.photos/seed/ai5/400/600",
"https://picsum.photos/seed/ai6/400/600",
"https://picsum.photos/seed/ai7/400/600",
"https://picsum.photos/seed/ai8/400/600",
"https://picsum.photos/seed/ai9/400/600",
"https://picsum.photos/seed/ai10/400/600",
"https://picsum.photos/seed/ai11/400/600",
"https://picsum.photos/seed/ai12/400/600",
"https://picsum.photos/seed/ai13/400/600",
"https://picsum.photos/seed/ai14/400/600",
"https://picsum.photos/seed/ai15/400/600",
)
}
LazyVerticalGrid(
columns = GridCells.Fixed(3),
horizontalArrangement = Arrangement.spacedBy(12.dp),
verticalArrangement = Arrangement.spacedBy(12.dp),
modifier = Modifier
.fillMaxWidth()
) {
items(presets) { url ->
Column(
modifier = Modifier
.fillMaxWidth()
.clickable { previewUrl = url }
) {
Box(
modifier = Modifier
.clip(RoundedCornerShape(12.dp))
.background(appColors.decentBackground)
) {
CustomAsyncImage(
imageUrl = url,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 4f),
contentDescription = "preset",
contentScale = ContentScale.Crop
)
}
Spacer(modifier = Modifier.height(6.dp))
Text(
text = "Heart Drive",
color = appColors.text,
fontSize = 12.sp
)
}
}
}
// 预览自定义背景弹窗
if (previewUrl != null) {
ModalBottomSheet(
onDismissRequest = { previewUrl = null },
sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true),
containerColor = appColors.secondaryBackground,
dragHandle = null,
modifier = Modifier
.fillMaxWidth()
.height(sheetHeight)
) {
Box(modifier = Modifier.fillMaxSize()) {
CustomAsyncImage(
imageUrl = previewUrl!!,
modifier = Modifier.fillMaxSize(),
contentDescription = "preview_bg",
contentScale = ContentScale.Crop
)
Column(modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp, vertical = 8.dp)) {
Box(
modifier = Modifier
.align(Alignment.CenterHorizontally)
.padding(horizontal = 12.dp, vertical = 16.dp)
) {
Text(text = stringResource(R.string.previewing_custom_background), color = Color.White, fontSize = 15.sp)
}
Column(modifier = Modifier.padding(8.dp)) {
Row {
Box(
modifier = Modifier
.clip(RoundedCornerShape(24.dp))
.background(Color.White)
.padding(horizontal = 14.dp, vertical = 8.dp)
) {
Text(text = stringResource(R.string.each_theme_unique_experience), color = Color.Black, fontSize = 12.sp)
}
}
Spacer(modifier = Modifier.height(12.dp))
Row {
Box(
modifier = Modifier
.clip(RoundedCornerShape(24.dp))
.background(Color.White)
.padding(horizontal = 14.dp, vertical = 8.dp)
) {
Text(text = stringResource(R.string.select_apply_to_use_theme), color = Color.Black, fontSize = 12.sp)
}
}
Spacer(modifier = Modifier.height(12.dp))
Row {
Spacer(modifier = Modifier.weight(1f))
Box(
modifier = Modifier
.clip(RoundedCornerShape(20.dp))
.background(Color(0xFF7C4DFF))
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
Text(text = stringResource(R.string.tap_cancel_to_preview_other_themes), color = Color.White, fontSize = 12.sp)
}
}
}
Spacer(modifier = Modifier.weight(1f))
// 底部按钮
Row(
modifier = Modifier.fillMaxWidth()
.padding(bottom = 60.dp),
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(12.dp))
.background(Color.White)
.clickable { previewUrl = null }
.padding(vertical = 12.dp),
contentAlignment = Alignment.Center
) {
Text(text = stringResource(R.string.cancel), color = Color.Black, fontSize = 14.sp)
}
Spacer(modifier = Modifier.size(12.dp))
Box(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(12.dp))
.background(
brush = Brush.horizontalGradient(
colors = listOf(
Color(0xFFEE2A33),
Color(0xFFD80264),
Color(0xFF664C92)
)
)
)
.clickable { }
.padding(vertical = 12.dp),
contentAlignment = Alignment.Center
) {
Text(text = stringResource(R.string.moment_ai_apply), color = Color.White, fontSize = 14.sp)
}
}
}
}
}
}
}
}
}

View File

@@ -230,6 +230,7 @@ fun MomentsList() {
when (it) { when (it) {
0 -> { 0 -> {
// 推荐页面 // 推荐页面
NewsScreen()
} }
1 -> { 1 -> {
// 短视频页面 // 短视频页面

View File

@@ -236,5 +236,17 @@
<string name="block_description_1">相手はあなたにメッセージを送信したり、あなたのプロフィールやコンテンツを見つけることができなくなります</string> <string name="block_description_1">相手はあなたにメッセージを送信したり、あなたのプロフィールやコンテンツを見つけることができなくなります</string>
<string name="block_description_2">相手はあなたにブロックされた通知を受け取りません</string> <string name="block_description_2">相手はあなたにブロックされた通知を受け取りません</string>
<string name="block_description_3">「設定」でいつでもブロックを解除できます</string> <string name="block_description_3">「設定」でいつでもブロックを解除できます</string>
<!-- Chat Settings -->
<string name="chat_settings">チャット設定</string>
<string name="chat_theme_settings">チャットテーマを設定</string>
<string name="custom_background">カスタム背景</string>
<string name="select_from_gallery">ギャラリーから選択</string>
<string name="featured_backgrounds">おすすめ背景</string>
<string name="previewing_custom_background">カスタム背景をプレビュー中</string>
<string name="each_theme_unique_experience">各テーマには独自の体験があります</string>
<string name="select_apply_to_use_theme">「適用」を選択してこのテーマを使用</string>
<string name="tap_cancel_to_preview_other_themes">「キャンセル」をタップして他のテーマをプレビュー</string>
</resources> </resources>

View File

@@ -240,4 +240,14 @@
<string name="block_description_2">对方不会收到自己被你拉黑的通知</string> <string name="block_description_2">对方不会收到自己被你拉黑的通知</string>
<string name="block_description_3">你可以随时"设置"中取消拉黑TA</string> <string name="block_description_3">你可以随时"设置"中取消拉黑TA</string>
<!-- Chat Settings -->
<string name="chat_settings">聊天设置</string>
<string name="chat_theme_settings">设置聊天主题</string>
<string name="custom_background">自定义背景</string>
<string name="select_from_gallery">从相册选择</string>
<string name="featured_backgrounds">精选背景</string>
<string name="previewing_custom_background">正在预览自定义背景</string>
<string name="each_theme_unique_experience">每个主题都有自己独特的体验</string>
<string name="select_apply_to_use_theme">选择"应用"可选中这个主题</string>
<string name="tap_cancel_to_preview_other_themes">轻触"取消"可预览其他主题</string>
</resources> </resources>

View File

@@ -234,4 +234,17 @@
<string name="block_description_1">They won\'t be able to send you messages or find your profile or content</string> <string name="block_description_1">They won\'t be able to send you messages or find your profile or content</string>
<string name="block_description_2">They won\'t receive a notification that you blocked them</string> <string name="block_description_2">They won\'t receive a notification that you blocked them</string>
<string name="block_description_3">You can unblock them anytime in "Settings"</string> <string name="block_description_3">You can unblock them anytime in "Settings"</string>
<!-- Chat Settings -->
<string name="chat_settings">Chat Settings</string>
<string name="chat_theme_settings">Set Chat Theme</string>
<string name="custom_background">Custom Background</string>
<string name="select_from_gallery">Select from Gallery</string>
<string name="featured_backgrounds">Featured Backgrounds</string>
<string name="previewing_custom_background">Previewing Custom Background</string>
<string name="each_theme_unique_experience">Each theme has its own unique experience</string>
<string name="select_apply_to_use_theme">Select "Apply" to use this theme</string>
<string name="tap_cancel_to_preview_other_themes">Tap "Cancel" to preview other themes</string>
</resources> </resources>