From 2ec7995e4dbf46b0d5932a0b578573128ec1023c Mon Sep 17 00:00:00 2001 From: Sean Proctor Date: Sun, 17 Nov 2024 05:18:14 -0500 Subject: [PATCH] implement multiple windows/connections --- .../warlockfe/warlock3/app/AppMenuBar.kt | 2 + .../kotlin/warlockfe/warlock3/app/Main.kt | 82 +++++++++++++++---- .../warlockfe/warlock3/app/WarlockApp.kt | 36 +++----- .../warlock3/app/di/JvmAppContainer.kt | 8 +- .../warlock3/compose/AppContainer.kt | 10 --- .../warlockfe/warlock3/compose/MainScreen.kt | 39 ++++----- .../warlock3/compose/macros/MacroCommands.kt | 47 +++++------ .../warlock3/compose/model/GameState.kt | 24 ++++-- .../ui/dashboard/DashboardViewModel.kt | 22 +++-- .../ui/dashboard/DashboardViewModelFactory.kt | 7 +- .../warlock3/compose/ui/game/GameViewModel.kt | 9 +- .../compose/ui/game/GameViewModelFactory.kt | 6 +- .../warlock3/compose/ui/game/WarlockEntry.kt | 4 +- .../warlock3/compose/ui/sge/SgeViewModel.kt | 33 ++++---- .../compose/ui/sge/SgeViewModelFactory.kt | 7 +- .../core/client/WarlockClientFactory.kt | 8 +- gradle/libs.versions.toml | 14 ++-- 17 files changed, 196 insertions(+), 162 deletions(-) diff --git a/app/src/main/kotlin/warlockfe/warlock3/app/AppMenuBar.kt b/app/src/main/kotlin/warlockfe/warlock3/app/AppMenuBar.kt index 124809c..1e7ede4 100644 --- a/app/src/main/kotlin/warlockfe/warlock3/app/AppMenuBar.kt +++ b/app/src/main/kotlin/warlockfe/warlock3/app/AppMenuBar.kt @@ -23,6 +23,7 @@ fun FrameWindowScope.AppMenuBar( windowRepository: WindowRepository, scriptEngineRegistry: ScriptManager, runScript: (File) -> Unit, + newWindow: () -> Unit, showSettings: () -> Unit, disconnect: (() -> Unit)?, warlockVersion: String, @@ -33,6 +34,7 @@ fun FrameWindowScope.AppMenuBar( var scriptDirectory by remember { mutableStateOf(null) } MenuBar { Menu("File") { + Item("New window", onClick = newWindow) Item("Settings", onClick = showSettings) if (characterId != null) { Item( diff --git a/app/src/main/kotlin/warlockfe/warlock3/app/Main.kt b/app/src/main/kotlin/warlockfe/warlock3/app/Main.kt index c08abb2..fe8b319 100644 --- a/app/src/main/kotlin/warlockfe/warlock3/app/Main.kt +++ b/app/src/main/kotlin/warlockfe/warlock3/app/Main.kt @@ -2,6 +2,8 @@ package warlockfe.warlock3.app import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.graphics.painter.BitmapPainter import androidx.compose.ui.graphics.painter.Painter @@ -15,13 +17,18 @@ import ca.gosyer.appdirs.AppDirs import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.cli.ArgParser import kotlinx.cli.ArgType +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.runBlocking import org.slf4j.simple.SimpleLogger.DEFAULT_LOG_LEVEL_KEY import warlockfe.warlock3.app.di.JvmAppContainer +import warlockfe.warlock3.compose.model.GameScreen +import warlockfe.warlock3.compose.model.GameState +import warlockfe.warlock3.compose.ui.window.StreamRegistryImpl import warlockfe.warlock3.compose.util.LocalLogger import warlockfe.warlock3.compose.util.insertDefaultMacrosIfNeeded +import warlockfe.warlock3.core.prefs.WindowRepository import warlockfe.warlock3.core.prefs.sql.Database import warlockfe.warlock3.core.sge.SimuGameCredentials import warlockfe.warlock3.core.util.WarlockDirs @@ -101,29 +108,61 @@ fun main(args: Array) { val clientSettings = appContainer.clientSettings val initialWidth = runBlocking { clientSettings.getWidth() } ?: 640 val initialHeight = runBlocking { clientSettings.getHeight() } ?: 480 - val windowState = WindowState(width = initialWidth.dp, height = initialHeight.dp) + val games = mutableStateListOf( + GameState( + windowRepository = WindowRepository(database.windowSettingsQueries, Dispatchers.IO), + streamRegistry = StreamRegistryImpl() + ).apply { + if (credentials != null) { + val client = appContainer.warlockClientFactory.createStormFrontClient( + credentials, + windowRepository, + streamRegistry + ) + client.connect() + val viewModel = appContainer.gameViewModelFactory.create(client, windowRepository, streamRegistry) + screen = GameScreen.ConnectedGameState(viewModel) + } + } + ) application { CompositionLocalProvider( LocalLogger provides logger ) { - Window( - title = "Warlock 3", - state = windowState, - icon = appIcon, - onCloseRequest = ::exitApplication, - ) { - WarlockApp( - appContainer = appContainer, - credentials = credentials, - ) - LaunchedEffect(windowState) { - snapshotFlow { windowState.size } - .onEach { size -> - clientSettings.putWidth(size.width.value.roundToInt()) - clientSettings.putHeight(size.height.value.roundToInt()) + games.forEachIndexed { index, gameState -> + val windowState = remember { WindowState(width = initialWidth.dp, height = initialHeight.dp) } + Window( + title = "Warlock 3 - ${gameState.getTitle()}", + state = windowState, + icon = appIcon, + onCloseRequest = { + games.removeAt(index) + if (games.isEmpty()) { + exitApplication() } - .launchIn(this) + }, + ) { + WarlockApp( + appContainer = appContainer, + gameState = gameState, + newWindow = { + games.add( + GameState( + windowRepository = WindowRepository(database.windowSettingsQueries, Dispatchers.IO), + streamRegistry = StreamRegistryImpl() + ) + ) + }, + ) + LaunchedEffect(windowState) { + snapshotFlow { windowState.size } + .onEach { size -> + clientSettings.putWidth(size.width.value.roundToInt()) + clientSettings.putHeight(size.height.value.roundToInt()) + } + .launchIn(this) + } } } } @@ -144,3 +183,12 @@ private val appIcon: Painter? by lazy { null } } + +private fun GameState.getTitle(): String { + return when (val screen = this.screen) { + GameScreen.Dashboard -> "Dashboard" + is GameScreen.ConnectedGameState -> screen.viewModel.properties.value["character"] ?: "N/A" + is GameScreen.NewGameState -> "New game" + is GameScreen.ErrorState -> "Error" + } +} \ No newline at end of file diff --git a/app/src/main/kotlin/warlockfe/warlock3/app/WarlockApp.kt b/app/src/main/kotlin/warlockfe/warlock3/app/WarlockApp.kt index f91d512..29e82b5 100644 --- a/app/src/main/kotlin/warlockfe/warlock3/app/WarlockApp.kt +++ b/app/src/main/kotlin/warlockfe/warlock3/app/WarlockApp.kt @@ -9,19 +9,19 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.window.FrameWindowScope import warlockfe.warlock3.compose.AppContainer import warlockfe.warlock3.compose.MainScreen +import warlockfe.warlock3.compose.model.GameScreen import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.theme.AppTheme import warlockfe.warlock3.core.client.GameCharacter -import warlockfe.warlock3.core.sge.SimuGameCredentials @Composable fun FrameWindowScope.WarlockApp( appContainer: AppContainer, - credentials: SimuGameCredentials?, + gameState: GameState, + newWindow: () -> Unit, ) { var showSettings by remember { mutableStateOf(false) } @@ -32,34 +32,21 @@ fun FrameWindowScope.WarlockApp( unhoverColor = MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.12f) ) ) { - val clipboardManager = LocalClipboardManager.current - val gameState = remember { - val initialGameState = if (credentials != null) { - val client = - appContainer.warlockClientFactory.createStormFrontClient(credentials) - client.connect() - val viewModel = - appContainer.gameViewModelFactory.create(client, clipboardManager) - GameState.ConnectedGameState(viewModel) - } else { - GameState.Dashboard - } - mutableStateOf(initialGameState) - } - val characterId = when (val currentState = gameState.value) { - is GameState.ConnectedGameState -> currentState.viewModel.client.characterId.collectAsState().value + val characterId = when (val screen = gameState.screen) { + is GameScreen.ConnectedGameState -> screen.viewModel.client.characterId.collectAsState().value else -> null } AppMenuBar( characterId = characterId, - windowRepository = appContainer.windowRepository, + windowRepository = gameState.windowRepository, scriptEngineRegistry = appContainer.scriptManager, + newWindow = newWindow, showSettings = { showSettings = true }, disconnect = null, runScript = { - val currentGameState = gameState.value - if (currentGameState is GameState.ConnectedGameState) { - currentGameState.viewModel.runScript(it) + val screen = gameState.screen + if (screen is GameScreen.ConnectedGameState) { + screen.viewModel.runScript(it) } }, warlockVersion = System.getProperty("app.version", "development") @@ -70,8 +57,7 @@ fun FrameWindowScope.WarlockApp( MainScreen( sgeViewModelFactory = appContainer.sgeViewModelFactory, dashboardViewModelFactory = appContainer.dashboardViewModelFactory, - gameState = gameState.value, - updateGameState = { gameState.value = it }, + gameState = gameState, updateCurrentCharacter = { currentCharacter = it } ) diff --git a/app/src/main/kotlin/warlockfe/warlock3/app/di/JvmAppContainer.kt b/app/src/main/kotlin/warlockfe/warlock3/app/di/JvmAppContainer.kt index 6226b5f..0590a1b 100644 --- a/app/src/main/kotlin/warlockfe/warlock3/app/di/JvmAppContainer.kt +++ b/app/src/main/kotlin/warlockfe/warlock3/app/di/JvmAppContainer.kt @@ -6,12 +6,14 @@ import warlockfe.warlock3.compose.AppContainer import warlockfe.warlock3.compose.resources.MR import warlockfe.warlock3.core.client.WarlockClient import warlockfe.warlock3.core.client.WarlockClientFactory +import warlockfe.warlock3.core.prefs.WindowRepository import warlockfe.warlock3.core.prefs.sql.Database import warlockfe.warlock3.core.script.ScriptManager import warlockfe.warlock3.core.sge.SgeClient import warlockfe.warlock3.core.sge.SgeClientFactory import warlockfe.warlock3.core.sge.SimuGameCredentials import warlockfe.warlock3.core.util.WarlockDirs +import warlockfe.warlock3.core.window.StreamRegistry import warlockfe.warlock3.scripting.WarlockScriptEngineRegistry import warlockfe.warlock3.stormfront.network.SgeClientImpl import warlockfe.warlock3.stormfront.network.StormfrontClient @@ -39,7 +41,11 @@ class JvmAppContainer( } override val warlockClientFactory: WarlockClientFactory = object : WarlockClientFactory { - override fun createStormFrontClient(credentials: SimuGameCredentials): WarlockClient { + override fun createStormFrontClient( + credentials: SimuGameCredentials, + windowRepository: WindowRepository, + streamRegistry: StreamRegistry, + ): WarlockClient { return StormfrontClient( host = credentials.host, port = credentials.port, diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/AppContainer.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/AppContainer.kt index 1c96078..5a32971 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/AppContainer.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/AppContainer.kt @@ -5,7 +5,6 @@ import warlockfe.warlock3.compose.components.CompassTheme import warlockfe.warlock3.compose.ui.dashboard.DashboardViewModelFactory import warlockfe.warlock3.compose.ui.game.GameViewModelFactory import warlockfe.warlock3.compose.ui.sge.SgeViewModelFactory -import warlockfe.warlock3.compose.ui.window.StreamRegistryImpl import warlockfe.warlock3.compose.util.loadCompassTheme import warlockfe.warlock3.core.client.WarlockClientFactory import warlockfe.warlock3.core.prefs.AccountRepository @@ -19,7 +18,6 @@ import warlockfe.warlock3.core.prefs.MacroRepository import warlockfe.warlock3.core.prefs.PresetRepository import warlockfe.warlock3.core.prefs.ScriptDirRepository import warlockfe.warlock3.core.prefs.VariableRepository -import warlockfe.warlock3.core.prefs.WindowRepository import warlockfe.warlock3.core.prefs.sql.Database import warlockfe.warlock3.core.script.ScriptManager import warlockfe.warlock3.core.sge.SgeClientFactory @@ -54,11 +52,6 @@ abstract class AppContainer( database.clientSettingQueries, ioDispatcher ) - val windowRepository = - WindowRepository( - database.windowSettingsQueries, - ioDispatcher - ) val scriptDirRepository = ScriptDirRepository( scriptDirQueries = database.scriptDirQueries, @@ -70,7 +63,6 @@ abstract class AppContainer( characterSettingsQueries = database.characterSettingQueries, ioDispatcher = ioDispatcher ) - val streamRegistry = StreamRegistryImpl() val aliasRepository = AliasRepository( database.aliasQueries, @@ -88,7 +80,6 @@ abstract class AppContainer( abstract val scriptManager: ScriptManager val gameViewModelFactory by lazy { GameViewModelFactory( - windowRepository = windowRepository, macroRepository = macroRepository, variableRepository = variableRepository, scriptManager = scriptManager, @@ -97,7 +88,6 @@ abstract class AppContainer( presetRepository = presetRepository, characterSettingsRepository = characterSettingsRepository, aliasRepository = aliasRepository, - streamRegistry = streamRegistry, ) } diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/MainScreen.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/MainScreen.kt index e958551..f616e8f 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/MainScreen.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/MainScreen.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalClipboardManager +import warlockfe.warlock3.compose.model.GameScreen import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.dashboard.DashboardView import warlockfe.warlock3.compose.ui.dashboard.DashboardViewModelFactory @@ -24,56 +24,47 @@ fun MainScreen( sgeViewModelFactory: SgeViewModelFactory, dashboardViewModelFactory: DashboardViewModelFactory, gameState: GameState, - updateGameState: (GameState) -> Unit, updateCurrentCharacter: (GameCharacter?) -> Unit, ) { - when (gameState) { - GameState.Dashboard -> { - val clipboardManager = LocalClipboardManager.current + when (val screen = gameState.screen) { + GameScreen.Dashboard -> { val viewModel = remember { - dashboardViewModelFactory.create( - updateGameState = updateGameState, - clipboardManager = clipboardManager, - ) + dashboardViewModelFactory.create(gameState) } DashboardView( viewModel = viewModel, - connectToSGE = { updateGameState(GameState.NewGameState) } + connectToSGE = { gameState.screen = GameScreen.NewGameState } ) } - GameState.NewGameState -> { - val clipboardManager = LocalClipboardManager.current + GameScreen.NewGameState -> { val viewModel = remember { - sgeViewModelFactory.create( - clipboardManager = clipboardManager, - updateGameState = updateGameState - ) + sgeViewModelFactory.create(gameState) } - SgeWizard(viewModel = viewModel, onCancel = { updateGameState(GameState.Dashboard) }) + SgeWizard(viewModel = viewModel, onCancel = { gameState.screen = GameScreen.Dashboard }) } - is GameState.ConnectedGameState -> { - val character = gameState.viewModel.character.collectAsState(null).value + is GameScreen.ConnectedGameState -> { + val character = screen.viewModel.character.collectAsState(null).value updateCurrentCharacter(character) GameView( - viewModel = gameState.viewModel, + viewModel = screen.viewModel, navigateToDashboard = { - updateGameState(GameState.Dashboard) + gameState.screen = GameScreen.Dashboard } ) } - is GameState.ErrorState -> { + is GameScreen.ErrorState -> { Column( modifier = Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally, ) { - Text(text = gameState.message) + Text(text = screen.message) Button( - onClick = { updateGameState(GameState.NewGameState) } + onClick = { gameState.screen = GameScreen.NewGameState } ) { Text("OK") } diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/macros/MacroCommands.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/macros/MacroCommands.kt index ad9237f..b40440f 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/macros/MacroCommands.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/macros/MacroCommands.kt @@ -1,42 +1,43 @@ package warlockfe.warlock3.compose.macros +import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.text.AnnotatedString import warlockfe.warlock3.compose.ui.game.GameViewModel -val macroCommands = mapOf Unit>( - "copy" to { - val textField = it.entryText +val macroCommands = mapOf Unit>( + "copy" to { viewModel, clipboard -> + val textField = viewModel.entryText val text = textField.text.substring(textField.selection.start, textField.selection.end) - it.clipboard.setText(AnnotatedString(text)) + clipboard.setText(AnnotatedString(text)) }, - "paste" to { - it.clipboard.getText()?.let { text -> - it.entryInsert(text.text) + "paste" to { viewModel, clipboard -> + clipboard.getText()?.let { text -> + viewModel.entryInsert(text.text) } }, - "prevhistory" to { - it.historyPrev() + "prevhistory" to { viewModel, _ -> + viewModel.historyPrev() }, - "nexthistory" to { - it.historyNext() + "nexthistory" to { viewModel, _ -> + viewModel.historyNext() }, - "stopscript" to { - it.stopScripts() + "stopscript" to { viewModel, _ -> + viewModel.stopScripts() }, - "pausescript" to { - it.pauseScripts() + "pausescript" to { viewModel, _ -> + viewModel.pauseScripts() }, - "repeatlast" to { - it.repeatCommand(0) + "repeatlast" to { viewModel, _ -> + viewModel.repeatCommand(0) }, - "returnorrepeatlast" to { - if (it.entryText.text.isBlank()) { - it.repeatCommand(0) + "returnorrepeatlast" to { viewModel, _ -> + if (viewModel.entryText.text.isBlank()) { + viewModel.repeatCommand(0) } else { - it.submit() + viewModel.submit() } }, - "repeatsecondtolast" to { - it.repeatCommand(1) + "repeatsecondtolast" to { viewModel, _ -> + viewModel.repeatCommand(1) } ) \ No newline at end of file diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/model/GameState.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/model/GameState.kt index 62c24ab..b5a0569 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/model/GameState.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/model/GameState.kt @@ -1,11 +1,23 @@ package warlockfe.warlock3.compose.model +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import warlockfe.warlock3.compose.ui.game.GameViewModel +import warlockfe.warlock3.core.prefs.WindowRepository +import warlockfe.warlock3.core.window.StreamRegistry -sealed class GameState { - object Dashboard : GameState() - object NewGameState : GameState() - data class ConnectedGameState(val viewModel: GameViewModel) : GameState() - - data class ErrorState(val message: String) : GameState() +class GameState( + val windowRepository: WindowRepository, + val streamRegistry: StreamRegistry, +) { + var screen by mutableStateOf(GameScreen.Dashboard) } + +sealed interface GameScreen { + data object Dashboard : GameScreen + data object NewGameState : GameScreen + data class ConnectedGameState(val viewModel: GameViewModel) : GameScreen + + data class ErrorState(val message: String) : GameScreen +} \ No newline at end of file diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModel.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModel.kt index 18c8263..61be805 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModel.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModel.kt @@ -1,6 +1,5 @@ package warlockfe.warlock3.compose.ui.dashboard -import androidx.compose.ui.platform.ClipboardManager import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi @@ -13,6 +12,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch +import warlockfe.warlock3.compose.model.GameScreen import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.game.GameViewModelFactory import warlockfe.warlock3.core.client.GameCharacter @@ -26,8 +26,7 @@ import java.net.UnknownHostException class DashboardViewModel( characterRepository: CharacterRepository, private val accountRepository: AccountRepository, - private val updateGameState: (GameState) -> Unit, - private val clipboardManager: ClipboardManager, + private val gameState: GameState, private val sgeClientFactory: SgeClientFactory, private val warlockClientFactory: WarlockClientFactory, private val gameViewModelFactory: GameViewModelFactory, @@ -75,25 +74,30 @@ class DashboardViewModel( client.selectCharacter(sgeCharacter.code) } } + is SgeEvent.SgeReadyToPlayEvent -> { val credentials = event.credentials try { - val sfClient = warlockClientFactory.createStormFrontClient(credentials) - sfClient.connect() - val gameViewModel = gameViewModelFactory.create(sfClient, clipboardManager) - updateGameState( - GameState.ConnectedGameState(gameViewModel) + val sfClient = warlockClientFactory.createStormFrontClient( + credentials, + gameState.windowRepository, + gameState.streamRegistry, ) + sfClient.connect() + val gameViewModel = gameViewModelFactory.create(sfClient, gameState.windowRepository, gameState.streamRegistry) + gameState.screen = GameScreen.ConnectedGameState(gameViewModel) } catch (e: UnknownHostException) { - updateGameState(GameState.ErrorState("Unknown host: ${e.message}")) + gameState.screen = GameScreen.ErrorState("Unknown host: ${e.message}") } client.close() cancelConnect() } + is SgeEvent.SgeErrorEvent -> { _message.value = "Error code (${event.errorCode})" connectJob?.cancel() } + else -> Unit // we don't care? } } diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModelFactory.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModelFactory.kt index 116a4cf..d0712f4 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModelFactory.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/dashboard/DashboardViewModelFactory.kt @@ -1,6 +1,5 @@ package warlockfe.warlock3.compose.ui.dashboard -import androidx.compose.ui.platform.ClipboardManager import kotlinx.coroutines.CoroutineDispatcher import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.game.GameViewModelFactory @@ -18,14 +17,12 @@ class DashboardViewModelFactory( private val ioDispatcher: CoroutineDispatcher, ) { fun create( - updateGameState: (GameState) -> Unit, - clipboardManager: ClipboardManager, + gameState: GameState ): DashboardViewModel { return DashboardViewModel( characterRepository = characterRepository, accountRepository = accountRepository, - updateGameState = updateGameState, - clipboardManager = clipboardManager, + gameState = gameState, gameViewModelFactory = gameViewModelFactory, sgeClientFactory = sgeClientFactory, warlockClientFactory = warlockClientFactory, diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModel.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModel.kt index a0c12a0..ee19863 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModel.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModel.kt @@ -83,7 +83,6 @@ class GameViewModel( presetRepository: PresetRepository, private val scriptManager: ScriptManager, val compassTheme: CompassTheme, - val clipboard: ClipboardManager, private val characterSettingsRepository: CharacterSettingsRepository, aliasRepository: AliasRepository, private val streamRegistry: StreamRegistry, @@ -397,7 +396,7 @@ class GameViewModel( } } - fun handleKeyPress(event: KeyEvent): Boolean { + fun handleKeyPress(event: KeyEvent, clipboard: ClipboardManager): Boolean { if (event.type != KeyEventType.KeyDown) { return false } @@ -412,12 +411,12 @@ class GameViewModel( // TODO: notify when macro fails to parse val tokens = parseMacroCommand(macroString).getOrNull() ?: return false - executeMacro(tokens) + executeMacro(tokens, clipboard) return true } - private fun executeMacro(tokens: List) { + private fun executeMacro(tokens: List, clipboard: ClipboardManager) { viewModelScope.launch { var movedCursor = false tokens.forEach { token -> @@ -445,7 +444,7 @@ class GameViewModel( is MacroToken.Command -> { val command = macroCommands[token.name] - command?.invoke(this@GameViewModel) + command?.invoke(this@GameViewModel, clipboard) } } } diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModelFactory.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModelFactory.kt index f05f02e..8f7b3df 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModelFactory.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/GameViewModelFactory.kt @@ -1,6 +1,5 @@ package warlockfe.warlock3.compose.ui.game -import androidx.compose.ui.platform.ClipboardManager import warlockfe.warlock3.compose.components.CompassTheme import warlockfe.warlock3.core.client.WarlockClient import warlockfe.warlock3.core.prefs.AliasRepository @@ -14,7 +13,6 @@ import warlockfe.warlock3.core.script.ScriptManager import warlockfe.warlock3.core.window.StreamRegistry class GameViewModelFactory( - private val windowRepository: WindowRepository, private val macroRepository: MacroRepository, private val variableRepository: VariableRepository, private val highlightRepository: HighlightRepository, @@ -23,9 +21,8 @@ class GameViewModelFactory( private val compassTheme: CompassTheme, private val characterSettingsRepository: CharacterSettingsRepository, private val aliasRepository: AliasRepository, - private val streamRegistry: StreamRegistry, ) { - fun create(client: WarlockClient, clipboard: ClipboardManager): GameViewModel = + fun create(client: WarlockClient, windowRepository: WindowRepository, streamRegistry: StreamRegistry): GameViewModel = GameViewModel( client = client, windowRepository = windowRepository, @@ -33,7 +30,6 @@ class GameViewModelFactory( variableRepository = variableRepository, scriptManager = scriptManager, compassTheme = compassTheme, - clipboard = clipboard, highlightRepository = highlightRepository, presetRepository = presetRepository, characterSettingsRepository = characterSettingsRepository, diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/WarlockEntry.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/WarlockEntry.kt index 0b5d535..766bf24 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/WarlockEntry.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/game/WarlockEntry.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.input.key.* +import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight @@ -47,13 +48,14 @@ fun WarlockEntry(modifier: Modifier, viewModel: GameViewModel) { val presets by viewModel.presets.collectAsState(emptyMap()) val style = presets["default"] ?: defaultStyles["default"] + val clipboard = LocalClipboardManager.current WarlockEntryContent( modifier = modifier, backgroundColor = style?.backgroundColor?.toColor() ?: Color.Unspecified, textColor = style?.textColor?.toColor() ?: MaterialTheme.colorScheme.onBackground, textField = viewModel.entryText, onValueChange = viewModel::updateEntryText, - onKeyPress = viewModel::handleKeyPress, + onKeyPress = { viewModel.handleKeyPress(it, clipboard) }, roundTime = roundTime, castTime = castTime, ) diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModel.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModel.kt index bb9af88..706c7d0 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModel.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModel.kt @@ -3,7 +3,6 @@ package warlockfe.warlock3.compose.ui.sge import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf -import androidx.compose.ui.platform.ClipboardManager import io.github.oshai.kotlinlogging.KotlinLogging import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -12,6 +11,7 @@ import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import warlockfe.warlock3.compose.model.GameScreen import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.game.GameViewModelFactory import warlockfe.warlock3.core.client.GameCharacter @@ -31,12 +31,11 @@ import java.net.UnknownHostException class SgeViewModel( private val clientSettingRepository: ClientSettingRepository, private val accountRepository: AccountRepository, - private val clipboardManager: ClipboardManager, private val characterRepository: CharacterRepository, private val warlockClientFactory: WarlockClientFactory, sgeClientFactory: SgeClientFactory, private val gameViewModelFactory: GameViewModelFactory, - updateGameState: (GameState) -> Unit, + private val gameState: GameState, ) : AutoCloseable { private val logger = KotlinLogging.logger { } @@ -118,14 +117,12 @@ class SgeViewModel( } try { - val client = warlockClientFactory.createStormFrontClient(credentials) + val client = warlockClientFactory.createStormFrontClient(credentials, gameState.windowRepository, gameState.streamRegistry) client.connect() - val gameViewModel = gameViewModelFactory.create(client, clipboardManager) - updateGameState( - GameState.ConnectedGameState(gameViewModel) - ) + val gameViewModel = gameViewModelFactory.create(client, gameState.windowRepository, gameState.streamRegistry) + gameState.screen = GameScreen.ConnectedGameState(gameViewModel) } catch (e: UnknownHostException) { - updateGameState(GameState.ErrorState("Unknown host: ${e.message}")) + gameState.screen = GameScreen.ErrorState("Unknown host: ${e.message}") } close() } @@ -181,13 +178,13 @@ class SgeViewModel( } } -sealed class SgeViewState { - object SgeConnecting : SgeViewState() - object SgeAccountSelector : SgeViewState() - class SgeGameSelector(val games: List) : SgeViewState() - class SgeCharacterSelector(val game: SgeGame, val characters: List) : SgeViewState() - object SgeLoadingGameList : SgeViewState() - data class SgeLoadingCharacterList(val game: SgeGame) : SgeViewState() - data class SgeConnectingToGame(val game: SgeGame, val character: SgeCharacter) : SgeViewState() - class SgeError(val error: String) : SgeViewState() +sealed interface SgeViewState { + data object SgeConnecting : SgeViewState + data object SgeAccountSelector : SgeViewState + data class SgeGameSelector(val games: List) : SgeViewState + data class SgeCharacterSelector(val game: SgeGame, val characters: List) : SgeViewState + data object SgeLoadingGameList : SgeViewState + data class SgeLoadingCharacterList(val game: SgeGame) : SgeViewState + data class SgeConnectingToGame(val game: SgeGame, val character: SgeCharacter) : SgeViewState + data class SgeError(val error: String) : SgeViewState } \ No newline at end of file diff --git a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModelFactory.kt b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModelFactory.kt index d40f418..ac7fa6c 100644 --- a/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModelFactory.kt +++ b/compose/src/commonMain/kotlin/warlockfe/warlock3/compose/ui/sge/SgeViewModelFactory.kt @@ -1,6 +1,5 @@ package warlockfe.warlock3.compose.ui.sge -import androidx.compose.ui.platform.ClipboardManager import warlockfe.warlock3.compose.model.GameState import warlockfe.warlock3.compose.ui.game.GameViewModelFactory import warlockfe.warlock3.core.client.WarlockClientFactory @@ -18,18 +17,16 @@ class SgeViewModelFactory( private val gameViewModelFactory: GameViewModelFactory, ) { fun create( - clipboardManager: ClipboardManager, - updateGameState: (GameState) -> Unit, + gameState: GameState, ): SgeViewModel { return SgeViewModel( clientSettingRepository = clientSettingRepository, accountRepository = accountRepository, - clipboardManager = clipboardManager, characterRepository = characterRepository, warlockClientFactory = warlockClientFactory, sgeClientFactory = sgeClientFactory, gameViewModelFactory = gameViewModelFactory, - updateGameState = updateGameState, + gameState = gameState, ) } } \ No newline at end of file diff --git a/core/src/commonMain/kotlin/warlockfe/warlock3/core/client/WarlockClientFactory.kt b/core/src/commonMain/kotlin/warlockfe/warlock3/core/client/WarlockClientFactory.kt index 3c5a340..0acc1e5 100644 --- a/core/src/commonMain/kotlin/warlockfe/warlock3/core/client/WarlockClientFactory.kt +++ b/core/src/commonMain/kotlin/warlockfe/warlock3/core/client/WarlockClientFactory.kt @@ -1,7 +1,13 @@ package warlockfe.warlock3.core.client +import warlockfe.warlock3.core.prefs.WindowRepository import warlockfe.warlock3.core.sge.SimuGameCredentials +import warlockfe.warlock3.core.window.StreamRegistry interface WarlockClientFactory { - fun createStormFrontClient(credentials: SimuGameCredentials): WarlockClient + fun createStormFrontClient( + credentials: SimuGameCredentials, + windowRepository: WindowRepository, + streamRegistry: StreamRegistry, + ): WarlockClient } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 387e033..5cb6000 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -16,15 +16,15 @@ sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } [versions] agp = "8.5.2" -androidx-activity = "1.9.2" +androidx-activity = "1.9.3" androidx-core-splashscreen = "1.0.1" antlr = "4.13.2" apache-commons-text = "1.12.0" appdirs = "1.2.0" -coil = "2.7.0" -compose = "1.7.0" +coil = "3.0.3" +compose = "1.7.1" compose-color-picker = "0.7.0" -conveyor = "1.11" +conveyor = "1.12" kotlin = "2.0.21" kotlinx-cli = "0.3.6" kotlinx-collections-immutable = "0.3.8" @@ -33,7 +33,7 @@ kotlin-logging = "7.0.0" moko-resources = "0.24.3" okio = "3.9.1" rhino = "1.7.15" -slf4j = "2.0.3" +slf4j = "2.0.16" sqldelight = "2.0.2" [libraries] @@ -44,8 +44,8 @@ androidx-core-splashscreen = { group = "androidx.core", name = "core-splashscree antlr4 = { group = "org.antlr", name = "antlr4", version.ref = "antlr" } apache-commons-text = { group = "org.apache.commons", name = "commons-text", version.ref = "apache-commons-text" } compose-color-picker = { group = "com.godaddy.android.colorpicker", name = "compose-color-picker", version.ref = "compose-color-picker" } -coil-compose = { group = "io.coil-kt", name = "coil-compose", version.ref = "coil" } -coil-svg = { group = "io.coil-kt", name = "coil-svg", version.ref = "coil" } +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +coil-svg = { group = "io.coil-kt.coil3", name = "coil-svg", version.ref = "coil" } kotlinx-cli = { group = "org.jetbrains.kotlinx", name = "kotlinx-cli", version.ref = "kotlinx-cli" } kotlinx-collections-immutable = { group = "org.jetbrains.kotlinx", name = "kotlinx-collections-immutable", version.ref = "kotlinx-collections-immutable" } kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinx-coroutines" }