修改若干bug调整暗色模式适配以及界面尺寸修改
修复进入应用后的深色模式下的底部导航框在不同标签下会出现透明 动态-动态/关注界面暗色模式适配 修复上下滑动切换短视频,部分视频会被缩放 修复点击底部导航栏的标签切换界面后视频还在播放 修改短视频评论框大小以及我的派币界面大小
This commit is contained in:
@@ -111,7 +111,7 @@ class DarkThemeColors : AppThemeData(
|
|||||||
chatActionColor = Color(0xFF3D3D3D),
|
chatActionColor = Color(0xFF3D3D3D),
|
||||||
brandColorsColor = Color(0xffD80264),
|
brandColorsColor = Color(0xffD80264),
|
||||||
tabSelectedBackground = Color(0xffffffff),
|
tabSelectedBackground = Color(0xffffffff),
|
||||||
tabUnselectedBackground = Color(0x2E7C7480),
|
tabUnselectedBackground = Color(0xFF1C1C1C),
|
||||||
tabSelectedText = Color(0xff000000),
|
tabSelectedText = Color(0xff000000),
|
||||||
tabUnselectedText = Color(0xffffffff),
|
tabUnselectedText = Color(0xffffffff),
|
||||||
bubbleBackground = Color(0xff2d2c2e),
|
bubbleBackground = Color(0xff2d2c2e),
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.Row
|
|||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.WindowInsets
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
import androidx.compose.foundation.layout.asPaddingValues
|
import androidx.compose.foundation.layout.asPaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.ime
|
import androidx.compose.foundation.layout.ime
|
||||||
@@ -157,6 +159,7 @@ fun CommentModalContent(
|
|||||||
}
|
}
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -196,12 +199,12 @@ fun CommentModalContent(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp)
|
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxSize()
|
||||||
|
.padding(horizontal = 16.dp)
|
||||||
) {
|
) {
|
||||||
item {
|
item {
|
||||||
CommentContent(
|
CommentContent(
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ fun AnimatedFavouriteIcon(
|
|||||||
modifier = modifier.graphicsLayer {
|
modifier = modifier.graphicsLayer {
|
||||||
rotationZ = animatableRotation.value
|
rotationZ = animatableRotation.value
|
||||||
},
|
},
|
||||||
|
colorFilter = if (!isFavourite) ColorFilter.tint(AppColors.text) else null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -468,6 +468,7 @@ fun MomentOperateBtn(@DrawableRes icon: Int, count: String) {
|
|||||||
.size(width = 24.dp, height = 24.dp),
|
.size(width = 24.dp, height = 24.dp),
|
||||||
painter = painterResource(id = icon),
|
painter = painterResource(id = icon),
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
|
colorFilter = ColorFilter.tint(AppColors.text)
|
||||||
)
|
)
|
||||||
if (count.isNotEmpty()) {
|
if (count.isNotEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
|
|||||||
@@ -269,7 +269,7 @@ fun IndexScreen() {
|
|||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> Agent()
|
0 -> Agent()
|
||||||
1 -> Home()
|
1 -> Home(isPageVisible = pagerState.currentPage == 1)
|
||||||
2 -> Add()
|
2 -> Add()
|
||||||
3 -> Notifications()
|
3 -> Notifications()
|
||||||
4 -> Profile()
|
4 -> Profile()
|
||||||
@@ -332,7 +332,9 @@ fun IndexScreen() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Home() {
|
fun Home(
|
||||||
|
isPageVisible: Boolean = true
|
||||||
|
) {
|
||||||
val systemUiController = rememberSystemUiController()
|
val systemUiController = rememberSystemUiController()
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
@@ -348,7 +350,7 @@ fun Home() {
|
|||||||
verticalArrangement = Arrangement.Center,
|
verticalArrangement = Arrangement.Center,
|
||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
MomentsList()
|
MomentsList(isPageVisible = isPageVisible)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,9 @@ import com.aiosman.ravenow.ui.index.tabs.moment.tabs.shorts.ShortVideoScreen
|
|||||||
*/
|
*/
|
||||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun MomentsList() {
|
fun MomentsList(
|
||||||
|
isPageVisible: Boolean = true
|
||||||
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
val navigationBarPaddings =
|
val navigationBarPaddings =
|
||||||
@@ -437,7 +439,7 @@ fun MomentsList() {
|
|||||||
}
|
}
|
||||||
1 -> {
|
1 -> {
|
||||||
// 短视频页面
|
// 短视频页面
|
||||||
ShortVideoScreen()
|
ShortVideoScreen(isPageVisible = pagerState.currentPage == 1 && isPageVisible)
|
||||||
}
|
}
|
||||||
2 -> {
|
2 -> {
|
||||||
// 动态页面 - 暂时显示时间线内容
|
// 动态页面 - 暂时显示时间线内容
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic
|
package com.aiosman.ravenow.ui.index.tabs.moment.tabs.dynamic
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
@@ -21,6 +22,7 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOut
|
import com.aiosman.ravenow.GuestLoginCheckOut
|
||||||
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
import com.aiosman.ravenow.GuestLoginCheckOutScene
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.LocalNavController
|
import com.aiosman.ravenow.LocalNavController
|
||||||
import com.aiosman.ravenow.ui.NavigationRoute
|
import com.aiosman.ravenow.ui.NavigationRoute
|
||||||
import com.aiosman.ravenow.ui.composables.MomentCard
|
import com.aiosman.ravenow.ui.composables.MomentCard
|
||||||
@@ -36,6 +38,7 @@ fun Dynamic() {
|
|||||||
val model = DynamicViewModel
|
val model = DynamicViewModel
|
||||||
val moments = model.moments
|
val moments = model.moments
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
val state = rememberPullRefreshState(model.refreshing, onRefresh = {
|
||||||
@@ -88,7 +91,7 @@ fun Dynamic() {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
) {
|
) {
|
||||||
Box(Modifier.pullRefresh(state)) {
|
Box(Modifier.pullRefresh(state)) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -31,7 +31,9 @@ import kotlinx.coroutines.launch
|
|||||||
* 短视频页面
|
* 短视频页面
|
||||||
*/
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun ShortVideoScreen() {
|
fun ShortVideoScreen(
|
||||||
|
isPageVisible: Boolean = true
|
||||||
|
) {
|
||||||
val viewModel = ShortVideoViewModel
|
val viewModel = ShortVideoViewModel
|
||||||
val allMoments = viewModel.moments
|
val allMoments = viewModel.moments
|
||||||
val navController = LocalNavController.current
|
val navController = LocalNavController.current
|
||||||
@@ -187,7 +189,8 @@ fun ShortVideoScreen() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onPageChanged = { idx -> currentIndex.value = idx }
|
onPageChanged = { idx -> currentIndex.value = idx },
|
||||||
|
isPageVisible = isPageVisible
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ fun TimelineMomentsList() {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
.padding(top = 188.dp),
|
.padding(top = 188.dp),
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
@@ -126,6 +127,7 @@ fun TimelineMomentsList() {
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
.padding(top = 188.dp),
|
.padding(top = 188.dp),
|
||||||
contentAlignment = Alignment.TopCenter
|
contentAlignment = Alignment.TopCenter
|
||||||
) {
|
) {
|
||||||
@@ -176,6 +178,7 @@ fun TimelineMomentsList() {
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
) {
|
) {
|
||||||
Box(Modifier.pullRefresh(state)) {
|
Box(Modifier.pullRefresh(state)) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|||||||
@@ -76,12 +76,129 @@ import com.aiosman.ravenow.entity.MomentEntity
|
|||||||
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
||||||
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
import com.aiosman.ravenow.ui.composables.CustomAsyncImage
|
||||||
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
// 激活状态的颜色(点赞/收藏时的红色)
|
// 激活状态的颜色(点赞/收藏时的红色)
|
||||||
private val ActiveIconColor = Color(0xFFD80264)
|
private val ActiveIconColor = Color(0xFFD80264)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建视频播放器视图
|
||||||
|
*/
|
||||||
|
private fun createVideoPlayerView(
|
||||||
|
context: android.content.Context,
|
||||||
|
exoPlayer: ExoPlayer,
|
||||||
|
onViewCreated: (PlayerView) -> Unit
|
||||||
|
): FrameLayout {
|
||||||
|
return FrameLayout(context).apply {
|
||||||
|
setBackgroundColor(Color.Black.toArgb())
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
)
|
||||||
|
|
||||||
|
val playerView = PlayerView(context).apply {
|
||||||
|
hideController()
|
||||||
|
useController = false
|
||||||
|
player = exoPlayer
|
||||||
|
resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||||
|
setShutterBackgroundColor(Color.Black.toArgb())
|
||||||
|
setKeepContentOnPlayerReset(false)
|
||||||
|
|
||||||
|
layoutParams = FrameLayout.LayoutParams(
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||||
|
Gravity.CENTER
|
||||||
|
)
|
||||||
|
|
||||||
|
// 布局完成后固定内容帧尺寸 - 使用更可靠的方法
|
||||||
|
var layoutListener: android.view.ViewTreeObserver.OnGlobalLayoutListener? = null
|
||||||
|
layoutListener = object : android.view.ViewTreeObserver.OnGlobalLayoutListener {
|
||||||
|
override fun onGlobalLayout() {
|
||||||
|
try {
|
||||||
|
val frame = findViewById<AspectRatioFrameLayout>(androidx.media3.ui.R.id.exo_content_frame)
|
||||||
|
if (frame != null && width > 0 && height > 0) {
|
||||||
|
// 确保内容帧的尺寸和缩放模式正确设置
|
||||||
|
val layoutParams = FrameLayout.LayoutParams(
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
Gravity.CENTER
|
||||||
|
)
|
||||||
|
frame.layoutParams = layoutParams
|
||||||
|
frame.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM)
|
||||||
|
// 强制重新布局
|
||||||
|
frame.requestLayout()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 post 确保在布局完成后执行
|
||||||
|
post {
|
||||||
|
viewTreeObserver.addOnGlobalLayoutListener(layoutListener)
|
||||||
|
// 延迟移除监听器,确保多次布局变化都能处理
|
||||||
|
postDelayed({
|
||||||
|
try {
|
||||||
|
layoutListener?.let { viewTreeObserver.removeOnGlobalLayoutListener(it) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略异常
|
||||||
|
}
|
||||||
|
}, 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加视频准备监听,确保视频准备就绪时也设置正确的缩放
|
||||||
|
exoPlayer.addListener(object : Player.Listener {
|
||||||
|
override fun onVideoSizeChanged(videoSize: androidx.media3.common.VideoSize) {
|
||||||
|
playerView.post {
|
||||||
|
try {
|
||||||
|
val frame = playerView.findViewById<AspectRatioFrameLayout>(androidx.media3.ui.R.id.exo_content_frame)
|
||||||
|
if (frame != null && playerView.width > 0 && playerView.height > 0) {
|
||||||
|
frame.layoutParams = FrameLayout.LayoutParams(
|
||||||
|
playerView.width,
|
||||||
|
playerView.height,
|
||||||
|
Gravity.CENTER
|
||||||
|
)
|
||||||
|
frame.setResizeMode(AspectRatioFrameLayout.RESIZE_MODE_ZOOM)
|
||||||
|
frame.requestLayout()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略异常
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onViewCreated(playerView)
|
||||||
|
addView(playerView)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理视频点击事件(播放/暂停)
|
||||||
|
*/
|
||||||
|
private fun handleVideoClick(
|
||||||
|
pauseIconVisibleState: MutableState<Boolean>,
|
||||||
|
exoPlayer: ExoPlayer,
|
||||||
|
scope: CoroutineScope
|
||||||
|
) {
|
||||||
|
scope.launch {
|
||||||
|
if (exoPlayer.isPlaying) {
|
||||||
|
// 正在播放:暂停
|
||||||
|
exoPlayer.pause()
|
||||||
|
pauseIconVisibleState.value = true
|
||||||
|
} else {
|
||||||
|
// 已暂停:恢复播放
|
||||||
|
exoPlayer.playWhenReady = true
|
||||||
|
exoPlayer.play()
|
||||||
|
pauseIconVisibleState.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShortViewCompose(
|
fun ShortViewCompose(
|
||||||
videoItemsUrl: List<String> = emptyList(),
|
videoItemsUrl: List<String> = emptyList(),
|
||||||
@@ -95,7 +212,8 @@ fun ShortViewCompose(
|
|||||||
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onShareClick: ((MomentEntity) -> Unit)? = null,
|
onShareClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onPageChanged: ((Int) -> Unit)? = null
|
onPageChanged: ((Int) -> Unit)? = null,
|
||||||
|
isPageVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
// 优先使用 videoMoments,如果没有则使用 videoItemsUrl
|
// 优先使用 videoMoments,如果没有则使用 videoItemsUrl
|
||||||
val items = if (videoMoments.isNotEmpty()) {
|
val items = if (videoMoments.isNotEmpty()) {
|
||||||
@@ -144,8 +262,10 @@ fun ShortViewCompose(
|
|||||||
.clip(RectangleShape),
|
.clip(RectangleShape),
|
||||||
state = pagerState,
|
state = pagerState,
|
||||||
orientation = Orientation.Vertical,
|
orientation = Orientation.Vertical,
|
||||||
offscreenLimit = 1
|
offscreenLimit = 1 // 只预加载相邻页面,减少内存占用
|
||||||
) {
|
) {
|
||||||
|
// 使用 key 确保每个页面独立,避免状态混乱
|
||||||
|
androidx.compose.runtime.key(page) {
|
||||||
pauseIconVisibleState.value = false
|
pauseIconVisibleState.value = false
|
||||||
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
||||||
videoMoments[page]
|
videoMoments[page]
|
||||||
@@ -157,6 +277,7 @@ fun ShortViewCompose(
|
|||||||
LaunchedEffect(pagerState.currentPage) {
|
LaunchedEffect(pagerState.currentPage) {
|
||||||
onPageChanged?.invoke(pagerState.currentPage)
|
onPageChanged?.invoke(pagerState.currentPage)
|
||||||
}
|
}
|
||||||
|
|
||||||
SingleVideoItemContent(
|
SingleVideoItemContent(
|
||||||
videoUrl = items[page],
|
videoUrl = items[page],
|
||||||
moment = currentMoment,
|
moment = currentMoment,
|
||||||
@@ -171,9 +292,11 @@ fun ShortViewCompose(
|
|||||||
onCommentAdded = onCommentAdded,
|
onCommentAdded = onCommentAdded,
|
||||||
onFavoriteClick = onFavoriteClick,
|
onFavoriteClick = onFavoriteClick,
|
||||||
onShareClick = onShareClick,
|
onShareClick = onShareClick,
|
||||||
onAvatarClick = onAvatarClick
|
onAvatarClick = onAvatarClick,
|
||||||
|
isPageVisible = isPageVisible
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LaunchedEffect(clickItemPosition) {
|
LaunchedEffect(clickItemPosition) {
|
||||||
delay(300)
|
delay(300)
|
||||||
@@ -197,7 +320,8 @@ private fun SingleVideoItemContent(
|
|||||||
onCommentAdded: ((MomentEntity) -> Unit)? = null,
|
onCommentAdded: ((MomentEntity) -> Unit)? = null,
|
||||||
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onShareClick: ((MomentEntity) -> Unit)? = null,
|
onShareClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onAvatarClick: ((MomentEntity) -> Unit)? = null
|
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
||||||
|
isPageVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -215,7 +339,8 @@ private fun SingleVideoItemContent(
|
|||||||
onCommentAdded = onCommentAdded,
|
onCommentAdded = onCommentAdded,
|
||||||
onFavoriteClick = onFavoriteClick,
|
onFavoriteClick = onFavoriteClick,
|
||||||
onShareClick = onShareClick,
|
onShareClick = onShareClick,
|
||||||
onAvatarClick = onAvatarClick
|
onAvatarClick = onAvatarClick,
|
||||||
|
isPageVisible = isPageVisible
|
||||||
)
|
)
|
||||||
VideoHeader.invoke()
|
VideoHeader.invoke()
|
||||||
if (moment != null && VideoBottom != null) {
|
if (moment != null && VideoBottom != null) {
|
||||||
@@ -247,22 +372,31 @@ fun VideoPlayer(
|
|||||||
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
onFavoriteClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onShareClick: ((MomentEntity) -> Unit)? = null,
|
onShareClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
||||||
|
isPageVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val lifecycleOwner = LocalLifecycleOwner.current
|
val lifecycleOwner = LocalLifecycleOwner.current
|
||||||
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
|
val configuration = androidx.compose.ui.platform.LocalConfiguration.current
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
val screenHeight = configuration.screenHeightDp.dp
|
||||||
val sheetHeight = screenHeight * 0.7f // 屏幕的一大半高度
|
val sheetHeight = screenHeight * 0.7f // 屏幕的70%高度
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
var showCommentModal by remember { mutableStateOf(false) }
|
||||||
var sheetState = rememberModalBottomSheetState(
|
var sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
)
|
)
|
||||||
val exoPlayer = remember {
|
|
||||||
ExoPlayer.Builder(context)
|
// 确保弹窗从下往上展开
|
||||||
.build()
|
LaunchedEffect(showCommentModal) {
|
||||||
.apply {
|
if (showCommentModal) {
|
||||||
// 创建带有认证头的 HttpDataSource.Factory
|
sheetState.expand()
|
||||||
|
} else {
|
||||||
|
sheetState.hide()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 使用 key 确保每个视频的 ExoPlayer 完全独立,避免状态残留
|
||||||
|
val exoPlayer = remember(videoUrl) {
|
||||||
|
ExoPlayer.Builder(context).build().apply {
|
||||||
|
// 创建带有认证头的数据源
|
||||||
val httpDataSourceFactory = DefaultHttpDataSource.Factory()
|
val httpDataSourceFactory = DefaultHttpDataSource.Factory()
|
||||||
.setUserAgent(Util.getUserAgent(context, context.packageName))
|
.setUserAgent(Util.getUserAgent(context, context.packageName))
|
||||||
.setDefaultRequestProperties(
|
.setDefaultRequestProperties(
|
||||||
@@ -272,70 +406,55 @@ fun VideoPlayer(
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// 创建 DataSource.Factory,使用自定义的 HttpDataSource.Factory
|
val dataSourceFactory = DefaultDataSourceFactory(context, httpDataSourceFactory)
|
||||||
val dataSourceFactory: DataSource.Factory = DefaultDataSourceFactory(
|
|
||||||
context,
|
|
||||||
httpDataSourceFactory
|
|
||||||
)
|
|
||||||
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
|
val source = ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
|
.createMediaSource(MediaItem.fromUri(Uri.parse(videoUrl)))
|
||||||
|
|
||||||
this.prepare(source)
|
prepare(source)
|
||||||
|
videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING
|
||||||
|
repeatMode = Player.REPEAT_MODE_ONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (pager == pagerState.currentPage) {
|
|
||||||
|
// 根据页面状态控制播放
|
||||||
|
LaunchedEffect(pager, pagerState.currentPage, isPageVisible) {
|
||||||
|
val isCurrentPage = pager == pagerState.currentPage && isPageVisible
|
||||||
|
if (isCurrentPage) {
|
||||||
|
// 当前页面且页面可见:恢复播放
|
||||||
exoPlayer.playWhenReady = true
|
exoPlayer.playWhenReady = true
|
||||||
exoPlayer.play()
|
exoPlayer.play()
|
||||||
|
pauseIconVisibleState.value = false
|
||||||
} else {
|
} else {
|
||||||
|
// 非当前页面或页面不可见:立即暂停并停止准备播放
|
||||||
|
exoPlayer.playWhenReady = false
|
||||||
exoPlayer.pause()
|
exoPlayer.pause()
|
||||||
|
pauseIconVisibleState.value = false
|
||||||
}
|
}
|
||||||
exoPlayer.videoScalingMode = C.VIDEO_SCALING_MODE_SCALE_TO_FIT
|
}
|
||||||
exoPlayer.repeatMode = Player.REPEAT_MODE_ONE
|
// 视频播放器容器 - 确保每个视频页面都被正确裁剪
|
||||||
// player box
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize(),
|
.fillMaxSize()
|
||||||
contentAlignment = Alignment.TopCenter
|
.clip(RectangleShape)
|
||||||
) {
|
) {
|
||||||
var playerView by remember { mutableStateOf<PlayerView?>(null) } // Store reference to PlayerView
|
var playerView by remember { mutableStateOf<PlayerView?>(null) }
|
||||||
|
|
||||||
|
// 使用 key 强制每个视频的 PlayerView 完全独立,避免布局状态残留
|
||||||
|
androidx.compose.runtime.key(videoUrl) {
|
||||||
AndroidView(
|
AndroidView(
|
||||||
factory = { context ->
|
factory = { ctx ->
|
||||||
// 创建一个 FrameLayout 作为容器
|
createVideoPlayerView(ctx, exoPlayer) { view ->
|
||||||
FrameLayout(context).apply {
|
playerView = view
|
||||||
// 设置背景颜色为黑色,用于显示黑边
|
|
||||||
setBackgroundColor(Color.Black.toArgb())
|
|
||||||
|
|
||||||
// 创建 PlayerView 并添加到 FrameLayout 中
|
|
||||||
val view = PlayerView(context).apply {
|
|
||||||
hideController()
|
|
||||||
useController = false
|
|
||||||
player = exoPlayer
|
|
||||||
resizeMode =
|
|
||||||
AspectRatioFrameLayout.RESIZE_MODE_FIXED_HEIGHT // 或 RESIZE_MODE_ZOOM
|
|
||||||
layoutParams = FrameLayout.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
|
||||||
Gravity.CENTER
|
|
||||||
)
|
|
||||||
}
|
|
||||||
addView(view)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
modifier = Modifier.noRippleClickable {
|
modifier = Modifier
|
||||||
pauseIconVisibleState.value = true
|
.fillMaxSize()
|
||||||
exoPlayer.pause()
|
.clip(RectangleShape)
|
||||||
scope.launch {
|
.noRippleClickable {
|
||||||
delay(100)
|
handleVideoClick(pauseIconVisibleState, exoPlayer, scope)
|
||||||
if (exoPlayer.isPlaying) {
|
|
||||||
exoPlayer.pause()
|
|
||||||
} else {
|
|
||||||
pauseIconVisibleState.value = false
|
|
||||||
exoPlayer.play()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (pauseIconVisibleState.value) {
|
if (pauseIconVisibleState.value) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -348,11 +467,16 @@ fun VideoPlayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Release ExoPlayer when the videoUrl changes or the composable leaves composition
|
// Release ExoPlayer when the videoUrl changes or the composable leaves composition
|
||||||
|
// 使用 key 后,DisposableEffect 会在 videoUrl 变化时自动触发
|
||||||
DisposableEffect(videoUrl) {
|
DisposableEffect(videoUrl) {
|
||||||
onDispose {
|
onDispose {
|
||||||
|
try {
|
||||||
exoPlayer.release()
|
exoPlayer.release()
|
||||||
playerView?.player = null
|
playerView?.player = null
|
||||||
playerView = null // Release the reference to the PlayerView
|
playerView = null // Release the reference to the PlayerView
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// 忽略释放时的异常
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -360,12 +484,16 @@ fun VideoPlayer(
|
|||||||
val observer = LifecycleEventObserver { _, event ->
|
val observer = LifecycleEventObserver { _, event ->
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_PAUSE -> {
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
exoPlayer.pause() // 应用进入后台时暂停
|
// 应用进入后台时暂停
|
||||||
|
exoPlayer.playWhenReady = false
|
||||||
|
exoPlayer.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.Event.ON_RESUME -> {
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
// 返回前台且为当前页面时恢复播放
|
||||||
if (pager == pagerState.currentPage) {
|
if (pager == pagerState.currentPage) {
|
||||||
exoPlayer.play() // 返回前台且为当前页面时恢复播放
|
exoPlayer.playWhenReady = true
|
||||||
|
exoPlayer.play()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -496,9 +624,17 @@ fun VideoPlayer(
|
|||||||
if (showCommentModal && moment != null) {
|
if (showCommentModal && moment != null) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = { showCommentModal = false },
|
onDismissRequest = {
|
||||||
|
scope.launch {
|
||||||
|
sheetState.hide()
|
||||||
|
}
|
||||||
|
showCommentModal = false
|
||||||
|
},
|
||||||
containerColor = AppColors.background,
|
containerColor = AppColors.background,
|
||||||
sheetState = sheetState,
|
sheetState = sheetState,
|
||||||
|
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(sheetHeight)
|
.height(sheetHeight)
|
||||||
@@ -513,6 +649,7 @@ fun VideoPlayer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatar(
|
fun UserAvatar(
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ fun PointsBottomSheet(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.fillMaxHeight(0.98f)
|
.fillMaxHeight(0.9f)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
// 头部
|
// 头部
|
||||||
@@ -162,18 +162,21 @@ fun PointsBottomSheet(
|
|||||||
TabItem(
|
TabItem(
|
||||||
text = stringResource(R.string.transaction_history),
|
text = stringResource(R.string.transaction_history),
|
||||||
isSelected = tab == 0,
|
isSelected = tab == 0,
|
||||||
onClick = { tab = 0 }
|
onClick = { tab = 0 },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
TabSpacer()
|
TabSpacer()
|
||||||
TabItem(
|
TabItem(
|
||||||
text = stringResource(R.string.how_to_earn),
|
text = stringResource(R.string.how_to_earn),
|
||||||
isSelected = tab == 1,
|
isSelected = tab == 1,
|
||||||
onClick = { tab = 1 }
|
onClick = { tab = 1 },
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(8.dp))
|
Spacer(Modifier.height(8.dp))
|
||||||
|
|
||||||
|
// 列表区域
|
||||||
if (tab == 0) {
|
if (tab == 0) {
|
||||||
PointsHistoryList(
|
PointsHistoryList(
|
||||||
items = PointsViewModel.logs,
|
items = PointsViewModel.logs,
|
||||||
@@ -183,8 +186,6 @@ fun PointsBottomSheet(
|
|||||||
} else {
|
} else {
|
||||||
HowToEarnList()
|
HowToEarnList()
|
||||||
}
|
}
|
||||||
|
|
||||||
Spacer(Modifier.height(24.dp))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,10 @@ private fun PointsHistoryList(
|
|||||||
) {
|
) {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
val numberFormat = remember { NumberFormat.getNumberInstance(Locale.getDefault()) }
|
||||||
LazyColumn {
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
items(items) { item ->
|
items(items) { item ->
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -260,6 +264,7 @@ private fun PointsHistoryList(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun HowToEarnList() {
|
private fun HowToEarnList() {
|
||||||
val AppColors = LocalAppTheme.current
|
val AppColors = LocalAppTheme.current
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RowItem(title: String, desc: String, amount: String) {
|
fun RowItem(title: String, desc: String, amount: String) {
|
||||||
Row(
|
Row(
|
||||||
@@ -288,13 +293,23 @@ private fun HowToEarnList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
LazyColumn(
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
item {
|
||||||
RowItem(stringResource(R.string.new_user_reward), stringResource(R.string.new_user_reward_desc), "+500")
|
RowItem(stringResource(R.string.new_user_reward), stringResource(R.string.new_user_reward_desc), "+500")
|
||||||
|
}
|
||||||
|
item {
|
||||||
RowItem(stringResource(R.string.daily_check_in), stringResource(R.string.daily_check_in_desc), "+10-50")
|
RowItem(stringResource(R.string.daily_check_in), stringResource(R.string.daily_check_in_desc), "+10-50")
|
||||||
|
}
|
||||||
|
item {
|
||||||
RowItem(stringResource(R.string.invite_friends), stringResource(R.string.invite_friends_desc), "+100")
|
RowItem(stringResource(R.string.invite_friends), stringResource(R.string.invite_friends_desc), "+100")
|
||||||
|
}
|
||||||
|
item {
|
||||||
RowItem(stringResource(R.string.complete_tasks), stringResource(R.string.complete_tasks_desc), "+20-200")
|
RowItem(stringResource(R.string.complete_tasks), stringResource(R.string.complete_tasks_desc), "+20-200")
|
||||||
|
}
|
||||||
|
item {
|
||||||
RowItem(stringResource(R.string.recharge_pai_coin), stringResource(R.string.recharge_pai_coin_desc), ">")
|
RowItem(stringResource(R.string.recharge_pai_coin), stringResource(R.string.recharge_pai_coin_desc), ">")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user