feat: 动态页面状态屏
This commit is contained in:
parent
71a11f3496
commit
a24d22e3f3
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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(),
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue