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
|
|
@ -95,6 +95,8 @@
|
||||||
<string name="network__socket__type_server">Serveur</string>
|
<string name="network__socket__type_server">Serveur</string>
|
||||||
<string name="network__socket__type_client">Client</string>
|
<string name="network__socket__type_client">Client</string>
|
||||||
<string name="network__socket__type_none">Aucun</string>
|
<string name="network__socket__type_none">Aucun</string>
|
||||||
|
<string name="network__connect__message">Vous êtes connecté au serveur</string>
|
||||||
|
<string name="network__disconnect__message">Vous êtes déconnecté du serveur</string>
|
||||||
|
|
||||||
<string name="roll_history__title">Historique des lancers</string>
|
<string name="roll_history__title">Historique des lancers</string>
|
||||||
<string name="roll_history__item__throw">lance</string>
|
<string name="roll_history__item__throw">lance</string>
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,30 @@
|
||||||
package com.pixelized.desktop.lwa
|
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.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.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.SnackbarHost
|
||||||
import androidx.compose.material.SnackbarHostState
|
import androidx.compose.material.SnackbarHostState
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.material.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.compositionLocalOf
|
import androidx.compose.runtime.compositionLocalOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
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.unit.dp
|
||||||
import androidx.compose.ui.window.ApplicationScope
|
import androidx.compose.ui.window.ApplicationScope
|
||||||
import androidx.compose.ui.window.Window
|
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.WindowsNavHost
|
||||||
import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetCreateWindow
|
import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetCreateWindow
|
||||||
import com.pixelized.desktop.lwa.navigation.window.destination.CharacterSheetWindow
|
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.screen.characterSheet.CharacterSheetMainNavHost
|
||||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
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
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
|
|
||||||
val LocalWindowController = compositionLocalOf<WindowController> {
|
val LocalWindowController = compositionLocalOf<WindowController> {
|
||||||
|
|
@ -33,14 +55,20 @@ val LocalSnackHost = compositionLocalOf<SnackbarHostState> {
|
||||||
error("Local Snack Controller is not yet ready")
|
error("Local Snack Controller is not yet ready")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val LocalErrorSnackHost = compositionLocalOf<SnackbarHostState> {
|
||||||
|
error("Local Snack Controller is not yet ready")
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview
|
@Preview
|
||||||
fun ApplicationScope.App() {
|
fun ApplicationScope.App() {
|
||||||
val snackHostState = remember { SnackbarHostState() }
|
val snackHostState = remember { SnackbarHostState() }
|
||||||
|
val errorSnackHostState = remember { SnackbarHostState() }
|
||||||
val windowController = remember { WindowController() }
|
val windowController = remember { WindowController() }
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalSnackHost provides snackHostState,
|
LocalSnackHost provides snackHostState,
|
||||||
|
LocalErrorSnackHost provides errorSnackHostState,
|
||||||
LocalWindowController provides windowController,
|
LocalWindowController provides windowController,
|
||||||
) {
|
) {
|
||||||
Window(
|
Window(
|
||||||
|
|
@ -57,14 +85,38 @@ fun ApplicationScope.App() {
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
snackbarHost = {
|
snackbarHost = {
|
||||||
SnackbarHost(
|
Column(
|
||||||
hostState = snackHostState,
|
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 = {
|
content = {
|
||||||
MainNavHost()
|
MainNavHost()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
NetworkSnackHandler(
|
||||||
|
snack = snackHostState,
|
||||||
|
)
|
||||||
WindowsHandler(
|
WindowsHandler(
|
||||||
windowController = windowController,
|
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
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.pixelized.desktop.lwa.composable.error.snack
|
||||||
|
|
||||||
|
import androidx.compose.material.SnackbarDuration
|
||||||
|
import androidx.compose.material.SnackbarHostState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import com.pixelized.desktop.lwa.LocalErrorSnackHost
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class ErrorSnackUio(
|
||||||
|
val message: String,
|
||||||
|
val action: String?,
|
||||||
|
val duration: SnackbarDuration,
|
||||||
|
) {
|
||||||
|
constructor(exception: Exception) : this(
|
||||||
|
message = exception.localizedMessage,
|
||||||
|
action = "Ok",
|
||||||
|
duration = SnackbarDuration.Indefinite,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ErrorSnack(
|
||||||
|
snack: SnackbarHostState = LocalErrorSnackHost.current,
|
||||||
|
error: SharedFlow<ErrorSnackUio>,
|
||||||
|
) {
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
error.collect {
|
||||||
|
snack.showSnackbar(
|
||||||
|
message = it.message,
|
||||||
|
actionLabel = it.action,
|
||||||
|
duration = it.duration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -103,6 +103,9 @@ object NetworkRepository {
|
||||||
fun connect(
|
fun connect(
|
||||||
host: String,
|
host: String,
|
||||||
port: Int,
|
port: Int,
|
||||||
|
onConnect: (Type) -> Unit = { },
|
||||||
|
onFailure: (Exception) -> Unit = { },
|
||||||
|
onClose: () -> Unit = { },
|
||||||
) {
|
) {
|
||||||
client = client()
|
client = client()
|
||||||
|
|
||||||
|
|
@ -112,7 +115,7 @@ object NetworkRepository {
|
||||||
client?.connectWebSocket(host = host, port = port) {
|
client?.connectWebSocket(host = host, port = port) {
|
||||||
_type.value = Type.CLIENT
|
_type.value = Type.CLIENT
|
||||||
_status.value = Status.CONNECTED
|
_status.value = Status.CONNECTED
|
||||||
println("Client launched")
|
onConnect(Type.CLIENT)
|
||||||
|
|
||||||
val job = launch {
|
val job = launch {
|
||||||
// send message to the server
|
// send message to the server
|
||||||
|
|
@ -133,12 +136,11 @@ object NetworkRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (exception: Exception) {
|
} catch (exception: Exception) {
|
||||||
// TODO
|
onFailure(exception)
|
||||||
println("WebSocket exception: ${exception.localizedMessage}")
|
|
||||||
} finally {
|
} finally {
|
||||||
println("Client close")
|
|
||||||
_type.value = Type.NONE
|
_type.value = Type.NONE
|
||||||
_status.value = Status.DISCONNECTED
|
_status.value = Status.DISCONNECTED
|
||||||
|
onClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,15 @@
|
||||||
package com.pixelized.desktop.lwa.screen.network
|
package com.pixelized.desktop.lwa.screen.network
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.SizeTransform
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInVertically
|
||||||
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.ScrollState
|
import androidx.compose.foundation.ScrollState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
|
@ -9,12 +17,15 @@ import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.verticalScroll
|
import androidx.compose.foundation.verticalScroll
|
||||||
|
import androidx.compose.material.CircularProgressIndicator
|
||||||
import androidx.compose.material.Icon
|
import androidx.compose.material.Icon
|
||||||
import androidx.compose.material.IconButton
|
import androidx.compose.material.IconButton
|
||||||
import androidx.compose.material.Scaffold
|
import androidx.compose.material.Scaffold
|
||||||
|
import androidx.compose.material.SnackbarDuration
|
||||||
import androidx.compose.material.Surface
|
import androidx.compose.material.Surface
|
||||||
import androidx.compose.material.Text
|
import androidx.compose.material.Text
|
||||||
import androidx.compose.material.TextButton
|
import androidx.compose.material.TextButton
|
||||||
|
|
@ -23,19 +34,25 @@ import androidx.compose.material.TopAppBar
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.pixelized.desktop.lwa.LocalSnackHost
|
||||||
|
import com.pixelized.desktop.lwa.composable.blur.BlurContent
|
||||||
|
import com.pixelized.desktop.lwa.composable.error.snack.ErrorSnack
|
||||||
import com.pixelized.desktop.lwa.navigation.screen.LocalScreenController
|
import com.pixelized.desktop.lwa.navigation.screen.LocalScreenController
|
||||||
|
import kotlinx.io.InternalIoApi
|
||||||
import lwacharactersheet.composeapp.generated.resources.Res
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__host__label
|
import lwacharactersheet.composeapp.generated.resources.network__host__label
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__player_name__label
|
import lwacharactersheet.composeapp.generated.resources.network__player_name__label
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
import lwacharactersheet.composeapp.generated.resources.network__port__label
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
import lwacharactersheet.composeapp.generated.resources.network__socket__connect_action
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.network__socket__disconnect_action
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
|
import lwacharactersheet.composeapp.generated.resources.network__socket__host_action
|
||||||
import lwacharactersheet.composeapp.generated.resources.network__title
|
import lwacharactersheet.composeapp.generated.resources.network__title
|
||||||
import org.jetbrains.compose.resources.stringResource
|
import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
@ -55,20 +72,55 @@ fun NetworkPage(
|
||||||
viewModel: NetworkViewModel = viewModel { NetworkViewModel() },
|
viewModel: NetworkViewModel = viewModel { NetworkViewModel() },
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreenController.current
|
val screen = LocalScreenController.current
|
||||||
|
val snack = LocalSnackHost.current
|
||||||
|
|
||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
NetworkContent(
|
Box(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
player = viewModel.network,
|
contentAlignment = Alignment.Center,
|
||||||
onBack = { screen.popBackStack() },
|
) {
|
||||||
onPlayerChange = viewModel::onPlayerNameChange,
|
BlurContent(
|
||||||
onHostChange = viewModel::onHostChange,
|
modifier = Modifier.fillMaxSize(),
|
||||||
onPortChange = viewModel::onPortChange,
|
controller = viewModel.controller,
|
||||||
onConnect = viewModel::connect,
|
) {
|
||||||
onHost = viewModel::host,
|
NetworkContent(
|
||||||
onDisconnect = viewModel::disconnect,
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
player = viewModel.network,
|
||||||
|
onBack = { screen.popBackStack() },
|
||||||
|
onPlayerChange = viewModel::onPlayerNameChange,
|
||||||
|
onHostChange = viewModel::onHostChange,
|
||||||
|
onPortChange = viewModel::onPortChange,
|
||||||
|
onConnect = viewModel::connect,
|
||||||
|
onHost = viewModel::host,
|
||||||
|
onDisconnect = viewModel::disconnect,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = Modifier.size(size = 64.dp),
|
||||||
|
targetState = viewModel.isLoading.value,
|
||||||
|
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
true -> CircularProgressIndicator()
|
||||||
|
else -> Box(modifier = Modifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(Unit) {
|
||||||
|
viewModel.message.collect {
|
||||||
|
snack.showSnackbar(
|
||||||
|
message = it,
|
||||||
|
duration = SnackbarDuration.Short,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ErrorSnack(
|
||||||
|
error = viewModel.networkError,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,17 @@ import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.pixelized.desktop.lwa.composable.blur.BlurContentController
|
||||||
|
import com.pixelized.desktop.lwa.composable.error.snack.ErrorSnackUio
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
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
|
||||||
|
|
||||||
class NetworkViewModel : ViewModel() {
|
class NetworkViewModel : ViewModel() {
|
||||||
private val repository = NetworkRepository
|
private val repository = NetworkRepository
|
||||||
|
|
@ -17,6 +27,17 @@ class NetworkViewModel : ViewModel() {
|
||||||
private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
|
private val host = mutableStateOf(NetworkRepository.DEFAULT_HOST)
|
||||||
private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
|
private val port = mutableStateOf(NetworkRepository.DEFAULT_PORT)
|
||||||
|
|
||||||
|
private val _networkError = MutableSharedFlow<ErrorSnackUio>()
|
||||||
|
val networkError: SharedFlow<ErrorSnackUio> get() = _networkError
|
||||||
|
|
||||||
|
private val _message = MutableSharedFlow<String>()
|
||||||
|
val message: SharedFlow<String> get() = _message
|
||||||
|
|
||||||
|
private val _isLoading = mutableStateOf(false)
|
||||||
|
val isLoading: State<Boolean> get() = _isLoading
|
||||||
|
|
||||||
|
val controller: BlurContentController = BlurContentController()
|
||||||
|
|
||||||
val network: State<NetworkPageUio>
|
val network: State<NetworkPageUio>
|
||||||
@Composable
|
@Composable
|
||||||
@Stable
|
@Stable
|
||||||
|
|
@ -54,7 +75,24 @@ class NetworkViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun connect() {
|
fun connect() {
|
||||||
repository.connect(host = host.value, port = port.value)
|
controller.show()
|
||||||
|
_isLoading.value = true
|
||||||
|
|
||||||
|
repository.connect(
|
||||||
|
host = host.value,
|
||||||
|
port = port.value,
|
||||||
|
onConnect = {
|
||||||
|
_isLoading.value = false
|
||||||
|
controller.hide()
|
||||||
|
},
|
||||||
|
onFailure = {
|
||||||
|
_isLoading.value = false
|
||||||
|
controller.hide()
|
||||||
|
viewModelScope.launch {
|
||||||
|
_networkError.emit(ErrorSnackUio(it))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disconnect() {
|
fun disconnect() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue