package com.gy.composestudy.view
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment.Companion.CenterHorizontally
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
private const val TAG = "LoadingWidget"
sealed class LoadingState<out R> {
object Loading : LoadingState<Nothing>()
data class Failure(val error : Throwable) : LoadingState<Nothing>()
data class Success<T>(val data : T) : LoadingState<T>()
val isLoading
get() = this is Loading
val isSuccess
get() = this is Success<*>
}
@Composable
fun DefaultLoading(modifier: Modifier = Modifier) {
CircularProgressIndicator(
modifier
.fillMaxWidth()
.wrapContentWidth())
}
@Composable
fun DefaultFailure(modifier: Modifier = Modifier, retry: () -> Unit) {
Text(text = "错误提示", modifier = modifier
.clickable(onClick = retry)
.fillMaxWidth()
.wrapContentWidth(CenterHorizontally))
}
@Composable
fun <T,K> LoadingContent(
modifier: Modifier = Modifier,
key: K,
updateKey: (K) -> Unit,
loader : suspend ()->T,
loading : @Composable ()->Unit = { DefaultLoading() },
failure : @Composable (error : Throwable, retry : ()->Unit)->Unit = { error, retry->
DefaultFailure(retry = retry)
},
success : @Composable BoxScope.(data : T)->Unit
) {
val state : LoadingState<T> by rememberRetryableLoadingState(loader = loader, key = key)
Box(modifier = modifier){
when(state){
is LoadingState.Loading -> loading()
is LoadingState.Success<T> -> success((state as LoadingState.Success<T>).data)
is LoadingState.Failure -> failure((state as LoadingState.Failure).error){
updateKey(key)
}
}
}
}
@Composable
fun <T> LoadingContent(
modifier: Modifier = Modifier,
initialValue: LoadingState<T>,
retry: () -> Unit,
loader : suspend ()->T,
loading : @Composable ()->Unit = { DefaultLoading() },
failure : @Composable (error : Throwable, retry : ()->Unit)->Unit = { error, _->
DefaultFailure(retry = retry)
},
success : @Composable BoxScope.(data : T)->Unit
) {
val state by rememberRetryableLoadingState(loader = loader, initialValue = initialValue, key = initialValue)
Box(modifier = modifier){
when(state){
is LoadingState.Loading -> loading()
is LoadingState.Success<T> -> success((state as LoadingState.Success<T>).data)
is LoadingState.Failure -> failure((state as LoadingState.Failure).error){
retry()
}
}
}
}
@Composable
fun <T> LoadingContent(
modifier: Modifier = Modifier,
loader : suspend ()->T,
loading : @Composable ()->Unit = { DefaultLoading() },
failure : @Composable (error : Throwable, retry : ()->Unit)->Unit = { error, retry->
DefaultFailure(retry = retry)
},
success : @Composable BoxScope.(data : T)->Unit
) {
var key by remember {
mutableStateOf(false)
}
LoadingContent(modifier, key, { k -> key = !k }, loader, loading, failure, success)
}
fun <T : Any> LazyListScope.loadingList(
value: State<LoadingState<List<T>>>,
retry: () -> Unit,
key : ((T) -> Any)?,
loading : @Composable ()->Unit = { DefaultLoading() },
failure : @Composable (error : Throwable, retry : ()->Unit) -> Unit = { err, re->
DefaultFailure(retry = retry)
},
success : @Composable (data : T)->Unit,
){
when(value.value){
is LoadingState.Loading -> item(key = "loading"){ loading() }
is LoadingState.Success<*> -> items((value.value as LoadingState.Success<List<T>>).data, key){
success(it)
}
is LoadingState.Failure -> item { failure((value.value as LoadingState.Failure).error, retry) }
}
}
@Composable
fun <T> rememberRetryableLoadingState(
initialValue: LoadingState<T> = LoadingState.Loading,
loader: suspend () -> T,
): Pair<State<LoadingState<T>>, () -> Unit> {
var key by remember {
mutableStateOf(false)
}
val update = remember {
{
key = !key
}
}
val loadingState: State<LoadingState<T>> = rememberRetryableLoadingState(key = key, loader = loader, initialValue = initialValue)
return (loadingState to update)
}
@Composable
fun <T, K> rememberRetryableLoadingState(
initialValue: LoadingState<T> = LoadingState.Loading,
key: K,
loader: suspend () -> T,
): State<LoadingState<T>> {
val loadingState: State<LoadingState<T>> = produceState(initialValue = initialValue, key) {
if (!initialValue.isSuccess)
value = try {
LoadingState.Success(loader())
}catch (e: Exception){
e.printStackTrace()
LoadingState.Failure(e)
}
}
return loadingState
}
原文地址:juejin.cn/post/718693…
