Add error management for the network client.
This commit is contained in:
		
							parent
							
								
									e880d37275
								
							
						
					
					
						commit
						f459877d55
					
				
					 6 changed files with 302 additions and 18 deletions
				
			
		| 
						 | 
				
			
			@ -1,15 +1,30 @@
 | 
			
		|||
package com.pixelized.desktop.lwa
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.layout.Arrangement
 | 
			
		||||
import androidx.compose.foundation.layout.Column
 | 
			
		||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
			
		||||
import androidx.compose.foundation.layout.padding
 | 
			
		||||
import androidx.compose.material.ButtonDefaults
 | 
			
		||||
import androidx.compose.material.MaterialTheme
 | 
			
		||||
import androidx.compose.material.Scaffold
 | 
			
		||||
import androidx.compose.material.ScaffoldState
 | 
			
		||||
import androidx.compose.material.SnackbarData
 | 
			
		||||
import androidx.compose.material.SnackbarDefaults
 | 
			
		||||
import androidx.compose.material.SnackbarDuration
 | 
			
		||||
import androidx.compose.material.SnackbarHost
 | 
			
		||||
import androidx.compose.material.SnackbarHostState
 | 
			
		||||
import androidx.compose.material.Surface
 | 
			
		||||
import androidx.compose.material.Text
 | 
			
		||||
import androidx.compose.material.TextButton
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.CompositionLocalProvider
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.compositionLocalOf
 | 
			
		||||
import androidx.compose.runtime.remember
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.graphics.Color
 | 
			
		||||
import androidx.compose.ui.graphics.Shape
 | 
			
		||||
import androidx.compose.ui.unit.Dp
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import androidx.compose.ui.window.ApplicationScope
 | 
			
		||||
import androidx.compose.ui.window.Window
 | 
			
		||||
| 
						 | 
				
			
			@ -21,8 +36,15 @@ import com.pixelized.desktop.lwa.navigation.window.WindowController
 | 
			
		|||
import com.pixelized.desktop.lwa.navigation.window.WindowsNavHost
 | 
			
		||||
import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetCreateWindow
 | 
			
		||||
import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetWindow
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
			
		||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
 | 
			
		||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
 | 
			
		||||
import com.pixelized.desktop.lwa.theme.LwaTheme
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.Res
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.network__connect__message
 | 
			
		||||
import lwacharactersheet.composeapp.generated.resources.network__disconnect__message
 | 
			
		||||
import org.jetbrains.compose.resources.getString
 | 
			
		||||
import org.jetbrains.compose.ui.tooling.preview.Preview
 | 
			
		||||
 | 
			
		||||
val LocalWindowController = compositionLocalOf<WindowController> {
 | 
			
		||||
| 
						 | 
				
			
			@ -33,14 +55,20 @@ val LocalSnackHost = compositionLocalOf<SnackbarHostState> {
 | 
			
		|||
    error("Local Snack Controller is not yet ready")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val LocalErrorSnackHost = compositionLocalOf<SnackbarHostState> {
 | 
			
		||||
    error("Local Snack Controller is not yet ready")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
@Preview
 | 
			
		||||
fun ApplicationScope.App() {
 | 
			
		||||
    val snackHostState = remember { SnackbarHostState() }
 | 
			
		||||
    val errorSnackHostState = remember { SnackbarHostState() }
 | 
			
		||||
    val windowController = remember { WindowController() }
 | 
			
		||||
 | 
			
		||||
    CompositionLocalProvider(
 | 
			
		||||
        LocalSnackHost provides snackHostState,
 | 
			
		||||
        LocalErrorSnackHost provides errorSnackHostState,
 | 
			
		||||
        LocalWindowController provides windowController,
 | 
			
		||||
    ) {
 | 
			
		||||
        Window(
 | 
			
		||||
| 
						 | 
				
			
			@ -57,14 +85,38 @@ fun ApplicationScope.App() {
 | 
			
		|||
                ) {
 | 
			
		||||
                    Scaffold(
 | 
			
		||||
                        snackbarHost = {
 | 
			
		||||
                            SnackbarHost(
 | 
			
		||||
                                hostState = snackHostState,
 | 
			
		||||
                            )
 | 
			
		||||
                            Column(
 | 
			
		||||
                                modifier = Modifier.padding(all = 8.dp),
 | 
			
		||||
                                verticalArrangement = Arrangement.spacedBy(space = 4.dp)
 | 
			
		||||
                            ) {
 | 
			
		||||
                                SnackbarHost(
 | 
			
		||||
                                    hostState = snackHostState,
 | 
			
		||||
                                    snackbar = {
 | 
			
		||||
                                        Snackbar(
 | 
			
		||||
                                            snackbarData = it,
 | 
			
		||||
                                        )
 | 
			
		||||
                                    }
 | 
			
		||||
                                )
 | 
			
		||||
                                SnackbarHost(
 | 
			
		||||
                                    hostState = errorSnackHostState,
 | 
			
		||||
                                    snackbar = {
 | 
			
		||||
                                        Snackbar(
 | 
			
		||||
                                            snackbarData = it,
 | 
			
		||||
                                            backgroundColor = MaterialTheme.colors.error,
 | 
			
		||||
                                            contentColor = MaterialTheme.colors.onError,
 | 
			
		||||
                                            actionColor = MaterialTheme.colors.onError,
 | 
			
		||||
                                        )
 | 
			
		||||
                                    }
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        },
 | 
			
		||||
                        content = {
 | 
			
		||||
                            MainNavHost()
 | 
			
		||||
                        }
 | 
			
		||||
                    )
 | 
			
		||||
                    NetworkSnackHandler(
 | 
			
		||||
                        snack = snackHostState,
 | 
			
		||||
                    )
 | 
			
		||||
                    WindowsHandler(
 | 
			
		||||
                        windowController = windowController,
 | 
			
		||||
                    )
 | 
			
		||||
| 
						 | 
				
			
			@ -97,4 +149,104 @@ private fun WindowsHandler(
 | 
			
		|||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun NetworkSnackHandler(
 | 
			
		||||
    snack: SnackbarHostState,
 | 
			
		||||
) {
 | 
			
		||||
    LaunchedEffect(Unit) {
 | 
			
		||||
        launch {
 | 
			
		||||
            var ignoreInitial = true
 | 
			
		||||
            NetworkRepository.status.collect {
 | 
			
		||||
                if (ignoreInitial) {
 | 
			
		||||
                    ignoreInitial = false
 | 
			
		||||
                } else {
 | 
			
		||||
                    val message = when (it) {
 | 
			
		||||
                        Status.CONNECTED -> getString(Res.string.network__connect__message)
 | 
			
		||||
                        Status.DISCONNECTED -> getString(Res.string.network__disconnect__message)
 | 
			
		||||
                    }
 | 
			
		||||
                    snack.showSnackbar(
 | 
			
		||||
                        message = message,
 | 
			
		||||
                        duration = SnackbarDuration.Short,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * <a href="https://material.io/components/snackbars" class="external" target="_blank">Material Design snackbar</a>.
 | 
			
		||||
 *
 | 
			
		||||
 * Snackbars provide brief messages about app processes at the bottom of the screen.
 | 
			
		||||
 *
 | 
			
		||||
 * Snackbars inform users of a process that an app has performed or will perform. They appear
 | 
			
		||||
 * temporarily, towards the bottom of the screen. They shouldn’t interrupt the user experience,
 | 
			
		||||
 * and they don’t require user input to disappear.
 | 
			
		||||
 *
 | 
			
		||||
 * A Snackbar can contain a single action. Because they disappear automatically, the action
 | 
			
		||||
 * shouldn't be "Dismiss" or "Cancel".
 | 
			
		||||
 *
 | 
			
		||||
 * 
 | 
			
		||||
 *
 | 
			
		||||
 * This version of snackbar is designed to work with [SnackbarData] provided by the
 | 
			
		||||
 * [SnackbarHost], which is usually used inside of the [Scaffold].
 | 
			
		||||
 *
 | 
			
		||||
 * This components provides only the visuals of the [Snackbar]. If you need to show a [Snackbar]
 | 
			
		||||
 * with defaults on the screen, use [ScaffoldState.snackbarHostState] and
 | 
			
		||||
 * [SnackbarHostState.showSnackbar]:
 | 
			
		||||
 *
 | 
			
		||||
 * @sample androidx.compose.material.samples.ScaffoldWithSimpleSnackbar
 | 
			
		||||
 *
 | 
			
		||||
 * If you want to customize appearance of the [Snackbar], you can pass your own version as a child
 | 
			
		||||
 * of the [SnackbarHost] to the [Scaffold]:
 | 
			
		||||
 * @sample androidx.compose.material.samples.ScaffoldWithCustomSnackbar
 | 
			
		||||
 *
 | 
			
		||||
 * @param snackbarData data about the current snackbar showing via [SnackbarHostState]
 | 
			
		||||
 * @param modifier modifiers for the Snackbar layout
 | 
			
		||||
 * @param actionOnNewLine whether or not action should be put on the separate line. Recommended
 | 
			
		||||
 * for action with long action text
 | 
			
		||||
 * @param shape Defines the Snackbar's shape as well as its shadow
 | 
			
		||||
 * @param backgroundColor background color of the Snackbar
 | 
			
		||||
 * @param contentColor color of the content to use inside the snackbar. Defaults to
 | 
			
		||||
 * either the matching content color for [backgroundColor], or, if it is not a color from
 | 
			
		||||
 * the theme, this will keep the same value set above this Surface.
 | 
			
		||||
 * @param actionColor color of the action
 | 
			
		||||
 * @param elevation The z-coordinate at which to place the SnackBar. This controls the size
 | 
			
		||||
 * of the shadow below the SnackBar
 | 
			
		||||
 */
 | 
			
		||||
@Composable
 | 
			
		||||
fun Snackbar(
 | 
			
		||||
    snackbarData: SnackbarData,
 | 
			
		||||
    modifier: Modifier = Modifier,
 | 
			
		||||
    actionOnNewLine: Boolean = false,
 | 
			
		||||
    shape: Shape = MaterialTheme.shapes.small,
 | 
			
		||||
    backgroundColor: Color = SnackbarDefaults.backgroundColor,
 | 
			
		||||
    contentColor: Color = MaterialTheme.colors.surface,
 | 
			
		||||
    actionColor: Color = SnackbarDefaults.primaryActionColor,
 | 
			
		||||
    elevation: Dp = 6.dp
 | 
			
		||||
) {
 | 
			
		||||
    val actionLabel = snackbarData.actionLabel
 | 
			
		||||
    val actionComposable: (@Composable () -> Unit)? = if (actionLabel != null) {
 | 
			
		||||
        @Composable {
 | 
			
		||||
            TextButton(
 | 
			
		||||
                colors = ButtonDefaults.textButtonColors(contentColor = actionColor),
 | 
			
		||||
                onClick = { snackbarData.performAction() },
 | 
			
		||||
                content = { Text(actionLabel) }
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    } else {
 | 
			
		||||
        null
 | 
			
		||||
    }
 | 
			
		||||
    androidx.compose.material.Snackbar(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        content = { Text(snackbarData.message) },
 | 
			
		||||
        action = actionComposable,
 | 
			
		||||
        actionOnNewLine = actionOnNewLine,
 | 
			
		||||
        shape = shape,
 | 
			
		||||
        backgroundColor = backgroundColor,
 | 
			
		||||
        contentColor = contentColor,
 | 
			
		||||
        elevation = elevation
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue