feat: 新版用户关注吧
This commit is contained in:
parent
af3ce0a9a2
commit
0be799a772
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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