feat: 新版用户关注吧
This commit is contained in:
parent
af3ce0a9a2
commit
0be799a772
|
|
@ -1,5 +1,6 @@
|
||||||
package com.huanchengfly.tieba.post.api.models
|
package com.huanchengfly.tieba.post.api.models
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import com.google.gson.annotations.SerializedName
|
import com.google.gson.annotations.SerializedName
|
||||||
import com.huanchengfly.tieba.post.models.BaseBean
|
import com.huanchengfly.tieba.post.models.BaseBean
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
@ -39,9 +40,10 @@ data class UserLikeForumBean(
|
||||||
val forumList: List<ForumBean> = persistentListOf(),
|
val forumList: List<ForumBean> = persistentListOf(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Immutable
|
||||||
@Serializable
|
@Serializable
|
||||||
data class ForumBean(
|
data class ForumBean(
|
||||||
val id: String? = null,
|
val id: String = "",
|
||||||
|
|
||||||
@JvmField
|
@JvmField
|
||||||
val name: String? = null,
|
val name: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,7 @@ import com.huanchengfly.tieba.post.arch.getOrNull
|
||||||
import com.huanchengfly.tieba.post.arch.pageViewModel
|
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.ExtendedTheme
|
||||||
import com.huanchengfly.tieba.post.ui.page.ProvideNavigator
|
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.page.user.post.UserPostPage
|
||||||
import com.huanchengfly.tieba.post.ui.widgets.Chip
|
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.Avatar
|
||||||
|
|
@ -283,7 +284,7 @@ fun UserProfilePage(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
Text(text = "concern_forums")
|
UserLikeForumPage(uid = it.get { id })
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
).toImmutableList()
|
).toImmutableList()
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue