feat: 转发贴显示原贴

This commit is contained in:
HuanCheng65 2023-10-06 13:06:07 +08:00
parent 34d6e12be5
commit 4c35d3afa8
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
5 changed files with 250 additions and 123 deletions

View File

@ -26,6 +26,10 @@ class ImmutableHolder<T>(val item: T) {
return wrapImmutable(getter(item)) return wrapImmutable(getter(item))
} }
fun <R> getNullableImmutable(getter: T.() -> R?): ImmutableHolder<R>? {
return getter(item)?.wrapImmutable()
}
fun <R> getImmutableList(getter: T.() -> List<R>): ImmutableList<ImmutableHolder<R>> { fun <R> getImmutableList(getter: T.() -> List<R>): ImmutableList<ImmutableHolder<R>> {
return getter(item).map { wrapImmutable(it) }.toImmutableList() return getter(item).map { wrapImmutable(it) }.toImmutableList()
} }

View File

@ -37,6 +37,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.api.models.protos.OriginThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.abstractText import com.huanchengfly.tieba.post.api.models.protos.abstractText
import com.huanchengfly.tieba.post.api.models.protos.frsPage.Classify import com.huanchengfly.tieba.post.api.models.protos.frsPage.Classify
@ -178,6 +179,7 @@ private fun ThreadList(
onClassifySelected: (Int) -> Unit, onClassifySelected: (Int) -> Unit,
forumRuleTitle: String? = null, forumRuleTitle: String? = null,
onOpenForumRule: (() -> Unit)? = null, onOpenForumRule: (() -> Unit)? = null,
onOriginThreadClicked: (OriginThreadInfo) -> Unit = {},
) { ) {
val windowSizeClass = LocalWindowSizeClass.current val windowSizeClass = LocalWindowSizeClass.current
val itemFraction = when (windowSizeClass.widthSizeClass) { val itemFraction = when (windowSizeClass.widthSizeClass) {
@ -259,6 +261,7 @@ private fun ThreadList(
onClick = onItemClicked, onClick = onItemClicked,
onReplyClick = onItemReplyClicked, onReplyClick = onItemReplyClicked,
onAgree = onAgree, onAgree = onAgree,
onClickOriginThread = onOriginThreadClicked,
) )
} }
} }
@ -418,6 +421,14 @@ fun ForumThreadListPage(
forumRuleTitle = forumRuleTitle, forumRuleTitle = forumRuleTitle,
onOpenForumRule = { onOpenForumRule = {
navigator.navigate(ForumRuleDetailPageDestination(forumId)) navigator.navigate(ForumRuleDetailPageDestination(forumId))
},
onOriginThreadClicked = {
navigator.navigate(
ThreadPageDestination(
threadId = it.tid.toLong(),
forumId = it.fid,
)
)
} }
) )
} }

View File

@ -147,6 +147,7 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
import com.huanchengfly.tieba.post.ui.widgets.compose.MyBackHandler import com.huanchengfly.tieba.post.ui.widgets.compose.MyBackHandler
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
import com.huanchengfly.tieba.post.ui.widgets.compose.OriginThreadCard
import com.huanchengfly.tieba.post.ui.widgets.compose.PromptDialog import com.huanchengfly.tieba.post.ui.widgets.compose.PromptDialog
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
import com.huanchengfly.tieba.post.ui.widgets.compose.TextWithMinWidth import com.huanchengfly.tieba.post.ui.widgets.compose.TextWithMinWidth
@ -1065,7 +1066,7 @@ fun ThreadPage(
) )
) )
}, },
) { ) { paddingValues ->
ModalBottomSheetLayout( ModalBottomSheetLayout(
sheetState = bottomSheetState, sheetState = bottomSheetState,
sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp), sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp),
@ -1180,7 +1181,7 @@ fun ThreadPage(
scrimColor = Color.Transparent, scrimColor = Color.Transparent,
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(it) .padding(paddingValues)
) { ) {
Box( Box(
modifier = Modifier modifier = Modifier
@ -1253,6 +1254,28 @@ fun ThreadPage(
confirmDeleteDialogState.show() confirmDeleteDialogState.show()
} }
thread?.getNullableImmutable { origin_thread_info }
.takeIf { thread?.get { is_share_thread } == 1 }
?.let {
OriginThreadCard(
originThreadInfo = it,
modifier = Modifier
.padding(horizontal = 16.dp)
.padding(bottom = 16.dp)
.clip(RoundedCornerShape(6.dp))
.background(ExtendedTheme.colors.floorCard)
.clickable {
navigator.navigate(
ThreadPageDestination(
threadId = it.get { tid.toLong() },
forumId = it.get { fid },
)
)
}
.padding(16.dp)
)
}
VerticalDivider( VerticalDivider(
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)

View File

@ -11,6 +11,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -58,6 +59,7 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachIndexed
import com.google.accompanist.placeholder.PlaceholderHighlight import com.google.accompanist.placeholder.PlaceholderHighlight
import com.google.accompanist.placeholder.material.fade import com.google.accompanist.placeholder.material.fade
@ -67,10 +69,13 @@ import com.huanchengfly.tieba.post.App
import com.huanchengfly.tieba.post.R import com.huanchengfly.tieba.post.R
import com.huanchengfly.tieba.post.activities.UserActivity import com.huanchengfly.tieba.post.activities.UserActivity
import com.huanchengfly.tieba.post.api.models.protos.Media import com.huanchengfly.tieba.post.api.models.protos.Media
import com.huanchengfly.tieba.post.api.models.protos.OriginThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.SimpleForum import com.huanchengfly.tieba.post.api.models.protos.SimpleForum
import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo import com.huanchengfly.tieba.post.api.models.protos.ThreadInfo
import com.huanchengfly.tieba.post.api.models.protos.User import com.huanchengfly.tieba.post.api.models.protos.User
import com.huanchengfly.tieba.post.api.models.protos.VideoInfo
import com.huanchengfly.tieba.post.api.models.protos.abstractText import com.huanchengfly.tieba.post.api.models.protos.abstractText
import com.huanchengfly.tieba.post.api.models.protos.renders
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
import com.huanchengfly.tieba.post.arch.ImmutableHolder import com.huanchengfly.tieba.post.arch.ImmutableHolder
import com.huanchengfly.tieba.post.arch.wrapImmutable import com.huanchengfly.tieba.post.arch.wrapImmutable
@ -90,6 +95,8 @@ import com.huanchengfly.tieba.post.utils.ImageUtil
import com.huanchengfly.tieba.post.utils.StringUtil import com.huanchengfly.tieba.post.utils.StringUtil
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
import com.huanchengfly.tieba.post.utils.appPreferences import com.huanchengfly.tieba.post.utils.appPreferences
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
@ -107,7 +114,8 @@ private val ImmutableHolder<Media>.url: String
private fun DefaultUserHeader( private fun DefaultUserHeader(
userProvider: () -> ImmutableHolder<User>, userProvider: () -> ImmutableHolder<User>,
timeProvider: () -> Int, timeProvider: () -> Int,
content: @Composable RowScope.() -> Unit modifier: Modifier = Modifier,
content: @Composable RowScope.() -> Unit,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val user = remember(userProvider) { userProvider() } val user = remember(userProvider) { userProvider() }
@ -146,7 +154,8 @@ private fun DefaultUserHeader(
) )
) )
}, },
content = content content = content,
modifier = modifier
) )
} }
@ -157,7 +166,8 @@ fun Card(
header: @Composable ColumnScope.() -> Unit = {}, header: @Composable ColumnScope.() -> Unit = {},
content: @Composable ColumnScope.() -> Unit = {}, content: @Composable ColumnScope.() -> Unit = {},
action: @Composable (ColumnScope.() -> Unit)? = null, action: @Composable (ColumnScope.() -> Unit)? = null,
onClick: (() -> Unit)? = null onClick: (() -> Unit)? = null,
contentPadding: PaddingValues = PaddingValues(horizontal = 16.dp),
) { ) {
val cardModifier = if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier val cardModifier = if (onClick != null) Modifier.clickable(onClick = onClick) else Modifier
@ -168,7 +178,7 @@ fun Card(
modifier = cardModifier modifier = cardModifier
.then(modifier) .then(modifier)
.then(paddingModifier) .then(paddingModifier)
.padding(horizontal = 16.dp) .padding(contentPadding)
) { ) {
header() header()
Column( Column(
@ -210,6 +220,7 @@ private fun Badge(
@Composable @Composable
fun ThreadContent( fun ThreadContent(
modifier: Modifier = Modifier,
title: String = "", title: String = "",
abstractText: String = "", abstractText: String = "",
tabName: String = "", tabName: String = "",
@ -245,12 +256,14 @@ fun ThreadContent(
EmoticonText( EmoticonText(
text = content, text = content,
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.fillMaxWidth()
.then(modifier),
fontSize = 15.sp, fontSize = 15.sp,
lineSpacing = 0.8.sp, lineSpacing = 0.8.sp,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 5, maxLines = 5,
style = MaterialTheme.typography.body1 style = MaterialTheme.typography.body1,
) )
} }
@ -368,16 +381,15 @@ private fun MediaPlaceholder(
@Composable @Composable
private fun ThreadMedia( private fun ThreadMedia(
item: ImmutableHolder<ThreadInfo>, forumId: Long,
forumName: String,
threadId: Long,
modifier: Modifier = Modifier,
medias: ImmutableList<ImmutableHolder<Media>> = persistentListOf(),
videoInfo: ImmutableHolder<VideoInfo>? = null,
) { ) {
val context = LocalContext.current val context = LocalContext.current
val isVideo = remember(item) {
item.isNotNull { videoInfo }
}
val medias = remember(item) {
item.getImmutableList { media }
}
val mediaCount = remember(medias) { val mediaCount = remember(medias) {
medias.size medias.size
} }
@ -393,7 +405,8 @@ private fun ThreadMedia(
else 0.5f else 0.5f
} }
if (isVideo) { Box(modifier = modifier) {
if (videoInfo != null) {
if (hideMedia) { if (hideMedia) {
MediaPlaceholder( MediaPlaceholder(
icon = { icon = {
@ -408,7 +421,6 @@ private fun ThreadMedia(
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
} else { } else {
val videoInfo = remember(item) { item.getImmutable { videoInfo!! } }
val aspectRatio = remember(videoInfo) { val aspectRatio = remember(videoInfo) {
max( max(
videoInfo videoInfo
@ -442,8 +454,16 @@ private fun ThreadMedia(
if (isSingleMedia) 2f else 3f if (isSingleMedia) 2f else 3f
} }
if (hideMedia) { if (hideMedia) {
val photoViewData = remember(item) { val photoViewData = remember(
getPhotoViewData(item.get(), 0) medias, forumId, forumName, threadId
) {
getPhotoViewData(
medias = medias.map { it.get() },
forumId = forumId,
forumName = forumName,
threadId = threadId,
index = 0
)
} }
MediaPlaceholder( MediaPlaceholder(
icon = { icon = {
@ -478,8 +498,16 @@ private fun ThreadMedia(
horizontalArrangement = Arrangement.spacedBy(4.dp) horizontalArrangement = Arrangement.spacedBy(4.dp)
) { ) {
showMedias.fastForEachIndexed { index, media -> showMedias.fastForEachIndexed { index, media ->
val photoViewData = remember(item, index) { val photoViewData = remember(
getPhotoViewData(item.get(), index) index, medias, forumId, forumName, threadId
) {
getPhotoViewData(
medias = medias.map { it.get() },
forumId = forumId,
forumName = forumName,
threadId = threadId,
index = index
)
} }
NetworkImage( NetworkImage(
imageUri = remember(media) { media.url }, imageUri = remember(media) { media.url },
@ -506,11 +534,52 @@ private fun ThreadMedia(
} }
} }
} }
}
@Composable
private fun ThreadMedia(
item: ImmutableHolder<ThreadInfo>,
modifier: Modifier = Modifier,
) {
ThreadMedia(
forumId = item.get { forumId },
forumName = item.get { forumName },
threadId = item.get { threadId },
medias = item.getImmutableList { media },
videoInfo = item.get { videoInfo }?.wrapImmutable(),
modifier = modifier,
)
}
@Composable
fun OriginThreadCard(
originThreadInfo: ImmutableHolder<OriginThreadInfo>,
modifier: Modifier = Modifier,
) {
val contentRenders = remember(originThreadInfo) { originThreadInfo.get { content.renders } }
Column(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
Column {
contentRenders.fastForEach {
it.Render()
}
}
ThreadMedia(
forumId = originThreadInfo.get { fid },
forumName = originThreadInfo.get { fname },
threadId = originThreadInfo.get { tid.toLong() },
medias = originThreadInfo.getImmutableList { media },
videoInfo = originThreadInfo.get { video_info }?.wrapImmutable()
)
}
}
@Composable @Composable
private fun ThreadForumInfo( private fun ThreadForumInfo(
item: ImmutableHolder<ThreadInfo>, item: ImmutableHolder<ThreadInfo>,
onClick: (SimpleForum) -> Unit onClick: (SimpleForum) -> Unit,
) { ) {
val hasForumInfo = remember(item) { item.isNotNull { forumInfo } } val hasForumInfo = remember(item) { item.isNotNull { forumInfo } }
if (hasForumInfo) { if (hasForumInfo) {
@ -617,6 +686,7 @@ fun FeedCard(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onReplyClick: (ThreadInfo) -> Unit = {}, onReplyClick: (ThreadInfo) -> Unit = {},
onClickForum: (SimpleForum) -> Unit = {}, onClickForum: (SimpleForum) -> Unit = {},
onClickOriginThread: (OriginThreadInfo) -> Unit = {},
dislikeAction: @Composable () -> Unit = {}, dislikeAction: @Composable () -> Unit = {},
) { ) {
Card( Card(
@ -625,7 +695,7 @@ fun FeedCard(
if (hasAuthor) { if (hasAuthor) {
DefaultUserHeader( DefaultUserHeader(
userProvider = { item.getImmutable { author!! } }, userProvider = { item.getImmutable { author!! } },
timeProvider = { item.get { lastTimeInt } } timeProvider = { item.get { lastTimeInt } },
) { dislikeAction() } ) { dislikeAction() }
} }
}, },
@ -636,10 +706,26 @@ fun FeedCard(
tabName = item.get { tabName }, tabName = item.get { tabName },
showTitle = item.get { isNoTitle != 1 && title.isNotBlank() }, showTitle = item.get { isNoTitle != 1 && title.isNotBlank() },
showAbstract = item.get { abstractText.isNotBlank() }, showAbstract = item.get { abstractText.isNotBlank() },
isGood = item.get { isGood == 1 } isGood = item.get { isGood == 1 },
) )
ThreadMedia(item = item) ThreadMedia(
item = item,
)
item.getNullableImmutable { origin_thread_info }
.takeIf { item.get { is_share_thread } == 1 }?.let {
OriginThreadCard(
originThreadInfo = it,
modifier = Modifier
.clip(RoundedCornerShape(6.dp))
.background(ExtendedTheme.colors.floorCard)
.clickable {
onClickOriginThread(it.get())
}
.padding(16.dp)
)
}
ThreadForumInfo(item = item, onClick = onClickForum) ThreadForumInfo(item = item, onClick = onClickForum)
}, },
@ -666,13 +752,13 @@ fun FeedCard(
} }
}, },
onClick = { onClick(item.get()) }, onClick = { onClick(item.get()) },
modifier = modifier modifier = modifier,
) )
} }
@Composable @Composable
private fun ActionBtnPlaceholder( private fun ActionBtnPlaceholder(
modifier: Modifier = Modifier modifier: Modifier = Modifier,
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier
@ -726,7 +812,7 @@ fun VideoPlayer(
videoUrl: String, videoUrl: String,
thumbnailUrl: String, thumbnailUrl: String,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String = "" title: String = "",
) { ) {
val context = LocalContext.current val context = LocalContext.current
val systemUiController = rememberSystemUiController() val systemUiController = rememberSystemUiController()

View File

@ -11,6 +11,7 @@ import "AlaLiveInfo.proto";
import "DislikeInfo.proto"; import "DislikeInfo.proto";
import "HotTWThreadInfo.proto"; import "HotTWThreadInfo.proto";
import "Media.proto"; import "Media.proto";
import "OriginThreadInfo.proto";
import "PbContent.proto"; import "PbContent.proto";
import "SimpleForum.proto"; import "SimpleForum.proto";
import "TiebaPlusAd.proto"; import "TiebaPlusAd.proto";
@ -56,7 +57,9 @@ message ThreadInfo {
int32 agreeNum = 124; int32 agreeNum = 124;
Agree agree = 126; Agree agree = 126;
int64 shareNum = 135; int64 shareNum = 135;
OriginThreadInfo origin_thread_info = 141;
repeated PbContent firstPostContent = 142; repeated PbContent firstPostContent = 142;
int32 is_share_thread = 143;
int32 isTopic = 148; int32 isTopic = 148;
string topicUserName = 149; string topicUserName = 149;
string topicH5Url = 150; string topicH5Url = 150;