From f92922c22845b5b7f80b82ffead9f69d09ccf411 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Sat, 9 Nov 2024 22:57:26 +0100 Subject: [PATCH] Add roll sharing feature over the WebSocket. --- .../composeResources/values/strings.xml | 3 + .../kotlin/com/pixelized/desktop/lwa/App.kt | 132 ++++++++++++++- .../desktop/lwa/navigation/MainNavHost.kt | 3 + .../navigation/destination/MainDestination.kt | 1 + .../destination/RollHistoryDestination.kt | 26 +++ .../repository/network/NetworkRepository.kt | 62 +++++--- .../repository/network/protocol/Message.kt | 5 +- .../network/protocol/MessageContent.kt | 6 + .../network/protocol/RollMessage.kt | 9 ++ .../repository/roll/RollHistoryRepository.kt | 41 +++++ .../detail/CharacterSheetPage.kt | 13 +- .../edit/CharacterSheetEditPage.kt | 2 +- .../desktop/lwa/screen/main/MainPage.kt | 150 +++++------------- .../lwa/screen/main/MainPageViewModel.kt | 23 --- .../desktop/lwa/screen/roll/RollViewModel.kt | 12 +- .../lwa/screen/rollhistory/RollHistoryPage.kt | 135 ++++++++++++++++ .../rollhistory/RollHistoryViewModel.kt | 36 +++++ 17 files changed, 476 insertions(+), 183 deletions(-) create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/RollHistoryDestination.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 84f3684..1407017 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -2,6 +2,7 @@ Créer une feuille de personnage Configuration réseau + Consulter l'historique des lancés Réussite critique Réussite spéciale @@ -81,4 +82,6 @@ Serveur Client Aucun + + Historique des lancés \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt index 9636519..8d33b3c 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -1,11 +1,16 @@ package com.pixelized.desktop.lwa import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.Scaffold +import androidx.compose.material.SnackbarHost +import androidx.compose.material.SnackbarHostState import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable +import androidx.compose.runtime.State import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp @@ -13,9 +18,19 @@ import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Window import androidx.compose.ui.window.rememberWindowState import com.pixelized.desktop.lwa.navigation.MainNavHost +import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination +import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination +import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost +import com.pixelized.desktop.lwa.screen.main.CharacterUio import com.pixelized.desktop.lwa.theme.LwaTheme +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title +import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.ui.tooling.preview.Preview +val LocalSnackHost = compositionLocalOf { + error("Local Snack Controller is not yet ready") +} val LocalWindowController = compositionLocalOf { error("Local Window Controller is not yet ready") } @@ -24,23 +39,47 @@ val LocalWindowController = compositionLocalOf { data class WindowController( private val onCloseRequest: () -> Unit ) { - fun close() = onCloseRequest() + val sheet: State> get() = _sheet + + val create: State> get() = _create + + fun showCreateCharacterSheet() { + _create.value = _create.value.toMutableSet().apply { add(size) } + } + + fun hideCreateCharacterSheet(id: Int) { + _create.value = _create.value.toMutableSet().apply { remove(id) } + } + + fun showCharacterSheet(sheet: CharacterUio) { + _sheet.value = _sheet.value.toMutableSet().apply { add(sheet) } + } + + fun hideCharacterSheet(sheet: CharacterUio) { + _sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) } + } + + fun closeWindows() = onCloseRequest() + + companion object { + private val _sheet = mutableStateOf>(emptySet()) + private val _create = mutableStateOf>(emptySet()) + } } @Composable @Preview fun ApplicationScope.App() { - val controller = remember { - WindowController( - onCloseRequest = ::exitApplication - ) - } + val controller = remember { WindowController(onCloseRequest = ::exitApplication) } + val snackHostState = remember { SnackbarHostState() } + CompositionLocalProvider( LocalWindowController provides controller, + LocalSnackHost provides snackHostState, ) { Window( onCloseRequest = { - controller.close() + controller.closeWindows() }, state = rememberWindowState( width = 320.dp + 64.dp, @@ -52,9 +91,86 @@ fun ApplicationScope.App() { Surface( modifier = Modifier.fillMaxSize() ) { - MainNavHost() + Scaffold( + snackbarHost = { + SnackbarHost( + hostState = snackHostState, + ) + }, + content = { + MainNavHost() + } + ) + HandleCharacterSheet( + sheets = controller.sheet, + onCloseRequest = { controller.hideCharacterSheet(sheet = it) } + ) + HandleCharacterSheetCreation( + sheets = controller.create, + onCloseRequest = { controller.hideCreateCharacterSheet(id = it) }, + ) } } } } +} + +@Composable +fun HandleCharacterSheet( + sheets: State>, + onCloseRequest: (id: CharacterUio) -> Unit, +) { + sheets.value.forEach { sheet -> + val controller = remember { + WindowController( + onCloseRequest = { onCloseRequest(sheet) } + ) + } + CompositionLocalProvider( + LocalWindowController provides controller, + ) { + Window( + onCloseRequest = { onCloseRequest(sheet) }, + state = rememberWindowState( + width = 400.dp + 64.dp, + height = 900.dp, + ), + title = sheet.name, + ) { + CharacterSheetMainNavHost( + startDestination = CharacterSheetDestination.navigationRoute(id = sheet.id) + ) + } + } + } +} + +@Composable +fun HandleCharacterSheetCreation( + sheets: State>, + onCloseRequest: (id: Int) -> Unit, +) { + sheets.value.forEach { sheet -> + val controller = remember { + WindowController( + onCloseRequest = { onCloseRequest(sheet) } + ) + } + CompositionLocalProvider( + LocalWindowController provides controller, + ) { + Window( + onCloseRequest = { controller.closeWindows() }, + state = rememberWindowState( + width = 400.dp + 64.dp, + height = 900.dp, + ), + title = stringResource(Res.string.character_sheet_edit__title), + ) { + CharacterSheetMainNavHost( + startDestination = CharacterSheetEditDestination.navigationRoute(id = null) + ) + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt index 22d6c92..6a9e995 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/MainNavHost.kt @@ -9,6 +9,8 @@ import androidx.navigation.compose.rememberNavController import com.pixelized.desktop.lwa.navigation.destination.MainDestination import com.pixelized.desktop.lwa.navigation.destination.composableMainPage import com.pixelized.desktop.lwa.navigation.destination.composableNetworkPage +import com.pixelized.desktop.lwa.navigation.destination.composableRollHistory +import com.pixelized.desktop.lwa.screen.main.MainPageViewModel val LocalScreenController = compositionLocalOf { error("MainNavHost controller is not yet ready") @@ -28,6 +30,7 @@ fun MainNavHost( ) { composableMainPage() composableNetworkPage() + composableRollHistory() } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt index 49c9c51..f65d578 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/MainDestination.kt @@ -4,6 +4,7 @@ import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable import com.pixelized.desktop.lwa.screen.main.MainPage +import com.pixelized.desktop.lwa.screen.main.MainPageViewModel object MainDestination { private const val ROUTE = "main" diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/RollHistoryDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/RollHistoryDestination.kt new file mode 100644 index 0000000..c3373ac --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/navigation/destination/RollHistoryDestination.kt @@ -0,0 +1,26 @@ +package com.pixelized.desktop.lwa.navigation.destination + +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.composable +import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryPage + +object RollHistoryDestination { + private const val ROUTE = "roll_history" + + fun baseRoute() = ROUTE + fun navigationRoute() = ROUTE +} + +fun NavGraphBuilder.composableRollHistory() { + composable( + route = RollHistoryDestination.baseRoute() + ) { + RollHistoryPage() + } +} + +fun NavHostController.navigateToRollHistory() { + val route = RollHistoryDestination.navigationRoute() + navigate(route = route) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt index d39b0e1..021d093 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/NetworkRepository.kt @@ -4,6 +4,7 @@ import com.pixelized.desktop.lwa.repository.network.helper.client import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket import com.pixelized.desktop.lwa.repository.network.helper.server import com.pixelized.desktop.lwa.repository.network.protocol.Message +import com.pixelized.desktop.lwa.repository.network.protocol.MessageContent import io.ktor.client.HttpClient import io.ktor.server.engine.EmbeddedServer import io.ktor.server.netty.NettyApplicationEngine @@ -15,8 +16,8 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.serialization.json.Json @@ -31,8 +32,9 @@ object NetworkRepository { private var server: Server? = null private var client: Client? = null - private val messageResponseFlow = MutableSharedFlow() - private val sharedFlow = messageResponseFlow.asSharedFlow() + private val outgoingMessageBuffer = MutableSharedFlow() + private val incomingMessageBuffer = MutableSharedFlow() + val data: SharedFlow get() = incomingMessageBuffer private val _player = MutableStateFlow("") val player: StateFlow get() = _player @@ -57,17 +59,19 @@ object NetworkRepository { println("Server launched") val job = launch { - sharedFlow.collect { message -> - println("Broadcast: $message") - send(Frame.Text(message)) + // send local message to the clients + outgoingMessageBuffer.collect { message -> + send(Json.encodeToFrame(message = message)) } } - runCatching { + // watching for clients incoming message incoming.consumeEach { frame -> if (frame is Frame.Text) { - val receivedText = frame.readText() - messageResponseFlow.emit(receivedText) + val message = Json.decodeFromFrame(frame = frame) + incomingMessageBuffer.emit(message) + // broadcast to clients the message + outgoingMessageBuffer.emit(message) } } }.onFailure { exception -> @@ -108,17 +112,17 @@ object NetworkRepository { println("Client launched") val job = launch { - sharedFlow.collect { message -> - println("Send: $message") - send(Frame.Text(message)) + // send message to the server + outgoingMessageBuffer.collect { message -> + send(Json.encodeToFrame(message = message)) } } - runBlocking { + // watching for server incoming message incoming.consumeEach { frame -> if (frame is Frame.Text) { - val receivedText = frame.readText() - println("client received: $receivedText") + val message = Json.decodeFromFrame(frame = frame) + incomingMessageBuffer.emit(message) } } }.also { @@ -144,15 +148,19 @@ object NetworkRepository { } } - fun share( - type: String, - value: String, + suspend fun share( + content: MessageContent, ) { if (status.value == Status.CONNECTED) { - scope.launch { - val message = Message(from = player.value, type = type, value = value) - val json = Json.encodeToJsonElement(message) - messageResponseFlow.emit(json.toString()) + val message = Message( + from = player.value, + value = content, + ) + // emit the message into the outgoing buffer + outgoingMessageBuffer.emit(message) + // emit the message into the incoming buffer IF we are the server + if (type.value == Type.SERVER) { + incomingMessageBuffer.emit(message) } } } @@ -167,4 +175,14 @@ object NetworkRepository { SERVER, NONE, } +} + +private fun Json.decodeFromFrame(frame: Frame.Text): Message { + val json = frame.readText() + return decodeFromString(json) +} + +private fun Json.encodeToFrame(message: Message): Frame { + val json = encodeToJsonElement(message) + return Frame.Text(text = json.toString()) } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt index 1048cc4..20904a0 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/Message.kt @@ -3,8 +3,7 @@ package com.pixelized.desktop.lwa.repository.network.protocol import kotlinx.serialization.Serializable @Serializable -class Message( +data class Message( val from: String, - val type: String, - val value: String, + val value: MessageContent, ) \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt new file mode 100644 index 0000000..bcfbb30 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/MessageContent.kt @@ -0,0 +1,6 @@ +package com.pixelized.desktop.lwa.repository.network.protocol + +import kotlinx.serialization.Serializable + +@Serializable +sealed interface MessageContent \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt new file mode 100644 index 0000000..1893b26 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/protocol/RollMessage.kt @@ -0,0 +1,9 @@ +package com.pixelized.desktop.lwa.repository.network.protocol + +import kotlinx.serialization.Serializable + +@Serializable +data class RollMessage( + val label: String, + val roll: Int, +): MessageContent \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt new file mode 100644 index 0000000..50557b2 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/roll/RollHistoryRepository.kt @@ -0,0 +1,41 @@ +package com.pixelized.desktop.lwa.repository.roll + +import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.network.protocol.Message +import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.mapNotNull +import kotlinx.coroutines.flow.shareIn +import kotlinx.coroutines.launch + +object RollHistoryRepository { + private val scope = CoroutineScope(Dispatchers.IO) + private val network = NetworkRepository + + val rolls: SharedFlow = network.data + .mapNotNull { it.takeIf { it.value is RollMessage } } + .shareIn( + scope = scope, + started = SharingStarted.Eagerly, + ) + + init { + scope.launch { + network.data.collect { + println(it) + } + } + } + + suspend fun share( + label: String, + roll: Int, + ) { + network.share( + content = RollMessage(label = label, roll = roll) + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt index 8677308..f371989 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/detail/CharacterSheetPage.kt @@ -24,7 +24,6 @@ import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.automirrored.filled.ArrowBack import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.runtime.Composable @@ -124,7 +123,7 @@ fun CharacterSheetPage( scope.launch { viewModel.deleteCharacter(id = sheet.id) if (screen.popBackStack().not()) { - window.close() + window.closeWindows() } } }, @@ -191,16 +190,6 @@ fun CharacterSheetPageContent( ) } }, - navigationIcon = { - IconButton( - onClick = onBack, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - } ) }, content = { paddingValues -> diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt index a4af668..94d0845 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/characterSheet/edit/CharacterSheetEditPage.kt @@ -92,7 +92,7 @@ fun CharacterSheetEditPage( scope.launch { viewModel.save() if (screen.popBackStack().not()) { - window.close() + window.closeWindows() } } }, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt index f7ee73e..6a5f456 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPage.kt @@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa.screen.main import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -27,16 +26,17 @@ import androidx.compose.ui.window.rememberWindowState import androidx.lifecycle.viewmodel.compose.viewModel import com.pixelized.desktop.lwa.LocalWindowController import com.pixelized.desktop.lwa.WindowController -import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox import com.pixelized.desktop.lwa.navigation.LocalScreenController import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination import com.pixelized.desktop.lwa.navigation.destination.navigateToNetwork +import com.pixelized.desktop.lwa.navigation.destination.navigateToRollHistory import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title import lwacharactersheet.composeapp.generated.resources.main_page__create_action import lwacharactersheet.composeapp.generated.resources.main_page__network_action +import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action import org.jetbrains.compose.resources.stringResource @Stable @@ -49,6 +49,7 @@ data class CharacterUio( fun MainPage( viewModel: MainPageViewModel = viewModel { MainPageViewModel() }, ) { + val window = LocalWindowController.current val screen = LocalScreenController.current Surface( @@ -64,10 +65,13 @@ fun MainPage( MainPageContent( characters = viewModel.characters, onCharacter = { - viewModel.showCharacterSheet(sheet = it) + window.showCharacterSheet(sheet = it) }, onCreateCharacter = { - viewModel.showCreateCharacterSheet() + window.showCreateCharacterSheet() + }, + onRollHistory = { + screen.navigateToRollHistory() }, onNetwork = { screen.navigateToNetwork() @@ -75,140 +79,72 @@ fun MainPage( ) } } - - HandleCharacterSheet( - sheets = viewModel.sheet, - onCloseRequest = { viewModel.hideCharacterSheet(sheet = it) } - ) - - HandleCharacterSheetCreation( - sheets = viewModel.create, - onCloseRequest = { viewModel.hideCreateCharacterSheet(id = it) }, - ) } -@Composable -fun HandleCharacterSheet( - sheets: State>, - onCloseRequest: (id: CharacterUio) -> Unit, -) { - sheets.value.forEach { sheet -> - val controller = remember { - WindowController( - onCloseRequest = { onCloseRequest(sheet) } - ) - } - CompositionLocalProvider( - LocalWindowController provides controller, - ) { - Window( - onCloseRequest = { onCloseRequest(sheet) }, - state = rememberWindowState( - width = 400.dp + 64.dp, - height = 900.dp, - ), - title = sheet.name, - ) { - CharacterSheetMainNavHost( - startDestination = CharacterSheetDestination.navigationRoute(id = sheet.id) - ) - } - } - } -} - -@Composable -fun HandleCharacterSheetCreation( - sheets: State>, - onCloseRequest: (id: Int) -> Unit, -) { - sheets.value.forEach { sheet -> - val controller = remember { - WindowController( - onCloseRequest = { onCloseRequest(sheet) } - ) - } - CompositionLocalProvider( - LocalWindowController provides controller, - ) { - Window( - onCloseRequest = { controller.close() }, - state = rememberWindowState( - width = 400.dp + 64.dp, - height = 900.dp, - ), - title = stringResource(Res.string.character_sheet_edit__title), - ) { - CharacterSheetMainNavHost( - startDestination = CharacterSheetEditDestination.navigationRoute(id = null) - ) - } - } - } -} - - @Composable fun MainPageContent( modifier: Modifier = Modifier, characters: State>, onCharacter: (CharacterUio) -> Unit, onCreateCharacter: () -> Unit, + onRollHistory: () -> Unit, onNetwork: () -> Unit, ) { Column( modifier = modifier, + verticalArrangement = Arrangement.spacedBy(16.dp), ) { - Spacer( - modifier = Modifier.weight(weight = 1f) - ) - DecoratedBox { - Column( - modifier = Modifier.padding(horizontal = 8.dp), - verticalArrangement = Arrangement.spacedBy(space = 32.dp), - ) { - Column { - characters.value.forEach { sheet -> - TextButton( - onClick = { onCharacter(sheet) }, - ) { - Text( - modifier = Modifier.fillMaxWidth(), - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - maxLines = 1, - text = sheet.name, - ) - } - } - } - + Column { + characters.value.forEach { sheet -> TextButton( - onClick = { onCreateCharacter() }, + onClick = { onCharacter(sheet) }, ) { Text( modifier = Modifier.fillMaxWidth(), - maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, - text = stringResource(Res.string.main_page__create_action), + maxLines = 1, + text = sheet.name, ) } } } - Spacer( - modifier = Modifier.weight(weight = 1f) - ) + TextButton( - onClick = { onNetwork() }, + onClick = onCreateCharacter, ) { Text( modifier = Modifier.fillMaxWidth(), maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, - text = stringResource(Res.string.main_page__network_action), + text = stringResource(Res.string.main_page__create_action), ) } + + Column { + TextButton( + onClick = onRollHistory, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + text = stringResource(Res.string.main_page__roll_history_action), + ) + } + TextButton( + onClick = onNetwork, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + maxLines = 1, + text = stringResource(Res.string.main_page__network_action), + ) + } + } } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt index 0947b2a..5190a83 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/main/MainPageViewModel.kt @@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa.screen.main import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.utils.extention.collectAsState @@ -12,12 +11,6 @@ class MainPageViewModel : ViewModel() { // using a variable to help with later injection. private val repository = CharacterSheetRepository - private val _sheet = mutableStateOf>(emptySet()) - val sheet: State> get() = _sheet - - private val _create = mutableStateOf>(emptySet()) - val create: State> get() = _create - val characters: State> @Composable @Stable @@ -31,20 +24,4 @@ class MainPageViewModel : ViewModel() { ) } } - - fun showCreateCharacterSheet() { - _create.value = _create.value.toMutableSet().apply { add(size) } - } - - fun hideCreateCharacterSheet(id: Int) { - _create.value = _create.value.toMutableSet().apply { remove(id) } - } - - fun showCharacterSheet(sheet: CharacterUio) { - _sheet.value = _sheet.value.toMutableSet().apply { add(sheet) } - } - - fun hideCharacterSheet(sheet: CharacterUio) { - _sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) } - } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt index e3b83d1..20bfb00 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/roll/RollViewModel.kt @@ -10,7 +10,7 @@ import com.pixelized.desktop.lwa.business.RollUseCase import com.pixelized.desktop.lwa.business.SkillStepUseCase import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository -import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio import kotlinx.coroutines.Job import kotlinx.coroutines.coroutineScope @@ -26,7 +26,7 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__success import org.jetbrains.compose.resources.getString class RollViewModel : ViewModel() { - private val network = NetworkRepository + private val repository = RollHistoryRepository private val _roll = mutableStateOf(RollUio(label = "", value = 0)) val roll: State get() = _roll @@ -138,13 +138,11 @@ class RollViewModel : ViewModel() { value = roll, ) - share(roll = roll) + launch { + repository.share(label = _roll.value.label, roll = roll) + } } } } } - - private fun share(roll: Int) { - network.share(type = "roll", value = "$roll") - } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt new file mode 100644 index 0000000..2023450 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryPage.kt @@ -0,0 +1,135 @@ +package com.pixelized.desktop.lwa.screen.rollhistory + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Scaffold +import androidx.compose.material.Surface +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Stable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.pixelized.desktop.lwa.navigation.LocalScreenController +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.roll_history__title +import org.jetbrains.compose.resources.stringResource + +@Stable +data class RollItemUio( + val from: String, + val label: String, + val roll: Int, +) + +@Composable +fun RollHistoryPage( + viewModel: RollHistoryViewModel = viewModel { RollHistoryViewModel() } +) { + val screen = LocalScreenController.current + + Surface( + modifier = Modifier.fillMaxSize(), + ) { + RollHistoryContent( + modifier = Modifier.fillMaxSize(), + rolls = viewModel.rolls, + onBack = { + screen.popBackStack() + }, + ) + } +} + +@Composable +private fun RollHistoryContent( + modifier: Modifier = Modifier, + rolls: State>, + onBack: () -> Unit, +) { + Scaffold( + modifier = modifier, + topBar = { + TopAppBar( + title = { + Text( + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = stringResource(Res.string.roll_history__title), + ) + }, + navigationIcon = { + IconButton( + onClick = onBack, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null, + ) + } + } + ) + }, + content = { + val state = rememberLazyListState() + + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = state, + reverseLayout = true, + contentPadding = PaddingValues(all = 24.dp), + verticalArrangement = Arrangement.spacedBy(space = 8.dp) + ) { + items(items = rolls.value) { + RollItem( + roll = it + ) + } + } + } + ) +} + +@Composable +private fun RollItem( + modifier: Modifier = Modifier, + roll: RollItemUio, +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(4.dp), + ) { + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Thin, + text = roll.from, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Light, + text = roll.label, + ) + Text( + modifier = Modifier.alignByBaseline(), + style = MaterialTheme.typography.body1, + fontWeight = FontWeight.Bold, + text = "${roll.roll}", + ) + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt new file mode 100644 index 0000000..356fe50 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/screen/rollhistory/RollHistoryViewModel.kt @@ -0,0 +1,36 @@ +package com.pixelized.desktop.lwa.screen.rollhistory + +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage +import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository +import kotlinx.coroutines.launch + +class RollHistoryViewModel : ViewModel() { + + private val repository = RollHistoryRepository + + private val _rolls = mutableStateOf((emptyList())) + val rolls: State> get() = _rolls + + init { + viewModelScope.launch { + repository.rolls.collect { + (it.value as? RollMessage)?.let { content -> + _rolls.value = _rolls.value.toMutableList().apply { + add( + index = 0, + element = RollItemUio( + from = it.from, + label = content.label, + roll = content.roll + ) + ) + } + } + } + } + } +} \ No newline at end of file