@@ -53,7 +53,8 @@
|
|||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.App.Starting"
|
android:theme="@style/Theme.App.Starting"
|
||||||
android:screenOrientation="portrait"
|
android:screenOrientation="portrait"
|
||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize"
|
||||||
|
android:configChanges="fontScale|orientation|screenSize|keyboardHidden">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.ActivityInfo
|
import android.content.pm.ActivityInfo
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
@@ -22,6 +23,8 @@ import androidx.compose.animation.SharedTransitionScope
|
|||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
@@ -57,6 +60,21 @@ class MainActivity : ComponentActivity() {
|
|||||||
private val scope = CoroutineScope(Dispatchers.Main)
|
private val scope = CoroutineScope(Dispatchers.Main)
|
||||||
val context = this
|
val context = this
|
||||||
|
|
||||||
|
override fun attachBaseContext(newBase: Context) {
|
||||||
|
// 禁用字体缩放,固定字体大小为系统默认大小
|
||||||
|
val configuration = Configuration(newBase.resources.configuration)
|
||||||
|
configuration.fontScale = 1.0f
|
||||||
|
val context = newBase.createConfigurationContext(configuration)
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
// 确保配置变化时字体缩放保持为 1.0
|
||||||
|
val config = Configuration(newConfig)
|
||||||
|
config.fontScale = 1.0f
|
||||||
|
super.onConfigurationChanged(config)
|
||||||
|
}
|
||||||
|
|
||||||
// 请求通知权限
|
// 请求通知权限
|
||||||
private val requestPermissionLauncher = registerForActivityResult(
|
private val requestPermissionLauncher = registerForActivityResult(
|
||||||
ActivityResultContracts.RequestPermission(),
|
ActivityResultContracts.RequestPermission(),
|
||||||
@@ -128,6 +146,15 @@ class MainActivity : ComponentActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
|
// 强制字体缩放为 1.0 - 通过覆盖 Density 来实现
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val fixedDensity = remember {
|
||||||
|
androidx.compose.ui.unit.Density(
|
||||||
|
density = density.density,
|
||||||
|
fontScale = 1.0f
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
var showSplash by remember { mutableStateOf(true) }
|
var showSplash by remember { mutableStateOf(true) }
|
||||||
|
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
@@ -139,7 +166,8 @@ class MainActivity : ComponentActivity() {
|
|||||||
SplashScreen()
|
SplashScreen()
|
||||||
} else {
|
} else {
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalAppTheme provides AppState.appTheme
|
LocalAppTheme provides AppState.appTheme,
|
||||||
|
LocalDensity provides fixedDensity
|
||||||
) {
|
) {
|
||||||
CheckUpdateDialog()
|
CheckUpdateDialog()
|
||||||
// 全局挂载积分底部弹窗 Host
|
// 全局挂载积分底部弹窗 Host
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.aiosman.ravenow
|
|||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import com.google.firebase.FirebaseApp
|
import com.google.firebase.FirebaseApp
|
||||||
import com.google.firebase.perf.FirebasePerformance
|
import com.google.firebase.perf.FirebasePerformance
|
||||||
@@ -11,6 +12,14 @@ import com.google.firebase.perf.FirebasePerformance
|
|||||||
*/
|
*/
|
||||||
class RaveNowApplication : Application() {
|
class RaveNowApplication : Application() {
|
||||||
|
|
||||||
|
override fun attachBaseContext(base: Context) {
|
||||||
|
// 禁用字体缩放,固定字体大小为系统默认大小
|
||||||
|
val configuration = Configuration(base.resources.configuration)
|
||||||
|
configuration.fontScale = 1.0f
|
||||||
|
val context = base.createConfigurationContext(configuration)
|
||||||
|
super.attachBaseContext(context)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ import androidx.lifecycle.ViewModel
|
|||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.entity.CommentEntity
|
import com.aiosman.ravenow.entity.CommentEntity
|
||||||
import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
|
import com.aiosman.ravenow.ui.composables.EditCommentBottomModal
|
||||||
@@ -88,6 +89,7 @@ fun CommentModalContent(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
val commentViewModel = model.commentsViewModel
|
val commentViewModel = model.commentsViewModel
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
|
|
||||||
@@ -99,10 +101,24 @@ fun CommentModalContent(
|
|||||||
var bottomPadding by remember { mutableStateOf(0.dp) }
|
var bottomPadding by remember { mutableStateOf(0.dp) }
|
||||||
var softwareKeyboardController = LocalSoftwareKeyboardController.current
|
var softwareKeyboardController = LocalSoftwareKeyboardController.current
|
||||||
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
var replyComment by remember { mutableStateOf<CommentEntity?>(null) }
|
||||||
|
var shouldAutoFocus by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
LaunchedEffect(imePadding) {
|
LaunchedEffect(imePadding) {
|
||||||
bottomPadding = imePadding.dp
|
bottomPadding = imePadding.dp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 当设置回复评论时,自动聚焦到输入框
|
||||||
|
LaunchedEffect(replyComment) {
|
||||||
|
if (replyComment != null) {
|
||||||
|
// 延迟一下,确保输入框已经渲染
|
||||||
|
kotlinx.coroutines.delay(100)
|
||||||
|
shouldAutoFocus = true
|
||||||
|
// 请求显示键盘
|
||||||
|
softwareKeyboardController?.show()
|
||||||
|
} else {
|
||||||
|
shouldAutoFocus = false
|
||||||
|
}
|
||||||
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
onDispose {
|
onDispose {
|
||||||
onDismiss()
|
onDismiss()
|
||||||
@@ -113,7 +129,7 @@ fun CommentModalContent(
|
|||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showCommentMenu = false
|
showCommentMenu = false
|
||||||
},
|
},
|
||||||
containerColor = Color.White,
|
containerColor = AppColors.background,
|
||||||
sheetState = rememberModalBottomSheetState(
|
sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
),
|
),
|
||||||
@@ -152,12 +168,13 @@ fun CommentModalContent(
|
|||||||
stringResource(R.string.comment),
|
stringResource(R.string.comment),
|
||||||
fontSize = 18.sp,
|
fontSize = 18.sp,
|
||||||
fontWeight = FontWeight.Bold,
|
fontWeight = FontWeight.Bold,
|
||||||
|
color = AppColors.text,
|
||||||
modifier = Modifier.align(Alignment.Center)
|
modifier = Modifier.align(Alignment.Center)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
HorizontalDivider(
|
HorizontalDivider(
|
||||||
color = Color(0xFFF7F7F7)
|
color = AppColors.divider
|
||||||
)
|
)
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -169,7 +186,7 @@ fun CommentModalContent(
|
|||||||
Text(
|
Text(
|
||||||
text = stringResource(id = R.string.comment_count, commentCount),
|
text = stringResource(id = R.string.comment_count, commentCount),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = Color(0xff666666)
|
color = AppColors.secondaryText
|
||||||
)
|
)
|
||||||
OrderSelectionComponent {
|
OrderSelectionComponent {
|
||||||
commentViewModel.order = it
|
commentViewModel.order = it
|
||||||
@@ -193,7 +210,9 @@ fun CommentModalContent(
|
|||||||
|
|
||||||
},
|
},
|
||||||
onReply = { parentComment, _, _, _ ->
|
onReply = { parentComment, _, _, _ ->
|
||||||
|
// 设置回复的评论,这样 EditCommentBottomModal 会显示回复输入框
|
||||||
|
// CommentContent 内部已经处理了游客模式检查,所以这里直接设置即可
|
||||||
|
replyComment = parentComment
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(72.dp))
|
Spacer(modifier = Modifier.height(72.dp))
|
||||||
@@ -204,9 +223,12 @@ fun CommentModalContent(
|
|||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.background(Color(0xfff7f7f7))
|
.background(AppColors.secondaryBackground)
|
||||||
) {
|
) {
|
||||||
EditCommentBottomModal(replyComment) {
|
EditCommentBottomModal(
|
||||||
|
replyComment = replyComment,
|
||||||
|
autoFocus = shouldAutoFocus
|
||||||
|
) {
|
||||||
commentViewModel.viewModelScope.launch {
|
commentViewModel.viewModelScope.launch {
|
||||||
if (replyComment != null) {
|
if (replyComment != null) {
|
||||||
if (replyComment?.parentCommentId != null) {
|
if (replyComment?.parentCommentId != null) {
|
||||||
@@ -224,6 +246,13 @@ fun CommentModalContent(
|
|||||||
// 顶级评论
|
// 顶级评论
|
||||||
commentViewModel.createComment(it)
|
commentViewModel.createComment(it)
|
||||||
}
|
}
|
||||||
|
// 评论创建成功后调用回调
|
||||||
|
onCommentAdded()
|
||||||
|
// 清空回复状态和自动聚焦状态
|
||||||
|
replyComment = null
|
||||||
|
shouldAutoFocus = false
|
||||||
|
// 隐藏键盘
|
||||||
|
softwareKeyboardController?.hide()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import androidx.compose.ui.focus.focusRequester
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.SolidColor
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
@@ -60,10 +61,15 @@ fun EditCommentBottomModal(
|
|||||||
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding()
|
||||||
val focusRequester = remember { FocusRequester() }
|
val focusRequester = remember { FocusRequester() }
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
val keyboardController = LocalSoftwareKeyboardController.current
|
||||||
|
|
||||||
LaunchedEffect(autoFocus) {
|
LaunchedEffect(autoFocus) {
|
||||||
if (autoFocus) {
|
if (autoFocus) {
|
||||||
|
// 延迟一下,确保输入框已经渲染完成
|
||||||
|
kotlinx.coroutines.delay(150)
|
||||||
focusRequester.requestFocus()
|
focusRequester.requestFocus()
|
||||||
|
// 显示键盘
|
||||||
|
keyboardController?.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +89,7 @@ fun EditCommentBottomModal(
|
|||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.clip(RoundedCornerShape(20.dp))
|
.clip(RoundedCornerShape(20.dp))
|
||||||
.background(Color.Gray.copy(alpha = 0.1f))
|
.background(AppColors.inputBackground)
|
||||||
.padding(horizontal = 16.dp, vertical = 16.dp)
|
.padding(horizontal = 16.dp, vertical = 16.dp)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
@@ -100,7 +106,7 @@ fun EditCommentBottomModal(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.focusRequester(focusRequester),
|
.focusRequester(focusRequester),
|
||||||
textStyle = TextStyle(
|
textStyle = TextStyle(
|
||||||
color = Color.Black,
|
color = AppColors.text,
|
||||||
fontWeight = FontWeight.Normal
|
fontWeight = FontWeight.Normal
|
||||||
),
|
),
|
||||||
decorationBox = { innerTextField ->
|
decorationBox = { innerTextField ->
|
||||||
|
|||||||
@@ -155,6 +155,17 @@ fun FavouriteListPage() {
|
|||||||
) {
|
) {
|
||||||
items(moments.itemCount) { idx ->
|
items(moments.itemCount) { idx ->
|
||||||
val momentItem = moments[idx] ?: return@items
|
val momentItem = moments[idx] ?: return@items
|
||||||
|
// 获取缩略图URL:优先使用图片,如果没有图片则使用视频缩略图
|
||||||
|
val thumbnailUrl = when {
|
||||||
|
momentItem.images.isNotEmpty() -> momentItem.images[0].thumbnail
|
||||||
|
momentItem.videos != null && momentItem.videos.isNotEmpty() -> {
|
||||||
|
momentItem.videos.first().thumbnailUrl ?: momentItem.videos.first().thumbnailDirectUrl
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thumbnailUrl == null) return@items
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -169,7 +180,7 @@ fun FavouriteListPage() {
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
imageUrl = momentItem.images[0].thumbnail,
|
imageUrl = thumbnailUrl,
|
||||||
contentDescription = "",
|
contentDescription = "",
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
|
|||||||
@@ -154,12 +154,12 @@ fun ShortVideoScreen() {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onCommentClick = { moment ->
|
onCommentClick = { moment ->
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
// 点击评论图标只是打开评论弹窗,不应该增加评论数
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
},
|
||||||
} else {
|
onCommentAdded = { moment ->
|
||||||
scope.launch {
|
// 评论添加后的回调,更新评论数
|
||||||
viewModel.onAddComment(moment.id)
|
scope.launch {
|
||||||
}
|
viewModel.onAddComment(moment.id)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFavoriteClick = { moment ->
|
onFavoriteClick = { moment ->
|
||||||
@@ -178,6 +178,15 @@ fun ShortVideoScreen() {
|
|||||||
onShareClick = { moment ->
|
onShareClick = { moment ->
|
||||||
// TODO: 实现分享功能
|
// TODO: 实现分享功能
|
||||||
},
|
},
|
||||||
|
onAvatarClick = { moment ->
|
||||||
|
// 点击头像进入用户界面
|
||||||
|
navController.navigate(
|
||||||
|
com.aiosman.ravenow.ui.NavigationRoute.AccountProfile.route.replace(
|
||||||
|
"{id}",
|
||||||
|
moment.authorId.toString()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
onPageChanged = { idx -> currentIndex.value = idx }
|
onPageChanged = { idx -> currentIndex.value = idx }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.Column
|
|||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.padding
|
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
|
||||||
@@ -43,7 +44,9 @@ import androidx.compose.ui.Alignment
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
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.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.LocalLifecycleOwner
|
import androidx.compose.ui.platform.LocalLifecycleOwner
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
@@ -67,6 +70,7 @@ import androidx.media3.exoplayer.ExoPlayer
|
|||||||
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
import androidx.media3.exoplayer.source.ProgressiveMediaSource
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
|
import com.aiosman.ravenow.LocalAppTheme
|
||||||
import com.aiosman.ravenow.R
|
import com.aiosman.ravenow.R
|
||||||
import com.aiosman.ravenow.entity.MomentEntity
|
import com.aiosman.ravenow.entity.MomentEntity
|
||||||
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
import com.aiosman.ravenow.ui.comment.CommentModalContent
|
||||||
@@ -75,6 +79,9 @@ import com.aiosman.ravenow.ui.modifiers.noRippleClickable
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
// 激活状态的颜色(点赞/收藏时的红色)
|
||||||
|
private val ActiveIconColor = Color(0xFFD80264)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun ShortViewCompose(
|
fun ShortViewCompose(
|
||||||
videoItemsUrl: List<String> = emptyList(),
|
videoItemsUrl: List<String> = emptyList(),
|
||||||
@@ -84,8 +91,10 @@ fun ShortViewCompose(
|
|||||||
videoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
videoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
||||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
onCommentClick: ((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,
|
||||||
onPageChanged: ((Int) -> Unit)? = null
|
onPageChanged: ((Int) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
// 优先使用 videoMoments,如果没有则使用 videoItemsUrl
|
// 优先使用 videoMoments,如果没有则使用 videoItemsUrl
|
||||||
@@ -159,8 +168,10 @@ fun ShortViewCompose(
|
|||||||
VideoBottom = videoBottom,
|
VideoBottom = videoBottom,
|
||||||
onLikeClick = onLikeClick,
|
onLikeClick = onLikeClick,
|
||||||
onCommentClick = onCommentClick,
|
onCommentClick = onCommentClick,
|
||||||
|
onCommentAdded = onCommentAdded,
|
||||||
onFavoriteClick = onFavoriteClick,
|
onFavoriteClick = onFavoriteClick,
|
||||||
onShareClick = onShareClick
|
onShareClick = onShareClick,
|
||||||
|
onAvatarClick = onAvatarClick
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,8 +194,10 @@ private fun SingleVideoItemContent(
|
|||||||
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
||||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
onCommentClick: ((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
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -199,8 +212,10 @@ private fun SingleVideoItemContent(
|
|||||||
pauseIconVisibleState = pauseIconVisibleState,
|
pauseIconVisibleState = pauseIconVisibleState,
|
||||||
onLikeClick = onLikeClick,
|
onLikeClick = onLikeClick,
|
||||||
onCommentClick = onCommentClick,
|
onCommentClick = onCommentClick,
|
||||||
|
onCommentAdded = onCommentAdded,
|
||||||
onFavoriteClick = onFavoriteClick,
|
onFavoriteClick = onFavoriteClick,
|
||||||
onShareClick = onShareClick
|
onShareClick = onShareClick,
|
||||||
|
onAvatarClick = onAvatarClick
|
||||||
)
|
)
|
||||||
VideoHeader.invoke()
|
VideoHeader.invoke()
|
||||||
if (moment != null && VideoBottom != null) {
|
if (moment != null && VideoBottom != null) {
|
||||||
@@ -228,12 +243,17 @@ fun VideoPlayer(
|
|||||||
pauseIconVisibleState: MutableState<Boolean>,
|
pauseIconVisibleState: MutableState<Boolean>,
|
||||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||||
onCommentClick: ((MomentEntity) -> Unit)? = null,
|
onCommentClick: ((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,
|
||||||
) {
|
) {
|
||||||
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 screenHeight = configuration.screenHeightDp.dp
|
||||||
|
val sheetHeight = screenHeight * 0.7f // 屏幕的一大半高度
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
var showCommentModal by remember { mutableStateOf(false) }
|
||||||
var sheetState = rememberModalBottomSheetState(
|
var sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
@@ -371,42 +391,51 @@ fun VideoPlayer(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (moment != null) {
|
if (moment != null) {
|
||||||
UserAvatar(avatarUrl = moment.avatar)
|
// 使用 key 确保状态变化时重新组合
|
||||||
VideoBtn(
|
androidx.compose.runtime.key(moment.id, moment.isFavorite) {
|
||||||
icon = R.drawable.rider_pro_video_like,
|
UserAvatar(
|
||||||
text = formatCount(moment.likeCount)
|
avatarUrl = moment.avatar,
|
||||||
) {
|
onClick = { onAvatarClick?.invoke(moment) }
|
||||||
moment?.let { onLikeClick?.invoke(it) }
|
)
|
||||||
}
|
VideoBtn(
|
||||||
VideoBtn(
|
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
||||||
icon = R.drawable.rider_pro_video_comment,
|
text = formatCount(moment.likeCount),
|
||||||
text = formatCount(moment.commentCount)
|
isActive = moment.liked,
|
||||||
) {
|
onClick = { onLikeClick?.invoke(moment) }
|
||||||
moment?.let {
|
)
|
||||||
showCommentModal = true
|
VideoBtn(
|
||||||
onCommentClick?.invoke(it)
|
icon = R.mipmap.icon_comment,
|
||||||
}
|
text = formatCount(moment.commentCount),
|
||||||
}
|
onClick = {
|
||||||
VideoBtn(
|
showCommentModal = true
|
||||||
icon = R.drawable.rider_pro_video_favor,
|
onCommentClick?.invoke(moment)
|
||||||
text = formatCount(moment.favoriteCount)
|
}
|
||||||
) {
|
)
|
||||||
moment?.let { onFavoriteClick?.invoke(it) }
|
VideoBtn(
|
||||||
}
|
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
||||||
VideoBtn(
|
text = formatCount(moment.favoriteCount),
|
||||||
icon = R.drawable.rider_pro_video_share,
|
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
|
||||||
text = formatCount(moment.shareCount)
|
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
|
||||||
) {
|
onClick = { onFavoriteClick?.invoke(moment) }
|
||||||
moment?.let { onShareClick?.invoke(it) }
|
)
|
||||||
|
VideoBtn(
|
||||||
|
icon = R.mipmap.icon_share,
|
||||||
|
text = formatCount(moment.shareCount),
|
||||||
|
onClick = { onShareClick?.invoke(moment) }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
UserAvatar()
|
UserAvatar()
|
||||||
VideoBtn(icon = R.drawable.rider_pro_video_like, text = "0")
|
VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0")
|
||||||
VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "0") {
|
VideoBtn(
|
||||||
showCommentModal = true
|
icon = R.mipmap.icon_comment,
|
||||||
}
|
text = "0",
|
||||||
VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "0")
|
onClick = {
|
||||||
VideoBtn(icon = R.drawable.rider_pro_video_share, text = "0")
|
showCommentModal = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
VideoBtn(icon = R.mipmap.icon_collect, text = "0")
|
||||||
|
VideoBtn(icon = R.mipmap.icon_share, text = "0")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -465,26 +494,44 @@ fun VideoPlayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (showCommentModal && moment != null) {
|
if (showCommentModal && moment != null) {
|
||||||
|
val AppColors = LocalAppTheme.current
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = { showCommentModal = false },
|
onDismissRequest = { showCommentModal = false },
|
||||||
containerColor = Color.White,
|
containerColor = AppColors.background,
|
||||||
sheetState = sheetState
|
sheetState = sheetState,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.height(sheetHeight)
|
||||||
) {
|
) {
|
||||||
CommentModalContent(postId = moment.id) {
|
CommentModalContent(
|
||||||
|
postId = moment.id,
|
||||||
}
|
commentCount = moment.commentCount,
|
||||||
|
onCommentAdded = {
|
||||||
|
onCommentAdded?.invoke(moment)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun UserAvatar(avatarUrl: String? = null) {
|
fun UserAvatar(
|
||||||
|
avatarUrl: String? = null,
|
||||||
|
onClick: (() -> Unit)? = null
|
||||||
|
) {
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp)
|
.padding(bottom = 16.dp)
|
||||||
.size(40.dp)
|
.size(40.dp)
|
||||||
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
||||||
.clip(RoundedCornerShape(40.dp))
|
.clip(RoundedCornerShape(40.dp))
|
||||||
|
.then(
|
||||||
|
if (onClick != null) {
|
||||||
|
Modifier.noRippleClickable { onClick() }
|
||||||
|
} else {
|
||||||
|
Modifier
|
||||||
|
}
|
||||||
|
)
|
||||||
) {
|
) {
|
||||||
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
@@ -512,19 +559,31 @@ private fun formatCount(count: Int): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun VideoBtn(@DrawableRes icon: Int, text: String, onClick: (() -> Unit)? = null) {
|
fun VideoBtn(
|
||||||
|
@DrawableRes icon: Int,
|
||||||
|
text: String,
|
||||||
|
onClick: (() -> Unit)? = null,
|
||||||
|
isActive: Boolean = false,
|
||||||
|
keepOriginalColor: Boolean = false // 是否保持原始颜色(不应用白色滤镜)
|
||||||
|
) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(bottom = 16.dp)
|
.padding(bottom = 16.dp)
|
||||||
.clickable {
|
.noRippleClickable {
|
||||||
onClick?.invoke()
|
onClick?.invoke()
|
||||||
},
|
},
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Image(
|
Image(
|
||||||
modifier = Modifier.size(36.dp),
|
modifier = Modifier.size(30.dp),
|
||||||
painter = painterResource(id = icon),
|
painter = painterResource(id = icon),
|
||||||
contentDescription = ""
|
contentDescription = "",
|
||||||
|
contentScale = ContentScale.FillBounds, // 填满容器,让图标看起来更大
|
||||||
|
colorFilter = when {
|
||||||
|
isActive -> ColorFilter.tint(ActiveIconColor)
|
||||||
|
keepOriginalColor -> null // 保持原始颜色
|
||||||
|
else -> ColorFilter.tint(Color.White) // 未激活状态时图标为白色
|
||||||
|
}
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
text = text,
|
text = text,
|
||||||
|
|||||||
BIN
app/src/main/res/mipmap-hdpi/ts_dsp_z_wxz_btn.png
Normal file
|
After Width: | Height: | Size: 970 B |
BIN
app/src/main/res/mipmap-hdpi/ts_dsp_z_xz_btn.png
Normal file
|
After Width: | Height: | Size: 423 B |
BIN
app/src/main/res/mipmap-mdpi/ts_dsp_z_wxz_btn.png
Normal file
|
After Width: | Height: | Size: 657 B |
BIN
app/src/main/res/mipmap-mdpi/ts_dsp_z_xz_btn.png
Normal file
|
After Width: | Height: | Size: 333 B |
BIN
app/src/main/res/mipmap-xhdpi/ts_dsp_z_wxz_btn.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ts_dsp_z_xz_btn.png
Normal file
|
After Width: | Height: | Size: 482 B |
BIN
app/src/main/res/mipmap-xxhdpi/ts_dsp_z_wxz_btn.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ts_dsp_z_xz_btn.png
Normal file
|
After Width: | Height: | Size: 647 B |
BIN
app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_wxz_btn.png
Normal file
|
After Width: | Height: | Size: 2.9 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_xz_btn.png
Normal file
|
After Width: | Height: | Size: 805 B |