From 3941c5dd76b4a04f229266cf3abbf05648c4c908 Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Fri, 28 Feb 2025 13:31:01 +0100 Subject: [PATCH] Change the access to some old screen from the new UI. --- composeApp/build.gradle.kts | 2 + .../drawable/ic_settings_24dp.xml | 9 + .../drawable/ic_timeline_24dp.xml | 9 + .../composeResources/values/strings.xml | 3 +- .../kotlin/com/pixelized/desktop/lwa/App.kt | 109 ++++--- .../com/pixelized/desktop/lwa/Module.kt | 6 +- .../repository/network/NetworkRepository.kt | 28 +- .../lwa/repository/network/helper/Client.kt | 2 +- .../repository/settings/SettingsFactory.kt | 3 +- .../lwa/repository/settings/model/Settings.kt | 2 +- .../screen/destination/NetworkDestination.kt | 4 +- .../window/destination/NetworkWindows.kt | 28 ++ .../lwa/ui/screen/campaign/CampaignScreen.kt | 44 +-- .../ui/screen/campaign/CampaignViewModel.kt | 28 +- .../campaign/toolbar/CampaignToolbar.kt | 145 +++++++++ .../lwa/ui/screen/campaign/toolbar/Toolbar.kt | 52 ---- .../detail/CharacterSheetPage.kt | 4 +- .../desktop/lwa/ui/screen/main/MainPage.kt | 288 +++++++++--------- .../{NetworkPage.kt => NetworkScreen.kt} | 228 ++++++++------ .../lwa/ui/screen/network/NetworkViewModel.kt | 35 ++- .../lwa/ui/screen/roll/RollViewModel.kt | 2 + .../ui/screen/rollhistory/RollHistoryItem.kt | 2 +- .../ui/screen/rollhistory/RollHistoryPage.kt | 4 +- .../rollhistory/RollHistoryViewModel.kt | 63 ++-- gradle/libs.versions.toml | 7 +- 25 files changed, 657 insertions(+), 450 deletions(-) create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_settings_24dp.xml create mode 100644 composeApp/src/commonMain/composeResources/drawable/ic_timeline_24dp.xml create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/destination/NetworkWindows.kt create mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt delete mode 100644 composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt rename composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/{NetworkPage.kt => NetworkScreen.kt} (51%) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 1a7af98..c7c8f0d 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -42,6 +42,8 @@ kotlin { implementation(libs.ktor.client.negotiation) // shell implementation(libs.turtle) + // log + implementation(libs.logback) } commonTest.dependencies { diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_settings_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_settings_24dp.xml new file mode 100644 index 0000000..d18bac0 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_settings_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_timeline_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_timeline_24dp.xml new file mode 100644 index 0000000..a38d9fd --- /dev/null +++ b/composeApp/src/commonMain/composeResources/drawable/ic_timeline_24dp.xml @@ -0,0 +1,9 @@ + + + diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index aff4200..7c6d410 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -7,10 +7,9 @@ Créer une feuille de personnage Ouvrir le dossier de sauvegarde - Consulter l'historique des lancers + Historique des lancers Configuration de la table - Jet de : Réussite si lancer inférieur ou égal à : %1$s Réussite critique 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 7213333..7da8014 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt @@ -26,9 +26,11 @@ 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.DpSize import androidx.compose.ui.unit.dp import androidx.compose.ui.window.ApplicationScope import androidx.compose.ui.window.Window +import androidx.compose.ui.window.rememberWindowState import com.pixelized.desktop.lwa.repository.network.NetworkRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler @@ -40,12 +42,15 @@ import com.pixelized.desktop.lwa.ui.navigation.window.WindowController import com.pixelized.desktop.lwa.ui.navigation.window.WindowsNavHost import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetEditWindow import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetWindow +import com.pixelized.desktop.lwa.ui.navigation.window.destination.NetworkWindows import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost +import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryPage +import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.ui.theme.LwaTheme import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking @@ -88,74 +93,82 @@ fun ApplicationScope.App() { ) { Window( onCloseRequest = ::exitApplication, + state = rememberWindowState(size = DpSize(width = 800.dp, height = maxWindowHeight)), title = runBlocking { getString(Res.string.app_name) }, onKeyEvent = { event -> keyEventHandlers.reversed().any { it(event) } }, ) { - LwaTheme { - Surface( - modifier = Modifier.fillMaxSize() - ) { - Scaffold( - snackbarHost = { - 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 = { - MainScreen() - } - ) - NetworkSnackHandler( - snack = snackHostState, - ) - WindowsHandler( - windowController = windowController, - ) - } - } + MainWindowScreen() } } } @Composable -private fun MainScreen( +private fun MainWindowScreen( campaignViewModel: CampaignViewModel = koinViewModel(), networkViewModel: NetworkViewModel = koinViewModel(), + rollViewModel: RollHistoryViewModel = koinViewModel(), ) { LaunchedEffect(Unit) { networkViewModel.connect() campaignViewModel.init() } - MainNavHost() + val snackHostState = LocalSnackHost.current + val errorSnackHostState = LocalErrorSnackHost.current + val windowController = LocalWindowController.current + + LwaTheme { + Surface( + modifier = Modifier.fillMaxSize() + ) { + Scaffold( + snackbarHost = { + 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, + rollViewModel = rollViewModel, + ) + } + } } @Composable private fun WindowsHandler( windowController: WindowController, + rollViewModel: RollHistoryViewModel = koinViewModel(), ) { WindowsNavHost( controller = windowController, @@ -173,7 +186,11 @@ private fun WindowsHandler( ), ) - is RollHistoryWindow -> RollHistoryPage() + is RollHistoryWindow -> RollHistoryPage( + viewModel = rollViewModel, + ) + + is NetworkWindows -> NetworkPage() } } ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt index cacb771..24df629 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt @@ -34,7 +34,6 @@ import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import io.ktor.client.HttpClient -import io.ktor.client.engine.HttpClientEngine import io.ktor.client.engine.okhttp.OkHttp import io.ktor.client.plugins.contentnegotiation.ContentNegotiation import io.ktor.client.plugins.websocket.WebSockets @@ -56,12 +55,9 @@ val appModuleDependencies val toolsDependencies get() = module { - single { - OkHttp.create() - } single { HttpClient( - engine = get() + engine = OkHttp.create(), ) { install(WebSockets) { pingIntervalMillis = 20_000 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 b91b069..eb56eff 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 @@ -7,6 +7,10 @@ import com.pixelized.desktop.lwa.utils.extention.encodeToFrame import com.pixelized.shared.lwa.protocol.websocket.Message import com.pixelized.shared.lwa.protocol.websocket.payload.MessagePayload import io.ktor.client.HttpClient +import io.ktor.client.engine.okhttp.OkHttp +import io.ktor.client.plugins.contentnegotiation.ContentNegotiation +import io.ktor.client.plugins.websocket.WebSockets +import io.ktor.serialization.kotlinx.json.json import io.ktor.websocket.Frame import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -22,10 +26,11 @@ import kotlinx.serialization.json.Json class NetworkRepository( private val settingsRepository: SettingsRepository, - private val client: HttpClient, + private val json: Json, ) { - private val scope = CoroutineScope(Dispatchers.IO) + private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default) private var networkJob: Job? = null + private var client: HttpClient? = null private val outgoingMessageBuffer = MutableSharedFlow() private val incomingMessageBuffer = MutableSharedFlow() @@ -41,10 +46,11 @@ class NetworkRepository( onFailure: (Exception) -> Unit = { }, onClose: () -> Unit = { }, ) { + client = buildNewClient() networkJob?.cancel() - networkJob = scope.launch { + networkJob = scope.launch(Dispatchers.IO) { try { - client.connectWebSocket(host = host, port = port) { + client?.connectWebSocket(host = host, port = port) { _status.value = Status.CONNECTED onConnect() @@ -78,7 +84,8 @@ class NetworkRepository( fun disconnect() { networkJob?.cancel() scope.launch { - client.close() + client?.close() + client = null } } @@ -96,6 +103,17 @@ class NetworkRepository( } } + private fun buildNewClient() = HttpClient( + engine = OkHttp.create(), + ) { + install(WebSockets) { + pingIntervalMillis = 20_000 + } + install(ContentNegotiation) { + json(json) + } + } + enum class Status { CONNECTED, DISCONNECTED diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt index 80f4259..eed0fa1 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/network/helper/Client.kt @@ -13,7 +13,7 @@ suspend fun HttpClient.connectWebSocket( ) { webSocket( method = HttpMethod.Get, - host = host, + host = host.removePrefix("http://"), port = port, path = "/ws", block = block, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt index 188faa6..e85735a 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/SettingsFactory.kt @@ -1,15 +1,14 @@ package com.pixelized.desktop.lwa.repository.settings -import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV1 +import com.pixelized.desktop.lwa.usecase.SettingsUseCase class SettingsFactory( private val useCase: SettingsUseCase, ) { - fun convertToJson( settings: Settings, ): SettingsJson { diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt index 8ac2ce4..8d2b853 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/repository/settings/model/Settings.kt @@ -5,5 +5,5 @@ data class Settings( val port: Int, val playerName: String, ) { - val root: String get() = "http://${host}:${port}" + val root: String get() = "http://${"${host}:${port}".removePrefix("http://")}" } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/NetworkDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/NetworkDestination.kt index 82a8af7..84cc322 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/NetworkDestination.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/NetworkDestination.kt @@ -3,7 +3,7 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.compose.composable -import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage +import com.pixelized.desktop.lwa.ui.screen.network.NetworkScreen object NetworkDestination { private const val ROUTE = "network" @@ -16,7 +16,7 @@ fun NavGraphBuilder.composableNetworkPage() { composable( route = NetworkDestination.baseRoute(), ) { - NetworkPage() + NetworkScreen() } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/destination/NetworkWindows.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/destination/NetworkWindows.kt new file mode 100644 index 0000000..d70afd4 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/window/destination/NetworkWindows.kt @@ -0,0 +1,28 @@ +package com.pixelized.desktop.lwa.ui.navigation.window.destination + +import androidx.compose.runtime.Stable +import androidx.compose.ui.unit.DpSize +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.ui.navigation.window.WindowController + +@Stable +class NetworkWindows( + title: String, + size: DpSize, +) : Window( + title = title, + size = size, +) + +fun WindowController.navigateToNetwork( + title: String = "", +) { + showWindow( + window = NetworkWindows( + title = title, size = DpSize( + width = 464.dp, + height = 300.dp, + ) + ) + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt index c24064f..e04a78d 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt @@ -13,12 +13,9 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width -import androidx.compose.material.Icon -import androidx.compose.material.IconButton import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.State -import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -34,33 +31,24 @@ import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.DpSize import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp -import com.pixelized.desktop.lwa.LocalWindowController import com.pixelized.desktop.lwa.ui.composable.blur.BlurContent import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentController import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialog import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler -import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController -import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToOldMainPage -import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon -import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.Toolbar +import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog import com.pixelized.desktop.lwa.ui.screen.roll.RollPage import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel import kotlinx.coroutines.launch -import lwacharactersheet.composeapp.generated.resources.Res -import lwacharactersheet.composeapp.generated.resources.ic_d20_24dp -import lwacharactersheet.composeapp.generated.resources.ic_table_24dp -import org.jetbrains.compose.resources.painterResource import org.koin.compose.viewmodel.koinViewModel @Composable fun MainPage( - campaignViewModel: CampaignViewModel = koinViewModel(), characterDetailViewModel: CharacterDetailViewModel = koinViewModel(), characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(), dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(), @@ -76,9 +64,7 @@ fun MainPage( else -> false } } - - val windows = LocalWindowController.current - val screen = LocalScreenController.current + val scope = rememberCoroutineScope() val blurController = rememberBlurContentController() @@ -92,31 +78,7 @@ fun MainPage( CampaignScreenLayout( modifier = Modifier.fillMaxSize(), top = { - Toolbar( - title = campaignViewModel.title.collectAsState(initial = "").value, - actions = { - IconButton( - onClick = { - windows.navigateToRollHistory() - }, - ) { - Icon( - painter = painterResource(Res.drawable.ic_d20_24dp), - contentDescription = null, - ) - } - IconButton( - onClick = { - screen.navigateToOldMainPage() - }, - ) { - Icon( - painter = painterResource(Res.drawable.ic_table_24dp), - contentDescription = null, - ) - } - }, - ) + CampaignToolbar() }, bottom = { Surface( diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt index 13cca63..c901665 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt @@ -5,8 +5,10 @@ import androidx.lifecycle.viewModelScope import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository +import com.pixelized.desktop.lwa.repository.network.NetworkRepository import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -14,6 +16,7 @@ class CampaignViewModel( private val characterRepository: CharacterSheetRepository, private val alterationRepository: AlterationRepository, private val campaignRepository: CampaignRepository, + private val network: NetworkRepository, ) : ViewModel() { val title: Flow = campaignRepository.campaignFlow @@ -21,15 +24,22 @@ class CampaignViewModel( fun init() { viewModelScope.launch { - campaignRepository.campaignFlow.collectLatest { - it.characters.keys.forEach { id -> - characterRepository.characterDetail( - characterSheetId = id.characterSheetId, - forceUpdate = true, - ) - alterationRepository.updateActiveAlterations( - characterInstanceId = id, - ) + combine( + network.status, + campaignRepository.campaignFlow, + ) { status, campaign -> + status to campaign + }.collectLatest { (status, campaign) -> + if (status == NetworkRepository.Status.CONNECTED) { + campaign.characters.keys.forEach { id -> + characterRepository.characterDetail( + characterSheetId = id.characterSheetId, + forceUpdate = true, + ) + alterationRepository.updateActiveAlterations( + characterInstanceId = id, + ) + } } } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt new file mode 100644 index 0000000..d301dce --- /dev/null +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt @@ -0,0 +1,145 @@ +package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar + +import androidx.compose.foundation.layout.padding +import androidx.compose.material.DropdownMenu +import androidx.compose.material.DropdownMenuItem +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.MoreVert +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.pixelized.desktop.lwa.LocalWindowController +import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController +import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToOldMainPage +import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToNetwork +import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory +import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel +import com.pixelized.desktop.lwa.ui.theme.lwa +import lwacharactersheet.composeapp.generated.resources.Res +import lwacharactersheet.composeapp.generated.resources.ic_settings_24dp +import lwacharactersheet.composeapp.generated.resources.ic_table_24dp +import lwacharactersheet.composeapp.generated.resources.ic_timeline_24dp +import lwacharactersheet.composeapp.generated.resources.main_page__network_action +import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action +import org.jetbrains.compose.resources.painterResource +import org.jetbrains.compose.resources.stringResource +import org.koin.compose.viewmodel.koinViewModel + +@Composable +fun CampaignToolbar( + campaignViewModel: CampaignViewModel = koinViewModel(), +) { + val windows = LocalWindowController.current + val screen = LocalScreenController.current + + val isOverflowMenuOpen = remember { mutableStateOf(false) } + + CampaignToolbarContent( + title = campaignViewModel.title.collectAsState(initial = ""), + isOverflowMenuOpen = isOverflowMenuOpen, + onOverflow = { + isOverflowMenuOpen.value = isOverflowMenuOpen.value.not() + }, + onDismissOverflowMenu = { + isOverflowMenuOpen.value = false + }, + onRollHistory = { + windows.navigateToRollHistory() + }, + onNetwork = { + windows.navigateToNetwork() + }, + onOlUi = { + screen.navigateToOldMainPage() + }, + ) +} + +@Composable +private fun CampaignToolbarContent( + modifier: Modifier = Modifier, + title: State, + isOverflowMenuOpen: State, + onOverflow: () -> Unit, + onDismissOverflowMenu: () -> Unit, + onRollHistory: () -> Unit, + onNetwork: () -> Unit, + onOlUi: () -> Unit, +) { + TopAppBar( + modifier = modifier, + title = { + Text( + text = title.value, + ) + }, + actions = { + IconButton( + onClick = onOverflow, + ) { + Icon( + imageVector = Icons.Default.MoreVert, + tint = MaterialTheme.colors.primary, + contentDescription = null, + ) + } + + DropdownMenu( + expanded = isOverflowMenuOpen.value, + onDismissRequest = onDismissOverflowMenu, + ) { + DropdownMenuItem( + onClick = { onDismissOverflowMenu(); onRollHistory() }, + ) { + Icon( + painter = painterResource(Res.drawable.ic_timeline_24dp), + tint = MaterialTheme.lwa.colorScheme.base.primary, + contentDescription = null, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + color = MaterialTheme.colors.primary, + text = stringResource(Res.string.main_page__roll_history_action), + ) + } + DropdownMenuItem( + onClick = { onDismissOverflowMenu(); onNetwork() }, + ) { + Icon( + painter = painterResource(Res.drawable.ic_settings_24dp), + tint = MaterialTheme.lwa.colorScheme.base.primary, + contentDescription = null, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + color = MaterialTheme.colors.primary, + text = stringResource(Res.string.main_page__network_action), + ) + } + DropdownMenuItem( + onClick = { onDismissOverflowMenu(); onOlUi() }, + ) { + Icon( + painter = painterResource(Res.drawable.ic_table_24dp), + tint = MaterialTheme.lwa.colorScheme.base.primary, + contentDescription = null, + ) + Text( + modifier = Modifier.padding(start = 8.dp), + color = MaterialTheme.colors.primary, + text = "Ancienne interface utilisateur", + ) + } + } + }, + ) +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt deleted file mode 100644 index 650d645..0000000 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/Toolbar.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.LocalContentColor -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Surface -import androidx.compose.material.Text -import androidx.compose.material.minimumInteractiveComponentSize -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.pixelized.desktop.lwa.ui.theme.lwa - -@Composable -fun Toolbar( - modifier: Modifier = Modifier, - title: String, - actions: @Composable RowScope.() -> Unit = { }, -) { - Surface( - modifier = modifier, - elevation = 1.dp, - ) { - Row( - modifier = Modifier - .fillMaxWidth() - .minimumInteractiveComponentSize(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - modifier = Modifier.padding(start = 16.dp), - style = MaterialTheme.typography.h6, - text = title, - ) - CompositionLocalProvider( - LocalContentColor provides MaterialTheme.lwa.colorScheme.base.primary, - ) { - Row( - modifier = Modifier.padding(end = 8.dp), - content = actions, - ) - } - } - } -} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt index 23dc7e0..4e94b05 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/characterSheet/detail/CharacterSheetPage.kt @@ -124,7 +124,7 @@ data class CharacterSheetPageUio( data class Roll( val label: String, val value: String, - val roll: RollActionUio?, + val roll: RollActionUio, ) } @@ -187,7 +187,7 @@ fun CharacterSheetPage( onUseSkill = viewModel::onUseSkill, onRoll = { roll -> blurController.show() - rollViewModel.prepareRoll(sheet = sheet, roll = roll) + rollViewModel.prepareRoll(roll.roll) viewModel.showRollOverlay() }, ) diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt index d759c0f..b8a7c37 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/main/MainPage.kt @@ -13,9 +13,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material.Icon +import androidx.compose.material.Scaffold import androidx.compose.material.Surface import androidx.compose.material.Text import androidx.compose.material.TextButton +import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.State @@ -32,7 +34,6 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToNetw import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheet import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToCharacterSheetEdit import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory -import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.Toolbar import com.pixelized.shared.lwa.model.campaign.Campaign import kotlinx.coroutines.runBlocking import lwacharactersheet.composeapp.generated.resources.Res @@ -117,168 +118,177 @@ fun MainPageContent( onNetwork: () -> Unit, onMainPage: () -> Unit, ) { - Column( + Scaffold( modifier = modifier, - ) { - Toolbar( - title = runBlocking { getString(Res.string.app_name) }, - actions = { - TextButton( - onClick = onMainPage, - ) { - Row( - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, + topBar = { + TopAppBar( + title = { + Text( + text = runBlocking { getString(Res.string.app_name) }, + ) + }, + actions = { + TextButton( + onClick = onMainPage, ) { - Icon( - painter = painterResource(Res.drawable.ic_swords_24dp), - contentDescription = null, - ) - Text( - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - maxLines = 1, - text = "Nouvelle interface utilisateur", - ) + Row( + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(Res.drawable.ic_swords_24dp), + contentDescription = null, + ) + Text( + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + maxLines = 1, + text = "Nouvelle interface utilisateur", + ) + } } } - } - ) - Box( - modifier = Modifier - .verticalScroll(state = scrollState) - .fillMaxSize() - .padding(horizontal = 16.dp), - contentAlignment = Alignment.Center, - ) { + ) + }, + content = { Column { - if (characters.value.isNotEmpty()) { + Box( + modifier = Modifier + .verticalScroll(state = scrollState) + .fillMaxSize() + .padding(horizontal = 16.dp), + contentAlignment = Alignment.Center, + ) { Column { - characters.value.forEach { sheet -> - TextButton( - onClick = { onCharacter(sheet) }, + if (characters.value.isNotEmpty()) { + Column { + characters.value.forEach { sheet -> + TextButton( + onClick = { onCharacter(sheet) }, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + maxLines = 1, + text = sheet.name, + ) + } + } + } + } + + if (characters.value.isNotEmpty() && npcs.value.isNotEmpty()) { + Spacer(modifier = Modifier.height(height = 24.dp)) + } + + if (npcs.value.isNotEmpty()) { + Column { + npcs.value.forEach { sheet -> + TextButton( + onClick = { onCharacter(sheet) }, + ) { + Text( + modifier = Modifier.fillMaxWidth(), + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + maxLines = 1, + text = sheet.name, + ) + } + } + } + } + + Spacer(modifier = Modifier.height(height = 24.dp)) + + TextButton( + onClick = onCreateCharacter, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { + Icon( + painter = painterResource(Res.drawable.ic_file_24dp), + contentDescription = null, + ) Text( - modifier = Modifier.fillMaxWidth(), + maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, - maxLines = 1, - text = sheet.name, + text = stringResource(Res.string.main_page__create_action), ) } } - } - } - if (characters.value.isNotEmpty() && npcs.value.isNotEmpty()) { - Spacer(modifier = Modifier.height(height = 24.dp)) - } - - if (npcs.value.isNotEmpty()) { - Column { - npcs.value.forEach { sheet -> - TextButton( - onClick = { onCharacter(sheet) }, + TextButton( + onClick = onOpenSaveDirectory, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, ) { + Icon( + painter = painterResource(Res.drawable.ic_folder_24dp), + contentDescription = null, + ) Text( - modifier = Modifier.fillMaxWidth(), + maxLines = 1, overflow = TextOverflow.Ellipsis, textAlign = TextAlign.Start, - maxLines = 1, - text = sheet.name, + text = stringResource(Res.string.main_page__open_save_directory), ) } } - } - } - Spacer(modifier = Modifier.height(height = 24.dp)) + TextButton( + enabled = enableRollHistory.value, + onClick = onRollHistory, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(Res.drawable.ic_d20_24dp), + contentDescription = null, + ) + Text( + maxLines = 1, + overflow = TextOverflow.Ellipsis, + textAlign = TextAlign.Start, + text = stringResource(Res.string.main_page__roll_history_action), + ) + } + } - TextButton( - onClick = onCreateCharacter, - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(Res.drawable.ic_file_24dp), - contentDescription = null, - ) - Text( - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - text = stringResource(Res.string.main_page__create_action), - ) - } - } - - TextButton( - onClick = onOpenSaveDirectory, - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(Res.drawable.ic_folder_24dp), - contentDescription = null, - ) - Text( - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - text = stringResource(Res.string.main_page__open_save_directory), - ) - } - } - - TextButton( - enabled = enableRollHistory.value, - onClick = onRollHistory, - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(Res.drawable.ic_d20_24dp), - contentDescription = null, - ) - Text( - maxLines = 1, - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - text = stringResource(Res.string.main_page__roll_history_action), - ) - } - } - - TextButton( - onClick = onNetwork, - ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - verticalAlignment = Alignment.CenterVertically, - ) { - Icon( - painter = painterResource(Res.drawable.ic_table_24dp), - contentDescription = null, - ) - Text( - overflow = TextOverflow.Ellipsis, - textAlign = TextAlign.Start, - maxLines = 1, - text = stringResource(Res.string.main_page__network_action), - ) + TextButton( + onClick = onNetwork, + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(Res.drawable.ic_table_24dp), + contentDescription = null, + ) + Text( + 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/ui/screen/network/NetworkPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt similarity index 51% rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkPage.kt rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt index 08cfbf5..69ac12e 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -82,7 +83,7 @@ data class NetworkPageUio( } @Composable -fun NetworkPage( +fun NetworkScreen( viewModel: NetworkViewModel = koinViewModel(), ) { val screen = LocalScreenController.current @@ -97,17 +98,43 @@ fun NetworkPage( ) { BlurContent( modifier = Modifier.fillMaxSize(), - controller = viewModel.controller, + controller = viewModel.blurController, ) { - NetworkContent( + Scaffold( modifier = Modifier.fillMaxSize(), - player = viewModel.network.collectAsState(), - onBack = { screen.popBackStack() }, - onPlayerChange = viewModel::onPlayerNameChange, - onHostChange = viewModel::onHostChange, - onPortChange = viewModel::onPortChange, - onConnect = viewModel::connect, - onDisconnect = viewModel::disconnect, + topBar = { + TopAppBar( + title = { + Text( + overflow = TextOverflow.Ellipsis, + maxLines = 1, + text = stringResource(Res.string.network__title), + ) + }, + navigationIcon = { + IconButton( + onClick = { screen.popBackStack() }, + ) { + Icon( + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = null, + ) + } + } + ) + }, + content = { paddingValues -> + NetworkContent( + modifier = Modifier.fillMaxSize(), + paddingValues = paddingValues, + player = viewModel.network.collectAsState(), + onPlayerChange = viewModel::onPlayerNameChange, + onHostChange = viewModel::onHostChange, + onPortChange = viewModel::onPortChange, + onConnect = viewModel::connect, + onDisconnect = viewModel::disconnect, + ) + } ) } @@ -138,96 +165,119 @@ fun NetworkPage( } } +@Composable +fun NetworkPage( + viewModel: NetworkViewModel = koinViewModel(), +) { + val snack = LocalSnackHost.current + + Surface { + BlurContent( + modifier = Modifier.fillMaxSize(), + controller = viewModel.blurController, + ) { + NetworkContent( + modifier = Modifier.fillMaxSize(), + player = viewModel.network.collectAsState(), + onPlayerChange = viewModel::onPlayerNameChange, + onHostChange = viewModel::onHostChange, + onPortChange = viewModel::onPortChange, + onConnect = viewModel::connect, + 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, + ) + } +} + @Composable private fun NetworkContent( modifier: Modifier = Modifier, scrollState: ScrollState = rememberScrollState(), + paddingValues: PaddingValues = PaddingValues(), player: State, - onBack: () -> Unit, onPlayerChange: (String) -> Unit, onHostChange: (String) -> Unit, onPortChange: (String) -> Unit, onConnect: () -> Unit, onDisconnect: () -> Unit, ) { - Scaffold( - modifier = modifier, - topBar = { - TopAppBar( - title = { - Text( - overflow = TextOverflow.Ellipsis, - maxLines = 1, - text = stringResource(Res.string.network__title), - ) - }, - navigationIcon = { - IconButton( - onClick = onBack, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.ArrowBack, - contentDescription = null, - ) - } - } + Column( + modifier = Modifier + .verticalScroll(state = scrollState) + .padding(paddingValues) + .padding(all = 16.dp) + .then(other = modifier), + ) { + TextField( + modifier = Modifier.fillMaxWidth(), + singleLine = true, + enabled = player.value.enableFields, + label = { Text(text = stringResource(Res.string.network__player_name__label)) }, + onValueChange = { onPlayerChange(it) }, + value = player.value.player, + ) + + Spacer( + modifier = Modifier.height(16.dp), + ) + + Row( + horizontalArrangement = Arrangement.spacedBy(space = 8.dp), + ) { + TextField( + modifier = Modifier.weight(1f), + singleLine = true, + enabled = player.value.enableFields, + label = { Text(text = stringResource(Res.string.network__host__label)) }, + onValueChange = { onHostChange(it) }, + value = player.value.host, + ) + TextField( + modifier = Modifier.width(100.dp), + singleLine = true, + enabled = player.value.enableFields, + label = { Text(text = stringResource(Res.string.network__port__label)) }, + onValueChange = { onPortChange(it) }, + value = player.value.port, ) - }, - content = { paddingValues -> - Column( - modifier = Modifier - .verticalScroll(state = scrollState) - .padding(paddingValues) - .padding(all = 16.dp), - ) { - TextField( - modifier = Modifier.fillMaxWidth(), - singleLine = true, - enabled = player.value.enableFields, - label = { Text(text = stringResource(Res.string.network__player_name__label)) }, - onValueChange = { onPlayerChange(it) }, - value = player.value.player, - ) - - Spacer( - modifier = Modifier.height(16.dp), - ) - - Row( - horizontalArrangement = Arrangement.spacedBy(space = 8.dp), - ) { - TextField( - modifier = Modifier.weight(1f), - singleLine = true, - enabled = player.value.enableFields, - label = { Text(text = stringResource(Res.string.network__host__label)) }, - onValueChange = { onHostChange(it) }, - value = player.value.host, - ) - TextField( - modifier = Modifier.width(100.dp), - singleLine = true, - enabled = player.value.enableFields, - label = { Text(text = stringResource(Res.string.network__port__label)) }, - onValueChange = { onPortChange(it) }, - value = player.value.port, - ) - } - - TextButton( - enabled = player.value.enableActions, - onClick = onConnect, - ) { - Text(text = stringResource(Res.string.network__socket__connect_action)) - } - - TextButton( - enabled = player.value.enableCancel, - onClick = onDisconnect, - ) { - Text(text = stringResource(Res.string.network__socket__disconnect_action)) - } - } } - ) + + TextButton( + enabled = player.value.enableActions, + onClick = onConnect, + ) { + Text(text = stringResource(Res.string.network__socket__connect_action)) + } + + TextButton( + enabled = player.value.enableCancel, + onClick = onDisconnect, + ) { + Text(text = stringResource(Res.string.network__socket__disconnect_action)) + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt index 9f44169..78418fb 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt @@ -9,6 +9,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.ui.composable.blur.BlurContentController import com.pixelized.desktop.lwa.ui.composable.error.ErrorSnackUio import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -22,8 +23,8 @@ class NetworkViewModel( private val factory: NetworkFactory, ) : ViewModel() { private val settings = settingsRepository.settings() - private val host = mutableStateOf(settings.host) - private val port = mutableStateOf(settings.port) + private val hostFlow = MutableStateFlow(settings.host) + private val portFlow = MutableStateFlow(settings.port) private val _networkError = MutableSharedFlow() val networkError: SharedFlow get() = _networkError @@ -34,17 +35,19 @@ class NetworkViewModel( private val _isLoading = mutableStateOf(false) val isLoading: State get() = _isLoading - val controller: BlurContentController = BlurContentController() + val blurController: BlurContentController = BlurContentController() val network: StateFlow = combine( settingsRepository.settingsFlow(), networkRepository.status, - ) { settings, status -> + hostFlow, + portFlow, + ) { settings, status, host, port -> factory.convertToUio( player = settings.playerName, status = status, - host = host.value, - port = port.value, + host = host, + port = port, ) }.stateIn( scope = viewModelScope, @@ -61,36 +64,36 @@ class NetworkViewModel( } fun onPortChange(port: String) { - this.port.value = port.toIntOrNull() ?: settings.port + this.portFlow.value = port.toIntOrNull() ?: settings.port } fun onHostChange(host: String) { - this.host.value = host + this.hostFlow.value = host } fun connect() { - controller.show() + blurController.show() _isLoading.value = true - if (settings.host != host.value || settings.port != port.value) { + if (settings.host != hostFlow.value || settings.port != portFlow.value) { settingsRepository.update( settings = settings.copy( - host = host.value, - port = port.value + host = hostFlow.value, + port = portFlow.value ) ) } networkRepository.connect( - host = host.value, - port = port.value, + host = hostFlow.value, + port = portFlow.value, onConnect = { _isLoading.value = false - controller.hide() + blurController.hide() }, onFailure = { _isLoading.value = false - controller.hide() + blurController.hide() viewModelScope.launch { _networkError.emit(ErrorSnackUio(it)) } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt index 093c6f8..2287c75 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt @@ -8,6 +8,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.ViewModel import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.network.NetworkRepository +import com.pixelized.desktop.lwa.repository.settings.SettingsRepository import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.CharacterSheetPageUio import com.pixelized.desktop.lwa.ui.screen.roll.DifficultyUio.Difficulty import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet @@ -33,6 +34,7 @@ import org.jetbrains.compose.resources.getString class RollViewModel( private val characterSheetRepository: CharacterSheetRepository, + private val settingsRepository: SettingsRepository, private val skillComputation: ExpressionUseCase, private val skillStepUseCase: SkillStepUseCase, private val networkRepository: NetworkRepository, diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryItem.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryItem.kt index 8689a5e..88f2abb 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryItem.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryItem.kt @@ -42,7 +42,7 @@ fun RollHistoryItem( modifier = Modifier.weight(1f) ) { Row( - horizontalArrangement = Arrangement.spacedBy(4.dp), + horizontalArrangement = Arrangement.spacedBy(space = 4.dp), ) { Text( modifier = Modifier.alignByBaseline(), diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryPage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryPage.kt index a295e1d..420aea3 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryPage.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryPage.kt @@ -13,6 +13,7 @@ import androidx.compose.material.Text import androidx.compose.material.TopAppBar import androidx.compose.runtime.Composable import androidx.compose.runtime.State +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Modifier import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -26,12 +27,13 @@ import org.koin.compose.viewmodel.koinViewModel fun RollHistoryPage( viewModel: RollHistoryViewModel = koinViewModel(), ) { + val rolls = viewModel.rolls.collectAsState() Surface( modifier = Modifier.fillMaxSize(), ) { RollHistoryContent( modifier = Modifier.fillMaxSize(), - rolls = viewModel.rolls, + rolls = rolls, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt index 3bcd53b..265ef37 100644 --- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt +++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/rollhistory/RollHistoryViewModel.kt @@ -1,47 +1,44 @@ package com.pixelized.desktop.lwa.ui.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.campaign.model.CharacterSheetPreview import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository import com.pixelized.desktop.lwa.repository.roll_history.RollHistoryRepository -import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.combine -import kotlinx.coroutines.launch +import kotlinx.coroutines.flow.stateIn class RollHistoryViewModel( - private val characterRepository: CharacterSheetRepository, - private val rollRepository: RollHistoryRepository, + characterRepository: CharacterSheetRepository, + rollRepository: RollHistoryRepository, ) : ViewModel() { - private val _rolls = mutableStateOf((emptyList())) - val rolls: State> get() = _rolls - - init { - viewModelScope.launch { - combine( - characterRepository.characterSheetPreviewFlow, - rollRepository.rolls, - ) { sheets: List, content -> - _rolls.value.toMutableList().apply { - val name = sheets.firstOrNull { it.id == content.characterId }?.name ?: "" - add( - index = 0, - element = RollHistoryItemUio( - character = name, - skillLabel = content.skillLabel, - rollDifficulty = content.rollDifficulty, - resultLabel = content.resultLabel, - rollValue = content.rollValue, - rollSuccessLimit = content.rollSuccessLimit, - ) - ) - } - }.collect { content -> - _rolls.value = content - } + private var _rolls: List = emptyList() + val rolls = combine( + characterRepository.characterSheetPreviewFlow, + rollRepository.rolls, + ) { sheets, message -> + val name = sheets.firstOrNull { it.id == message.characterId }?.name ?: "" + val roll = RollHistoryItemUio( + character = name, + skillLabel = message.skillLabel, + rollDifficulty = message.rollDifficulty, + resultLabel = message.resultLabel, + rollValue = message.rollValue, + rollSuccessLimit = message.rollSuccessLimit, + ) + _rolls = _rolls.toMutableList().also { + if (it.size == MAX_ROLL_HISTORY_SIZE) it.removeAt(index = it.lastIndex) + it.add(index = 0, element = roll) } + _rolls + }.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = _rolls, + ) + + companion object { + const val MAX_ROLL_HISTORY_SIZE = 100 } } \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 221c87d..b0e746f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,8 +7,8 @@ androidx-lifecycle = "2.8.3" androidx-navigation = "2.8.0-alpha10" ktor = "3.0.1" koin = "4.0.0" -turtle = "0.5.0" -logback = "1.5.11" +turtle = "0.10.0" +logback = "1.5.17" coil = "3.1.0" [plugins] @@ -47,6 +47,7 @@ ktor-server-core = { group = 'io.ktor', name = "ktor-server-core", version.ref = ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref = "ktor" } ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" } ktor-server-negotiation = { group = 'io.ktor', name = 'ktor-server-content-negotiation', version.ref = "ktor" } - +# Utilitary run command line. turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" } +# Utilitary logging use by Ktor. logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" } \ No newline at end of file