feat: 新版用户关注吧

This commit is contained in:
HuanCheng65 2023-10-08 00:25:52 +08:00
parent af3ce0a9a2
commit 0be799a772
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
4 changed files with 372 additions and 2 deletions

View File

@ -1,5 +1,6 @@
package com.huanchengfly.tieba.post.api.models
import androidx.compose.runtime.Immutable
import com.google.gson.annotations.SerializedName
import com.huanchengfly.tieba.post.models.BaseBean
import kotlinx.collections.immutable.persistentListOf
@ -39,9 +40,10 @@ data class UserLikeForumBean(
val forumList: List<ForumBean> = persistentListOf(),
)
@Immutable
@Serializable
data class ForumBean(
val id: String? = null,
val id: String = "",
@JvmField
val name: String? = null,

View File

@ -69,6 +69,7 @@ import com.huanchengfly.tieba.post.arch.getOrNull
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
import com.huanchengfly.tieba.post.ui.page.user.likeforum.UserLikeForumPage
import com.huanchengfly.tieba.post.ui.page.user.post.UserPostPage
import com.huanchengfly.tieba.post.ui.widgets.Chip
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
@ -283,7 +284,7 @@ fun UserProfilePage(
)
},
content = {
Text(text = "concern_forums")
UserLikeForumPage(uid = it.get { id })
}
),
).toImmutableList()

View File

@ -0,0 +1,199 @@
package com.huanchengfly.tieba.post.ui.page.user.likeforum
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.rememberPullRefreshState
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.unit.dp
import com.huanchengfly.tieba.post.api.models.UserLikeForumBean
import com.huanchengfly.tieba.post.arch.GlobalEvent
import com.huanchengfly.tieba.post.arch.collectPartialAsState
import com.huanchengfly.tieba.post.arch.getOrNull
import com.huanchengfly.tieba.post.arch.onGlobalEvent
import com.huanchengfly.tieba.post.arch.pageViewModel
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
import com.huanchengfly.tieba.post.ui.common.theme.compose.pullRefreshIndicator
import com.huanchengfly.tieba.post.ui.page.LocalNavigator
import com.huanchengfly.tieba.post.ui.page.destinations.ForumPageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad
import com.huanchengfly.tieba.post.ui.widgets.compose.LoadMoreLayout
import com.huanchengfly.tieba.post.ui.widgets.compose.MyLazyColumn
import com.huanchengfly.tieba.post.ui.widgets.compose.Sizes
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun UserLikeForumPage(
uid: Long,
viewModel: UserLikeForumViewModel = pageViewModel(),
) {
val navigator = LocalNavigator.current
LazyLoad(loaded = viewModel.initialized) {
viewModel.send(UserLikeForumUiIntent.Refresh(uid))
viewModel.initialized = true
}
val isRefreshing by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::isRefreshing,
initial = true
)
val isLoadingMore by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::isLoadingMore,
initial = false
)
val error by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::error,
initial = null
)
val currentPage by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::currentPage,
initial = 1
)
val hasMore by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::hasMore,
initial = false
)
val forums by viewModel.uiState.collectPartialAsState(
prop1 = UserLikeForumUiState::forums,
initial = persistentListOf()
)
val isEmpty by remember {
derivedStateOf { forums.isEmpty() }
}
val isError by remember {
derivedStateOf { error != null }
}
onGlobalEvent<GlobalEvent.Refresh>(
filter = { it.key == "user_profile" }
) {
viewModel.send(UserLikeForumUiIntent.Refresh(uid))
}
StateScreen(
isEmpty = isEmpty,
isError = isError,
isLoading = isRefreshing,
onReload = {
viewModel.send(UserLikeForumUiIntent.Refresh(uid))
},
errorScreen = { ErrorScreen(error = error.getOrNull()) },
) {
val pullRefreshState = rememberPullRefreshState(
refreshing = isRefreshing,
onRefresh = ::reload
)
val lazyListState = rememberLazyListState()
Box {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = {
viewModel.send(UserLikeForumUiIntent.LoadMore(uid, currentPage))
},
loadEnd = !hasMore,
lazyListState = lazyListState
) {
UserLikeForumList(
forums = { forums },
onClickForum = { forumBean ->
forumBean.name?.let {
navigator.navigate(ForumPageDestination(it))
}
},
lazyListState = lazyListState
)
}
PullRefreshIndicator(
refreshing = isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = ExtendedTheme.colors.pullRefreshIndicator,
contentColor = ExtendedTheme.colors.primary,
)
}
}
}
@Composable
private fun UserLikeForumList(
forums: () -> ImmutableList<UserLikeForumBean.ForumBean>,
onClickForum: (UserLikeForumBean.ForumBean) -> Unit,
lazyListState: LazyListState = rememberLazyListState(),
) {
val data = remember(forums) { forums() }
MyLazyColumn(state = lazyListState) {
items(
items = data,
key = { it.id }
) {
UserLikeForumItem(
item = it,
onClick = {
onClickForum(it)
},
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp)
)
}
}
}
@Composable
private fun UserLikeForumItem(
item: UserLikeForumBean.ForumBean,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = Modifier
.clickable(onClick = onClick)
.then(modifier),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
Avatar(
data = item.avatar,
size = Sizes.Medium,
contentDescription = null
)
Column(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
Text(text = item.name.orEmpty(), style = MaterialTheme.typography.subtitle1)
item.slogan.takeUnless { it.isNullOrEmpty() }?.let {
Text(
text = it,
style = MaterialTheme.typography.body2,
color = ExtendedTheme.colors.textSecondary
)
}
}
}
}

View File

@ -0,0 +1,168 @@
package com.huanchengfly.tieba.post.ui.page.user.likeforum
import androidx.compose.runtime.Immutable
import com.huanchengfly.tieba.post.api.TiebaApi
import com.huanchengfly.tieba.post.api.models.UserLikeForumBean
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.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
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 UserLikeForumViewModel @Inject constructor() :
BaseViewModel<UserLikeForumUiIntent, UserLikeForumPartialChange, UserLikeForumUiState, UiEvent>() {
override fun createInitialState(): UserLikeForumUiState = UserLikeForumUiState()
override fun createPartialChangeProducer(): PartialChangeProducer<UserLikeForumUiIntent, UserLikeForumPartialChange, UserLikeForumUiState> =
UserLikeForumPartialChangeProducer
private object UserLikeForumPartialChangeProducer :
PartialChangeProducer<UserLikeForumUiIntent, UserLikeForumPartialChange, UserLikeForumUiState> {
@OptIn(ExperimentalCoroutinesApi::class)
override fun toPartialChangeFlow(intentFlow: Flow<UserLikeForumUiIntent>): Flow<UserLikeForumPartialChange> =
merge(
intentFlow.filterIsInstance<UserLikeForumUiIntent.Refresh>()
.flatMapConcat { it.toPartialChangeFlow() },
intentFlow.filterIsInstance<UserLikeForumUiIntent.LoadMore>()
.flatMapConcat { it.toPartialChangeFlow() },
)
private fun UserLikeForumUiIntent.Refresh.toPartialChangeFlow(): Flow<UserLikeForumPartialChange.Refresh> =
TiebaApi.getInstance()
.userLikeForumFlow(uid.toString())
.map<UserLikeForumBean, UserLikeForumPartialChange.Refresh> {
UserLikeForumPartialChange.Refresh.Success(
page = 1,
hasMore = it.hasMore == "1",
forums = it.forumList.forumList,
)
}
.onStart { emit(UserLikeForumPartialChange.Refresh.Start) }
.catch { emit(UserLikeForumPartialChange.Refresh.Failure(it)) }
private fun UserLikeForumUiIntent.LoadMore.toPartialChangeFlow(): Flow<UserLikeForumPartialChange.LoadMore> =
TiebaApi.getInstance()
.userLikeForumFlow(uid.toString(), page + 1)
.map<UserLikeForumBean, UserLikeForumPartialChange.LoadMore> {
UserLikeForumPartialChange.LoadMore.Success(
page = page + 1,
hasMore = it.hasMore == "1",
forums = it.forumList.forumList,
)
}
.onStart { emit(UserLikeForumPartialChange.LoadMore.Start) }
.catch { emit(UserLikeForumPartialChange.LoadMore.Failure(it)) }
}
}
sealed interface UserLikeForumUiIntent : UiIntent {
data class Refresh(val uid: Long) : UserLikeForumUiIntent
data class LoadMore(
val uid: Long,
val page: Int,
) : UserLikeForumUiIntent
}
sealed interface UserLikeForumPartialChange : PartialChange<UserLikeForumUiState> {
sealed class Refresh : UserLikeForumPartialChange {
override fun reduce(oldState: UserLikeForumUiState): UserLikeForumUiState = when (this) {
is Start -> {
oldState.copy(
isRefreshing = true,
)
}
is Success -> {
oldState.copy(
isRefreshing = false,
error = null,
currentPage = page,
hasMore = hasMore,
forums = forums.toImmutableList(),
)
}
is Failure -> {
oldState.copy(
isRefreshing = false,
error = error.wrapImmutable(),
)
}
}
data object Start : Refresh()
data class Success(
val page: Int,
val hasMore: Boolean,
val forums: List<UserLikeForumBean.ForumBean>,
) : Refresh()
data class Failure(val error: Throwable) : Refresh()
}
sealed class LoadMore : UserLikeForumPartialChange {
override fun reduce(oldState: UserLikeForumUiState): UserLikeForumUiState = when (this) {
is Start -> {
oldState.copy(
isLoadingMore = true,
)
}
is Success -> {
val uniqueForums = (oldState.forums + forums).distinctBy { it.id }
oldState.copy(
isLoadingMore = false,
error = null,
currentPage = page,
hasMore = hasMore,
forums = uniqueForums.toImmutableList(),
)
}
is Failure -> {
oldState.copy(
isLoadingMore = false,
error = error.wrapImmutable(),
)
}
}
data object Start : LoadMore()
data class Success(
val page: Int,
val hasMore: Boolean,
val forums: List<UserLikeForumBean.ForumBean>,
) : LoadMore()
data class Failure(val error: Throwable) : LoadMore()
}
}
@Immutable
data class UserLikeForumUiState(
val isRefreshing: Boolean = false,
val isLoadingMore: Boolean = false,
val error: ImmutableHolder<Throwable>? = null,
val currentPage: Int = 1,
val hasMore: Boolean = false,
val forums: ImmutableList<UserLikeForumBean.ForumBean> = persistentListOf(),
) : UiState