Change the access to some old screen from the new UI.
This commit is contained in:
		
							parent
							
								
									1bf0cb7c0a
								
							
						
					
					
						commit
						3941c5dd76
					
				
					 25 changed files with 657 additions and 450 deletions
				
			
		| 
						 | 
				
			
			@ -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()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<HttpClientEngine> {
 | 
			
		||||
            OkHttp.create()
 | 
			
		||||
        }
 | 
			
		||||
        single {
 | 
			
		||||
            HttpClient(
 | 
			
		||||
                engine = get()
 | 
			
		||||
                engine = OkHttp.create(),
 | 
			
		||||
            ) {
 | 
			
		||||
                install(WebSockets) {
 | 
			
		||||
                    pingIntervalMillis = 20_000
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<Message>()
 | 
			
		||||
    private val incomingMessageBuffer = MutableSharedFlow<Message>()
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,7 +13,7 @@ suspend fun HttpClient.connectWebSocket(
 | 
			
		|||
) {
 | 
			
		||||
    webSocket(
 | 
			
		||||
        method = HttpMethod.Get,
 | 
			
		||||
        host = host,
 | 
			
		||||
        host = host.removePrefix("http://"),
 | 
			
		||||
        port = port,
 | 
			
		||||
        path = "/ws",
 | 
			
		||||
        block = block,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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 {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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://")}"
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
            )
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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(
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String> = 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,
 | 
			
		||||
                        )
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<String>,
 | 
			
		||||
    isOverflowMenuOpen: State<Boolean>,
 | 
			
		||||
    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",
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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()
 | 
			
		||||
                        },
 | 
			
		||||
                    )
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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),
 | 
			
		||||
                                )
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
        },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<NetworkPageUio>,
 | 
			
		||||
    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))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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<ErrorSnackUio>()
 | 
			
		||||
    val networkError: SharedFlow<ErrorSnackUio> get() = _networkError
 | 
			
		||||
| 
						 | 
				
			
			@ -34,17 +35,19 @@ class NetworkViewModel(
 | 
			
		|||
    private val _isLoading = mutableStateOf(false)
 | 
			
		||||
    val isLoading: State<Boolean> get() = _isLoading
 | 
			
		||||
 | 
			
		||||
    val controller: BlurContentController = BlurContentController()
 | 
			
		||||
    val blurController: BlurContentController = BlurContentController()
 | 
			
		||||
 | 
			
		||||
    val network: StateFlow<NetworkPageUio> = 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))
 | 
			
		||||
                }
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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(),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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,
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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<RollHistoryItemUio>()))
 | 
			
		||||
    val rolls: State<List<RollHistoryItemUio>> get() = _rolls
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            combine(
 | 
			
		||||
                characterRepository.characterSheetPreviewFlow,
 | 
			
		||||
                rollRepository.rolls,
 | 
			
		||||
            ) { sheets: List<CharacterSheetPreview>, 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<RollHistoryItemUio> = 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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue