pref: 加载错误显示

This commit is contained in:
HuanCheng65 2023-03-12 00:04:03 +08:00
parent addbc80bc0
commit cf6c42ae1c
No known key found for this signature in database
GPG Key ID: E9031EF91A805148
4 changed files with 357 additions and 89 deletions

View File

@ -6,7 +6,6 @@ import androidx.compose.animation.animateContentSize
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues 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.Avatar
import com.huanchengfly.tieba.post.ui.widgets.compose.Button 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.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.LongClickMenu
import com.huanchengfly.tieba.post.ui.widgets.compose.TextButton 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.Toolbar
import com.huanchengfly.tieba.post.ui.widgets.compose.accountNavIconIfCompact import com.huanchengfly.tieba.post.ui.widgets.compose.accountNavIconIfCompact
import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState import com.huanchengfly.tieba.post.ui.widgets.compose.rememberDialogState
@ -153,8 +154,8 @@ fun SearchBox(
@Composable @Composable
private fun Header( private fun Header(
text: String, text: String,
invert: Boolean = false, modifier: Modifier = Modifier,
modifier: Modifier = Modifier invert: Boolean = false
) { ) {
Chip( Chip(
text = text, text = text,
@ -391,6 +392,11 @@ fun HomePage(
prop1 = HomeUiState::topForums, prop1 = HomeUiState::topForums,
initial = emptyList() 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) } var listSingle by remember { mutableStateOf(context.appPreferences.listSingle) }
val gridCells by remember { derivedStateOf { getGridCells(context, listSingle) } } val gridCells by remember { derivedStateOf { getGridCells(context, listSingle) } }
@ -421,9 +427,12 @@ fun HomePage(
) { contentPaddings -> ) { contentPaddings ->
StateScreen( StateScreen(
isEmpty = forums.isEmpty(), isEmpty = forums.isEmpty(),
isError = false, isError = isError,
isLoading = isLoading, isLoading = isLoading,
modifier = Modifier.padding(contentPaddings), modifier = Modifier.padding(contentPaddings),
onReload = {
viewModel.send(HomeUiIntent.Refresh)
},
emptyScreen = { emptyScreen = {
EmptyScreen( EmptyScreen(
loggedIn = account != null, loggedIn = account != null,
@ -433,6 +442,9 @@ fun HomePage(
}, },
loadingScreen = { loadingScreen = {
HomePageSkeletonScreen(listSingle = listSingle, gridCells = gridCells) HomePageSkeletonScreen(listSingle = listSingle, gridCells = gridCells)
},
errorScreen = {
error?.let { ErrorScreen(error = it) }
} }
) { ) {
val pullRefreshState = rememberPullRefreshState( val pullRefreshState = rememberPullRefreshState(
@ -516,11 +528,11 @@ private fun HomePageSkeletonScreen(
Column { Column {
Header( Header(
text = stringResource(id = R.string.title_top_forum), text = stringResource(id = R.string.title_top_forum),
invert = true,
modifier = Modifier.placeholder( modifier = Modifier.placeholder(
visible = true, visible = true,
color = ExtendedTheme.colors.chip color = ExtendedTheme.colors.chip
) ),
invert = true
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
@ -541,11 +553,11 @@ private fun HomePageSkeletonScreen(
Column { Column {
Header( Header(
text = stringResource(id = R.string.forum_list_title), text = stringResource(id = R.string.forum_list_title),
invert = true,
modifier = Modifier.placeholder( modifier = Modifier.placeholder(
visible = true, visible = true,
color = ExtendedTheme.colors.chip color = ExtendedTheme.colors.chip
) ),
invert = true
) )
Spacer(modifier = Modifier.height(8.dp)) Spacer(modifier = Modifier.height(8.dp))
} }
@ -563,13 +575,15 @@ fun EmptyScreen(
onOpenExplore: () -> Unit onOpenExplore: () -> Unit
) { ) {
val context = LocalContext.current val context = LocalContext.current
Column( TipScreen(
modifier = Modifier title = {
.fillMaxSize() if (!loggedIn) {
.padding(16.dp), Text(text = stringResource(id = R.string.title_empty_login))
horizontalAlignment = Alignment.CenterHorizontally, } else {
verticalArrangement = Arrangement.spacedBy(8.dp, alignment = CenterVertically) Text(text = stringResource(id = R.string.title_empty))
) { }
},
image = {
val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_astronaut)) val composition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.lottie_astronaut))
LottieAnimation( LottieAnimation(
composition = composition, composition = composition,
@ -578,19 +592,19 @@ fun EmptyScreen(
.fillMaxWidth() .fillMaxWidth()
.aspectRatio(2f) .aspectRatio(2f)
) )
},
message = {
if (!loggedIn) { if (!loggedIn) {
Text(
text = stringResource(id = R.string.title_empty_login),
style = MaterialTheme.typography.h6,
color = ExtendedTheme.colors.text,
textAlign = TextAlign.Center,
)
Text( Text(
text = stringResource(id = R.string.home_empty_login), text = stringResource(id = R.string.home_empty_login),
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
color = ExtendedTheme.colors.textSecondary, color = ExtendedTheme.colors.textSecondary,
textAlign = TextAlign.Center textAlign = TextAlign.Center
) )
}
},
actions = {
if (!loggedIn) {
Button( Button(
onClick = { onClick = {
context.goToActivity<LoginActivity>() context.goToActivity<LoginActivity>()
@ -600,13 +614,6 @@ fun EmptyScreen(
) { ) {
Text(text = stringResource(id = R.string.button_login)) 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,
)
} }
if (canOpenExplore) { if (canOpenExplore) {
TextButton( TextButton(
@ -617,5 +624,6 @@ fun EmptyScreen(
Text(text = stringResource(id = R.string.button_go_to_explore)) Text(text = stringResource(id = R.string.button_go_to_explore))
} }
} }
} },
)
} }

View File

@ -15,24 +15,29 @@ import org.litepal.LitePal
class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState, HomeUiEvent>() { class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState, HomeUiEvent>() {
override fun createInitialState(): HomeUiState = HomeUiState() 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? = override fun dispatchEvent(partialChange: HomePartialChange): UiEvent? =
when (partialChange) { when (partialChange) {
is HomePartialChange.Refresh.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
is HomePartialChange.TopForums.Delete.Failure -> CommonUiEvent.Toast(partialChange.errorMessage) is HomePartialChange.TopForums.Delete.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
is HomePartialChange.TopForums.Add.Failure -> CommonUiEvent.Toast(partialChange.errorMessage) is HomePartialChange.TopForums.Add.Failure -> CommonUiEvent.Toast(partialChange.errorMessage)
else -> null else -> null
} }
object HomePartialChangeProducer : PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> { object HomePartialChangeProducer :
PartialChangeProducer<HomeUiIntent, HomePartialChange, HomeUiState> {
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
override fun toPartialChangeFlow(intentFlow: Flow<HomeUiIntent>): Flow<HomePartialChange> { override fun toPartialChangeFlow(intentFlow: Flow<HomeUiIntent>): Flow<HomePartialChange> {
return merge( return merge(
intentFlow.filterIsInstance<HomeUiIntent.Refresh>().flatMapConcat { produceRefreshPartialChangeFlow() }, intentFlow.filterIsInstance<HomeUiIntent.Refresh>()
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Delete>().flatMapConcat { it.toPartialChangeFlow() }, .flatMapConcat { produceRefreshPartialChangeFlow() },
intentFlow.filterIsInstance<HomeUiIntent.TopForums.Add>().flatMapConcat { it.toPartialChangeFlow() }, intentFlow.filterIsInstance<HomeUiIntent.TopForums.Delete>()
intentFlow.filterIsInstance<HomeUiIntent.Unfollow>().flatMapConcat { it.toPartialChangeFlow() }, .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) HomePartialChange.Refresh.Success(forums, topForums)
} }
.onStart { emit(HomePartialChange.Refresh.Start) } .onStart { emit(HomePartialChange.Refresh.Start) }
.catch { emit(HomePartialChange.Refresh.Failure(it.getErrorMessage())) } .catch { emit(HomePartialChange.Refresh.Failure(it)) }
private fun HomeUiIntent.TopForums.Delete.toPartialChangeFlow() = private fun HomeUiIntent.TopForums.Delete.toPartialChangeFlow() =
flow { flow {
@ -79,7 +84,8 @@ class HomeViewModel : BaseViewModel<HomeUiIntent, HomePartialChange, HomeUiState
.catch { emit(HomePartialChange.TopForums.Add.Failure(it.getErrorMessage())) } .catch { emit(HomePartialChange.TopForums.Add.Failure(it.getErrorMessage())) }
private fun HomeUiIntent.Unfollow.toPartialChangeFlow() = 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> { .map<CommonResponse, HomePartialChange.Unfollow> {
HomePartialChange.Unfollow.Success(forumId) HomePartialChange.Unfollow.Success(forumId)
} }
@ -109,6 +115,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
topForums = oldState.topForums.filterNot { it.forumId == forumId }, topForums = oldState.topForums.filterNot { it.forumId == forumId },
) )
} }
is Failure -> oldState is Failure -> oldState
} }
@ -120,8 +127,14 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
sealed class Refresh : HomePartialChange { sealed class Refresh : HomePartialChange {
override fun reduce(oldState: HomeUiState): HomeUiState = override fun reduce(oldState: HomeUiState): HomeUiState =
when (this) { when (this) {
is Success -> oldState.copy(isLoading = false, forums = forums, topForums = topForums) is Success -> oldState.copy(
is Failure -> oldState.copy(isLoading = false) isLoading = false,
forums = forums,
topForums = topForums,
error = null
)
is Failure -> oldState.copy(isLoading = false, error = error)
Start -> oldState.copy(isLoading = true) Start -> oldState.copy(isLoading = true)
} }
@ -133,7 +146,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
) : Refresh() ) : Refresh()
data class Failure( data class Failure(
val errorMessage: String val error: Throwable
) : Refresh() ) : Refresh()
} }
@ -160,6 +173,7 @@ sealed interface HomePartialChange : PartialChange<HomeUiState> {
topForums = oldState.forums.filter { topForumsId.contains(it.forumId) } topForums = oldState.forums.filter { topForumsId.contains(it.forumId) }
) )
} }
is Failure -> oldState is Failure -> oldState
} }
@ -174,6 +188,7 @@ data class HomeUiState(
val isLoading: Boolean = true, val isLoading: Boolean = true,
val forums: List<Forum> = emptyList(), val forums: List<Forum> = emptyList(),
val topForums: List<Forum> = emptyList(), val topForums: List<Forum> = emptyList(),
val error: Throwable? = null,
) : UiState { ) : UiState {
data class Forum( data class Forum(
val avatar: String, val avatar: String,

View File

@ -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
)
}

View File

@ -2,29 +2,42 @@ package com.huanchengfly.tieba.post.ui.widgets.compose.states
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text import androidx.compose.material.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource 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.R
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.widgets.compose.EmptyPlaceholder import com.huanchengfly.tieba.post.ui.widgets.compose.EmptyPlaceholder
val DefaultLoadingScreen: @Composable () -> Unit = { val DefaultLoadingScreen: @Composable StateScreenScope.() -> Unit = {
CircularProgressIndicator(modifier = Modifier.size(48.dp), color = MaterialTheme.colors.primary) 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() EmptyPlaceholder()
} }
val DefaultErrorScreen: @Composable () -> Unit = { val DefaultErrorScreen: @Composable StateScreenScope.() -> Unit = {
Text( Text(
text = stringResource(id = R.string.error_tip), text = stringResource(id = R.string.error_tip),
style = MaterialTheme.typography.body1, style = MaterialTheme.typography.body1,
@ -39,12 +52,14 @@ fun StateScreen(
isLoading: Boolean, isLoading: Boolean,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
onReload: (() -> Unit)? = null, onReload: (() -> Unit)? = null,
emptyScreen: @Composable () -> Unit = DefaultEmptyScreen, clickToReload: Boolean = false,
errorScreen: @Composable () -> Unit = DefaultErrorScreen, emptyScreen: @Composable StateScreenScope.() -> Unit = DefaultEmptyScreen,
loadingScreen: @Composable () -> Unit = DefaultLoadingScreen, errorScreen: @Composable StateScreenScope.() -> Unit = DefaultErrorScreen,
content: @Composable () -> Unit, 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, enabled = isEmpty && !isLoading,
onClick = onReload onClick = onReload
) else Modifier ) else Modifier
@ -56,15 +71,26 @@ fun StateScreen(
contentAlignment = Alignment.Center contentAlignment = Alignment.Center
) { ) {
if (!isEmpty) { if (!isEmpty) {
content() stateScreenScope.content()
} else { } else {
if (isLoading) { if (isLoading) {
loadingScreen() stateScreenScope.loadingScreen()
} else if (isError) { } else if (isError) {
errorScreen() stateScreenScope.errorScreen()
} else { } else {
emptyScreen() stateScreenScope.emptyScreen()
} }
} }
} }
} }
class StateScreenScope(
private val onReload: (() -> Unit)? = null
) {
val canReload: Boolean
get() = onReload != null
fun reload() {
onReload?.invoke()
}
}