Merge branch 'main' into atm2

This commit is contained in:
2025-11-18 21:46:59 +08:00
87 changed files with 725 additions and 371 deletions

View File

@@ -0,0 +1,15 @@
package com.aiosman.ravenow.ui.account
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
@Composable
fun ZodiacBottomSheetHost() {
val show = ZodiacSheetManager.visible.collectAsState(false).value
if (show) {
ZodiacSelectBottomSheet(
onClose = { ZodiacSheetManager.close() }
)
}
}

View File

@@ -1,39 +1,64 @@
package com.aiosman.ravenow.ui.account
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.asPaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.itemsIndexed
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.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.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.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.unit.Velocity
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import com.aiosman.ravenow.AppState
import com.aiosman.ravenow.LocalAppTheme
import com.aiosman.ravenow.LocalNavController
import com.aiosman.ravenow.R
import com.aiosman.ravenow.ui.comment.NoticeScreenHeader
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
// 星座资源ID列表
val ZODIAC_SIGN_RES_IDS = listOf(
@@ -51,6 +76,27 @@ val ZODIAC_SIGN_RES_IDS = listOf(
R.string.zodiac_pisces
)
/**
* 根据星座资源ID获取对应的图片资源ID
*/
fun getZodiacImageResId(zodiacResId: Int): Int {
return when (zodiacResId) {
R.string.zodiac_aries -> R.mipmap.baiyang
R.string.zodiac_taurus -> R.mipmap.jingniu
R.string.zodiac_gemini -> R.mipmap.shuangzi
R.string.zodiac_cancer -> R.mipmap.juxie
R.string.zodiac_leo -> R.mipmap.shizi
R.string.zodiac_virgo -> R.mipmap.chunv
R.string.zodiac_libra -> R.mipmap.tiancheng
R.string.zodiac_scorpio -> R.mipmap.tianxie
R.string.zodiac_sagittarius -> R.mipmap.sheshou
R.string.zodiac_capricorn -> R.mipmap.moxie
R.string.zodiac_aquarius -> R.mipmap.shuiping
R.string.zodiac_pisces -> R.mipmap.shuangyu
else -> R.mipmap.xingzuo // 默认使用占位图片
}
}
/**
* 根据存储的星座字符串可能是任何语言找到对应的资源ID
* 如果找不到返回null
@@ -72,37 +118,173 @@ fun findZodiacResId(storedZodiac: String?): Int? {
return null
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ZodiacSelectScreen() {
val navController = LocalNavController.current
fun ZodiacSelectBottomSheet(
onClose: () -> Unit
) {
val appColors = LocalAppTheme.current
val isDarkMode = AppState.darkMode
val model = AccountEditViewModel
val currentZodiacResId = findZodiacResId(model.zodiac)
val sheetBackgroundColor = if (isDarkMode) {
appColors.secondaryBackground
} else {
Color(0xFFFFFFFF)
}
Column(
modifier = Modifier
.fillMaxSize()
.background(appColors.profileBackground)
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
// 确保弹窗展开
LaunchedEffect(Unit) {
sheetState.expand()
}
// 监听状态变化,确保弹窗始终展开(防止拖拽关闭和滑动)
LaunchedEffect(sheetState.currentValue, sheetState.targetValue, sheetState.isVisible) {
// 如果弹窗被拖拽关闭或位置发生变化,立即重新展开
if (!sheetState.isVisible || sheetState.targetValue != androidx.compose.material3.SheetValue.Expanded) {
kotlinx.coroutines.delay(10) // 短暂延迟确保状态更新
sheetState.expand()
}
}
val statusBarPadding = WindowInsets.systemBars.asPaddingValues()
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val offsetY = screenHeight * 0.07f - statusBarPadding.calculateTopPadding()
ModalBottomSheet(
onDismissRequest = onClose,
sheetState = sheetState,
containerColor = sheetBackgroundColor, // 根据主题自适应背景
dragHandle = null
) {
// 头部
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 16.dp)
.fillMaxHeight(0.95f)
.offset(y = offsetY)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 8.dp
)
) {
NoticeScreenHeader(
title = stringResource(R.string.choose_zodiac),
moreIcon = false
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
) {
// 头部 - 使用 Box 实现绝对居中布局
Box(
modifier = Modifier
.fillMaxWidth()
.height(48.dp),
contentAlignment = Alignment.Center
) {
val cancelButtonGradientColors = if (isDarkMode) {
listOf(
Color(0xFF3A3A3C),
Color(0xFF2C2C2E)
)
} else {
listOf(
Color(0xFFFFFFFF),
Color(0xFFF8F8F8)
)
}
val cancelButtonContentColor = if (isDarkMode) Color(0xFFFFFFFF) else Color(0xFF404040)
// 左上角返回按钮 - 根据 Swift 代码样式,带淡灰色渐变背景
Row(
modifier = Modifier
.align(Alignment.CenterStart)
.height(36.dp)
.clip(RoundedCornerShape(18.dp)) // 圆角 100.0 在 36dp 高度下接近完全圆角
.background(
brush = Brush.linearGradient(
colors = cancelButtonGradientColors
// 不指定 start 和 end默认从左上到右下
)
)
.noRippleClickable { onClose() }
.padding(horizontal = 8.dp), // 内部 padding 确保内容不贴边
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
// 左箭头图标
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = null,
modifier = Modifier.size(17.dp),
colorFilter = ColorFilter.tint(cancelButtonContentColor)
)
// "取消" 文字
Text(
text = "取消",
fontSize = 17.sp,
fontWeight = FontWeight.Medium,
color = cancelButtonContentColor,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
// 列表
LazyColumn(
modifier = Modifier.fillMaxSize(),
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 16.dp, vertical = 8.dp)
// 中间标题 - 绝对居中
Text(
text = stringResource(R.string.choose_zodiac),
color = appColors.text,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
Spacer(Modifier.height(12.dp))
// 创建 NestedScrollConnection 来阻止滚动事件向上传播到 ModalBottomSheet
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// 不消费任何事件,让 LazyVerticalGrid 先处理
return Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
// 消费 LazyVerticalGrid 处理后的剩余滚动事件,防止传递到 ModalBottomSheet
return available
}
override suspend fun onPreFling(available: Velocity): Velocity {
// 不消费惯性滚动,让 LazyVerticalGrid 先处理
return Velocity.Zero
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
// 消费 LazyVerticalGrid 处理后的剩余惯性滚动,防止传递到 ModalBottomSheet
return available
}
}
}
// 网格列表 - 2列
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
.fillMaxWidth()
.weight(1f)
.nestedScroll(nestedScrollConnection),
contentPadding = PaddingValues(
start = 8.dp,
top = 8.dp,
end = 8.dp,
bottom = 8.dp
),
horizontalArrangement = Arrangement.spacedBy(10.dp),
verticalArrangement = Arrangement.spacedBy(10.dp)
) {
items(ZODIAC_SIGN_RES_IDS.size) { index ->
val zodiacResId = ZODIAC_SIGN_RES_IDS[index]
itemsIndexed(ZODIAC_SIGN_RES_IDS) { index, zodiacResId ->
val zodiacText = stringResource(zodiacResId)
ZodiacItem(
zodiac = zodiacText,
@@ -115,13 +297,26 @@ fun ZodiacSelectScreen() {
AppState.UserId?.let { uid ->
com.aiosman.ravenow.AppStore.setUserZodiac(uid, zodiacText)
}
onClose()
}
)
}
}
}
}
}
}
// 保留原有的 ZodiacSelectScreen 用于导航路由(如果需要)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ZodiacSelectScreen() {
val navController = com.aiosman.ravenow.LocalNavController.current
ZodiacSelectBottomSheet(
onClose = {
navController.navigateUp()
}
)
Spacer(modifier = Modifier.height(8.dp))
}
}
}
}
@Composable
@@ -132,41 +327,58 @@ fun ZodiacItem(
onClick: () -> Unit
) {
val appColors = LocalAppTheme.current
val isDarkMode = AppState.darkMode
Box(
// 卡片背景色:浅灰色 (250, 249, 251)
// 暗色模式下使用比背景色更亮的颜色,以形成对比
val cardBackgroundColor = if (isDarkMode) {
Color(0xFF2A2A2A) // 比 secondaryBackground (0xFF1C1C1C) 更亮的灰色
} else {
Color(0xFFFAF9FB)
}
Column(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(16.dp))
.background(if (isSelected) appColors.main.copy(alpha = 0.1f) else Color.White)
.aspectRatio(1.1f) // 增加宽高比,使高度相对更低
.shadow(
elevation = if (isDarkMode) 8.dp else 2.dp, // 深色模式下更强的阴影
shape = RoundedCornerShape(21.dp),
spotColor = if (isDarkMode) Color.Black.copy(alpha = 0.5f) else Color.Black.copy(alpha = 0.1f)
)
.clip(RoundedCornerShape(21.dp))
.background(cardBackgroundColor)
.clickable(
indication = null,
interactionSource = remember { MutableInteractionSource() }
) {
onClick()
}
.padding(16.dp)
.padding(horizontal = 24.dp, vertical = 12.dp), // 减小垂直padding确保文本不被遮挡
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
// 星座图标 - 使用对应星座的图片
Box(
modifier = Modifier.size(100.dp),
contentAlignment = Alignment.Center
) {
Image(
painter = painterResource(id = getZodiacImageResId(zodiacResId)),
contentDescription = zodiac,
modifier = Modifier.size(100.dp)
)
}
// 星座名称 - 使用负间距让文本向上移动,与图标更靠近
Text(
text = zodiac,
fontSize = 17.sp,
fontWeight = FontWeight.Normal,
color = if (isSelected) appColors.main else appColors.text,
modifier = Modifier.weight(1f)
)
if (isSelected) {
Icon(
imageVector = Icons.Default.Check,
contentDescription = "Selected",
modifier = Modifier.size(20.dp),
tint = appColors.main
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = appColors.text,
textAlign = TextAlign.Center,
modifier = Modifier.offset(y = (-20).dp) // 负间距,让文本进一步向上移动
)
}
}
}
}

View File

@@ -0,0 +1,19 @@
package com.aiosman.ravenow.ui.account
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
object ZodiacSheetManager {
private val _visible = MutableStateFlow(false)
val visible: StateFlow<Boolean> = _visible.asStateFlow()
fun open() {
_visible.value = true
}
fun close() {
_visible.value = false
}
}

View File

@@ -75,6 +75,8 @@ import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
import android.widget.Toast
import java.io.File
import androidx.activity.compose.BackHandler
import com.aiosman.ravenow.ui.account.ZodiacBottomSheetHost
import com.aiosman.ravenow.ui.account.ZodiacSheetManager
/**
* 编辑用户资料界面
@@ -190,6 +192,9 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
darkIcons = !AppState.darkMode, // 根据暗色模式决定图标颜色
maskBoxBackgroundColor = Color.Transparent
) {
// 挂载星座选择弹窗
ZodiacBottomSheetHost()
Box(
modifier = Modifier
.fillMaxSize()
@@ -448,9 +453,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
iconResDark = R.mipmap.frame_4, // 星座暗色模式图标
iconResLight = R.mipmap.xingzuo, // 星座亮色模式图标
onClick = {
debouncedNavigation {
navController.navigate(NavigationRoute.ZodiacSelect.route)
}
ZodiacSheetManager.open()
}
)
}

View File

@@ -10,6 +10,7 @@ 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.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
@@ -34,8 +35,11 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.rememberTextMeasurer
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewModelScope
@@ -565,10 +569,27 @@ fun AddAgentScreen() {
.padding(horizontal = 16.dp)
.align(Alignment.Start)
) {
val density = LocalDensity.current
val textMeasurer = rememberTextMeasurer()
val autoLabel = stringResource(R.string.create_agent_auto)
val measuredTextWidth = remember(autoLabel, textMeasurer) {
textMeasurer.measure(
text = AnnotatedString(autoLabel),
style = TextStyle(
color = appColors.text,
fontWeight = FontWeight.W600,
fontSize = 14.sp
)
).size.width
}
val textWidthDp = with(density) { measuredTextWidth.toDp() }
val contentWidth = 24.dp + 18.dp + 8.dp + textWidthDp
val boxWidth = if (contentWidth > 140.dp) 250.dp else 140.dp
Box(
modifier = Modifier
.align(Alignment.Start)
.width(140.dp)
.width(boxWidth)
.height(40.dp)
.shadow(
elevation = 10.dp,
@@ -616,7 +637,7 @@ fun AddAgentScreen() {
)
Spacer(modifier = Modifier.width(8.dp))
Text(
text = stringResource(R.string.create_agent_auto),
text = autoLabel,
color = appColors.text,
fontWeight = FontWeight.W600,
fontSize = 14.sp

View File

@@ -21,6 +21,10 @@ import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -433,10 +437,10 @@ fun AddMemberAiAgentListScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
} else {
@@ -465,10 +469,10 @@ fun AddMemberAiAgentListScreen(
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载更多...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}
@@ -533,10 +537,10 @@ fun AddMemberFriendListScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
} else {
@@ -565,10 +569,10 @@ fun AddMemberFriendListScreen(
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载更多...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -11,6 +11,10 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -78,10 +82,10 @@ fun AiAgentListScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
} else {
@@ -111,10 +115,10 @@ fun AiAgentListScreen(
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载更多...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -11,6 +11,10 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
@@ -78,10 +82,10 @@ fun FriendListScreen(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
} else {
@@ -111,10 +115,10 @@ fun FriendListScreen(
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Text(
text = "加载更多...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -399,7 +399,7 @@ fun GroupChatInfoScreen(groupId: String) {
)
Spacer(modifier = Modifier.width(10.dp))
Text(
text = "群资料设置",
text = stringResource(R.string.group_chat_info_group_settings),
style = androidx.compose.ui.text.TextStyle(
color = AppColors.text,
fontSize = 15.sp

View File

@@ -41,6 +41,7 @@ import android.widget.Toast
import androidx.compose.ui.graphics.Brush
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.stringResource
@Composable
fun GroupMemoryManageContent(
@@ -79,7 +80,7 @@ fun GroupMemoryManageContent(
) {
// 中间标题 - 绝对居中,不受其他组件影响
Text(
text = "记忆管理",
text = stringResource(R.string.group_chat_info_memory_manage2),
style = TextStyle(
color = Color.Black,
fontSize = 17.sp,
@@ -107,7 +108,7 @@ fun GroupMemoryManageContent(
colorFilter = ColorFilter.tint(Color.Black)
)
Text(
text = "返回",
text = stringResource(R.string.back_upper),
style = TextStyle(
color = Color.Black,
fontSize = 15.sp,
@@ -148,7 +149,7 @@ fun GroupMemoryManageContent(
) {
Row(horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically) {
Row(verticalAlignment = Alignment.CenterVertically) {
Text("已付费:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Text(stringResource(R.string.memory_paid), style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"${quota?.purchasedCount ?: 0}",
@@ -156,7 +157,7 @@ fun GroupMemoryManageContent(
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("已使用:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Text(stringResource(R.string.memory_used), style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"${quota?.currentCount ?: 0}",
@@ -165,7 +166,7 @@ fun GroupMemoryManageContent(
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Text("可用上限:", style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Text(stringResource(R.string.upper_limit), style = TextStyle(color = Color(0x993C3C43), fontSize = 13.sp))
Spacer(Modifier.width(3.dp))
Text(
"50",
@@ -235,12 +236,12 @@ fun GroupMemoryManageContent(
Spacer(Modifier.height(10.dp))
Text(
text = "暂无记忆",
text = stringResource(R.string.no_memory),
style = TextStyle(color = Color.Black, fontSize = 16.sp, fontWeight = FontWeight.SemiBold)
)
Spacer(Modifier.height(6.dp))
Text(
text = "点击上方按钮添加群记忆",
text = stringResource(R.string.add_memory),
style = TextStyle(color = Color.Black, fontSize = 14.sp, fontWeight = FontWeight.Normal)
)
}

View File

@@ -70,6 +70,7 @@ import androidx.compose.ui.platform.LocalLayoutDirection
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.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@@ -574,38 +575,33 @@ fun SideMenuContent(
// 顶部状态栏间距
val statusBarHeight = WindowInsets.systemBars.asPaddingValues().calculateTopPadding()
// 扫一扫功能入口 - 右边距离右边66pt
// 扫一扫功能入口
Row(
modifier = Modifier
.align(Alignment.TopEnd)
.offset(x = (-112).dp, y = 88.dp)
.offset(x = (-60).dp, y = 88.dp)
.noRippleClickable {
// 扫一扫功能:跳转到扫码页面
coroutineScope.launch {
onClose()
navController.navigate(NavigationRoute.ScanQr.route)
}
},
horizontalArrangement = Arrangement.spacedBy(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 扫一扫图标(使用现有图标或占位)
Image(
painter = painterResource(id = R.mipmap.sao),
contentDescription = null,
modifier = Modifier.size(24.dp),
colorFilter = ColorFilter.tint(iconColor)
)
}
// 绝对定位的"扫一扫"文字上方71.5dp右侧66dp
Text(
text = stringResource(R.string.scan_qr),
fontSize = 14.sp,
color = textColor,
modifier = Modifier
.align(Alignment.TopEnd)
.offset(x = (-66).dp, y = 91.5.dp)
textAlign = TextAlign.Center
)
}
// QR码图标 - 右边距离右边112dp上边距离上边68pt
Image(
painter = painterResource(id = R.mipmap.qr_code_icon),

View File

@@ -91,6 +91,10 @@ import androidx.compose.ui.graphics.Brush
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.ui.platform.LocalConfiguration
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
// 检测是否接近列表底部的扩展函数
fun LazyListState.isScrolledToEnd(buffer: Int = 3): Boolean {
@@ -382,25 +386,19 @@ fun Agent() {
}
}
// 加载更多指示器(仅在展示发现更多时显示)
// 加载更多指示器(仅在展示"发现更多"时显示)
if (viewModel.isLoadingMore) {
item {
Row(
Box(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 24.dp),
horizontalArrangement = Arrangement.Center
contentAlignment = Alignment.Center
) {
androidx.compose.material3.CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.text,
strokeWidth = 2.dp
)
Spacer(modifier = Modifier.width(12.dp))
androidx.compose.material3.Text(
text = "加载中...",
color = AppColors.secondaryText,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}
@@ -920,26 +918,16 @@ fun ChatRoomCard(
modifier = Modifier
.size(120.dp)
.background(
color = AppColors.background,
color = Color.Transparent,
shape = RoundedCornerShape(12.dp)
),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(32.dp),
color = AppColors.main
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(96.dp)
)
Spacer(modifier = Modifier.height(12.dp))
androidx.compose.material3.Text(
text = "加入中...",
fontSize = 14.sp,
color = AppColors.text
)
}
}
}
}

View File

@@ -49,6 +49,10 @@ import com.aiosman.ravenow.ui.composables.rememberDebouncer
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
import com.aiosman.ravenow.utils.NetworkUtils
import com.aiosman.ravenow.ui.network.ReloadButton
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
/**
* 智能体聊天列表页面
@@ -187,9 +191,10 @@ fun AgentChatListScreen() {
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -35,6 +35,8 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.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
@@ -212,7 +214,7 @@ fun NewsCommentModal(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "${currentCommentCount}条评论",
text = stringResource(R.string.comment_count, currentCommentCount),
fontSize = 15.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text
@@ -237,6 +239,34 @@ fun NewsCommentModal(
.fillMaxWidth()
.weight(1f)
) {
val isCommentListEmpty = !commentViewModel.isLoading &&
currentCommentCount == 0 &&
commentViewModel.commentsList.isEmpty() &&
commentViewModel.addedCommentList.isEmpty()
if (isCommentListEmpty) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(bottom = 32.dp),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Image(
painter = painterResource(id = R.mipmap.invalid_name_3),
contentDescription = null,
modifier = Modifier
.width(160.dp)
.height(130.dp)
)
Spacer(modifier = Modifier.height(12.dp))
Text(
text = stringResource(R.string.news_no_comments),
fontSize = 15.sp,
color = AppColors.text
)
}
} else {
LazyColumn {
item {
CommentContent(
@@ -258,6 +288,7 @@ fun NewsCommentModal(
}
}
}
}
// 底部输入栏
Column(

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -401,7 +402,7 @@ fun NewsItem(
count = "",
isActive = false,
text = stringResource(R.string.share),
textSize = 8.sp
textSize = 8.sp,
)
}
}
@@ -425,7 +426,7 @@ fun NewsActionButton(
Row(
modifier = modifier
.width(60.dp)
.defaultMinSize(minWidth = 60.dp)
.background(
color = AppColors.secondaryBackground,
shape = RoundedCornerShape(16.dp)
@@ -449,7 +450,8 @@ fun NewsActionButton(
Spacer(modifier = Modifier.width(4.dp))
Text(
text = count,
fontSize = 12.sp,
fontSize = 8.sp,
fontWeight = FontWeight.Bold,
color = AppColors.text
)
}
@@ -458,7 +460,8 @@ fun NewsActionButton(
Text(
text = text,
fontSize = textSize,
color = AppColors.text
color = AppColors.text,
fontWeight = FontWeight.Bold
)
}
}

View File

@@ -6,10 +6,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.VerticalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -75,19 +79,13 @@ fun RecommendScreen() {
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally
) {
CircularProgressIndicator(color = AppColors.main)
Text(
text = "加载中...",
modifier = Modifier.padding(top = 16.dp),
color = AppColors.text,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(96.dp)
)
}
}
}
// 错误状态
errorMessage != null && recommendationsList.isEmpty() -> {

View File

@@ -3,11 +3,13 @@ package com.aiosman.ravenow.ui.index.tabs.moment.tabs.shorts
import android.util.Log
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -77,21 +79,13 @@ fun ShortVideoScreen(
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = androidx.compose.foundation.layout.Arrangement.Center
) {
CircularProgressIndicator(color = AppColors.main)
Text(
text = "加载中...",
modifier = Modifier.padding(top = 16.dp),
color = AppColors.text,
fontSize = 14.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(96.dp)
)
}
}
}
// 错误状态
else if (momentLoader.error != null && videoMoments.isEmpty()) {
Box(
@@ -130,10 +124,10 @@ fun ShortVideoScreen(
.background(AppColors.background),
contentAlignment = Alignment.Center
) {
Text(
text = "准备加载...",
color = AppColors.text,
fontSize = 16.sp
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -683,19 +683,27 @@ fun TopNavigationBar(
// 仅本人主页显示积分:收集全局积分
val pointsBalanceState = if (isSelf) PointService.pointsBalance.collectAsState(initial = null) else null
// 响应式监听暗色模式变化 - 通过 LocalAppTheme 的变化来触发重组
// 当 AppState.darkMode 改变时AppState.appTheme 也会改变,从而触发 LocalAppTheme 更新
val darkMode = AppState.darkMode
// 根据背景透明度和暗色模式决定图标颜色
// 暗色模式下:图标始终为白色
// 亮色模式下根据背景透明度决定透明度为1时变黑否则为白色
val iconColor = if (AppState.darkMode) {
val iconColor = remember(darkMode, backgroundAlpha) {
if (darkMode) {
Color.White // 暗色模式下图标始终为白色
} else {
if (backgroundAlpha >= 1f) Color.Black else Color.White
}
val cardBorderColor = if (AppState.darkMode) {
}
val cardBorderColor = remember(darkMode, backgroundAlpha) {
if (darkMode) {
Color.White.copy(alpha = 0.35f) // 暗色模式下使用半透明白色,避免整体变白
} else {
if (backgroundAlpha >= 1f) Color.Black else Color.White
}
}
Box(
modifier = Modifier
@@ -709,10 +717,10 @@ fun TopNavigationBar(
val totalHeight = statusBarHeight + navigationBarHeight
// 根据滚动位置计算背景颜色,从透明逐渐变为实色填充,尽快完成
val toolbarBackgroundColor = remember(backgroundAlpha) {
val toolbarBackgroundColor = remember(backgroundAlpha, darkMode) {
val progress = backgroundAlpha.coerceIn(0f, 1f)
if (AppState.darkMode) {
if (darkMode) {
// 暗色模式下:从透明逐渐变为黑色实色填充
Color.Black.copy(alpha = progress)
} else {
@@ -744,9 +752,9 @@ fun TopNavigationBar(
// 左侧:互动数据卡片(仅自己的界面显示)
if (isSelf) {
// 根据 toolbar 背景透明度动态调整卡片背景
val cardBackgroundColor = remember(backgroundAlpha) {
val cardBackgroundColor = remember(backgroundAlpha, darkMode) {
val smoothProgress = backgroundAlpha.coerceIn(0f, 1f)
if (AppState.darkMode) {
if (darkMode) {
// 暗色模式:保持在半透明灰白区间,避免滚动到顶部后变纯白
val minAlpha = 0.18f
val maxAlpha = 0.35f
@@ -786,7 +794,7 @@ fun TopNavigationBar(
text = pointsBalanceState?.value?.balance?.let { numberFormat.format(it) } ?: "--",
fontSize = 14.sp,
fontWeight = FontWeight.W500,
color = if (AppState.darkMode) Color.White else Color.Black, // 暗色模式下为白色,亮色模式下为黑色
color = remember(darkMode) { if (darkMode) Color.White else Color.Black }, // 暗色模式下为白色,亮色模式下为黑色
textAlign = TextAlign.Center
)
}
@@ -839,7 +847,7 @@ fun TopNavigationBar(
verticalAlignment = Alignment.CenterVertically
) {
// 返回按钮:深色模式下为白色,亮色模式下为黑色
val backButtonColor = if (AppState.darkMode) Color.White else Color.Black
val backButtonColor = remember(darkMode) { if (darkMode) Color.White else Color.Black }
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = "Back",

View File

@@ -25,6 +25,10 @@ import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.rememberLottieComposition
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
@@ -184,9 +188,10 @@ fun GroupChatEmptyContent(
.padding(16.dp),
contentAlignment = Alignment.Center
) {
CircularProgressIndicator(
modifier = Modifier.size(24.dp),
color = AppColors.main
LottieAnimation(
composition = rememberLottieComposition(LottieCompositionSpec.Asset("star_Loader.lottie")).value,
iterations = LottieConstants.IterateForever,
modifier = Modifier.size(80.dp)
)
}
}

View File

@@ -10,10 +10,12 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Text
@@ -102,40 +104,21 @@ fun UserItem(
// 统计数据
Row(
modifier = Modifier.weight(1f),
modifier = Modifier
.weight(1f)
.fillMaxWidth(),
horizontalArrangement = Arrangement.spacedBy(0.dp),
verticalAlignment = Alignment.CenterVertically
) {
// 帖子数
Column(
modifier = Modifier
.width(80.dp)
.height(40.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = postCount.toString(),
fontWeight = FontWeight.Medium,
fontSize = 15.sp,
color = AppColors.text,
textAlign = TextAlign.Center
StatColumn(
modifier = Modifier.weight(1f),
value = postCount.toString(),
label = stringResource(R.string.posts)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = stringResource(R.string.posts),
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
color = AppColors.text,
textAlign = TextAlign.Center
)
}
// 粉丝数
Column(
StatColumn(
modifier = Modifier
.width(80.dp)
.height(40.dp)
.weight(1f)
.noRippleClickable {
followerDebouncer {
navController.navigate(
@@ -146,32 +129,13 @@ fun UserItem(
)
}
},
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = formattedFollowerCount,
fontWeight = FontWeight.Medium,
fontSize = 15.sp,
color = AppColors.text,
textAlign = TextAlign.Center
value = formattedFollowerCount,
label = stringResource(R.string.followers_upper)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = stringResource(R.string.followers_upper),
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
color = AppColors.text,
textAlign = TextAlign.Center
)
}
// 关注数
Column(
StatColumn(
modifier = Modifier
.width(80.dp)
.height(40.dp)
.offset(x = 6.dp)
.weight(1f)
.noRippleClickable {
followingDebouncer {
navController.navigate(
@@ -182,25 +146,9 @@ fun UserItem(
)
}
},
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = accountProfileEntity.followingCount.toString(),
fontWeight = FontWeight.Medium,
fontSize = 15.sp,
color = AppColors.text,
textAlign = TextAlign.Center
value = accountProfileEntity.followingCount.toString(),
label = stringResource(R.string.following_upper)
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = stringResource(R.string.following_upper),
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
color = AppColors.text,
textAlign = TextAlign.Center
)
}
}
}
@@ -290,6 +238,38 @@ fun UserItem(
}
}
@Composable
private fun StatColumn(
modifier: Modifier = Modifier,
value: String,
label: String
) {
val AppColors = LocalAppTheme.current
Column(
modifier = modifier
.heightIn(min = 40.dp)
.wrapContentWidth(Alignment.CenterHorizontally),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = value,
fontWeight = FontWeight.Medium,
fontSize = 15.sp,
color = AppColors.text,
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.height(2.dp))
Text(
text = label,
fontWeight = FontWeight.Normal,
fontSize = 11.sp,
color = AppColors.text,
textAlign = TextAlign.Center
)
}
}
@Composable
private fun ProfileTag(
text: String,

View File

@@ -274,14 +274,14 @@ fun HistorySection(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = "历史搜索",
text = stringResource(R.string.recent_search),
color = AppColors.text,
fontSize = 16.sp,
fontWeight = FontWeight.W600
)
Spacer(modifier = Modifier.weight(1f))
Text(
text = "清空",
text = stringResource(R.string.clear),
color = AppColors.secondaryText,
fontSize = 14.sp,
modifier = Modifier.noRippleClickable { onClear() }

View File

@@ -18,6 +18,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet
@@ -33,7 +34,6 @@ import android.graphics.BitmapFactory
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
@@ -112,7 +112,7 @@ fun DraftBoxBottomSheet(
contentAlignment = Alignment.Center
) {
Text(
text = "暂无草稿",
text = stringResource(R.string.no_drafts),
fontSize = 16.sp,
color = AppColors.secondaryText
)
@@ -122,7 +122,7 @@ fun DraftBoxBottomSheet(
modifier = Modifier
.fillMaxWidth()
.weight(1f),
verticalArrangement = Arrangement.spacedBy(12.dp)
verticalArrangement = Arrangement.spacedBy(0.dp)
) {
itemsIndexed(drafts) { index, draft ->
DraftItem(
@@ -141,12 +141,22 @@ fun DraftBoxBottomSheet(
AppColors = AppColors,
context = context
)
// 在草稿项之间添加分割线(最后一个不添加)
if (index < drafts.size - 1) {
Spacer(modifier = Modifier.height(12.dp))
Divider(
color = AppColors.secondaryText.copy(alpha = 0.2f),
thickness = 0.5.dp,
modifier = Modifier.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.height(12.dp))
}
}
}
// 底部提示
Text(
text = "仅保存最近5个草稿",
text = stringResource(R.string.only_save_the_last_5_drafts),
fontSize = 12.sp,
color = AppColors.secondaryText,
modifier = Modifier
@@ -172,7 +182,7 @@ private fun DraftItem(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(12.dp))
.background(Color.White)
.background(AppColors.secondaryBackground)
.padding(12.dp)
) {
Column {
@@ -206,7 +216,8 @@ private fun DraftItem(
DraftImageThumbnail(
imageItem = imageItem,
context = context,
modifier = Modifier.size(55.dp)
modifier = Modifier.size(55.dp),
AppColors = AppColors
)
}
@@ -216,7 +227,7 @@ private fun DraftItem(
modifier = Modifier
.size(55.dp)
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFAF9FB)),
.background(AppColors.inputBackground),
contentAlignment = Alignment.Center
) {
Text(
@@ -250,7 +261,7 @@ private fun DraftItem(
verticalAlignment = Alignment.CenterVertically
) {
Icon(
painter = painterResource(id = R.drawable.rider_pro_moment_apply),
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = "edit",
modifier = Modifier.size(16.dp),
tint = AppColors.text
@@ -297,12 +308,13 @@ private fun DraftItem(
private fun DraftImageThumbnail(
imageItem: DraftImageItem,
context: android.content.Context,
modifier: Modifier = Modifier
modifier: Modifier = Modifier,
AppColors: AppThemeData
) {
Box(
modifier = modifier
.clip(RoundedCornerShape(12.dp))
.background(Color(0xFFFAF9FB))
) {
val file = File(context.cacheDir, imageItem.filename)
if (file.exists()) {
@@ -338,7 +350,7 @@ private fun DraftImageThumbnail(
painter = painterResource(id = R.drawable.rider_pro_new_post_add_pic),
contentDescription = "image",
modifier = Modifier.size(24.dp),
tint = Color.Gray
tint = AppColors.secondaryText
)
}
}

View File

@@ -221,7 +221,7 @@ fun NewPostScreen() {
e.printStackTrace()
Toast.makeText(
context,
"文案优化失败:${e.message ?: "请稍后重试"}",
"${e.message ?: "请稍后重试"}",
Toast.LENGTH_SHORT
).show()
} finally {
@@ -345,6 +345,7 @@ fun NewPostScreen() {
// 底部背景图
if (isRequesting) {
if (!AppState.darkMode) {
Image(
painter = painterResource(id = R.mipmap.component),
contentDescription = null,
@@ -355,6 +356,7 @@ fun NewPostScreen() {
.offset(y = (-40).dp),
contentScale = ContentScale.FillBounds
)
}
// 加载动画显示在背景图上方按钮下方80dp区域
Box(

View File

@@ -106,6 +106,11 @@ data class Draft(
val createdAt: Long = System.currentTimeMillis()
)
object NewPostViewModel : ViewModel() {
private data class DraftSnapshot(
val textContent: String,
val images: List<DraftImageItem>
)
var momentService: MomentService = MomentServiceImpl()
var textContent by mutableStateOf("")
var aiTextContent by mutableStateOf("")
@@ -117,6 +122,7 @@ object NewPostViewModel : ViewModel() {
var currentPhotoUri: Uri? = null
var draft: Draft? = null
private var draftSaved = false // 标记草稿是否已保存,避免重复保存
private var lastLoadedDraftSnapshot: DraftSnapshot? = null
// watch textContent change and save draft
// fun saveDraft() {
// draft = Draft(textContent, imageList.map {
@@ -131,6 +137,7 @@ object NewPostViewModel : ViewModel() {
imageList = listOf()
relPostId = null
draftSaved = false // 重置保存标志
lastLoadedDraftSnapshot = null
}
fun asNewPostWithImageUris(imageUris: List<String>) {
@@ -214,13 +221,24 @@ object NewPostViewModel : ViewModel() {
return
}
val currentSnapshot = DraftSnapshot(
textContent = textContent,
images = imageList.map { DraftImageItem.fromImageItem(it) }
)
if (lastLoadedDraftSnapshot == currentSnapshot) {
draftSaved = true
return
}
val draftStore = DraftStore(context)
val draft = Draft(
textContent = textContent,
imageList = imageList.map { DraftImageItem.fromImageItem(it) }
textContent = currentSnapshot.textContent,
imageList = currentSnapshot.images
)
draftStore.saveDraft(draft)
draftSaved = true // 标记已保存
lastLoadedDraftSnapshot = currentSnapshot
}
/**
@@ -230,6 +248,10 @@ object NewPostViewModel : ViewModel() {
textContent = draft.textContent
aiTextContent = ""
draftSaved = false // 加载草稿后重置保存标志,允许重新保存
lastLoadedDraftSnapshot = DraftSnapshot(
textContent = draft.textContent,
images = draft.imageList
)
// 从草稿的图片URI恢复图片列表
imageList = withContext(Dispatchers.IO) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -210,7 +210,16 @@
<string name="text_error_password_too_long">パスワードは%1$d文字を超えることはできません</string>
<string name="block">ブロック</string>
<string name="read_full_article">全文を読む</string>
<string name="news_no_comments">まだ誰もノックしてないよ</string>
<string name="drafts">ドラフトボックス</string>
<string name="no_drafts">草稿はしばらくありません</string>
<string name="only_save_the_last_5_drafts">最後の5つのドラフトのみ保存</string>
<string name="memory_paid">支払い済み:</string>
<string name="memory_used">使用済み:</string>
<string name="upper_limit">利用可能上限:</string>
<string name="no_memory">記憶がありません</string>
<string name="add_memory">上部のボタンをクリックしてグループ記憶を追加してください</string>
<string name="recent_search">けんさくりれき</string>
<!-- Create Bottom Sheet -->
<string name="create_title">作成</string>
@@ -274,6 +283,7 @@
<string name="group_chat_info_memory_description">AIは記憶に基づいてグループチャットであなたをより理解します</string>
<string name="group_chat_info_add_memory">メモリを追加</string>
<string name="group_chat_info_memory_manage">メモリ管理</string>
<string name="group_chat_info_memory_manage2">メモリ管理</string>
<string name="group_chat_info_group_settings">グループ設定</string>
<string name="group_chat_info_group_visibility">グループの可視性</string>
<string name="group_chat_info_locked">ロック中</string>

View File

@@ -70,7 +70,11 @@
<string name="new_password_tip1">请输入新密码</string>
<string name="confirm_new_password">确认新密码</string>
<string name="confirm_new_password_tip1">请确认新密码</string>
<string name="memory_paid">已付费:</string>
<string name="memory_used">已使用:</string>
<string name="upper_limit">可用上限:</string>
<string name="no_memory">暂无记忆</string>
<string name="add_memory">点击上方按钮添加群记忆</string>
<string name="cancel">取消</string>
<string name="bio">个性签名</string>
@@ -213,7 +217,11 @@
<string name="chatting_now">人正在热聊…</string>
<string name="block">拉黑</string>
<string name="read_full_article">查看全文</string>
<string name="news_no_comments">还没有人敲门</string>
<string name="drafts">草稿箱</string>
<string name="no_drafts">暂无草稿</string>
<string name="only_save_the_last_5_drafts">仅保存最近5个草稿</string>
<string name="recent_search">历史搜索</string>
<!-- Create Bottom Sheet -->
<string name="create_title">创建</string>
@@ -277,6 +285,7 @@
<string name="group_chat_info_memory_description">AI 会根据记忆在群聊里更懂你</string>
<string name="group_chat_info_add_memory">添加记忆</string>
<string name="group_chat_info_memory_manage">记忆管理</string>
<string name="group_chat_info_memory_manage2">记忆管理</string>
<string name="group_chat_info_group_settings">群资料设置</string>
<string name="group_chat_info_group_visibility">群可见性</string>
<string name="group_chat_info_locked">待解锁</string>
@@ -367,21 +376,6 @@
<string name="complete_tasks_desc">完成任务可获得奖励</string>
<string name="recharge_pai_coin">充值派币</string>
<string name="recharge_pai_coin_desc">多种套餐可选,立即充值</string>
<!-- Transaction History Reasons -->
<string name="earn_register">新用户注册奖励</string>
<string name="earn_daily">每日签到奖励</string>
<string name="earn_task">任务完成奖励</string>
<string name="earn_invite">邀请好友奖励</string>
<string name="earn_recharge">充值获得</string>
<string name="spend_group_create">创建群聊</string>
<string name="spend_group_expand">扩容群聊</string>
<string name="spend_agent_private">Agent 私密模式</string>
<string name="spend_agent_memory">Agent 记忆添加</string>
<string name="spend_room_memory">房间记忆添加</string>
<string name="spend_chat_background">自定义聊天背景</string>
<string name="spend_schedule_event">定时事件解锁</string>
<string name="create_group_chat_failed">创建群聊失败: %1$s</string>
<string name="create_group_chat_confirm_title">创建群聊确认</string>
<string name="create_group_chat_required_cost">需要消耗:</string>
@@ -401,6 +395,20 @@
<string name="explore">去探索</string>
<string name="reply_to_user">回复@%1$s</string>
<string name="error_select_at_least_one_image">请至少选择一张图片</string>
<!-- Transaction History Reasons -->
<string name="earn_register">新用户注册奖励</string>
<string name="earn_daily">每日签到奖励</string>
<string name="earn_task">任务完成奖励</string>
<string name="earn_invite">好友邀请奖励</string>
<string name="earn_recharge">充值</string>
<string name="spend_group_create">群聊创建</string>
<string name="spend_group_expand">群聊扩展</string>
<string name="spend_agent_private">智能体私密模式</string>
<string name="spend_agent_memory">智能体记忆添加</string>
<string name="spend_room_memory">房间记忆添加</string>
<string name="spend_chat_background">自定义聊天背景</string>
<string name="spend_schedule_event">日程事件解锁</string>
<string name="awaiting_traveler">等一位旅人~</string>
<string name="no_one_pinged_yet">还没有人来打扰你</string>
<string name="cosmos_awaits">小宇宙等你探索。</string>

View File

@@ -209,7 +209,16 @@
<string name="text_error_password_too_long">Password cannot exceed %1$d characters</string>
<string name="block">Block</string>
<string name="read_full_article">Read full article</string>
<string name="news_no_comments">No ones knocked yet</string>
<string name="drafts">drafts</string>
<string name="no_drafts">No draft</string>
<string name="only_save_the_last_5_drafts">Only save the last 5 drafts</string>
<string name="memory_paid">Paid:</string>
<string name="memory_used">Used:</string>
<string name="upper_limit">Available Limit:</string>
<string name="no_memory">No memory yet</string>
<string name="add_memory">Click the button above to add group memory</string>
<string name="recent_search">Recent Search</string>
<!-- Create Bottom Sheet -->
<string name="create_title">Create</string>
@@ -273,6 +282,7 @@
<string name="group_chat_info_memory_description">AI will understand you better in group chat based on memory</string>
<string name="group_chat_info_add_memory">Add Memory</string>
<string name="group_chat_info_memory_manage">Memory Management</string>
<string name="group_chat_info_memory_manage2">Memory</string>
<string name="group_chat_info_group_settings">Group Settings</string>
<string name="group_chat_info_group_visibility">Group Visibility</string>
<string name="group_chat_info_locked">Locked</string>
@@ -361,21 +371,6 @@
<string name="complete_tasks_desc">Complete tasks to earn rewards</string>
<string name="recharge_pai_coin">Recharge Pai Coin</string>
<string name="recharge_pai_coin_desc">Multiple packages available, recharge now</string>
<!-- Transaction History Reasons -->
<string name="earn_register">New User Registration Reward</string>
<string name="earn_daily">Daily Check-in Reward</string>
<string name="earn_task">Task Completion Reward</string>
<string name="earn_invite">Invite Friends Reward</string>
<string name="earn_recharge">Recharge</string>
<string name="spend_group_create">Create Group Chat</string>
<string name="spend_group_expand">Expand Group Chat</string>
<string name="spend_agent_private">Agent Private Mode</string>
<string name="spend_agent_memory">Agent Memory Added</string>
<string name="spend_room_memory">Room Memory Added</string>
<string name="spend_chat_background">Custom Chat Background</string>
<string name="spend_schedule_event">Scheduled Event Unlocked</string>
<string name="create_group_chat_failed">Failed to create group chat: %1$s</string>
<string name="create_group_chat_confirm_title">Create Group Chat</string>
<string name="create_group_chat_required_cost">Required consumption:</string>
@@ -395,6 +390,20 @@
<string name="explore">Explore</string>
<string name="reply_to_user">Reply @%1$s</string>
<string name="error_select_at_least_one_image">Please select at least one image</string>
<!-- Transaction History Reasons -->
<string name="earn_register">New User Registration Reward</string>
<string name="earn_daily">Daily Check-in Reward</string>
<string name="earn_task">Task Completion Reward</string>
<string name="earn_invite">Friend Invitation Reward</string>
<string name="earn_recharge">Recharge</string>
<string name="spend_group_create">Group Chat Creation</string>
<string name="spend_group_expand">Group Chat Expansion</string>
<string name="spend_agent_private">Agent Private Mode</string>
<string name="spend_agent_memory">Agent Memory Addition</string>
<string name="spend_room_memory">Room Memory Addition</string>
<string name="spend_chat_background">Custom Chat Background</string>
<string name="spend_schedule_event">Schedule Event Unlock</string>
<string name="awaiting_traveler">Awaiting a traveler</string>
<string name="no_one_pinged_yet">No one has pinged you yet</string>
<string name="cosmos_awaits">Your cosmos awaits.</string>