feat: 查看吧信息
This commit is contained in:
parent
58459c8f8d
commit
8ec45b90db
|
|
@ -9,7 +9,7 @@ import androidx.compose.animation.core.animateFloatAsState
|
|||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.rememberScrollableState
|
||||
import androidx.compose.foundation.gestures.scrollable
|
||||
|
|
@ -104,6 +104,7 @@ import com.huanchengfly.tieba.post.toastShort
|
|||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
|
||||
import com.huanchengfly.tieba.post.ui.page.destinations.ForumDetailPageDestination
|
||||
import com.huanchengfly.tieba.post.ui.page.forum.threadlist.ForumThreadListPage
|
||||
import com.huanchengfly.tieba.post.ui.page.forum.threadlist.ForumThreadListUiEvent
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
|
|
@ -113,7 +114,6 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.ClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCardPlaceholder
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MenuScope
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||
|
|
@ -197,29 +197,15 @@ private fun ForumHeaderPlaceholder(
|
|||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.placeholder(
|
||||
visible = true,
|
||||
highlight = PlaceholderHighlight.fade(),
|
||||
)
|
||||
.padding(vertical = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
StatCardItem(
|
||||
statNum = 0,
|
||||
statText = stringResource(id = R.string.text_stat_follow)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumHeader(
|
||||
forumInfoImmutableHolder: ImmutableHolder<ForumInfo>,
|
||||
onOpenForumInfo: () -> Unit,
|
||||
onBtnClick: () -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (forum) = forumInfoImmutableHolder
|
||||
Column(
|
||||
|
|
@ -238,6 +224,15 @@ private fun ForumHeader(
|
|||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onOpenForumInfo
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_forum, forum.name),
|
||||
|
|
@ -245,6 +240,12 @@ private fun ForumHeader(
|
|||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
// Icon(
|
||||
// imageVector = Icons.Rounded.KeyboardArrowRight,
|
||||
// contentDescription = null,
|
||||
// modifier = Modifier.size(16.dp)
|
||||
// )
|
||||
}
|
||||
AnimatedVisibility(visible = forum.is_like == 1) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(4.dp)) {
|
||||
LinearProgressIndicator(
|
||||
|
|
@ -301,28 +302,28 @@ private fun ForumHeader(
|
|||
}
|
||||
}
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(color = ExtendedTheme.colors.chip)
|
||||
.padding(vertical = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
StatCardItem(
|
||||
statNum = forum.member_num,
|
||||
statText = stringResource(id = R.string.text_stat_follow)
|
||||
)
|
||||
HorizontalDivider(color = Color(if (ExtendedTheme.colors.isNightMode) 0xFF808080 else 0xFFDEDEDE))
|
||||
StatCardItem(
|
||||
statNum = forum.thread_num,
|
||||
statText = stringResource(id = R.string.text_stat_threads)
|
||||
)
|
||||
HorizontalDivider(color = Color(if (ExtendedTheme.colors.isNightMode) 0xFF808080 else 0xFFDEDEDE))
|
||||
StatCardItem(
|
||||
statNum = forum.post_num,
|
||||
statText = stringResource(id = R.string.title_stat_posts_num)
|
||||
)
|
||||
}
|
||||
// Row(
|
||||
// modifier = Modifier
|
||||
// .clip(RoundedCornerShape(8.dp))
|
||||
// .background(color = ExtendedTheme.colors.chip)
|
||||
// .padding(vertical = 20.dp),
|
||||
// verticalAlignment = Alignment.CenterVertically,
|
||||
// ) {
|
||||
// StatCardItem(
|
||||
// statNum = forum.member_num,
|
||||
// statText = stringResource(id = R.string.text_stat_follow)
|
||||
// )
|
||||
// HorizontalDivider(color = Color(if (ExtendedTheme.colors.isNightMode) 0xFF808080 else 0xFFDEDEDE))
|
||||
// StatCardItem(
|
||||
// statNum = forum.thread_num,
|
||||
// statText = stringResource(id = R.string.text_stat_threads)
|
||||
// )
|
||||
// HorizontalDivider(color = Color(if (ExtendedTheme.colors.isNightMode) 0xFF808080 else 0xFFDEDEDE))
|
||||
// StatCardItem(
|
||||
// statNum = forum.post_num,
|
||||
// statText = stringResource(id = R.string.title_stat_posts_num)
|
||||
// )
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -651,11 +652,14 @@ fun ForumPage(
|
|||
),
|
||||
exit = shrinkVertically()
|
||||
) {
|
||||
if (forumInfo != null) {
|
||||
forumInfo?.let {
|
||||
ForumHeader(
|
||||
forumInfoImmutableHolder = forumInfo!!,
|
||||
forumInfoImmutableHolder = it,
|
||||
onOpenForumInfo = {
|
||||
navigator.navigate(ForumDetailPageDestination(forumId = it.get { this.id }))
|
||||
},
|
||||
onBtnClick = {
|
||||
val (forum) = forumInfo!!
|
||||
val (forum) = it
|
||||
when {
|
||||
forum.is_like != 1 -> viewModel.send(
|
||||
ForumUiIntent.Like(
|
||||
|
|
|
|||
|
|
@ -0,0 +1,232 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.forum.detail
|
||||
|
||||
import android.graphics.Typeface
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
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.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.models.protos.PbContent
|
||||
import com.huanchengfly.tieba.post.api.models.protos.RecommendForumInfo
|
||||
import com.huanchengfly.tieba.post.api.models.protos.plainText
|
||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||
import com.huanchengfly.tieba.post.arch.collectPartialAsState
|
||||
import com.huanchengfly.tieba.post.arch.getOrNull
|
||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.TiebaLiteTheme
|
||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.BackNavigationIcon
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Container
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.HorizontalDivider
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.MyScaffold
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TitleCentredToolbar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
|
||||
import com.huanchengfly.tieba.post.utils.StringUtil.getShortNumString
|
||||
import com.ramcosta.composedestinations.annotation.Destination
|
||||
import com.ramcosta.composedestinations.navigation.DestinationsNavigator
|
||||
|
||||
@Destination
|
||||
@Composable
|
||||
fun ForumDetailPage(
|
||||
forumId: Long,
|
||||
navigator: DestinationsNavigator,
|
||||
viewModel: ForumDetailViewModel = pageViewModel(),
|
||||
) {
|
||||
LazyLoad(loaded = viewModel.initialized) {
|
||||
viewModel.send(ForumDetailUiIntent.Load(forumId))
|
||||
viewModel.initialized = true
|
||||
}
|
||||
|
||||
val isLoading by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = ForumDetailUiState::isLoading,
|
||||
initial = true
|
||||
)
|
||||
val error by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = ForumDetailUiState::error,
|
||||
initial = null
|
||||
)
|
||||
val forumInfo by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = ForumDetailUiState::forumInfo,
|
||||
initial = null
|
||||
)
|
||||
|
||||
val isEmpty by remember {
|
||||
derivedStateOf { forumInfo == null }
|
||||
}
|
||||
val isError by remember {
|
||||
derivedStateOf { error != null }
|
||||
}
|
||||
|
||||
StateScreen(
|
||||
isEmpty = isEmpty,
|
||||
isError = isError,
|
||||
isLoading = isLoading,
|
||||
onReload = {
|
||||
viewModel.send(ForumDetailUiIntent.Load(forumId))
|
||||
},
|
||||
errorScreen = {
|
||||
ErrorScreen(error = error.getOrNull())
|
||||
}
|
||||
) {
|
||||
MyScaffold(
|
||||
topBar = {
|
||||
TitleCentredToolbar(
|
||||
title = {
|
||||
Text(text = stringResource(id = R.string.title_forum_info))
|
||||
},
|
||||
navigationIcon = {
|
||||
BackNavigationIcon {
|
||||
navigator.navigateUp()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
) { paddingValues ->
|
||||
Container(modifier = Modifier.padding(paddingValues)) {
|
||||
forumInfo?.let {
|
||||
ForumDetailContent(
|
||||
forumInfo = it,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ForumDetailContent(
|
||||
forumInfo: ImmutableHolder<RecommendForumInfo>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val intro = remember(forumInfo) {
|
||||
forumInfo.get { content.plainText }
|
||||
}
|
||||
Column(
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.verticalScroll(rememberScrollState()),
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp)
|
||||
) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Avatar(
|
||||
data = forumInfo.get { avatar },
|
||||
size = Sizes.Medium,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_forum, forumInfo.get { forum_name }),
|
||||
style = MaterialTheme.typography.h6
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(color = ExtendedTheme.colors.chip)
|
||||
.padding(vertical = 20.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
StatCardItem(
|
||||
statNum = forumInfo.get { member_count },
|
||||
statText = stringResource(id = R.string.text_stat_follow)
|
||||
)
|
||||
HorizontalDivider(color = Color(if (ExtendedTheme.colors.isNightMode) 0xFF808080 else 0xFFDEDEDE))
|
||||
StatCardItem(
|
||||
statNum = forumInfo.get { thread_count },
|
||||
statText = stringResource(id = R.string.text_stat_threads)
|
||||
)
|
||||
}
|
||||
Column(
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Chip(text = stringResource(id = R.string.title_forum_intro))
|
||||
Column {
|
||||
Text(text = forumInfo.get { slogan }, style = MaterialTheme.typography.body1)
|
||||
Text(text = intro, style = MaterialTheme.typography.body1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RowScope.StatCardItem(
|
||||
statNum: Int,
|
||||
statText: String,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = statNum.getShortNumString(),
|
||||
fontSize = 20.sp,
|
||||
fontFamily = FontFamily(
|
||||
Typeface.createFromAsset(
|
||||
LocalContext.current.assets,
|
||||
"bebas.ttf"
|
||||
)
|
||||
),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(2.dp))
|
||||
Text(
|
||||
text = statText,
|
||||
fontSize = 12.sp,
|
||||
color = ExtendedTheme.colors.textSecondary
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview("ForumDetailPage", backgroundColor = 0xFFFFFFFF, showBackground = true)
|
||||
@Composable
|
||||
fun PreviewForumDetailPage() {
|
||||
TiebaLiteTheme {
|
||||
ForumDetailContent(
|
||||
forumInfo = RecommendForumInfo(
|
||||
forum_name = "minecraft",
|
||||
slogan = "位于百度贴吧的像素点之家",
|
||||
content = listOf(
|
||||
PbContent(
|
||||
type = 0,
|
||||
text = "minecraft……",
|
||||
)
|
||||
),
|
||||
member_count = 2520287,
|
||||
thread_count = 31531580
|
||||
).wrapImmutable(),
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.huanchengfly.tieba.post.ui.page.forum.detail
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.huanchengfly.tieba.post.api.TiebaApi
|
||||
import com.huanchengfly.tieba.post.api.models.protos.RecommendForumInfo
|
||||
import com.huanchengfly.tieba.post.api.models.protos.getForumDetail.GetForumDetailResponse
|
||||
import com.huanchengfly.tieba.post.arch.BaseViewModel
|
||||
import com.huanchengfly.tieba.post.arch.ImmutableHolder
|
||||
import com.huanchengfly.tieba.post.arch.PartialChange
|
||||
import com.huanchengfly.tieba.post.arch.PartialChangeProducer
|
||||
import com.huanchengfly.tieba.post.arch.UiEvent
|
||||
import com.huanchengfly.tieba.post.arch.UiIntent
|
||||
import com.huanchengfly.tieba.post.arch.UiState
|
||||
import com.huanchengfly.tieba.post.arch.wrapImmutable
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.filterIsInstance
|
||||
import kotlinx.coroutines.flow.flatMapConcat
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.merge
|
||||
import kotlinx.coroutines.flow.onStart
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class ForumDetailViewModel @Inject constructor() :
|
||||
BaseViewModel<ForumDetailUiIntent, ForumDetailPartialChange, ForumDetailUiState, UiEvent>() {
|
||||
override fun createInitialState(): ForumDetailUiState = ForumDetailUiState()
|
||||
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<ForumDetailUiIntent, ForumDetailPartialChange, ForumDetailUiState> =
|
||||
ForumDetailPartialChangeProducer
|
||||
|
||||
private object ForumDetailPartialChangeProducer :
|
||||
PartialChangeProducer<ForumDetailUiIntent, ForumDetailPartialChange, ForumDetailUiState> {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun toPartialChangeFlow(intentFlow: Flow<ForumDetailUiIntent>): Flow<ForumDetailPartialChange> =
|
||||
merge(
|
||||
intentFlow.filterIsInstance<ForumDetailUiIntent.Load>()
|
||||
.flatMapConcat { it.producePartialChange() }
|
||||
)
|
||||
|
||||
private fun ForumDetailUiIntent.Load.producePartialChange(): Flow<ForumDetailPartialChange.Load> =
|
||||
TiebaApi.getInstance()
|
||||
.getForumDetailFlow(forumId)
|
||||
.map<GetForumDetailResponse, ForumDetailPartialChange.Load> {
|
||||
val forumInfo = it.data_?.forum_info
|
||||
checkNotNull(forumInfo) { "forumInfo is null" }
|
||||
ForumDetailPartialChange.Load.Success(
|
||||
forumInfo
|
||||
)
|
||||
}
|
||||
.onStart { emit(ForumDetailPartialChange.Load.Start) }
|
||||
.catch { emit(ForumDetailPartialChange.Load.Failure(it)) }
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ForumDetailUiIntent : UiIntent {
|
||||
data class Load(val forumId: Long) : ForumDetailUiIntent
|
||||
}
|
||||
|
||||
sealed interface ForumDetailPartialChange : PartialChange<ForumDetailUiState> {
|
||||
sealed class Load : ForumDetailPartialChange {
|
||||
override fun reduce(oldState: ForumDetailUiState): ForumDetailUiState = when (this) {
|
||||
Start -> oldState.copy(
|
||||
isLoading = true,
|
||||
)
|
||||
|
||||
is Success -> oldState.copy(
|
||||
isLoading = false,
|
||||
error = null,
|
||||
forumInfo = forumInfo.wrapImmutable()
|
||||
)
|
||||
|
||||
is Failure -> oldState.copy(
|
||||
isLoading = false,
|
||||
error = error.wrapImmutable()
|
||||
)
|
||||
}
|
||||
|
||||
data object Start : Load()
|
||||
|
||||
data class Success(val forumInfo: RecommendForumInfo) : Load()
|
||||
|
||||
data class Failure(val error: Throwable) : Load()
|
||||
}
|
||||
}
|
||||
|
||||
@Immutable
|
||||
data class ForumDetailUiState(
|
||||
val isLoading: Boolean = true,
|
||||
val error: ImmutableHolder<Throwable>? = null,
|
||||
|
||||
val forumInfo: ImmutableHolder<RecommendForumInfo>? = null,
|
||||
) : UiState
|
||||
|
|
@ -742,6 +742,7 @@
|
|||
<string name="button_open">打开</string>
|
||||
<string name="btn_hide">隐藏</string>
|
||||
<string name="desc_search_sug">搜索联想:%s</string>
|
||||
<string name="title_forum_intro">本吧简介</string>
|
||||
<string name="title_forum_rule">本吧吧规</string>
|
||||
<string name="desc_forum_rule">吧规</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Reference in New Issue