Merge pull request #96 from Kevinlinpr/nagisa

调整个人资料编辑简介框、修改短视频暂停图标、修复短视频界面点暂停锁屏后再解锁会自动播放视频
This commit is contained in:
2025-12-01 10:44:20 +08:00
committed by GitHub
11 changed files with 68 additions and 27 deletions

View File

@@ -70,6 +70,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.draw.shadow import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.text.TextLayoutResult
import com.aiosman.ravenow.ConstVars import com.aiosman.ravenow.ConstVars
import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher import com.aiosman.ravenow.ui.composables.pickupAndCompressLauncher
import android.widget.Toast import android.widget.Toast
@@ -407,7 +409,7 @@ fun AccountEditScreen2(onUpdateBanner: ((Uri, File, Context) -> Unit)? = null,)
ProfileInfoCard( ProfileInfoCard(
label = stringResource(R.string.personal_intro), label = stringResource(R.string.personal_intro),
value = model.bio, value = model.bio,
placeholder = stringResource(R.string.bio_placeholder), placeholder = "",
onValueChange = { onBioChange(it) }, onValueChange = { onBioChange(it) },
isMultiline = true isMultiline = true
) )
@@ -564,20 +566,30 @@ fun ProfileInfoCard(
isMultiline: Boolean = false isMultiline: Boolean = false
) { ) {
val appColors = LocalAppTheme.current val appColors = LocalAppTheme.current
var isFocused by remember { mutableStateOf(false) }
var lineCount by remember { mutableStateOf(1) }
// 根据行数决定对齐方式:单行时居中,多行时顶部对齐
val verticalAlignment = if (isMultiline) {
if (lineCount <= 1) Alignment.CenterVertically else Alignment.Top
} else {
Alignment.CenterVertically
}
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp个人简介66dp .height(if (isMultiline) 66.dp else 56.dp) // 昵称框高度56dp个人简介66dp
.clip(RoundedCornerShape(16.dp)) .clip(RoundedCornerShape(16.dp))
.background(appColors.secondaryBackground), .background(appColors.secondaryBackground),
contentAlignment = if (isMultiline) Alignment.TopStart else Alignment.CenterStart contentAlignment = if (isMultiline && lineCount > 1) Alignment.TopStart else Alignment.CenterStart
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.padding(vertical = if (isMultiline) 11.dp else 0.dp), .padding(vertical = if (isMultiline && lineCount > 1) 11.dp else 0.dp),
verticalAlignment = if (isMultiline) Alignment.Top else Alignment.CenterVertically verticalAlignment = verticalAlignment
) { ) {
// 标签 // 标签
Box( Box(
@@ -600,10 +612,26 @@ fun ProfileInfoCard(
Box( Box(
modifier = Modifier.weight(1f) modifier = Modifier.weight(1f)
) { ) {
if (value.isEmpty()) { // 对于个人简介isMultiline = true当值为空且没有焦点时显示图标
if (value.isEmpty() && isMultiline && !isFocused) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(44.dp),
contentAlignment = Alignment.CenterStart
) {
Icon(
painter = painterResource(id = R.mipmap.icons_infor_edit),
contentDescription = null,
modifier = Modifier.size(16.dp),
tint = appColors.secondaryText
)
}
} else if (value.isEmpty() && !isMultiline && placeholder.isNotEmpty()) {
// 对于非多行输入框,仍然显示 placeholder 文字
Text( Text(
text = placeholder, text = placeholder,
fontSize = if (isMultiline) 15.sp else 17.sp, fontSize = 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
color = appColors.secondaryText, color = appColors.secondaryText,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
@@ -613,7 +641,11 @@ fun ProfileInfoCard(
BasicTextField( BasicTextField(
value = value, value = value,
onValueChange = onValueChange, onValueChange = onValueChange,
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.onFocusChanged { focusState ->
isFocused = focusState.isFocused
},
textStyle = androidx.compose.ui.text.TextStyle( textStyle = androidx.compose.ui.text.TextStyle(
fontSize = if (isMultiline) 15.sp else 17.sp, fontSize = if (isMultiline) 15.sp else 17.sp,
fontWeight = FontWeight.Normal, fontWeight = FontWeight.Normal,
@@ -621,7 +653,12 @@ fun ProfileInfoCard(
), ),
cursorBrush = SolidColor(appColors.text), cursorBrush = SolidColor(appColors.text),
maxLines = if (isMultiline) Int.MAX_VALUE else 1, maxLines = if (isMultiline) Int.MAX_VALUE else 1,
singleLine = !isMultiline singleLine = !isMultiline,
onTextLayout = { textLayoutResult: TextLayoutResult ->
if (isMultiline) {
lineCount = textLayoutResult.lineCount
}
}
) )
} }
} }

View File

@@ -16,10 +16,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
@@ -35,6 +32,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
@@ -99,6 +97,7 @@ fun VideoRecommendationItem(
skipPartiallyExpanded = true skipPartiallyExpanded = true
) )
var pauseIconVisibleState by remember { mutableStateOf(false) } var pauseIconVisibleState by remember { mutableStateOf(false) }
var shouldResumeAfterLifecyclePause by remember { mutableStateOf(false) }
// 防抖:记录上次双击时间,防止快速重复双击 // 防抖:记录上次双击时间,防止快速重复双击
val lastDoubleTapTime = remember { mutableStateOf(0L) } val lastDoubleTapTime = remember { mutableStateOf(0L) }
val doubleTapDebounceTime = 500L // 500ms 防抖时间 val doubleTapDebounceTime = 500L // 500ms 防抖时间
@@ -202,13 +201,13 @@ fun VideoRecommendationItem(
) )
if (pauseIconVisibleState) { if (pauseIconVisibleState) {
Icon( Image(
imageVector = Icons.Default.PlayArrow, painter = painterResource(R.mipmap.dt_ts_sp_bf_btn),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
.size(80.dp), .size(80.dp),
tint = Color.White colorFilter = ColorFilter.tint(Color.White)
) )
} }
} }
@@ -341,10 +340,12 @@ fun VideoRecommendationItem(
val observer = LifecycleEventObserver { _, event -> val observer = LifecycleEventObserver { _, event ->
when (event) { when (event) {
Lifecycle.Event.ON_PAUSE -> { Lifecycle.Event.ON_PAUSE -> {
shouldResumeAfterLifecyclePause = exoPlayer.isPlaying && !pauseIconVisibleState
exoPlayer.pause() exoPlayer.pause()
} }
Lifecycle.Event.ON_RESUME -> { Lifecycle.Event.ON_RESUME -> {
if (isVisible) { if (isVisible && shouldResumeAfterLifecyclePause) {
pauseIconVisibleState = false
exoPlayer.play() exoPlayer.play()
} }
} }
@@ -420,3 +421,4 @@ private fun VideoBtn(
) )
} }
} }

View File

@@ -26,10 +26,7 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.material3.rememberModalBottomSheetState
@@ -321,9 +318,9 @@ private fun SingleVideoItemContent(
isPageVisible: Boolean = true isPageVisible: Boolean = true
) { ) {
// 将暂停状态移到每个视频项内部,使用 remember 保存,避免在点赞/关注时被重置 // 将暂停状态移到每个视频项内部,使用 remember 保存,避免在点赞/关注时被重置
val pauseIconVisibleState = remember(pager) { val pauseIconVisibleState = remember(pager) { mutableStateOf(false) }
mutableStateOf(false) // 记录进入后台前是否在播放,用于决定是否需要自动恢复播放
} val shouldResumeAfterLifecyclePause = remember(pager) { mutableStateOf(false) }
// 当页面切换时,重置暂停状态 // 当页面切换时,重置暂停状态
LaunchedEffect(pager, pagerState.currentPage) { LaunchedEffect(pager, pagerState.currentPage) {
@@ -343,6 +340,7 @@ private fun SingleVideoItemContent(
pagerState = pagerState, pagerState = pagerState,
pager = pager, pager = pager,
pauseIconVisibleState = pauseIconVisibleState, pauseIconVisibleState = pauseIconVisibleState,
shouldResumeAfterLifecyclePause = shouldResumeAfterLifecyclePause,
onLikeClick = onLikeClick, onLikeClick = onLikeClick,
onCommentClick = onCommentClick, onCommentClick = onCommentClick,
onCommentAdded = onCommentAdded, onCommentAdded = onCommentAdded,
@@ -375,6 +373,7 @@ fun VideoPlayer(
pagerState: PagerState, pagerState: PagerState,
pager: Int, pager: Int,
pauseIconVisibleState: MutableState<Boolean>, pauseIconVisibleState: MutableState<Boolean>,
shouldResumeAfterLifecyclePause: MutableState<Boolean>,
onLikeClick: ((MomentEntity) -> Unit)? = null, onLikeClick: ((MomentEntity) -> Unit)? = null,
onCommentClick: ((MomentEntity) -> Unit)? = null, onCommentClick: ((MomentEntity) -> Unit)? = null,
onCommentAdded: ((MomentEntity) -> Unit)? = null, onCommentAdded: ((MomentEntity) -> Unit)? = null,
@@ -507,8 +506,8 @@ fun VideoPlayer(
} }
if (pauseIconVisibleState.value) { if (pauseIconVisibleState.value) {
Icon( Image(
imageVector = Icons.Default.PlayArrow, painter = painterResource(R.mipmap.dt_ts_sp_bf_btn),
contentDescription = null, contentDescription = null,
modifier = Modifier modifier = Modifier
.align(Alignment.Center) .align(Alignment.Center)
@@ -535,15 +534,21 @@ fun VideoPlayer(
when (event) { when (event) {
Lifecycle.Event.ON_PAUSE -> { Lifecycle.Event.ON_PAUSE -> {
// 应用进入后台时暂停 // 应用进入后台时暂停
shouldResumeAfterLifecyclePause.value = exoPlayer.isPlaying && !pauseIconVisibleState.value
exoPlayer.playWhenReady = false exoPlayer.playWhenReady = false
exoPlayer.pause() exoPlayer.pause()
} }
Lifecycle.Event.ON_RESUME -> { Lifecycle.Event.ON_RESUME -> {
// 返回前台且为当前页面时恢复播放 // 返回前台且为当前页面时恢复播放
if (pager == pagerState.currentPage) { if (
pager == pagerState.currentPage &&
isPageVisible &&
shouldResumeAfterLifecyclePause.value
) {
exoPlayer.playWhenReady = true exoPlayer.playWhenReady = true
exoPlayer.play() exoPlayer.play()
pauseIconVisibleState.value = false
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 798 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 618 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -334,7 +334,6 @@
<string name="error_bio_too_long">自己紹介の長さは100文字を超えることはできません</string> <string name="error_bio_too_long">自己紹介の長さは100文字を超えることはできません</string>
<string name="error_load_profile_failed">ユーザープロフィールの読み込みに失敗しました。もう一度お試しください</string> <string name="error_load_profile_failed">ユーザープロフィールの読み込みに失敗しました。もう一度お試しください</string>
<string name="nickname_placeholder">ニックネームを入力</string> <string name="nickname_placeholder">ニックネームを入力</string>
<string name="bio_placeholder">私の世界へようこそ。魔法について何かお見せします</string>
<string name="save">保存</string> <string name="save">保存</string>
<string name="choose_mbti">MBTIを選択</string> <string name="choose_mbti">MBTIを選択</string>
<string name="mbti_description">MBTIは心理学理論に基づく人格評価ツールです。人々がエネルギーを獲得する方法内向-外向)、情報を収集する方法(感覚-直感)、意思決定を行う方法(思考-感情)、生活様式(判断-知覚の好みを理解することで、人格タイプを16種類に分類します。</string> <string name="mbti_description">MBTIは心理学理論に基づく人格評価ツールです。人々がエネルギーを獲得する方法内向-外向)、情報を収集する方法(感覚-直感)、意思決定を行う方法(思考-感情)、生活様式(判断-知覚の好みを理解することで、人格タイプを16種類に分類します。</string>

View File

@@ -326,7 +326,6 @@
<string name="error_load_profile_failed">加载用户资料失败,请重试</string> <string name="error_load_profile_failed">加载用户资料失败,请重试</string>
<string name="network_error_check_network">网络错误,请检查网络</string> <string name="network_error_check_network">网络错误,请检查网络</string>
<string name="nickname_placeholder">请输入昵称</string> <string name="nickname_placeholder">请输入昵称</string>
<string name="bio_placeholder">欢迎来到我的世界,我会向你展示一些关于魔法的内容</string>
<string name="save">保存</string> <string name="save">保存</string>
<string name="choose_mbti">选择 MBTI</string> <string name="choose_mbti">选择 MBTI</string>
<string name="mbti_description">MBTI是基于心理学理论的人格测评工具。了解人们获取能量方式(内向-外向)、收集信息方式(感觉-直觉)、做决策方式(思维-情感)、生活方式(判断-知觉)的偏好,将人格类型分为16种。</string> <string name="mbti_description">MBTI是基于心理学理论的人格测评工具。了解人们获取能量方式(内向-外向)、收集信息方式(感觉-直觉)、做决策方式(思维-情感)、生活方式(判断-知觉)的偏好,将人格类型分为16种。</string>

View File

@@ -334,7 +334,6 @@
<string name="error_load_profile_failed">Failed to load user profile, please try again</string> <string name="error_load_profile_failed">Failed to load user profile, please try again</string>
<string name="network_error_check_network">Network error, please check your network</string> <string name="network_error_check_network">Network error, please check your network</string>
<string name="nickname_placeholder">Value</string> <string name="nickname_placeholder">Value</string>
<string name="bio_placeholder">Welcome to my fantiac word i will show you something about magic</string>
<string name="save">Save</string> <string name="save">Save</string>
<string name="choose_mbti">Choose MBTI</string> <string name="choose_mbti">Choose MBTI</string>
<string name="mbti_description">MBTI is a personality assessment tool based on psychological theory. By understanding people\'s preferences in how they acquire energy (introversion-extraversion), collect information (sensing-intuition), make decisions (thinking-feeling), and live their lives (judging-perceiving), personality types are divided into 16 kinds.</string> <string name="mbti_description">MBTI is a personality assessment tool based on psychological theory. By understanding people\'s preferences in how they acquire energy (introversion-extraversion), collect information (sensing-intuition), make decisions (thinking-feeling), and live their lives (judging-perceiving), personality types are divided into 16 kinds.</string>