feat: 动态页面状态屏

This commit is contained in:
HuanCheng65 2023-10-05 02:06:52 +08:00
parent 71a11f3496
commit a24d22e3f3
No known key found for this signature in database
GPG Key ID: 5EC9DD60A32C7360
2 changed files with 112 additions and 72 deletions

View File

@ -33,6 +33,7 @@ import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
@ -62,11 +63,13 @@ import com.huanchengfly.tieba.post.ui.page.destinations.ThreadPageDestination
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip import com.huanchengfly.tieba.post.ui.widgets.compose.BlockTip
import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent import com.huanchengfly.tieba.post.ui.widgets.compose.BlockableContent
import com.huanchengfly.tieba.post.ui.widgets.compose.Container 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.FeedCard import com.huanchengfly.tieba.post.ui.widgets.compose.FeedCard
import com.huanchengfly.tieba.post.ui.widgets.compose.LazyLoad 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.LoadMoreLayout
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.VerticalDivider import com.huanchengfly.tieba.post.ui.widgets.compose.VerticalDivider
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreen
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -98,6 +101,10 @@ fun PersonalizedPage(
prop1 = PersonalizedUiState::data, prop1 = PersonalizedUiState::data,
initial = persistentListOf() initial = persistentListOf()
) )
val error by viewModel.uiState.collectPartialAsState(
prop1 = PersonalizedUiState::error,
initial = null
)
val refreshPosition by viewModel.uiState.collectPartialAsState( val refreshPosition by viewModel.uiState.collectPartialAsState(
prop1 = PersonalizedUiState::refreshPosition, prop1 = PersonalizedUiState::refreshPosition,
initial = 0 initial = 0
@ -112,6 +119,16 @@ fun PersonalizedPage(
) )
val lazyListState = rememberLazyListState() val lazyListState = rememberLazyListState()
viewModel.bindScrollToTopEvent(lazyListState = lazyListState) viewModel.bindScrollToTopEvent(lazyListState = lazyListState)
val isEmpty by remember {
derivedStateOf {
data.isEmpty()
}
}
val isError by remember {
derivedStateOf {
error != null
}
}
var refreshCount by remember { var refreshCount by remember {
mutableIntStateOf(0) mutableIntStateOf(0)
} }
@ -147,77 +164,91 @@ fun PersonalizedPage(
// } // }
// } // }
// } // }
Box(modifier = Modifier.pullRefresh(pullRefreshState)) { StateScreen(
LoadMoreLayout( isEmpty = isEmpty,
isLoading = isLoadingMore, isError = isError,
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) }, isLoading = isRefreshing,
loadEnd = false, onReload = { viewModel.send(PersonalizedUiIntent.Refresh) },
lazyListState = lazyListState, errorScreen = {
isEmpty = data.isEmpty() error?.let {
) { ErrorScreen(
FeedList( error = it.get()
state = lazyListState, )
dataProvider = { data },
refreshPositionProvider = { refreshPosition },
hiddenThreadIdsProvider = { hiddenThreadIds },
onItemClick = {
navigator.navigate(
ThreadPageDestination(
it.id,
it.forumId,
threadInfo = it
)
)
},
onItemReplyClick = {
navigator.navigate(
ThreadPageDestination(
it.id,
it.forumId,
scrollToReply = true
)
)
},
onAgree = {
viewModel.send(
PersonalizedUiIntent.Agree(
it.threadId,
it.firstPostId,
it.agree?.hasAgree ?: 0
)
)
},
onDislike = { item, clickTime, reasons ->
viewModel.send(
PersonalizedUiIntent.Dislike(
item.forumInfo?.id ?: 0,
item.threadId,
reasons,
clickTime
)
)
},
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) }
) {
navigator.navigate(ForumPageDestination(it))
} }
} }
) {
Box(modifier = Modifier.pullRefresh(pullRefreshState)) {
LoadMoreLayout(
isLoading = isLoadingMore,
onLoadMore = { viewModel.send(PersonalizedUiIntent.LoadMore(currentPage + 1)) },
loadEnd = false,
lazyListState = lazyListState,
isEmpty = data.isEmpty()
) {
FeedList(
state = lazyListState,
dataProvider = { data },
refreshPositionProvider = { refreshPosition },
hiddenThreadIdsProvider = { hiddenThreadIds },
onItemClick = {
navigator.navigate(
ThreadPageDestination(
it.id,
it.forumId,
threadInfo = it
)
)
},
onItemReplyClick = {
navigator.navigate(
ThreadPageDestination(
it.id,
it.forumId,
scrollToReply = true
)
)
},
onAgree = {
viewModel.send(
PersonalizedUiIntent.Agree(
it.threadId,
it.firstPostId,
it.agree?.hasAgree ?: 0
)
)
},
onDislike = { item, clickTime, reasons ->
viewModel.send(
PersonalizedUiIntent.Dislike(
item.forumInfo?.id ?: 0,
item.threadId,
reasons,
clickTime
)
)
},
onRefresh = { viewModel.send(PersonalizedUiIntent.Refresh) }
) {
navigator.navigate(ForumPageDestination(it))
}
}
PullRefreshIndicator( PullRefreshIndicator(
refreshing = isRefreshing, refreshing = isRefreshing,
state = pullRefreshState, state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter), modifier = Modifier.align(Alignment.TopCenter),
backgroundColor = ExtendedTheme.colors.pullRefreshIndicator, backgroundColor = ExtendedTheme.colors.pullRefreshIndicator,
contentColor = ExtendedTheme.colors.primary, contentColor = ExtendedTheme.colors.primary,
) )
AnimatedVisibility( AnimatedVisibility(
visible = showRefreshTip, visible = showRefreshTip,
enter = fadeIn() + slideInVertically(), enter = fadeIn() + slideInVertically(),
exit = slideOutVertically() + fadeOut(), exit = slideOutVertically() + fadeOut(),
modifier = Modifier.align(Alignment.TopCenter) modifier = Modifier.align(Alignment.TopCenter)
) { ) {
RefreshTip(refreshCount = refreshCount) RefreshTip(refreshCount = refreshCount)
}
} }
} }
} }

View File

@ -145,7 +145,7 @@ class PersonalizedViewModel @Inject constructor() :
} }
sealed interface PersonalizedUiIntent : UiIntent { sealed interface PersonalizedUiIntent : UiIntent {
object Refresh : PersonalizedUiIntent data object Refresh : PersonalizedUiIntent
data class LoadMore(val page: Int) : PersonalizedUiIntent data class LoadMore(val page: Int) : PersonalizedUiIntent
@ -282,10 +282,14 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
data = (data + oldState.data).toImmutableList(), data = (data + oldState.data).toImmutableList(),
refreshPosition = if (oldState.data.isEmpty()) 0 else data.size refreshPosition = if (oldState.data.isEmpty()) 0 else data.size
) )
is Failure -> oldState.copy(isRefreshing = false)
is Failure -> oldState.copy(
isRefreshing = false,
error = error.wrapImmutable()
)
} }
object Start: Refresh() data object Start : Refresh()
data class Success( data class Success(
val data: List<ThreadItemData>, val data: List<ThreadItemData>,
@ -305,10 +309,14 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
currentPage = currentPage, currentPage = currentPage,
data = (oldState.data + data).toImmutableList(), data = (oldState.data + data).toImmutableList(),
) )
is Failure -> oldState.copy(isLoadingMore = false)
is Failure -> oldState.copy(
isLoadingMore = false,
error = error.wrapImmutable()
)
} }
object Start: LoadMore() data object Start : LoadMore()
data class Success( data class Success(
val currentPage: Int, val currentPage: Int,
@ -325,6 +333,7 @@ sealed interface PersonalizedPartialChange : PartialChange<PersonalizedUiState>
data class PersonalizedUiState( data class PersonalizedUiState(
val isRefreshing: Boolean = true, val isRefreshing: Boolean = true,
val isLoadingMore: Boolean = false, val isLoadingMore: Boolean = false,
val error: ImmutableHolder<Throwable>? = null,
val currentPage: Int = 1, val currentPage: Int = 1,
val data: ImmutableList<ThreadItemData> = persistentListOf(), val data: ImmutableList<ThreadItemData> = persistentListOf(),
val hiddenThreadIds: ImmutableList<Long> = persistentListOf(), val hiddenThreadIds: ImmutableList<Long> = persistentListOf(),