pref: 加载错误显示
This commit is contained in:
parent
addbc80bc0
commit
cf6c42ae1c
|
|
@ -6,7 +6,6 @@ import androidx.compose.animation.animateContentSize
|
|||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
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.PaddingValues
|
||||
|
|
@ -81,8 +80,10 @@ import com.huanchengfly.tieba.post.ui.widgets.compose.ActionItem
|
|||
import com.huanchengfly.tieba.post.ui.widgets.compose.Avatar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Button
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ConfirmDialog
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.ErrorScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.LongClickMenu
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TextButton
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.TipScreen
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.Toolbar
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.accountNavIconIfCompact
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
|
||||
|
|
@ -153,8 +154,8 @@ fun SearchBox(
|
|||
@Composable
|
||||
private fun Header(
|
||||
text: String,
|
||||
invert: Boolean = false,
|
||||
modifier: Modifier = Modifier
|
||||
modifier: Modifier = Modifier,
|
||||
invert: Boolean = false
|
||||
) {
|
||||
Chip(
|
||||
text = text,
|
||||
|
|
@ -391,6 +392,11 @@ fun HomePage(
|
|||
prop1 = HomeUiState::topForums,
|
||||
initial = emptyList()
|
||||
)
|
||||
val error by viewModel.uiState.collectPartialAsState(
|
||||
prop1 = HomeUiState::error,
|
||||
initial = null
|
||||
)
|
||||
val isError by remember { derivedStateOf { error != null } }
|
||||
var listSingle by remember { mutableStateOf(context.appPreferences.listSingle) }
|
||||
val gridCells by remember { derivedStateOf { getGridCells(context, listSingle) } }
|
||||
|
||||
|
|
@ -421,9 +427,12 @@ fun HomePage(
|
|||
) { contentPaddings ->
|
||||
StateScreen(
|
||||
isEmpty = forums.isEmpty(),
|
||||
isError = false,
|
||||
isError = isError,
|
||||
isLoading = isLoading,
|
||||
modifier = Modifier.padding(contentPaddings),
|
||||
onReload = {
|
||||
viewModel.send(HomeUiIntent.Refresh)
|
||||
},
|
||||
emptyScreen = {
|
||||
EmptyScreen(
|
||||
loggedIn = account != null,
|
||||
|
|
@ -433,6 +442,9 @@ fun HomePage(
|
|||
},
|
||||
loadingScreen = {
|
||||
HomePageSkeletonScreen(listSingle = listSingle, gridCells = gridCells)
|
||||
},
|
||||
errorScreen = {
|
||||
error?.let { ErrorScreen(error = it) }
|
||||
}
|
||||
) {
|
||||
val pullRefreshState = rememberPullRefreshState(
|
||||
|
|
@ -516,11 +528,11 @@ private fun HomePageSkeletonScreen(
|
|||
Column {
|
||||
Header(
|
||||
text = stringResource(id = R.string.title_top_forum),
|
||||
invert = true,
|
||||
modifier = Modifier.placeholder(
|
||||
visible = true,
|
||||
color = ExtendedTheme.colors.chip
|
||||
)
|
||||
),
|
||||
invert = true
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
|
@ -541,11 +553,11 @@ private fun HomePageSkeletonScreen(
|
|||
Column {
|
||||
Header(
|
||||
text = stringResource(id = R.string.forum_list_title),
|
||||
invert = true,
|
||||
modifier = Modifier.placeholder(
|
||||
visible = true,
|
||||
color = ExtendedTheme.colors.chip
|
||||
)
|
||||
),
|
||||
invert = true
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
}
|
||||
|
|
@ -563,59 +575,55 @@ fun EmptyScreen(
|
|||
onOpenExplore: () -> Unit
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp, alignment = CenterVertically)
|
||||
) {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_astronaut))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
if (!loggedIn) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_empty_login),
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = ExtendedTheme.colors.text,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.home_empty_login),
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = ExtendedTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
Button(
|
||||
onClick = {
|
||||
context.goToActivity<LoginActivity>()
|
||||
},
|
||||
TipScreen(
|
||||
title = {
|
||||
if (!loggedIn) {
|
||||
Text(text = stringResource(id = R.string.title_empty_login))
|
||||
} else {
|
||||
Text(text = stringResource(id = R.string.title_empty))
|
||||
}
|
||||
},
|
||||
image = {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_astronaut))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.button_login))
|
||||
}
|
||||
} else {
|
||||
Text(
|
||||
text = stringResource(id = R.string.title_empty),
|
||||
style = MaterialTheme.typography.h6,
|
||||
color = ExtendedTheme.colors.text,
|
||||
textAlign = TextAlign.Center,
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
}
|
||||
if (canOpenExplore) {
|
||||
TextButton(
|
||||
onClick = onOpenExplore,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.button_go_to_explore))
|
||||
},
|
||||
message = {
|
||||
if (!loggedIn) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.home_empty_login),
|
||||
style = MaterialTheme.typography.body1,
|
||||
color = ExtendedTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
actions = {
|
||||
if (!loggedIn) {
|
||||
Button(
|
||||
onClick = {
|
||||
context.goToActivity<LoginActivity>()
|
||||
},
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.button_login))
|
||||
}
|
||||
}
|
||||
if (canOpenExplore) {
|
||||
TextButton(
|
||||
onClick = onOpenExplore,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.button_go_to_explore))
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -15,24 +15,29 @@ import org.litepal.LitePal
|
|||
class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState, HomeUiEvent>() {
|
||||
override fun createInitialState(): HomeUiState = HomeUiState()
|
||||
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> = HomePartialChangeProducer
|
||||
override fun createPartialChangeProducer(): PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> =
|
||||
HomePartialChangeProducer
|
||||
|
||||
override fun dispatchEvent(partialChange: HomePartialChange): UiEvent? =
|
||||
when (partialChange) {
|
||||
is HomePartialChange.Refresh.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
|
||||
is HomePartialChange.TopForums.Delete.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
|
||||
is HomePartialChange.TopForums.Add.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
|
||||
else -> null
|
||||
}
|
||||
|
||||
object HomePartialChangeProducer : PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> {
|
||||
object HomePartialChangeProducer :
|
||||
PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> {
|
||||
@OptIn(FlowPreview::class)
|
||||
override fun toPartialChangeFlow(intentFlow: Flow<HomeUiIntent>): Flow<HomePartialChange> {
|
||||
return merge(
|
||||
intentFlow.filterIsInstance<HomeUiIntent.Refresh>().flatMapConcat { produceRefreshPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Delete>().flatMapConcat { it.toPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Add>().flatMapConcat { it.toPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.Unfollow>().flatMapConcat { it.toPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.Refresh>()
|
||||
.flatMapConcat { produceRefreshPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Delete>()
|
||||
.flatMapConcat { it.toPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Add>()
|
||||
.flatMapConcat { it.toPartialChangeFlow() },
|
||||
intentFlow.filterIsInstance<HomeUiIntent.Unfollow>()
|
||||
.flatMapConcat { it.toPartialChangeFlow() },
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -54,7 +59,7 @@ class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState
|
|||
HomePartialChange.Refresh.Success(forums, topForums)
|
||||
}
|
||||
.onStart { emit(HomePartialChange.Refresh.Start) }
|
||||
.catch { emit(HomePartialChange.Refresh.Failure(it.getErrorMessage())) }
|
||||
.catch { emit(HomePartialChange.Refresh.Failure(it)) }
|
||||
|
||||
private fun HomeUiIntent.TopForums.Delete.toPartialChangeFlow() =
|
||||
flow {
|
||||
|
|
@ -79,7 +84,8 @@ class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState
|
|||
.catch { emit(HomePartialChange.TopForums.Add.Failure(it.getErrorMessage())) }
|
||||
|
||||
private fun HomeUiIntent.Unfollow.toPartialChangeFlow() =
|
||||
TiebaApi.getInstance().unlikeForumFlow(forumId, forumName, AccountUtil.getLoginInfo()!!.tbs)
|
||||
TiebaApi.getInstance()
|
||||
.unlikeForumFlow(forumId, forumName, AccountUtil.getLoginInfo()!!.tbs)
|
||||
.map<CommonResponse, HomePartialChange.Unfollow> {
|
||||
HomePartialChange.Unfollow.Success(forumId)
|
||||
}
|
||||
|
|
@ -93,9 +99,9 @@ sealed interface HomeUiIntent : UiIntent {
|
|||
data class Unfollow(val forumId: String, val forumName: String) : HomeUiIntent
|
||||
|
||||
sealed interface TopForums : HomeUiIntent {
|
||||
data class Delete(val forumId: String): TopForums
|
||||
data class Delete(val forumId: String) : TopForums
|
||||
|
||||
data class Add(val forum: HomeUiState.Forum): TopForums
|
||||
data class Add(val forum: HomeUiState.Forum) : TopForums
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +115,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
topForums = oldState.topForums.filterNot { it.forumId == forumId },
|
||||
)
|
||||
}
|
||||
|
||||
is Failure -> oldState
|
||||
}
|
||||
|
||||
|
|
@ -120,8 +127,14 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
sealed class Refresh : HomePartialChange {
|
||||
override fun reduce(oldState: HomeUiState): HomeUiState =
|
||||
when (this) {
|
||||
is Success -> oldState.copy(isLoading = false, forums = forums, topForums = topForums)
|
||||
is Failure -> oldState.copy(isLoading = false)
|
||||
is Success -> oldState.copy(
|
||||
isLoading = false,
|
||||
forums = forums,
|
||||
topForums = topForums,
|
||||
error = null
|
||||
)
|
||||
|
||||
is Failure -> oldState.copy(isLoading = false, error = error)
|
||||
Start -> oldState.copy(isLoading = true)
|
||||
}
|
||||
|
||||
|
|
@ -133,7 +146,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
) : Refresh()
|
||||
|
||||
data class Failure(
|
||||
val errorMessage: String
|
||||
val error: Throwable
|
||||
) : Refresh()
|
||||
}
|
||||
|
||||
|
|
@ -160,6 +173,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
|
|||
topForums = oldState.forums.filter { topForumsId.contains(it.forumId) }
|
||||
)
|
||||
}
|
||||
|
||||
is Failure -> oldState
|
||||
}
|
||||
|
||||
|
|
@ -174,6 +188,7 @@ data class HomeUiState(
|
|||
val isLoading: Boolean = true,
|
||||
val forums: List<Forum> = emptyList(),
|
||||
val topForums: List<Forum> = emptyList(),
|
||||
val error: Throwable? = null,
|
||||
) : UiState {
|
||||
data class Forum(
|
||||
val avatar: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,219 @@
|
|||
package com.huanchengfly.tieba.post.ui.widgets.compose
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.ProvideTextStyle
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.LottieConstants
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.NoConnectivityException
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.TiebaApiException
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorCode
|
||||
import com.huanchengfly.tieba.post.api.retrofit.exception.getErrorMessage
|
||||
import com.huanchengfly.tieba.post.arch.BaseComposeActivity.Companion.LocalWindowSizeClass
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.common.windowsizeclass.WindowWidthSizeClass
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.states.StateScreenScope
|
||||
|
||||
@Composable
|
||||
fun TipScreen(
|
||||
title: @Composable (ColumnScope.() -> Unit),
|
||||
modifier: Modifier = Modifier,
|
||||
image: @Composable (ColumnScope.() -> Unit) = {},
|
||||
message: @Composable (ColumnScope.() -> Unit) = {},
|
||||
actions: @Composable (ColumnScope.() -> Unit) = {}
|
||||
) {
|
||||
val widthFraction =
|
||||
if (LocalWindowSizeClass.current.widthSizeClass == WindowWidthSizeClass.Compact) 0.9f else 0.5f
|
||||
Column(modifier = modifier) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(fraction = widthFraction)
|
||||
.padding(16.dp)
|
||||
.verticalScroll(
|
||||
rememberScrollState()
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp, alignment = Alignment.CenterVertically)
|
||||
) {
|
||||
image()
|
||||
ProvideTextStyle(
|
||||
value = MaterialTheme.typography.h6.copy(
|
||||
color = ExtendedTheme.colors.text,
|
||||
fontWeight = FontWeight.Bold,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
) {
|
||||
title()
|
||||
}
|
||||
ProvideTextStyle(
|
||||
value = MaterialTheme.typography.body1.copy(
|
||||
color = ExtendedTheme.colors.textSecondary,
|
||||
textAlign = TextAlign.Center
|
||||
)
|
||||
) {
|
||||
message()
|
||||
}
|
||||
actions()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class ErrorType {
|
||||
NETWORK,
|
||||
SERVER,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun StateScreenScope.ErrorScreen(
|
||||
error: Throwable,
|
||||
modifier: Modifier = Modifier,
|
||||
showReload: Boolean = true,
|
||||
actions: @Composable (ColumnScope.() -> Unit) = {},
|
||||
) {
|
||||
ErrorTipScreen(
|
||||
error = error,
|
||||
modifier = modifier,
|
||||
actions = {
|
||||
if (showReload && canReload) {
|
||||
Button(onClick = { reload() }) {
|
||||
Text(text = stringResource(id = R.string.btn_reload))
|
||||
}
|
||||
}
|
||||
|
||||
actions()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorTipScreen(
|
||||
error: Throwable,
|
||||
modifier: Modifier = Modifier,
|
||||
actions: @Composable (ColumnScope.() -> Unit) = {},
|
||||
) {
|
||||
val errorType = when (error) {
|
||||
is NoConnectivityException -> ErrorType.NETWORK
|
||||
is TiebaApiException -> ErrorType.SERVER
|
||||
else -> ErrorType.UNKNOWN
|
||||
}
|
||||
val errorMessage = error.getErrorMessage()
|
||||
val errorCode = error.getErrorCode()
|
||||
ErrorTipScreen(
|
||||
errorType = errorType,
|
||||
errorMessage = errorMessage,
|
||||
modifier = modifier,
|
||||
errorCode = errorCode,
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ErrorTipScreen(
|
||||
errorType: ErrorType,
|
||||
errorMessage: String,
|
||||
modifier: Modifier = Modifier,
|
||||
errorCode: Int? = null,
|
||||
appendMessage: @Composable (ColumnScope.() -> Unit) = {},
|
||||
actions: @Composable (ColumnScope.() -> Unit) = {},
|
||||
) {
|
||||
TipScreen(
|
||||
title = {
|
||||
when (errorType) {
|
||||
ErrorType.NETWORK -> {
|
||||
Text(text = stringResource(id = R.string.title_no_internet_connectivity))
|
||||
}
|
||||
|
||||
ErrorType.SERVER -> {
|
||||
Text(text = stringResource(id = R.string.title_api_error))
|
||||
}
|
||||
|
||||
ErrorType.UNKNOWN -> {
|
||||
Text(text = stringResource(id = R.string.title_unknown_error))
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = modifier,
|
||||
image = {
|
||||
when (errorType) {
|
||||
ErrorType.NETWORK -> {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_no_internet))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
}
|
||||
|
||||
ErrorType.SERVER -> {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_error))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
}
|
||||
|
||||
ErrorType.UNKNOWN -> {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_bug_hunting))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
message = {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally
|
||||
) {
|
||||
when (errorType) {
|
||||
ErrorType.NETWORK -> {
|
||||
Text(
|
||||
text = stringResource(
|
||||
id = R.string.message_no_internet_connectivity,
|
||||
errorMessage
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
ErrorType.SERVER -> {
|
||||
val errorCodeText = "($errorCode)".takeIf { errorCode != null }.orEmpty()
|
||||
Text(text = "$errorMessage$errorCodeText")
|
||||
}
|
||||
|
||||
ErrorType.UNKNOWN -> {
|
||||
Text(text = stringResource(id = R.string.message_unknown_error))
|
||||
}
|
||||
}
|
||||
appendMessage()
|
||||
}
|
||||
},
|
||||
actions = actions
|
||||
)
|
||||
}
|
||||
|
|
@ -2,29 +2,42 @@ package com.huanchengfly.tieba.post.ui.widgets.compose.states
|
|||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.aspectRatio
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.CircularProgressIndicator
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.airbnb.lottie.compose.LottieAnimation
|
||||
import com.airbnb.lottie.compose.LottieCompositionSpec
|
||||
import com.airbnb.lottie.compose.LottieConstants
|
||||
import com.airbnb.lottie.compose.rememberLottieComposition
|
||||
import com.huanchengfly.tieba.post.R
|
||||
import com.huanchengfly.tieba.post.ui.common.theme.compose.ExtendedTheme
|
||||
import com.huanchengfly.tieba.post.ui.widgets.compose.EmptyPlaceholder
|
||||
|
||||
val DefaultLoadingScreen: @Composable () -> Unit = {
|
||||
CircularProgressIndicator(modifier = Modifier.size(48.dp), color = MaterialTheme.colors.primary)
|
||||
val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = {
|
||||
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_loading_paperplane))
|
||||
LottieAnimation(
|
||||
composition = composition,
|
||||
iterations = LottieConstants.IterateForever,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(2f)
|
||||
)
|
||||
// CircularProgressIndicator(modifier = Modifier.size(48.dp), color = MaterialTheme.colors.primary)
|
||||
}
|
||||
|
||||
val DefaultEmptyScreen: @Composable () -> Unit = {
|
||||
val DefaultEmptyScreen: @Composable StateScreenScope.() -> Unit = {
|
||||
EmptyPlaceholder()
|
||||
}
|
||||
|
||||
val DefaultErrorScreen: @Composable () -> Unit = {
|
||||
val DefaultErrorScreen: @Composable StateScreenScope.() -> Unit = {
|
||||
Text(
|
||||
text = stringResource(id = R.string.error_tip),
|
||||
style = MaterialTheme.typography.body1,
|
||||
|
|
@ -39,12 +52,14 @@ fun StateScreen(
|
|||
isLoading: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
onReload: (() -> Unit)? = null,
|
||||
emptyScreen: @Composable () -> Unit = DefaultEmptyScreen,
|
||||
errorScreen: @Composable () -> Unit = DefaultErrorScreen,
|
||||
loadingScreen: @Composable () -> Unit = DefaultLoadingScreen,
|
||||
content: @Composable () -> Unit,
|
||||
clickToReload: Boolean = false,
|
||||
emptyScreen: @Composable StateScreenScope.() -> Unit = DefaultEmptyScreen,
|
||||
errorScreen: @Composable StateScreenScope.() -> Unit = DefaultErrorScreen,
|
||||
loadingScreen: @Composable StateScreenScope.() -> Unit = DefaultLoadingScreen,
|
||||
content: @Composable StateScreenScope.() -> Unit,
|
||||
) {
|
||||
val clickableModifier = if (onReload != null) Modifier.clickable(
|
||||
val stateScreenScope = remember(key1 = onReload) { StateScreenScope(onReload) }
|
||||
val clickableModifier = if (onReload != null && clickToReload) Modifier.clickable(
|
||||
enabled = isEmpty && !isLoading,
|
||||
onClick = onReload
|
||||
) else Modifier
|
||||
|
|
@ -56,15 +71,26 @@ fun StateScreen(
|
|||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
if (!isEmpty) {
|
||||
content()
|
||||
stateScreenScope.content()
|
||||
} else {
|
||||
if (isLoading) {
|
||||
loadingScreen()
|
||||
stateScreenScope.loadingScreen()
|
||||
} else if (isError) {
|
||||
errorScreen()
|
||||
stateScreenScope.errorScreen()
|
||||
} else {
|
||||
emptyScreen()
|
||||
stateScreenScope.emptyScreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class StateScreenScope(
|
||||
private val onReload: (() -> Unit)? = null
|
||||
) {
|
||||
val canReload: Boolean
|
||||
get() = onReload != null
|
||||
|
||||
fun reload() {
|
||||
onReload?.invoke()
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue