1 Commits

6 changed files with 357 additions and 338 deletions

View File

@@ -20,11 +20,13 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
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.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@@ -47,6 +49,7 @@ 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
@@ -107,48 +110,35 @@ fun MbtiSelectBottomSheet(
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
containerColor = Color.Transparent,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
dragHandle = {}
) {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.95f)
.offset(y = offsetY)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 8.dp
)
.fillMaxHeight()
.padding(top = 8.dp)
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)),
color = sheetBackgroundColor,
tonalElevation = 0.dp,
shadowElevation = 0.dp,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
// 头部 - 使用 Box 实现绝对居中布局
// 头部
Box(
modifier = Modifier
.fillMaxWidth()
@@ -168,23 +158,27 @@ fun MbtiSelectBottomSheet(
}
val cancelButtonContentColor = if (isDarkMode) Color(0xFFFFFFFF) else Color(0xFF404040)
// 左上角返回按钮
Row(
// 左上角返回按钮:整体「箭头 + 取消」在按钮内居中
Box(
modifier = Modifier
.align(Alignment.CenterStart)
.height(36.dp)
.clip(RoundedCornerShape(18.dp))
.width(91.dp)
.height(44.dp)
.clip(RoundedCornerShape(1000.dp))
.background(
brush = Brush.linearGradient(
colors = cancelButtonGradientColors
)
)
.noRippleClickable { onClose() }
.padding(horizontal = 8.dp),
.noRippleClickable { onClose() },
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
.padding(horizontal = 12.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
// 左箭头图标
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = null,
@@ -192,61 +186,64 @@ fun MbtiSelectBottomSheet(
colorFilter = ColorFilter.tint(cancelButtonContentColor)
)
// "取消" 文字
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "取消",
fontSize = 17.sp,
fontWeight = FontWeight.Medium,
color = cancelButtonContentColor,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
textAlign = TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Clip
)
}
}
// 中间标题 - 绝对居中
// 中间标题
Text(
text = stringResource(R.string.choose_mbti),
color = appColors.text,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
textAlign = TextAlign.Center
)
}
Spacer(Modifier.height(12.dp))
// 创建 NestedScrollConnection 来阻止滚动事件向上传ModalBottomSheet
// NestedScroll阻止滚动事件向上传到 BottomSheet
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// 不消费任何事件,让 LazyColumn 先处理
return Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
// 消费 LazyColumn 处理后的剩余滚动事件,防止传递到 ModalBottomSheet
override fun onPostScroll(
consumed: Offset,
available: Offset,
source: NestedScrollSource
): Offset {
return available
}
override suspend fun onPreFling(available: Velocity): Velocity {
// 不消费惯性滚动,让 LazyColumn 先处理
return Velocity.Zero
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
// 消费 LazyColumn 处理后的剩余惯性滚动,防止传递到 ModalBottomSheet
return available
}
}
}
// MBTI解释文字背景色
val descriptionBackgroundColor = if (isDarkMode) {
Color(0xFF2A2A2A) // 比 secondaryBackground (0xFF1C1C1C) 更亮的灰色
Color(0xFF2A2A2A)
} else {
Color(0xFFFAF9FB)
}
// 使用LazyColumn包裹解释文字和MBTI类型网格使它们一起滚动
// 列表:上面是说明文字,下面是 MBTI 网格
LazyColumn(
modifier = Modifier
.fillMaxWidth()
@@ -260,7 +257,7 @@ fun MbtiSelectBottomSheet(
),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
// MBTI解释文字 - 作为第一个item
// 说明文字
item {
Box(
modifier = Modifier
@@ -278,15 +275,17 @@ fun MbtiSelectBottomSheet(
}
}
// MBTI类型网格 - 手动创建2列网格布局
// MBTI 类型网格2 列)
itemsIndexed(MBTI_TYPES.chunked(2)) { rowIndex, rowItems ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(bottom = if (rowIndex < MBTI_TYPES.chunked(2).size - 1) 10.dp else 0.dp),
.padding(
bottom = if (rowIndex < MBTI_TYPES.chunked(2).size - 1) 10.dp else 0.dp
),
horizontalArrangement = Arrangement.spacedBy(10.dp)
) {
rowItems.forEachIndexed { colIndex, mbti ->
rowItems.forEach { mbti ->
Box(
modifier = Modifier.weight(1f)
) {
@@ -294,14 +293,13 @@ fun MbtiSelectBottomSheet(
mbti = mbti,
isSelected = mbti == currentMbti,
onClick = {
// 保存MBTI类型
model.mbti = mbti
onClose()
}
)
}
}
// 如果这一行只有1个item添加一个空的Spacer来保持布局
if (rowItems.size == 1) {
Spacer(modifier = Modifier.weight(1f))
}
@@ -311,6 +309,7 @@ fun MbtiSelectBottomSheet(
}
}
}
}
}
// 保留原有的 MbtiSelectScreen 用于导航路由(如果需要)
@@ -358,30 +357,26 @@ fun MbtiItem(
) {
onClick()
}
.padding(horizontal = 24.dp, vertical = 12.dp),
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// MBTI图标 - 使用占位图片
Box(
modifier = Modifier.size(100.dp),
contentAlignment = Alignment.Center
) {
// 直接把 MBTI 图标和文字放在灰色卡片内部,布局与星座保持一致
Image(
painter = painterResource(id = getMbtiImageResId(mbti, isDarkMode)),
contentDescription = mbti,
modifier = Modifier.size(100.dp)
modifier = Modifier.size(96.dp)
)
}
// MBTI名称 - 使用负间距让文本向上移动,与图标更靠近
Spacer(modifier = Modifier.height(0.dp))
Text(
text = mbti,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = appColors.text,
textAlign = TextAlign.Center,
modifier = Modifier.offset(y = (-20).dp)
modifier = Modifier.offset(y = (-10).dp)
)
}
}

View File

@@ -28,6 +28,7 @@ import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
@@ -47,6 +48,7 @@ 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.input.nestedscroll.NestedScrollConnection
import androidx.compose.ui.input.nestedscroll.NestedScrollSource
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -135,48 +137,36 @@ fun ZodiacSelectBottomSheet(
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
// 对齐发布动态草稿箱样式:底层透明,内容区域自己绘制圆角和背景
containerColor = Color.Transparent,
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
dragHandle = {}
) {
Box(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.95f)
.offset(y = offsetY)
.padding(
start = 16.dp,
end = 16.dp,
bottom = 8.dp
)
.fillMaxHeight()
.padding(top = 8.dp)
) {
Surface(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)),
color = sheetBackgroundColor,
tonalElevation = 0.dp,
shadowElevation = 0.dp,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()
.padding(horizontal = 16.dp, vertical = 8.dp)
) {
// 头部 - 使用 Box 实现绝对居中布局
// 头部 - 使用 Box 实现绝对居中布局(对齐草稿箱样式)
Box(
modifier = Modifier
.fillMaxWidth()
@@ -196,24 +186,28 @@ fun ZodiacSelectBottomSheet(
}
val cancelButtonContentColor = if (isDarkMode) Color(0xFFFFFFFF) else Color(0xFF404040)
// 左上角返回按钮 - 根据 Swift 代码样式,带淡灰色渐变背景
Row(
// 左上角返回按钮:参考 iOS 设计,整体「箭头 + 取消」在 91x44 的按钮内居中
Box(
modifier = Modifier
.align(Alignment.CenterStart)
.height(36.dp)
.clip(RoundedCornerShape(18.dp)) // 圆角 100.0 在 36dp 高度下接近完全圆角
.width(91.dp)
.height(44.dp)
.clip(RoundedCornerShape(1000.dp))
.background(
brush = Brush.linearGradient(
colors = cancelButtonGradientColors
// 不指定 start 和 end默认从左上到右下
)
)
.noRippleClickable { onClose() }
.padding(horizontal = 8.dp), // 内部 padding 确保内容不贴边
.noRippleClickable { onClose() },
contentAlignment = Alignment.Center
) {
Row(
modifier = Modifier
// 不再固定宽度,让内容自然占位,避免裁剪掉“消”字
.padding(horizontal = 12.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
// 左箭头图标
Image(
painter = painterResource(id = R.drawable.rider_pro_back_icon),
contentDescription = null,
@@ -221,17 +215,20 @@ fun ZodiacSelectBottomSheet(
colorFilter = ColorFilter.tint(cancelButtonContentColor)
)
// "取消" 文字
Spacer(modifier = Modifier.width(4.dp))
Text(
text = "取消",
fontSize = 17.sp,
fontWeight = FontWeight.Medium,
color = cancelButtonContentColor,
textAlign = androidx.compose.ui.text.style.TextAlign.Center
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
maxLines = 1,
overflow = TextOverflow.Clip
)
}
}
// 中间标题 - 绝对居中
Text(
text = stringResource(R.string.choose_zodiac),
color = appColors.text,
@@ -243,32 +240,34 @@ fun ZodiacSelectBottomSheet(
Spacer(Modifier.height(12.dp))
// 创建 NestedScrollConnection 来阻止滚动事件向上传播到 ModalBottomSheet
// 创建 NestedScrollConnection
// 1. 不抢在列表前面消费事件,让 LazyVerticalGrid 正常滚动
// 2. 在列表滚动之后把剩余滚动吃掉,避免继续传递到 BottomSheet 去触发下拉关闭
val nestedScrollConnection = remember {
object : NestedScrollConnection {
override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
// 不消费任何事件,让 LazyVerticalGrid 先处理
// 不在这里消费,先让 LazyVerticalGrid 自己处理滚动
return Offset.Zero
}
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
// 消费 LazyVerticalGrid 处理后的剩余滚动事件,防止传递到 ModalBottomSheet
// 列表滚动完之后,把剩余滚动(尤其是向下拖拽)全部吃掉,防止再传给 BottomSheet
return available
}
override suspend fun onPreFling(available: Velocity): Velocity {
// 不消费惯性滚动,让 LazyVerticalGrid 先处理
// 不抢在列表前面处理 fling,让 LazyVerticalGrid 先做惯性滚动
return Velocity.Zero
}
override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
// 消费 LazyVerticalGrid 处理后的剩余惯性滚动,防止传递到 ModalBottomSheet
// 列表惯性滚动之后,把剩余的 fling 速度吃掉,避免带动 BottomSheet 下滑关闭
return available
}
}
}
// 网格列表 - 2列
// 网格列表 - 2列(与草稿箱一样放在内容区域内部滚动)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
modifier = Modifier
@@ -291,7 +290,6 @@ fun ZodiacSelectBottomSheet(
zodiacResId = zodiacResId,
isSelected = zodiacResId == currentZodiacResId,
onClick = {
// 保存当前语言的星座文本
model.zodiac = zodiacText
onClose()
}
@@ -301,6 +299,7 @@ fun ZodiacSelectBottomSheet(
}
}
}
}
}
// 保留原有的 ZodiacSelectScreen 用于导航路由(如果需要)
@@ -336,9 +335,9 @@ fun ZodiacItem(
Column(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(1.1f) // 增加宽高比,使高度相对更低
.aspectRatio(1.1f)
.shadow(
elevation = if (isDarkMode) 8.dp else 2.dp, // 深色模式下更强的阴影
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)
)
@@ -350,30 +349,27 @@ fun ZodiacItem(
) {
onClick()
}
.padding(horizontal = 24.dp, vertical = 12.dp), // 减小垂直padding确保文本不被遮挡
.padding(horizontal = 16.dp, vertical = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// 星座图标 - 使用对应星座的图片
Box(
modifier = Modifier.size(100.dp),
contentAlignment = Alignment.Center
) {
// 直接把图标和文字放在灰色卡片内部,不再额外嵌套一层 Box
Image(
painter = painterResource(id = getZodiacImageResId(zodiacResId)),
contentDescription = zodiac,
modifier = Modifier.size(100.dp)
// 图标稍微放大一些,让视觉更聚焦在星座图标上
modifier = Modifier.size(96.dp)
)
}
// 星座名称 - 使用负间距让文本向上移动,与图标更靠近
Spacer(modifier = Modifier.height(0.dp))
Text(
text = zodiac,
fontSize = 14.sp,
fontWeight = FontWeight.Medium,
color = appColors.text,
textAlign = TextAlign.Center,
modifier = Modifier.offset(y = (-20).dp) // 负间距,让文本进一步向上移动
modifier = Modifier.offset(y = (-10).dp) // 再整体向上偏移 5dp共 10dp
)
}
}

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.HorizontalDivider
@@ -29,6 +30,7 @@ import androidx.compose.runtime.LaunchedEffect
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
@@ -40,6 +42,7 @@ 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 kotlinx.coroutines.launch
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
@@ -107,6 +110,8 @@ fun CommentModalContent(
var softwareKeyboardController = LocalSoftwareKeyboardController.current
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
var shouldAutoFocus by remember { mutableStateOf(false) }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
LaunchedEffect(imePadding) {
bottomPadding = imePadding.dp
@@ -213,6 +218,9 @@ fun CommentModalContent(
) {
commentViewModel.order = it
commentViewModel.reloadComment()
scope.launch {
listState.scrollToItem(0)
}
}
}
Box(
@@ -223,7 +231,8 @@ fun CommentModalContent(
LazyColumn(
modifier = Modifier
.fillMaxSize()
.padding(horizontal = 16.dp)
.padding(horizontal = 16.dp),
state = listState
) {
item {
CommentContent(

View File

@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
@@ -30,6 +31,7 @@ import androidx.compose.runtime.LaunchedEffect
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
@@ -144,6 +146,8 @@ fun NewsCommentModal(
var showCommentMenu by remember { mutableStateOf(false) }
var contextComment by remember { mutableStateOf<CommentEntity?>(null) }
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
// 菜单弹窗
if (showCommentMenu) {
@@ -231,6 +235,9 @@ fun NewsCommentModal(
) {
commentViewModel.order = it
commentViewModel.reloadComment()
scope.launch {
listState.scrollToItem(0)
}
}
}
}
@@ -269,7 +276,9 @@ fun NewsCommentModal(
)
}
} else {
LazyColumn {
LazyColumn(
state = listState
) {
item {
CommentContent(
viewModel = commentViewModel,

View File

@@ -347,6 +347,11 @@ fun VideoRecommendationItem(
if (isVisible && shouldResumeAfterLifecyclePause) {
pauseIconVisibleState = false
exoPlayer.play()
} else {
// 未自动恢复播放时,如果当前可见且视频已暂停,则显示暂停图标
if (isVisible && !exoPlayer.isPlaying) {
pauseIconVisibleState = true
}
}
}
else -> {}

View File

@@ -549,6 +549,11 @@ fun VideoPlayer(
exoPlayer.playWhenReady = true
exoPlayer.play()
pauseIconVisibleState.value = false
} else {
// 未自动恢复播放时,如果当前页面视频处于暂停状态,则显示暂停图标
if (!exoPlayer.isPlaying) {
pauseIconVisibleState.value = true
}
}
}