Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Unverified Commit 45504350 authored by cketti's avatar cketti Committed by GitHub
Browse files

Merge pull request #8585 from cketti/update_ContentLoadingView

Change `ContentLoadingView` to pass state
parents 7bfa318c 5d43d94f
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -99,7 +99,7 @@ fun LazyGridScope.stateItems() {
@Composable
private fun StatefulContentLoadingView() {
    val state = remember {
        mutableStateOf(ContentLoadingState.Loading)
        mutableStateOf<ContentLoadingState>(ContentLoadingState.Loading)
    }

    ContentLoadingView(
+31 −21
Original line number Diff line number Diff line
@@ -30,27 +30,6 @@ internal fun ContentLoadingViewLoadingPreview() {
    }
}

@Composable
@Preview(showBackground = true)
internal fun ContentLoadingViewInteractivePreview() {
    PreviewWithThemes {
        val state = remember {
            mutableStateOf(ContentLoadingState.Loading)
        }

        DefaultContentLoadingView(
            state = state.value,
            modifier = Modifier
                .clickable {
                    when (state.value) {
                        ContentLoadingState.Loading -> state.value = ContentLoadingState.Content
                        ContentLoadingState.Content -> state.value = ContentLoadingState.Loading
                    }
                },
        )
    }
}

@Composable
private fun DefaultContentLoadingView(
    state: ContentLoadingState,
@@ -67,3 +46,34 @@ private fun DefaultContentLoadingView(
        modifier = modifier.fillMaxSize(),
    )
}

@Composable
@Preview(showBackground = true)
internal fun ContentLoadingViewInteractivePreview() {
    PreviewWithThemes {
        val state = remember {
            mutableStateOf(State(isLoading = true, content = "Hello world"))
        }

        ContentLoadingView(
            state = state.value,
            loading = {
                TextTitleMedium(text = "Loading...")
            },
            content = { targetState ->
                TextTitleMedium(text = targetState.content)
            },
            modifier = Modifier
                .clickable {
                    val currentValue = state.value
                    state.value = currentValue.copy(isLoading = currentValue.isLoading.not())
                }
                .fillMaxSize(),
        )
    }
}

private data class State(
    override val isLoading: Boolean,
    val content: String,
) : LoadingState
+41 −9
Original line number Diff line number Diff line
@@ -6,13 +6,30 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier

/**
 * A container view that can animate between a loading view and a content view.
 *
 * @param STATE The type of the state being passed to this view.
 *
 * @param state The state relevant for displaying the content inside this view.
 * @param loading When `state.isLoading` is `true`, this composable function is displayed.
 * @param content When `state.isLoading` is `false`, this composable function is displayed with [state] being passed as
 *   the argument.
 *
 * **IMPORTANT**: This is a delicate API whose usage should be carefully reviewed. It is using [AnimatedContent] and
 * inherits its caveats.
 *
 * The [loading] and [content] composable functions should only use the state being passed to them (if any). If you
 * disregard this advice, make sure to read the documentation of [AnimatedContent] to learn when the composable
 * functions are invoked and what that means for the external state a function fetches.
 */
@Composable
fun ContentLoadingView(
    state: ContentLoadingState,
fun <STATE : LoadingState> ContentLoadingView(
    state: STATE,
    loading: @Composable () -> Unit,
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.Center,
    content: @Composable () -> Unit,
    content: @Composable (STATE) -> Unit,
) {
    Box(
        modifier = modifier,
@@ -21,16 +38,31 @@ fun ContentLoadingView(
        AnimatedContent(
            targetState = state,
            label = "ContentLoadingView",
            contentKey = { targetState -> targetState.isLoading },
        ) { targetState ->
            when (targetState) {
                ContentLoadingState.Loading -> loading()
                ContentLoadingState.Content -> content()
            if (targetState.isLoading) {
                loading()
            } else {
                content(targetState)
            }
        }
    }
}

enum class ContentLoadingState {
    Loading,
    Content,
/**
 * Signals [ContentLoadingView] which of its composable function parameters to execute/display.
 */
interface LoadingState {
    val isLoading: Boolean
}

/**
 * Helper that can be use as `state` argument for [ContentLoadingView] when none of the composable function parameters
 * need access to any state.
 */
sealed class ContentLoadingState private constructor(
    override val isLoading: Boolean,
) : LoadingState {
    data object Loading : ContentLoadingState(isLoading = true)
    data object Content : ContentLoadingState(isLoading = false)
}