From 58944bd0916ef19ca29a9442fda5c7fe5e34ddb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E5=B8=86?= <3031465419@qq.com> Date: Tue, 11 Nov 2025 16:58:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=8A=A8=E6=80=81-=E7=9F=AD?= =?UTF-8?q?=E8=A7=86=E9=A2=91=E7=95=8C=E9=9D=A2=E7=9A=84=E5=90=84=E7=A7=8D?= =?UTF-8?q?bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复动态-短视频界面点赞/收藏后图标未改变 修改动态-短视频界面点赞/收藏/评论/分享前后图标 修复系统字体大小设置为150%出现字体堆叠 修复收藏视频动态后点击收藏夹必现闪退 修复动态-短视频界面每次点击评论图标,评论总数都会+1以及评论弹框中总评论数为0以及评论弹框中无法回复评论以及无法点击头像进入用户主页 评论界面做了深色模式适配 --- app/src/main/AndroidManifest.xml | 3 +- .../java/com/aiosman/ravenow/MainActivity.kt | 30 +++- .../com/aiosman/ravenow/RaveNowApplication.kt | 9 ++ .../ravenow/ui/comment/CommentModal.kt | 41 ++++- .../ui/composables/EditCommentBottomModal.kt | 10 +- .../ravenow/ui/favourite/FavouriteListPage.kt | 13 +- .../moment/tabs/shorts/ShortVideoScreen.kt | 21 ++- .../ui/index/tabs/shorts/ShortViewCompose.kt | 151 ++++++++++++------ .../main/res/mipmap-hdpi/ts_dsp_z_wxz_btn.png | Bin 0 -> 970 bytes .../main/res/mipmap-hdpi/ts_dsp_z_xz_btn.png | Bin 0 -> 423 bytes .../main/res/mipmap-mdpi/ts_dsp_z_wxz_btn.png | Bin 0 -> 657 bytes .../main/res/mipmap-mdpi/ts_dsp_z_xz_btn.png | Bin 0 -> 333 bytes .../res/mipmap-xhdpi/ts_dsp_z_wxz_btn.png | Bin 0 -> 1308 bytes .../main/res/mipmap-xhdpi/ts_dsp_z_xz_btn.png | Bin 0 -> 482 bytes .../res/mipmap-xxhdpi/ts_dsp_z_wxz_btn.png | Bin 0 -> 1977 bytes .../res/mipmap-xxhdpi/ts_dsp_z_xz_btn.png | Bin 0 -> 647 bytes .../res/mipmap-xxxhdpi/ts_dsp_z_wxz_btn.png | Bin 0 -> 2949 bytes .../res/mipmap-xxxhdpi/ts_dsp_z_xz_btn.png | Bin 0 -> 805 bytes 18 files changed, 215 insertions(+), 63 deletions(-) create mode 100644 app/src/main/res/mipmap-hdpi/ts_dsp_z_wxz_btn.png create mode 100644 app/src/main/res/mipmap-hdpi/ts_dsp_z_xz_btn.png create mode 100644 app/src/main/res/mipmap-mdpi/ts_dsp_z_wxz_btn.png create mode 100644 app/src/main/res/mipmap-mdpi/ts_dsp_z_xz_btn.png create mode 100644 app/src/main/res/mipmap-xhdpi/ts_dsp_z_wxz_btn.png create mode 100644 app/src/main/res/mipmap-xhdpi/ts_dsp_z_xz_btn.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ts_dsp_z_wxz_btn.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ts_dsp_z_xz_btn.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_wxz_btn.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_xz_btn.png diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4d71b37..fa31758 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -53,7 +53,8 @@ android:label="@string/app_name" android:theme="@style/Theme.App.Starting" android:screenOrientation="portrait" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustResize" + android:configChanges="fontScale|orientation|screenSize|keyboardHidden"> diff --git a/app/src/main/java/com/aiosman/ravenow/MainActivity.kt b/app/src/main/java/com/aiosman/ravenow/MainActivity.kt index c439be3..c566ccf 100644 --- a/app/src/main/java/com/aiosman/ravenow/MainActivity.kt +++ b/app/src/main/java/com/aiosman/ravenow/MainActivity.kt @@ -7,6 +7,7 @@ import android.content.Context import android.content.Intent import android.content.pm.ActivityInfo import android.content.pm.PackageManager +import android.content.res.Configuration import android.net.Uri import android.os.Build import android.os.Bundle @@ -22,6 +23,8 @@ import androidx.compose.animation.SharedTransitionScope import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.CompositionLocalProvider 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.view.WindowCompat import androidx.lifecycle.ProcessLifecycleOwner @@ -57,6 +60,21 @@ class MainActivity : ComponentActivity() { private val scope = CoroutineScope(Dispatchers.Main) 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( ActivityResultContracts.RequestPermission(), @@ -128,6 +146,15 @@ class MainActivity : ComponentActivity() { } 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) } LaunchedEffect(Unit) { @@ -139,7 +166,8 @@ class MainActivity : ComponentActivity() { SplashScreen() } else { CompositionLocalProvider( - LocalAppTheme provides AppState.appTheme + LocalAppTheme provides AppState.appTheme, + LocalDensity provides fixedDensity ) { CheckUpdateDialog() // 全局挂载积分底部弹窗 Host diff --git a/app/src/main/java/com/aiosman/ravenow/RaveNowApplication.kt b/app/src/main/java/com/aiosman/ravenow/RaveNowApplication.kt index d9b948e..367187a 100644 --- a/app/src/main/java/com/aiosman/ravenow/RaveNowApplication.kt +++ b/app/src/main/java/com/aiosman/ravenow/RaveNowApplication.kt @@ -2,6 +2,7 @@ package com.aiosman.ravenow import android.app.Application import android.content.Context +import android.content.res.Configuration import android.util.Log import com.google.firebase.FirebaseApp import com.google.firebase.perf.FirebasePerformance @@ -11,6 +12,14 @@ import com.google.firebase.perf.FirebasePerformance */ 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() { super.onCreate() diff --git a/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentModal.kt b/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentModal.kt index c3483b7..7902829 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentModal.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/comment/CommentModal.kt @@ -40,6 +40,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.R import com.aiosman.ravenow.entity.CommentEntity import com.aiosman.ravenow.ui.composables.EditCommentBottomModal @@ -88,6 +89,7 @@ fun CommentModalContent( } ) val commentViewModel = model.commentsViewModel + val AppColors = LocalAppTheme.current var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() LaunchedEffect(Unit) { @@ -99,10 +101,24 @@ fun CommentModalContent( var bottomPadding by remember { mutableStateOf(0.dp) } var softwareKeyboardController = LocalSoftwareKeyboardController.current var replyComment by remember { mutableStateOf(null) } + var shouldAutoFocus by remember { mutableStateOf(false) } LaunchedEffect(imePadding) { bottomPadding = imePadding.dp } + + // 当设置回复评论时,自动聚焦到输入框 + LaunchedEffect(replyComment) { + if (replyComment != null) { + // 延迟一下,确保输入框已经渲染 + kotlinx.coroutines.delay(100) + shouldAutoFocus = true + // 请求显示键盘 + softwareKeyboardController?.show() + } else { + shouldAutoFocus = false + } + } DisposableEffect(Unit) { onDispose { onDismiss() @@ -113,7 +129,7 @@ fun CommentModalContent( onDismissRequest = { showCommentMenu = false }, - containerColor = Color.White, + containerColor = AppColors.background, sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true ), @@ -153,12 +169,13 @@ fun CommentModalContent( stringResource(R.string.comment), fontSize = 18.sp, fontWeight = FontWeight.Bold, + color = AppColors.text, modifier = Modifier.align(Alignment.Center) ) } HorizontalDivider( - color = Color(0xFFF7F7F7) + color = AppColors.divider ) Row( modifier = Modifier @@ -170,7 +187,7 @@ fun CommentModalContent( Text( text = stringResource(id = R.string.comment_count, commentCount), fontSize = 14.sp, - color = Color(0xff666666) + color = AppColors.secondaryText ) OrderSelectionComponent { commentViewModel.order = it @@ -194,7 +211,9 @@ fun CommentModalContent( }, onReply = { parentComment, _, _, _ -> - + // 设置回复的评论,这样 EditCommentBottomModal 会显示回复输入框 + // CommentContent 内部已经处理了游客模式检查,所以这里直接设置即可 + replyComment = parentComment }, ) Spacer(modifier = Modifier.height(72.dp)) @@ -205,9 +224,12 @@ fun CommentModalContent( Column( modifier = Modifier .fillMaxWidth() - .background(Color(0xfff7f7f7)) + .background(AppColors.secondaryBackground) ) { - EditCommentBottomModal(replyComment) { + EditCommentBottomModal( + replyComment = replyComment, + autoFocus = shouldAutoFocus + ) { commentViewModel.viewModelScope.launch { if (replyComment != null) { if (replyComment?.parentCommentId != null) { @@ -225,6 +247,13 @@ fun CommentModalContent( // 顶级评论 commentViewModel.createComment(it) } + // 评论创建成功后调用回调 + onCommentAdded() + // 清空回复状态和自动聚焦状态 + replyComment = null + shouldAutoFocus = false + // 隐藏键盘 + softwareKeyboardController?.hide() } } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/composables/EditCommentBottomModal.kt b/app/src/main/java/com/aiosman/ravenow/ui/composables/EditCommentBottomModal.kt index 86ec076..1dd4b09 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/composables/EditCommentBottomModal.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/composables/EditCommentBottomModal.kt @@ -36,6 +36,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle @@ -60,10 +61,15 @@ fun EditCommentBottomModal( var navBarHeight = WindowInsets.navigationBars.asPaddingValues().calculateBottomPadding() val focusRequester = remember { FocusRequester() } val context = LocalContext.current + val keyboardController = LocalSoftwareKeyboardController.current LaunchedEffect(autoFocus) { if (autoFocus) { + // 延迟一下,确保输入框已经渲染完成 + kotlinx.coroutines.delay(150) focusRequester.requestFocus() + // 显示键盘 + keyboardController?.show() } } @@ -83,7 +89,7 @@ fun EditCommentBottomModal( .fillMaxWidth() .weight(1f) .clip(RoundedCornerShape(20.dp)) - .background(Color.Gray.copy(alpha = 0.1f)) + .background(AppColors.inputBackground) .padding(horizontal = 16.dp, vertical = 16.dp) ) { Row( @@ -100,7 +106,7 @@ fun EditCommentBottomModal( .weight(1f) .focusRequester(focusRequester), textStyle = TextStyle( - color = Color.Black, + color = AppColors.text, fontWeight = FontWeight.Normal ), decorationBox = { innerTextField -> diff --git a/app/src/main/java/com/aiosman/ravenow/ui/favourite/FavouriteListPage.kt b/app/src/main/java/com/aiosman/ravenow/ui/favourite/FavouriteListPage.kt index b448376..f27f4cd 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/favourite/FavouriteListPage.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/favourite/FavouriteListPage.kt @@ -155,6 +155,17 @@ fun FavouriteListPage() { ) { items(moments.itemCount) { idx -> 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( modifier = Modifier .fillMaxWidth() @@ -169,7 +180,7 @@ fun FavouriteListPage() { } ) { CustomAsyncImage( - imageUrl = momentItem.images[0].thumbnail, + imageUrl = thumbnailUrl, contentDescription = "", modifier = Modifier .fillMaxSize() diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/shorts/ShortVideoScreen.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/shorts/ShortVideoScreen.kt index e58de32..d4e1a47 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/shorts/ShortVideoScreen.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/moment/tabs/shorts/ShortVideoScreen.kt @@ -154,12 +154,12 @@ fun ShortVideoScreen() { } }, onCommentClick = { moment -> - if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) { - navController.navigate(NavigationRoute.Login.route) - } else { - scope.launch { - viewModel.onAddComment(moment.id) - } + // 点击评论图标只是打开评论弹窗,不应该增加评论数 + }, + onCommentAdded = { moment -> + // 评论添加后的回调,更新评论数 + scope.launch { + viewModel.onAddComment(moment.id) } }, onFavoriteClick = { moment -> @@ -178,6 +178,15 @@ fun ShortVideoScreen() { onShareClick = { moment -> // TODO: 实现分享功能 }, + onAvatarClick = { moment -> + // 点击头像进入用户界面 + navController.navigate( + com.aiosman.ravenow.ui.NavigationRoute.AccountProfile.route.replace( + "{id}", + moment.authorId.toString() + ) + ) + }, onPageChanged = { idx -> currentIndex.value = idx } ) } diff --git a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt index 68c368a..3b412e6 100644 --- a/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt +++ b/app/src/main/java/com/aiosman/ravenow/ui/index/tabs/shorts/ShortViewCompose.kt @@ -19,6 +19,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape @@ -43,7 +44,9 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.painterResource @@ -67,6 +70,7 @@ import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView +import com.aiosman.ravenow.LocalAppTheme import com.aiosman.ravenow.R import com.aiosman.ravenow.entity.MomentEntity 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.launch +// 激活状态的颜色(点赞/收藏时的红色) +private val ActiveIconColor = Color(0xFFD80264) + @Composable fun ShortViewCompose( videoItemsUrl: List = emptyList(), @@ -84,8 +91,10 @@ fun ShortViewCompose( videoBottom: @Composable ((MomentEntity) -> Unit)? = null, onLikeClick: ((MomentEntity) -> Unit)? = null, onCommentClick: ((MomentEntity) -> Unit)? = null, + onCommentAdded: ((MomentEntity) -> Unit)? = null, onFavoriteClick: ((MomentEntity) -> Unit)? = null, onShareClick: ((MomentEntity) -> Unit)? = null, + onAvatarClick: ((MomentEntity) -> Unit)? = null, onPageChanged: ((Int) -> Unit)? = null ) { // 优先使用 videoMoments,如果没有则使用 videoItemsUrl @@ -159,8 +168,10 @@ fun ShortViewCompose( VideoBottom = videoBottom, onLikeClick = onLikeClick, onCommentClick = onCommentClick, + onCommentAdded = onCommentAdded, onFavoriteClick = onFavoriteClick, - onShareClick = onShareClick + onShareClick = onShareClick, + onAvatarClick = onAvatarClick ) } @@ -183,8 +194,10 @@ private fun SingleVideoItemContent( VideoBottom: @Composable ((MomentEntity) -> Unit)? = null, onLikeClick: ((MomentEntity) -> Unit)? = null, onCommentClick: ((MomentEntity) -> Unit)? = null, + onCommentAdded: ((MomentEntity) -> Unit)? = null, onFavoriteClick: ((MomentEntity) -> Unit)? = null, - onShareClick: ((MomentEntity) -> Unit)? = null + onShareClick: ((MomentEntity) -> Unit)? = null, + onAvatarClick: ((MomentEntity) -> Unit)? = null ) { Box( modifier = Modifier @@ -199,8 +212,10 @@ private fun SingleVideoItemContent( pauseIconVisibleState = pauseIconVisibleState, onLikeClick = onLikeClick, onCommentClick = onCommentClick, + onCommentAdded = onCommentAdded, onFavoriteClick = onFavoriteClick, - onShareClick = onShareClick + onShareClick = onShareClick, + onAvatarClick = onAvatarClick ) VideoHeader.invoke() if (moment != null && VideoBottom != null) { @@ -228,12 +243,17 @@ fun VideoPlayer( pauseIconVisibleState: MutableState, onLikeClick: ((MomentEntity) -> Unit)? = null, onCommentClick: ((MomentEntity) -> Unit)? = null, + onCommentAdded: ((MomentEntity) -> Unit)? = null, onFavoriteClick: ((MomentEntity) -> Unit)? = null, onShareClick: ((MomentEntity) -> Unit)? = null, + onAvatarClick: ((MomentEntity) -> Unit)? = null, ) { val context = LocalContext.current val scope = rememberCoroutineScope() 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 sheetState = rememberModalBottomSheetState( skipPartiallyExpanded = true @@ -371,42 +391,51 @@ fun VideoPlayer( horizontalAlignment = Alignment.CenterHorizontally ) { if (moment != null) { - UserAvatar(avatarUrl = moment.avatar) - VideoBtn( - icon = R.drawable.rider_pro_video_like, - text = formatCount(moment.likeCount) - ) { - moment?.let { onLikeClick?.invoke(it) } - } - VideoBtn( - icon = R.drawable.rider_pro_video_comment, - text = formatCount(moment.commentCount) - ) { - moment?.let { - showCommentModal = true - onCommentClick?.invoke(it) - } - } - VideoBtn( - icon = R.drawable.rider_pro_video_favor, - text = formatCount(moment.favoriteCount) - ) { - moment?.let { onFavoriteClick?.invoke(it) } - } - VideoBtn( - icon = R.drawable.rider_pro_video_share, - text = formatCount(moment.shareCount) - ) { - moment?.let { onShareClick?.invoke(it) } + // 使用 key 确保状态变化时重新组合 + androidx.compose.runtime.key(moment.id, moment.isFavorite) { + UserAvatar( + avatarUrl = moment.avatar, + onClick = { onAvatarClick?.invoke(moment) } + ) + VideoBtn( + icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like, + text = formatCount(moment.likeCount), + isActive = moment.liked, + onClick = { onLikeClick?.invoke(moment) } + ) + VideoBtn( + icon = R.mipmap.icon_comment, + text = formatCount(moment.commentCount), + onClick = { + showCommentModal = true + onCommentClick?.invoke(moment) + } + ) + VideoBtn( + icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect, + text = formatCount(moment.favoriteCount), + isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色 + keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色 + onClick = { onFavoriteClick?.invoke(moment) } + ) + VideoBtn( + icon = R.mipmap.icon_share, + text = formatCount(moment.shareCount), + onClick = { onShareClick?.invoke(moment) } + ) } } else { UserAvatar() - VideoBtn(icon = R.drawable.rider_pro_video_like, text = "0") - VideoBtn(icon = R.drawable.rider_pro_video_comment, text = "0") { - showCommentModal = true - } - VideoBtn(icon = R.drawable.rider_pro_video_favor, text = "0") - VideoBtn(icon = R.drawable.rider_pro_video_share, text = "0") + VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0") + VideoBtn( + icon = R.mipmap.icon_comment, + text = "0", + onClick = { + 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) { + val AppColors = LocalAppTheme.current ModalBottomSheet( onDismissRequest = { showCommentModal = false }, - containerColor = Color.White, - sheetState = sheetState + containerColor = AppColors.background, + sheetState = sheetState, + modifier = Modifier + .fillMaxWidth() + .height(sheetHeight) ) { - CommentModalContent(postId = moment.id) { - - } + CommentModalContent( + postId = moment.id, + commentCount = moment.commentCount, + onCommentAdded = { + onCommentAdded?.invoke(moment) + } + ) } } } @Composable -fun UserAvatar(avatarUrl: String? = null) { +fun UserAvatar( + avatarUrl: String? = null, + onClick: (() -> Unit)? = null +) { Box( modifier = Modifier .padding(bottom = 16.dp) .size(40.dp) .border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp)) .clip(RoundedCornerShape(40.dp)) + .then( + if (onClick != null) { + Modifier.noRippleClickable { onClick() } + } else { + Modifier + } + ) ) { if (avatarUrl != null && avatarUrl.isNotEmpty()) { CustomAsyncImage( @@ -512,19 +559,31 @@ private fun formatCount(count: Int): String { } @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( modifier = Modifier .padding(bottom = 16.dp) - .clickable { + .noRippleClickable { onClick?.invoke() }, horizontalAlignment = Alignment.CenterHorizontally, ) { Image( - modifier = Modifier.size(36.dp), + modifier = Modifier.size(30.dp), 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, diff --git a/app/src/main/res/mipmap-hdpi/ts_dsp_z_wxz_btn.png b/app/src/main/res/mipmap-hdpi/ts_dsp_z_wxz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..4067323960de1fa9e4b4971fb8367f64b30961a6 GIT binary patch literal 970 zcmV;*12z1KP)Px#QBX`&MMrQ<000010RaF20099300000000960|y5O z0s;d0`T2HscJuS|{r&yf+1bd*$f>ERO-)Vb=jYPW(oauM;Nal9ySwb{?2?j_fPjGi z|NpC#^4$Oc02*{sPE!CO6(Yo2gzN?gPo0+_Hj@_ ztw;YRdG%t?_iQwyx@8P1jH#`v-7<{hoh5`Tj)8?FUM7V>6Dll)fJ8#6OntYS&4eII zkx&L}EP{~I&D~VoLo(EvmDN}e7#}qf>LDmyD3T(eA3f+ewvosZp`fbJ z=!uM`&^Z)=Op1Ahu6X-BO^NlpBR!kl4G52!wWiE?`zmlJ7m-;)YbY| zuV`kM9)cmlf0DBPZC|;HTLT(O|4rTvV?mwr^Q<^r8+dWew$s<*VDCraEk@l&A9`i0cZ*OD+?(Gc2mW>n)*&7mXjdfdi zTX4H(F3Qs%eF sZ>TB|-ZDNvhW`9yb3a7l8v5nmKXI-}X~exSRsaA107*qoM6N<$f)yRNh5!Hn literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ts_dsp_z_xz_btn.png b/app/src/main/res/mipmap-hdpi/ts_dsp_z_xz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..1a72fd32214d8c8ba15595fd7d3b9653a3926341 GIT binary patch literal 423 zcmeAS@N?(olHy`uVBq!ia0vp^x**KL3?xsO{9g&ASkfJR9T^xl_H+M9WCijWi-X*q z7}lMWc?nV(;1lBNUf}aT!|;EB!T(Id{~1RAa|}RS5F42^`kw(*V3g0K^a!YtuO!GX znBjpy{Ze_m`2FuMr1;*! zK|&@qlNAGtUeRY%+D75ByfA@5Ez_Hy-6AV6E^L!9fk!#odXT+eP2c*_9 zW@w8&EI%(HVX>iee?r2A!)pJ68Ws6x|JcpaRvU4Y`@udZRyLLH>xrWG8!p>M-2Lyz zxVt_+k@s2o`+Eik28qJ;Ts+V1-aPEN>bRgI<)BC%Q+Q$!PC{xWt~$(69Cl`ro{jN literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ts_dsp_z_wxz_btn.png b/app/src/main/res/mipmap-mdpi/ts_dsp_z_wxz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..f952fd64d68f1f94935c0fcb1dc2ac45934823d3 GIT binary patch literal 657 zcmV;C0&e|@P)Px#OHfQyMMrQ<0001eeSP`)`2hg|000010RadI2mk;8 z0s;a~PEP#%{I0I9@$vD>%F5^G=j`n4;Nalc*x23O-SzeL*4Ebd_xF^Pl>h(#voiaZ z0000ObW%=J02@H%4ImT&1qm_pUCVW`zN40t*oE6stT7-z0005NNklG!EQvtw2a))|MRA^! zF-#sd=Lv_lzhEIcpB7uON)H?7p=*2N-HD2hR=sV@nlzsKo?P6CgcjU%(>$x$D&>2a zMt!&Js35)vWMw8|(^zcAfz6C<_Odx+y!|3Ckj$x5kA&C|3LaZ_swyhc%uZt$nib=v zCBpGkYwy*c5L_6qVFXDaJ=BKiQ>_E3KjC*R-0p)IXucwHFjUS-Dx;WZ|}yL9MyR8*Ce<|O)6bM_sCEazI+ zW{D1gmc!qZXlTjVyJ|G@6vh>Dr(nHxQ8HBdge z_aQH>jAv%y9jJ*>*rVgU@^t3Xpm&OTqeWU$w4D)H_eFP|JD2TQ2LUwMZkWfsC>Ih@ zM+^y3n(E$GADP2*?ese4JZX1N$$!jUtE?k49CK1K!lvI6;x#X;^) z4C~IxyaaNL1AIbU-3xsFXBz#_H2j|dL`MG$44@1kHy6S-1S!Ze2FYfD*hZ6o7yz{k zlmz(&GsM^5v=r!H_&|RDh4=Xh?`t21ZUf5A@pN$v(Kvr}QXv0f1&)@B9SXO4|NqZE zE_L~q-^<7EI-i_QwBUbm-|NV#sh7%+X`GpKnXx-VT=dNIJ8OOU3uT|p6~ERh!8=(` z^z5ExzuY+Zt})y^e6#viU-cpW)A4fm?i{|-G~G1STel*8_6_6P{X5edcI;Y}E!8_G zb=HS2=bLrkgyT-_*jg>PPAmEJr6p$UZ2Kj=N_%I`WqJ;DCxfS} KpUXO@geCypJ&)Z0 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ts_dsp_z_wxz_btn.png b/app/src/main/res/mipmap-xhdpi/ts_dsp_z_wxz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..1e933d9a1a64701873424015367d900a85873709 GIT binary patch literal 1308 zcmV+%1>^dOP)0{{R32=ueI00004XF*Lt006O% z3;baP00001b5ch_0Itp)=>Px#NKi~vMHLkl00000008;<`2hg|00000002jDOa%o6 z0s;bab92?z)ksK4>FMdl#>V{o{Hv?0;Nal#@$sXhqq@4f_4W0Vl9K=b|Ff*rl>h($ z7j#liQvnnz<_;Sp00am{k~p?|@?WIOWNq0~r!qWw000C}Nklga3ew~Bv5qXX*W#U{HA5cM|gzH3@FQL z>Gye@x&TF_c0h;|E**M7VXVnS8w1PfS!|2}s;b*P5aYPTzW2*^Can8$qmN|UHiebL zg*g5DZ1u*RZrILM**YLXh%=@$v)vShemuPZkt#s~!fETw$M$HAd96Ep=JnAHrn~nx zA!R>-7$!LLWH7uTOu;1W`gMpqF8)3-lpO*RB>{+G9c^uc!R(0k6ezPC6Ph{wsDLRpW)n&bbo1frfelkp02P7B=roQ^B5>;1OJOH=LbS|1 zh!FAQ3=7w?ujPVNm}s{_D=a>Prp23AwAV-oI47DZIjAoB55Fziuav?u9yLM&2 zNR%3iTc3df_WKm5#qL^FWiMnP@pb}2lJgl5O@g{U@<9(Optdw^Du8Gh$n@C{AAe?= zI2*BjIplaA-j0ka36Y8#S4haa`eIz|7>Q?>B9Aa(Y|JD0NmhUl4^cvP3CO47A4(@( z;6Q4rma|xjcILql1Mmx>XxYYTkIw+*7S%{mY+ndfT>TPiV0)>m7T452AR!@cVMFdm z%C_Gq0bWflX3QP(rsBqwz5!tuUW*1P<4C5+lUmppKGCUk-s&krg~K<*o>coQ>u9`F zQK73_HEFY#qH$Gwg@jYk99kZvZpdE3!E|@keICf1rmwgaOjn$uRc@GyaK6e zbx#8s`VW>mk1{B*bEBxF-pI7iI7&O<>^c#nN2DOD9L*0>6ijCrKj7Tp_6aGNr=$!j z_9SlS<={l^PG&Doc&|HgHLP;S>lln8!oCQv$aR)`Sx`PxP~qcimM=YlP$WR_N%^nF zoeW<%q;8zkZptEb3Su_Apd^jV?(tuMB{^2JLsnj~Zy88>9t8XK_uL&ODO7g(B;jF)J{F|>ekkGZ zx^prwfgJS!pAc8~0-ygm2LCe+|7QS^(fc}+h~k4bB^&o>-& z6Hw3K>!_?zcCz|;MskNu_*qGntqKQNCfgS8Xe{LGsolevwS_qJet$5HZPvN0=ICD*ajPAtZGJHM-hl&s{ST zSSHq1aOVdqJLzh{d`w`{-NKoCXBTCEnAv;zt#HCm`RoI- zVhx_|kIE!=)XZr1Hx7H-c0%>KT+OVF7WYKAM#S{pJ#TQJBZ%w3DHEAv)=ATLdAJ6M zZVRe3^!e7oV3z5Y@v~fov3lXT6+Ql998u@yXnGw8*5-K2p|Dhlkzu1uv%=*%?$f|P OX7F_Nb6Mw<&;$THUd3hr literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ts_dsp_z_wxz_btn.png b/app/src/main/res/mipmap-xxhdpi/ts_dsp_z_wxz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..a403d1129717f68c187710cabf5430f6a6bc7d93 GIT binary patch literal 1977 zcmV;q2S)gbP)Px#MNmvsMMrQ<0RaI40RaI40SE{P1Ox;E0s;>Y4+RAU z0s;bHU|<3Q0^8f$`T6Cdp=H|}M&WwzV>+9?P|NjA{;#U9w02Xvo zPE!CMB`F6L8UqRsLNJ`>(DQgf3t?@{pOkI;m@* z<~l%`O@>c#4N91>n7>Mwg|suRXcYgFsGkQN0nE zy};G?bKbX;UTpY;`O~ZUy?0}3n51Co2uTRXZ(z9Sw;>xlNMcp(WFqe_25jd=XI53u zOsO6+0OV7fR?SX(Moe?>PPa?HHm~(83X`fKWDyy6!5X$+xCHT}jAZxew<$SmppO-U zO9is13XXi6kfp@8SdU7qd=t!}9o~fqrY25DlCpA4CC$AEhH)2KvnmyArPe*(%v+^k(6) zNQuhG^LYv9oLy1?y{llDHga7s3|ttP;hYmgIjmro*GKO2s-uz4I?-%oe_iOue#cY7 zT0gNqT_%Z3r+IL<0;Oc6i4NY`pFfA?&(>OmB6ruGic8JBgt4I0z6h>JgcZy}(+d|N zIPv8(FsQ}%@zlV5a@T-o4~XQ=^83w<9Hcw_hNpDOgp2Bfg^rd>28AjqIUg%02PQ+r z8hU0k&tldvTR9C{Rgba5-(K@=3O2 zHNw-sM-ECq%;X&Fk^1n%?doJM$?)4Dh z=Jknw=6MM7{OoI=Lcp#+?SX3)lMS|$*%Ba|r0$tz-Q@3-$W5R~AYwMr-^TLcN(v6t zM2CkOoM!p03*kyxbJW0>SLH9}e{&*b!--#&Q6UAT=oCA#`PQtbF^gO{qvSpGUdiuknmBm8Cwy1yAS~6a;44H+rZo?%QM|p6T3N2lMUJ@W!(qEF}ayC&4SYb>86Q|WG?6wETDYM&9B%NzH_PH- zH%g%~5h00qbl{;bjxECsN5tLwF|>DaA|>J>&*ah!1oh&CRQj;j$$(~6Q5(IF$4NF+ z74;+Jo#G4W_?i+lJ07TEQ1puX^_u22j)B)lv6N72kq%hK8RkUkh-`|}^xgW4IGqmO zhIL!sQ#yY*zY)$xzwQ8?jfI9e%Fwsp0p5>~l>PhhlX3^W>zd}fCt^I6)kAq2_HRbT zx07>ZeqK7_Bp5sO-+x#keSgiN4Mij*#rIv2=;i=V#&H|NaD%fBoR5d2N^z{pvS9;VTMUZ5vVIm(l#U6fJ!e z(w#Lfzr_0SpV#0AI7i6P0C5Ir00000 LNkvXXu0mjfosQ5s literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ts_dsp_z_xz_btn.png b/app/src/main/res/mipmap-xxhdpi/ts_dsp_z_xz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..cc06edf940e438b14a1b117903ab60191b4451b4 GIT binary patch literal 647 zcmV;20(kw2P)Px#Cs0gOMMrQ<|86n=Xfgk3G5={Y|7bD)X)^z4GXHQf z|7bG*X)*t3GXH5Z2L0fm0000CbW%=J03YCnWxemA5IRrL8yR&L0005zNkl^V~il05x^i=z$~f2v5<*{1VMrrWMpOnfg%+P3V~Px0fT7ZBS27S)C3Gx z0=*AK*Y>Sl``Z1_5XAoS-Y+lr?%sQMC&W}!3DJeg`Ntz7yR{!RJ#8Pib}R*a-6mff zx->oXJcHfTtc>YFEZ%2e?)UmdBTifft8&c zgYGbI73aoUoYz2ZS!%wq| zB=9W}xZ_Sx({3R8UA6>_A|?#QUI zGN7zun}5do!R*cJ@%FLA*xKDZJuJ4Olf6x)E2<_rEuoICqP3LAf1&QcOW+ZM{HuKF zTWRgRX25<7wVHs>vWAD<_$Qn_cho?qgOo;_GP^!$kcr@Sb@JRZ!V%cT&lzwq=?pA* h3R`+@cK>=t`~{u?+A;jw$2I@}002ovPDHLkV1m=zC;|Wg literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_wxz_btn.png b/app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_wxz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..719007f78c3223e2b1a826da2377220a72634530 GIT binary patch literal 2949 zcmV;03wrd4P)Px#MNmvsMMrQ<0s;a80s;a80ud1r3kwSb1Oyx$90UXe z2nYxQ0s?SwaQXT9^78V?$jH~%*ZlnaJv}|==jW@dtKZ+>jg5`}|Nkg~!WsYo02Xvo zPE!CMB`O6883GdyFG=RmgP8I>v}UDL4+6G{000WSNklQ?DD30Bi0%%Q7jJ}@C>hu z6KVse0FcM^!7zOSDBRhhxSJpLH;RcsGKvK(9{F7lvWOo_$=+KMFy^-Rn;-Tc9uuzF z{4@e<0LwiZ9+I7KIw~YDc6V=pa=xA2Kvd)rOqc<@o^y*XzoJYJxs5HysPdqY>TMPg^wl!9F8ELSb6? z!=l*bQ$M`I_4R-v;pltML%-!mKg|9KH}F3TU#$5VN-u#RvVh*&kd#C?8qGLu<)KFX z1aLTyW_QNo5!H%FQaJkNyJ#FDune!LcQY2d0xcp3=;4EGJT0JPwX{P1}(%b@TJk1l4IL2u58}7f2@uXEKp? z&@s2q{%0PiQfevzIy*qcOw${WQC2P-w_$X{d*hmxL2J>D*Se@3Kv&sh;v(8|YCF^T z(hlUeM*F9RwCrkhSzABFT&=HvC6J5_p-t!GvNxV+@z1%%EgkX>i>Pd_s|9nxVOKCl zh!){ZD5o&zTNciki+AU+E%;qZKOygf)6j;X?GhmeDPF-q0Hx|Oa15S~FG33^aU~EOm z4qSob6H^app67Tk!|nS)%10Dx1W3(6KnWh}>tnQ5h!I$0V)Ez&q;hcXv_8Xv%xRgc zY!v6Sv7pH1t-50CQL_-Z%QOwC%$+BmHJ&k17VO;dj387R{3mBYk-{MMTrSjljGwT? z2`W(toZ39ODFYlUns7Z7a{!1wWCa1y7!<_Q$BRv}6zS31J1g$!0?gAbSMnZ10Mv6A z1DEIC6~`W%#)T{{LjN%OV!pV&T9i(lW=Tk9oLE;fYfcr0-bEh(;>q3IpucN8DzRm~ zfD9aH2*3_N1vCh^YND{Ly1Eh^k4In5#G|-+3zds;d+lRusxT@a#1q@5!IO%<@xg(K z2i3cmUpc`t!<$-MH{ZK79s+x4LHP0AQSNK9S!Y6RHp&SaCtm(RIM3jI#0q$214Aen z^=*e#JVj+89=C9+uwX#kUv9L*nLu6X?a@)jB$x5m7b`qs(IF;2Lj($!xvLgxKKd#i z7UI2^>Z_}PK#FgJ@k}=g>frAK(F1=v#mgw;DXGDdVzZ?7_Y3cm+Ib5l=Qe4}uFZ6M}rZ z^NyX|ispM3PZ!Gvqe3+v*7wG?vOARe0-mz{2jYw)|cG#_hzRT!j63E zjwQm<%l<|DJsS`A&!Ol6Ku!BL#WR)T;Zlt!9K4a#x&luoKKEHX(SEA4t72 R${5 zagzW~9~Y^5>1@M}YSY`HI6AnpFM~<_=m6QG5+0Q9*cxv?Pp2Pp}7~Cg7#xgpV z|D^eblj6)T+FlRvb_bJ3N`S`jj zW6ll7V+@+rU;7dILv?jqEeXg!T1ELinR;W$$9qL5`Wkb7gGfn#~S%s|#{Ro9Owgv}KN)%JB`mDA5Vn&|W@?Le!QoVpdzSLGBd6xf8;v3!TcQp41d`44m;STO51J z;a@`Cql2`Hj{$DCK#@5bmjfJj`m&01*dFChH4>Th*3W{R1RnuJrNaX=>3|jp3X-16 zaW2Wy5|vdWk`a5SedP!Wdy$QpG(YoZfDZ7`WH`IKw47w%we`5}Fzre?3V8hi3DJ`~ zt1Q7!az|zY`}J&6vEktj2F7=kW;lf_!Rhg&Y|m#U15`eWtZ*~vSb-^bD#zV$rz7%h zR%xAHEGeK`H*?Hdh{~6i^JS(kd-AwLAhS--kIJ3f2Wnigts!H_5;1dXBv{fdC!r=W1_iSEu~Ewp6EqqT)AYtbd8mx6{H#0% z)OUelLYvMK549W0)`&TP6i!p$=97ensA5(uql`3x){iuVK*)J+`*7re6sky!e9nY~ zL)y=T;O@9J$;^lcS)D3yK(R#{!e^lAJ{XS$SkOi|37K^uaE|4^5tDVyaAL(JA*?gv zQ6ija<#3Lqnrt`B0kT%a*x{5Ro@4qdO6xtG_)5-Z%}|dQC!#aqxjCMgQB}$i?;|*e zO9OA97Es3T0TH2x0~0XEOnmZu<#1h)a>X;;J~aZh?|cKEp=dJ{@o5l#3;n5dh|18a zGvsfJNKhJPjyyB+w0Uqh2iZhL|$ep!3-e-=9 zyuM8ePRIRzPek_3igwPwb>Xu(=2#zewxYLHw1pIC>3x=dOoa29=_C>gI@{qHZ{?j1 zk9@z-IJVtIBBG&=>))@!?+`Qyek`C9v1&{~-y@nZFZn3y?3J3HvQeY7X`Ff9CO@_0 z+;Ka;6ZQs?L6Z^AH%~qj5gA0P5>4|J%PDXqD58vIJ561DCQ?i6_<5uB>T4N?NUTfp zj#|jnD3{`w;6W<$BfJe4UJjX_kjUObiPn78a7w(EM?hpCEz_o+(MyhmlKtymp2sZ~ zi!2Svy&-aeOenl&3Yz+b@A6AY$w=_#Zv4_Kdkgm$e_4Pddc-d_^O{WlhVL8%PCO#w zFHQeWFobHuIArXda#?N;{aQ%LmjfEY-TJLxq(CMTew(17A0?1~;PV*rL@~)m{y{9< v+3065{`iMO$ZIHn$45*ojQ3p8|L6D*kY>z2zP7vj00000NkvXXu0mjfj`?z) literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_xz_btn.png b/app/src/main/res/mipmap-xxxhdpi/ts_dsp_z_xz_btn.png new file mode 100644 index 0000000000000000000000000000000000000000..08c9e2ce91ec179d2c124e4896785b15d50adc02 GIT binary patch literal 805 zcmV+=1KRwFP)Px#Cs0gOMMrQ<|7bG*Xfpq8GXH5Y|7bG*X)^z5G5>Hg z|7kJ*YBB$4GXH5Zhoy(b0000CbW%=J0GQw(X1hY~5PvVwpGQ+70007nNkl0Nef*#WLwrnkBko zy);jc8&#T3kWSSK%$E|K@>=qP0h4+5OUb1lE)u4cw8|X!8J>~ZaGmqgPcOVoI4X0- zvz{5tL{Oej-w#=B^->aI4#PBUYPs+lgTSf}N$_!{9Fq{=i@> zPBIv4PdWj@M8W-Kuk-~9|C;a<3OB<(D+9$?LV z)jfEyOK>TU*`+QzEEOk6VufdnU8PCsIJ-)cQPzl)hHCg}W9Kk9_5PUf9SS#3I(TQi z!)Lp5K&SB-!PiQUb2G3nfuOtpxcBsk&X&*vN3kzrD(){ZKcSG31W(#6 zk5*$`){&K-bn8g*Jmx9tF0jA`?Vk14zcR9yTYAI73#v2T{bg((ix6A);&}k9MHRa4 zFQV?U>XfqhhH~|aM3?ZVv#b`)EB@igpU$#7OGV7p0&896^}|*~(oaWcDm20kjo*qJ zj+(8RIuE%$RRTMRGO|o9T?esOVcpC10HZ@YqDd7s+Dd1%)5om@IxnJDrwDIWO<`f+(M