Merge pull request #79 from Kevinlinpr/nagisa
调整我的派币界面ui,添加缺省图,修复bug,以及其他更改
4
.idea/deploymentTargetSelector.xml
generated
@@ -4,10 +4,10 @@
|
|||||||
<selectionStates>
|
<selectionStates>
|
||||||
<SelectionState runConfigName="app">
|
<SelectionState runConfigName="app">
|
||||||
<option name="selectionMode" value="DROPDOWN" />
|
<option name="selectionMode" value="DROPDOWN" />
|
||||||
<DropdownSelection timestamp="2025-11-05T12:24:27.034893100Z">
|
<DropdownSelection timestamp="2025-11-11T06:03:31.167121900Z">
|
||||||
<Target type="DEFAULT_BOOT">
|
<Target type="DEFAULT_BOOT">
|
||||||
<handle>
|
<handle>
|
||||||
<DeviceId pluginId="PhysicalDevice" identifier="serial=c328a150" />
|
<DeviceId pluginId="PhysicalDevice" identifier="serial=f800b364" />
|
||||||
</handle>
|
</handle>
|
||||||
</Target>
|
</Target>
|
||||||
</DropdownSelection>
|
</DropdownSelection>
|
||||||
|
|||||||
31
.idea/gradle.xml
generated
@@ -1,20 +1,19 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
<option name="testRunner" value="CHOOSE_PER_TEST" />
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
<option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
|
||||||
<option name="modules">
|
<option name="modules">
|
||||||
<set>
|
<set>
|
||||||
<option value="$PROJECT_DIR$" />
|
<option value="$PROJECT_DIR$" />
|
||||||
<option value="$PROJECT_DIR$/app" />
|
<option value="$PROJECT_DIR$/app" />
|
||||||
</set>
|
</set>
|
||||||
</option>
|
|
||||||
<option name="resolveExternalAnnotations" value="false" />
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
</option>
|
||||||
</component>
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
</project>
|
</project>
|
||||||
@@ -447,7 +447,7 @@ fun MomentContentGroup(
|
|||||||
}
|
}
|
||||||
if (!momentEntity.momentTextContent.isNullOrEmpty()) {
|
if (!momentEntity.momentTextContent.isNullOrEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = momentEntity.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity.momentTextContent),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
.padding(start = 16.dp, end = 16.dp, top = 8.dp),
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ fun DiscoverView() {
|
|||||||
// 文本内容区域,限制最大高度
|
// 文本内容区域,限制最大高度
|
||||||
if (!momentItem.momentTextContent.isNullOrEmpty()) {
|
if (!momentItem.momentTextContent.isNullOrEmpty()) {
|
||||||
androidx.compose.material3.Text(
|
androidx.compose.material3.Text(
|
||||||
text = momentItem.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentItem.momentTextContent),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
@@ -47,27 +48,38 @@ fun FullArticleModal(
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
val screenHeight = configuration.screenHeightDp.dp
|
||||||
val sheetHeight = screenHeight * 0.9f // 90% 高度
|
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||||
|
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||||
|
val maxSheetHeight = when {
|
||||||
|
screenHeight.value < 600 -> screenHeight * 0.75f
|
||||||
|
screenHeight.value <= 800 -> screenHeight * 0.7f
|
||||||
|
else -> screenHeight * 0.65f
|
||||||
|
}
|
||||||
|
|
||||||
|
val sheetState = rememberModalBottomSheetState(
|
||||||
|
skipPartiallyExpanded = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// 确保弹窗从下往上展开
|
||||||
|
androidx.compose.runtime.LaunchedEffect(Unit) {
|
||||||
|
sheetState.expand()
|
||||||
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = onDismiss,
|
onDismissRequest = onDismiss,
|
||||||
sheetState = rememberModalBottomSheetState(
|
sheetState = sheetState,
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
containerColor = appColors.background,
|
containerColor = appColors.background,
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||||
) {
|
) {
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = maxSheetHeight)
|
||||||
) {
|
) {
|
||||||
// 滚动内容
|
// 滚动内容
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(1f)
|
.fillMaxSize()
|
||||||
.verticalScroll(rememberScrollState())
|
.verticalScroll(rememberScrollState())
|
||||||
) {
|
) {
|
||||||
// 新闻图片区域 - 固定高度和宽度
|
// 新闻图片区域 - 固定高度和宽度
|
||||||
@@ -75,7 +87,6 @@ fun FullArticleModal(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(250.dp)
|
.height(250.dp)
|
||||||
|
|
||||||
.background(color = appColors.secondaryBackground)
|
.background(color = appColors.secondaryBackground)
|
||||||
) {
|
) {
|
||||||
if (moment.images.isNotEmpty()) {
|
if (moment.images.isNotEmpty()) {
|
||||||
@@ -149,11 +160,13 @@ fun FullArticleModal(
|
|||||||
|
|
||||||
// 帖子内容
|
// 帖子内容
|
||||||
NewsContent(
|
NewsContent(
|
||||||
content = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""),
|
content = com.aiosman.ravenow.utils.Utils.unescapeHtml(
|
||||||
|
if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: "")
|
||||||
|
),
|
||||||
images = moment.images,
|
images = moment.images,
|
||||||
context = context
|
context = context
|
||||||
)
|
)
|
||||||
Spacer(modifier = Modifier.height(200.dp))
|
Spacer(modifier = Modifier.height(200.dp))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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.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.navigationBars
|
import androidx.compose.foundation.layout.navigationBars
|
||||||
@@ -200,7 +201,9 @@ fun NewsCommentModal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.background(AppColors.background)
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(AppColors.background)
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -229,33 +232,29 @@ fun NewsCommentModal(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 评论列表
|
// 评论列表
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
Box(
|
LazyColumn {
|
||||||
modifier = Modifier.fillMaxWidth()
|
item {
|
||||||
) {
|
CommentContent(
|
||||||
LazyColumn {
|
viewModel = commentViewModel,
|
||||||
item {
|
onLongClick = { comment ->
|
||||||
CommentContent(
|
showCommentMenu = true
|
||||||
viewModel = commentViewModel,
|
contextComment = comment
|
||||||
onLongClick = { comment ->
|
},
|
||||||
showCommentMenu = true
|
onReply = { parentComment, _, _, _ ->
|
||||||
contextComment = comment
|
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
||||||
},
|
debouncedNavigation {
|
||||||
onReply = { parentComment, _, _, _ ->
|
navController.navigate(NavigationRoute.Login.route)
|
||||||
if (GuestLoginCheckOut.needLogin(GuestLoginCheckOutScene.COMMENT_MOMENT)) {
|
|
||||||
debouncedNavigation {
|
|
||||||
navController.navigate(NavigationRoute.Login.route)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
replyComment = parentComment
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
replyComment = parentComment
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ import androidx.compose.foundation.layout.Spacer
|
|||||||
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.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
import androidx.compose.foundation.layout.statusBars
|
import androidx.compose.foundation.layout.statusBars
|
||||||
|
import androidx.compose.foundation.layout.WindowInsets
|
||||||
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.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
@@ -180,23 +182,38 @@ fun NewsScreen() {
|
|||||||
if (showCommentModal && selectedMoment != null) {
|
if (showCommentModal && selectedMoment != null) {
|
||||||
val configuration = LocalConfiguration.current
|
val configuration = LocalConfiguration.current
|
||||||
val screenHeight = configuration.screenHeightDp.dp
|
val screenHeight = configuration.screenHeightDp.dp
|
||||||
val sheetHeight = screenHeight * 0.67f // 三分之二高度
|
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||||
|
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||||
|
val maxSheetHeight = when {
|
||||||
|
screenHeight.value < 600 -> screenHeight * 0.75f
|
||||||
|
screenHeight.value <= 800 -> screenHeight * 0.7f
|
||||||
|
else -> screenHeight * 0.65f
|
||||||
|
}
|
||||||
|
|
||||||
|
val commentSheetState = rememberModalBottomSheetState(
|
||||||
|
skipPartiallyExpanded = true
|
||||||
|
)
|
||||||
|
|
||||||
|
// 确保弹窗从下往上展开
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
commentSheetState.expand()
|
||||||
|
}
|
||||||
|
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
onDismissRequest = {
|
onDismissRequest = {
|
||||||
showCommentModal = false
|
showCommentModal = false
|
||||||
},
|
},
|
||||||
sheetState = rememberModalBottomSheetState(
|
sheetState = commentSheetState,
|
||||||
skipPartiallyExpanded = true
|
|
||||||
),
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(sheetHeight),
|
|
||||||
containerColor = AppColors.background,
|
containerColor = AppColors.background,
|
||||||
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
shape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp),
|
||||||
) {
|
) {
|
||||||
NewsCommentModal(
|
Box(
|
||||||
postId = selectedMoment?.id,
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(max = maxSheetHeight)
|
||||||
|
) {
|
||||||
|
NewsCommentModal(
|
||||||
|
postId = selectedMoment?.id,
|
||||||
commentCount = selectedMoment?.commentCount ?: 0,
|
commentCount = selectedMoment?.commentCount ?: 0,
|
||||||
onDismiss = {
|
onDismiss = {
|
||||||
showCommentModal = false
|
showCommentModal = false
|
||||||
@@ -207,7 +224,8 @@ fun NewsScreen() {
|
|||||||
onCommentDeleted = {
|
onCommentDeleted = {
|
||||||
selectedMoment?.id?.let { model.onDeleteComment(it) }
|
selectedMoment?.id?.let { model.onDeleteComment(it) }
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,7 +305,9 @@ fun NewsItem(
|
|||||||
|
|
||||||
// 新闻内容(超出使用省略号)
|
// 新闻内容(超出使用省略号)
|
||||||
Text(
|
Text(
|
||||||
text = if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: ""),
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(
|
||||||
|
if (moment.newsContent.isNotEmpty()) moment.newsContent else (moment.momentTextContent ?: "")
|
||||||
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ fun PostRecommendationItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.8f)
|
.fillMaxWidth(0.8f)
|
||||||
.padding(top = 4.dp),
|
.padding(top = 4.dp),
|
||||||
text = moment.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ fun VideoRecommendationItem(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth(0.8f)
|
.fillMaxWidth(0.8f)
|
||||||
.padding(top = 4.dp),
|
.padding(top = 4.dp),
|
||||||
text = moment.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||||
|
|||||||
@@ -389,18 +389,6 @@ fun ProfileV3(
|
|||||||
bottomEnd = 32.dp
|
bottomEnd = 32.dp
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.let {
|
|
||||||
if (isSelf && isMain) {
|
|
||||||
it.noRippleClickable {
|
|
||||||
Intent(Intent.ACTION_PICK).apply {
|
|
||||||
type = "image/*"
|
|
||||||
pickBannerImageLauncher.launch(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
) {
|
) {
|
||||||
CustomAsyncImage(
|
CustomAsyncImage(
|
||||||
LocalContext.current,
|
LocalContext.current,
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ 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.height
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
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
|
||||||
@@ -252,9 +253,6 @@ fun ShortViewCompose(
|
|||||||
val initialLayout = remember {
|
val initialLayout = remember {
|
||||||
mutableStateOf(true)
|
mutableStateOf(true)
|
||||||
}
|
}
|
||||||
val pauseIconVisibleState = remember {
|
|
||||||
mutableStateOf(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
Pager(
|
Pager(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
@@ -266,7 +264,6 @@ fun ShortViewCompose(
|
|||||||
) {
|
) {
|
||||||
// 使用 key 确保每个页面独立,避免状态混乱
|
// 使用 key 确保每个页面独立,避免状态混乱
|
||||||
androidx.compose.runtime.key(page) {
|
androidx.compose.runtime.key(page) {
|
||||||
pauseIconVisibleState.value = false
|
|
||||||
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
val currentMoment = if (videoMoments.isNotEmpty() && page < videoMoments.size) {
|
||||||
videoMoments[page]
|
videoMoments[page]
|
||||||
} else {
|
} else {
|
||||||
@@ -284,7 +281,6 @@ fun ShortViewCompose(
|
|||||||
pagerState = pagerState,
|
pagerState = pagerState,
|
||||||
pager = page,
|
pager = page,
|
||||||
initialLayout = initialLayout,
|
initialLayout = initialLayout,
|
||||||
pauseIconVisibleState = pauseIconVisibleState,
|
|
||||||
VideoHeader = videoHeader,
|
VideoHeader = videoHeader,
|
||||||
VideoBottom = videoBottom,
|
VideoBottom = videoBottom,
|
||||||
onLikeClick = onLikeClick,
|
onLikeClick = onLikeClick,
|
||||||
@@ -312,7 +308,6 @@ private fun SingleVideoItemContent(
|
|||||||
pagerState: PagerState,
|
pagerState: PagerState,
|
||||||
pager: Int,
|
pager: Int,
|
||||||
initialLayout: MutableState<Boolean>,
|
initialLayout: MutableState<Boolean>,
|
||||||
pauseIconVisibleState: MutableState<Boolean>,
|
|
||||||
VideoHeader: @Composable() () -> Unit = {},
|
VideoHeader: @Composable() () -> Unit = {},
|
||||||
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
VideoBottom: @Composable ((MomentEntity) -> Unit)? = null,
|
||||||
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
onLikeClick: ((MomentEntity) -> Unit)? = null,
|
||||||
@@ -323,6 +318,18 @@ private fun SingleVideoItemContent(
|
|||||||
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
onAvatarClick: ((MomentEntity) -> Unit)? = null,
|
||||||
isPageVisible: Boolean = true
|
isPageVisible: Boolean = true
|
||||||
) {
|
) {
|
||||||
|
// 将暂停状态移到每个视频项内部,使用 remember 保存,避免在点赞/关注时被重置
|
||||||
|
val pauseIconVisibleState = remember(pager) {
|
||||||
|
mutableStateOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 当页面切换时,重置暂停状态
|
||||||
|
LaunchedEffect(pager, pagerState.currentPage) {
|
||||||
|
if (pager != pagerState.currentPage) {
|
||||||
|
pauseIconVisibleState.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
@@ -379,7 +386,13 @@ fun VideoPlayer(
|
|||||||
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 // 屏幕的70%高度
|
// 根据屏幕高度计算最大高度,用于限制弹窗最大高度
|
||||||
|
// 小屏幕(< 600dp):75%,中等屏幕(600-800dp):70%,大屏幕(> 800dp):65%
|
||||||
|
val maxSheetHeight = when {
|
||||||
|
screenHeight.value < 600 -> screenHeight * 0.75f
|
||||||
|
screenHeight.value <= 800 -> screenHeight * 0.7f
|
||||||
|
else -> screenHeight * 0.65f
|
||||||
|
}
|
||||||
var showCommentModal by remember { mutableStateOf(false) }
|
var showCommentModal by remember { mutableStateOf(false) }
|
||||||
var sheetState = rememberModalBottomSheetState(
|
var sheetState = rememberModalBottomSheetState(
|
||||||
skipPartiallyExpanded = true
|
skipPartiallyExpanded = true
|
||||||
@@ -417,18 +430,34 @@ fun VideoPlayer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据页面状态控制播放
|
// 根据页面状态控制播放
|
||||||
|
// 使用 remember 保存上一次的当前页面,只在页面真正切换时才重置暂停状态
|
||||||
|
val previousCurrentPage = remember(pager) { mutableStateOf(pagerState.currentPage) }
|
||||||
LaunchedEffect(pager, pagerState.currentPage, isPageVisible) {
|
LaunchedEffect(pager, pagerState.currentPage, isPageVisible) {
|
||||||
val isCurrentPage = pager == pagerState.currentPage && isPageVisible
|
val isCurrentPage = pager == pagerState.currentPage && isPageVisible
|
||||||
|
val pageChanged = previousCurrentPage.value != pagerState.currentPage
|
||||||
|
|
||||||
if (isCurrentPage) {
|
if (isCurrentPage) {
|
||||||
// 当前页面且页面可见:恢复播放
|
// 当前页面且页面可见
|
||||||
exoPlayer.playWhenReady = true
|
if (pageChanged) {
|
||||||
exoPlayer.play()
|
// 页面切换了(从其他页面切换到当前页面),恢复播放并重置暂停状态
|
||||||
pauseIconVisibleState.value = false
|
exoPlayer.playWhenReady = true
|
||||||
|
exoPlayer.play()
|
||||||
|
pauseIconVisibleState.value = false
|
||||||
|
previousCurrentPage.value = pagerState.currentPage
|
||||||
|
} else {
|
||||||
|
// 页面未切换,保持当前的播放/暂停状态
|
||||||
|
// 如果用户手动暂停了,不要自动恢复播放;如果正在播放,保持播放
|
||||||
|
if (!pauseIconVisibleState.value && !exoPlayer.isPlaying) {
|
||||||
|
exoPlayer.playWhenReady = true
|
||||||
|
exoPlayer.play()
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// 非当前页面或页面不可见:立即暂停并停止准备播放
|
// 非当前页面或页面不可见:立即暂停并停止准备播放
|
||||||
exoPlayer.playWhenReady = false
|
exoPlayer.playWhenReady = false
|
||||||
exoPlayer.pause()
|
exoPlayer.pause()
|
||||||
pauseIconVisibleState.value = false
|
pauseIconVisibleState.value = false
|
||||||
|
previousCurrentPage.value = pagerState.currentPage
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 视频播放器容器 - 确保每个视频页面都被正确裁剪
|
// 视频播放器容器 - 确保每个视频页面都被正确裁剪
|
||||||
@@ -519,39 +548,38 @@ fun VideoPlayer(
|
|||||||
horizontalAlignment = Alignment.CenterHorizontally
|
horizontalAlignment = Alignment.CenterHorizontally
|
||||||
) {
|
) {
|
||||||
if (moment != null) {
|
if (moment != null) {
|
||||||
// 使用 key 确保状态变化时重新组合
|
// 使用 key 确保状态变化时重新组合,包含所有可能变化的状态以避免不必要的重组
|
||||||
androidx.compose.runtime.key(moment.id, moment.isFavorite) {
|
// 移除 key,让 Compose 自动处理重组,避免头像闪烁
|
||||||
UserAvatar(
|
UserAvatar(
|
||||||
avatarUrl = moment.avatar,
|
avatarUrl = moment.avatar,
|
||||||
onClick = { onAvatarClick?.invoke(moment) }
|
onClick = { onAvatarClick?.invoke(moment) }
|
||||||
)
|
)
|
||||||
VideoBtn(
|
VideoBtn(
|
||||||
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
icon = if (moment.liked) R.drawable.rider_pro_moment_liked else R.drawable.rider_pro_moment_like,
|
||||||
text = formatCount(moment.likeCount),
|
text = formatCount(moment.likeCount),
|
||||||
isActive = moment.liked,
|
isActive = moment.liked,
|
||||||
onClick = { onLikeClick?.invoke(moment) }
|
onClick = { onLikeClick?.invoke(moment) }
|
||||||
)
|
)
|
||||||
VideoBtn(
|
VideoBtn(
|
||||||
icon = R.mipmap.icon_comment,
|
icon = R.mipmap.icon_comment,
|
||||||
text = formatCount(moment.commentCount),
|
text = formatCount(moment.commentCount),
|
||||||
onClick = {
|
onClick = {
|
||||||
showCommentModal = true
|
showCommentModal = true
|
||||||
onCommentClick?.invoke(moment)
|
onCommentClick?.invoke(moment)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
VideoBtn(
|
VideoBtn(
|
||||||
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
icon = if (moment.isFavorite) R.mipmap.icon_variant_2 else R.mipmap.icon_collect,
|
||||||
text = formatCount(moment.favoriteCount),
|
text = formatCount(moment.favoriteCount),
|
||||||
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
|
isActive = false, // 收藏后不使用红色滤镜,保持图标原本颜色
|
||||||
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
|
keepOriginalColor = moment.isFavorite, // 收藏后保持原始颜色
|
||||||
onClick = { onFavoriteClick?.invoke(moment) }
|
onClick = { onFavoriteClick?.invoke(moment) }
|
||||||
)
|
)
|
||||||
VideoBtn(
|
VideoBtn(
|
||||||
icon = R.mipmap.icon_share,
|
icon = R.mipmap.icon_share,
|
||||||
text = formatCount(moment.shareCount),
|
text = formatCount(moment.shareCount),
|
||||||
onClick = { onShareClick?.invoke(moment) }
|
onClick = { onShareClick?.invoke(moment) }
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
UserAvatar()
|
UserAvatar()
|
||||||
VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0")
|
VideoBtn(icon = R.drawable.rider_pro_moment_like, text = "0")
|
||||||
@@ -609,7 +637,7 @@ fun VideoPlayer(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(top = 4.dp),
|
.padding(top = 4.dp),
|
||||||
text = moment.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(moment.momentTextContent),
|
||||||
fontSize = 16.sp,
|
fontSize = 16.sp,
|
||||||
color = Color.White,
|
color = Color.White,
|
||||||
style = TextStyle(fontWeight = FontWeight.Bold),
|
style = TextStyle(fontWeight = FontWeight.Bold),
|
||||||
@@ -637,7 +665,7 @@ fun VideoPlayer(
|
|||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(sheetHeight)
|
.heightIn(max = maxSheetHeight)
|
||||||
) {
|
) {
|
||||||
CommentModalContent(
|
CommentModalContent(
|
||||||
postId = moment.id,
|
postId = moment.id,
|
||||||
@@ -656,32 +684,36 @@ fun UserAvatar(
|
|||||||
avatarUrl: String? = null,
|
avatarUrl: String? = null,
|
||||||
onClick: (() -> Unit)? = null
|
onClick: (() -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
Box(
|
// 使用 key 确保当 avatarUrl 不变时不会重新创建组件,避免闪烁
|
||||||
modifier = Modifier
|
androidx.compose.runtime.key(avatarUrl) {
|
||||||
.padding(bottom = 16.dp)
|
Box(
|
||||||
.size(40.dp)
|
modifier = Modifier
|
||||||
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
.padding(bottom = 16.dp)
|
||||||
.clip(RoundedCornerShape(40.dp))
|
.size(40.dp)
|
||||||
.then(
|
.border(width = 3.dp, color = Color.White, shape = RoundedCornerShape(40.dp))
|
||||||
if (onClick != null) {
|
.clip(RoundedCornerShape(40.dp))
|
||||||
Modifier.noRippleClickable { onClick() }
|
.then(
|
||||||
} else {
|
if (onClick != null) {
|
||||||
Modifier
|
Modifier.noRippleClickable { onClick() }
|
||||||
}
|
} else {
|
||||||
)
|
Modifier
|
||||||
) {
|
}
|
||||||
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
)
|
||||||
CustomAsyncImage(
|
) {
|
||||||
imageUrl = avatarUrl,
|
if (avatarUrl != null && avatarUrl.isNotEmpty()) {
|
||||||
contentDescription = "用户头像",
|
CustomAsyncImage(
|
||||||
modifier = Modifier.fillMaxSize(),
|
imageUrl = avatarUrl,
|
||||||
defaultRes = R.drawable.default_avatar
|
contentDescription = "用户头像",
|
||||||
)
|
modifier = Modifier.fillMaxSize(),
|
||||||
} else {
|
defaultRes = R.drawable.default_avatar,
|
||||||
Image(
|
showShimmer = false // 禁用 shimmer 效果,避免闪烁
|
||||||
painter = painterResource(id = R.drawable.default_avatar),
|
)
|
||||||
contentDescription = "用户头像"
|
} else {
|
||||||
)
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.default_avatar),
|
||||||
|
contentDescription = "用户头像"
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1281,7 +1281,7 @@ fun PostDetails(
|
|||||||
) {
|
) {
|
||||||
if (!momentEntity?.momentTextContent.isNullOrEmpty()) {
|
if (!momentEntity?.momentTextContent.isNullOrEmpty()) {
|
||||||
Text(
|
Text(
|
||||||
text = momentEntity?.momentTextContent ?: "",
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(momentEntity?.momentTextContent),
|
||||||
fontSize = 14.sp,
|
fontSize = 14.sp,
|
||||||
color = AppColors.text,
|
color = AppColors.text,
|
||||||
)
|
)
|
||||||
@@ -1396,7 +1396,7 @@ fun CommentItem(
|
|||||||
|
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = commentEntity.comment,
|
text = com.aiosman.ravenow.utils.Utils.unescapeHtml(commentEntity.comment),
|
||||||
fontSize = 13.sp,
|
fontSize = 13.sp,
|
||||||
maxLines = Int.MAX_VALUE,
|
maxLines = Int.MAX_VALUE,
|
||||||
softWrap = true,
|
softWrap = true,
|
||||||
|
|||||||
@@ -116,4 +116,22 @@ object Utils {
|
|||||||
|
|
||||||
return compressedFile
|
return compressedFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HTML 反转义函数
|
||||||
|
* 将 HTML 实体编码转换为对应的字符
|
||||||
|
* 例如:' -> ', " -> ", & -> &, < -> <, > -> >
|
||||||
|
*/
|
||||||
|
fun unescapeHtml(html: String?): String {
|
||||||
|
if (html == null) return ""
|
||||||
|
|
||||||
|
return html
|
||||||
|
.replace("'", "'")
|
||||||
|
.replace(""", "\"")
|
||||||
|
.replace("&", "&")
|
||||||
|
.replace("<", "<")
|
||||||
|
.replace(">", ">")
|
||||||
|
.replace(""", "\"")
|
||||||
|
.replace("'", "'")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
BIN
app/src/main/res/mipmap-hdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 801 B |
BIN
app/src/main/res/mipmap-hdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 631 B |
BIN
app/src/main/res/mipmap-hdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 572 B |
BIN
app/src/main/res/mipmap-hdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 637 B |
BIN
app/src/main/res/mipmap-hdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 644 B |
BIN
app/src/main/res/mipmap-mdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
app/src/main/res/mipmap-mdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 459 B |
BIN
app/src/main/res/mipmap-mdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 474 B |
BIN
app/src/main/res/mipmap-mdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 458 B |
BIN
app/src/main/res/mipmap-mdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 479 B |
BIN
app/src/main/res/mipmap-xhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 726 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 754 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 730 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 800 B |
BIN
app/src/main/res/mipmap-xhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 830 B |
BIN
app/src/main/res/mipmap-xxhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 1007 B |
BIN
app/src/main/res/mipmap-xxhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 1.0 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/group_427319679.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_calendar.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_credit_card.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_task.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/icons_users.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
@@ -21,5 +21,4 @@ kotlin.code.style=official
|
|||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
org.gradle.daemon=true
|
org.gradle.daemon=trueja
|
||||||
ja
|
|
||||||
|
|||||||