Add roll sharing feature over the WebSocket.
This commit is contained in:
		
							parent
							
								
									0e5fee6771
								
							
						
					
					
						commit
						f92922c228
					
				
					 17 changed files with 476 additions and 183 deletions
				
			
		| 
						 | 
					@ -2,6 +2,7 @@
 | 
				
			||||||
<resources>
 | 
					<resources>
 | 
				
			||||||
    <string name="main_page__create_action">Créer une feuille de personnage</string>
 | 
					    <string name="main_page__create_action">Créer une feuille de personnage</string>
 | 
				
			||||||
    <string name="main_page__network_action">Configuration réseau</string>
 | 
					    <string name="main_page__network_action">Configuration réseau</string>
 | 
				
			||||||
 | 
					    <string name="main_page__roll_history_action">Consulter l'historique des lancés</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <string name="roll_page__critical_success">Réussite critique</string>
 | 
					    <string name="roll_page__critical_success">Réussite critique</string>
 | 
				
			||||||
    <string name="roll_page__special_success">Réussite spéciale</string>
 | 
					    <string name="roll_page__special_success">Réussite spéciale</string>
 | 
				
			||||||
| 
						 | 
					@ -81,4 +82,6 @@
 | 
				
			||||||
    <string name="network__socket__type_server">Serveur</string>
 | 
					    <string name="network__socket__type_server">Serveur</string>
 | 
				
			||||||
    <string name="network__socket__type_client">Client</string>
 | 
					    <string name="network__socket__type_client">Client</string>
 | 
				
			||||||
    <string name="network__socket__type_none">Aucun</string>
 | 
					    <string name="network__socket__type_none">Aucun</string>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <string name="roll_history__title">Historique des lancés</string>
 | 
				
			||||||
</resources>
 | 
					</resources>
 | 
				
			||||||
| 
						 | 
					@ -1,11 +1,16 @@
 | 
				
			||||||
package com.pixelized.desktop.lwa
 | 
					package com.pixelized.desktop.lwa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
					import androidx.compose.foundation.layout.fillMaxSize
 | 
				
			||||||
 | 
					import androidx.compose.material.Scaffold
 | 
				
			||||||
 | 
					import androidx.compose.material.SnackbarHost
 | 
				
			||||||
 | 
					import androidx.compose.material.SnackbarHostState
 | 
				
			||||||
import androidx.compose.material.Surface
 | 
					import androidx.compose.material.Surface
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.CompositionLocalProvider
 | 
					import androidx.compose.runtime.CompositionLocalProvider
 | 
				
			||||||
import androidx.compose.runtime.Stable
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.compositionLocalOf
 | 
					import androidx.compose.runtime.compositionLocalOf
 | 
				
			||||||
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
import androidx.compose.runtime.remember
 | 
					import androidx.compose.runtime.remember
 | 
				
			||||||
import androidx.compose.ui.Modifier
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
import androidx.compose.ui.unit.dp
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
| 
						 | 
					@ -13,9 +18,19 @@ import androidx.compose.ui.window.ApplicationScope
 | 
				
			||||||
import androidx.compose.ui.window.Window
 | 
					import androidx.compose.ui.window.Window
 | 
				
			||||||
import androidx.compose.ui.window.rememberWindowState
 | 
					import androidx.compose.ui.window.rememberWindowState
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.MainNavHost
 | 
					import com.pixelized.desktop.lwa.navigation.MainNavHost
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.screen.main.CharacterUio
 | 
				
			||||||
import com.pixelized.desktop.lwa.theme.LwaTheme
 | 
					import com.pixelized.desktop.lwa.theme.LwaTheme
 | 
				
			||||||
 | 
					import lwacharactersheet.composeapp.generated.resources.Res
 | 
				
			||||||
 | 
					import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
 | 
				
			||||||
 | 
					import org.jetbrains.compose.resources.stringResource
 | 
				
			||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
 | 
					import org.jetbrains.compose.ui.tooling.preview.Preview
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val LocalSnackHost = compositionLocalOf<SnackbarHostState> {
 | 
				
			||||||
 | 
					    error("Local Snack Controller is not yet ready")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
val LocalWindowController = compositionLocalOf<WindowController> {
 | 
					val LocalWindowController = compositionLocalOf<WindowController> {
 | 
				
			||||||
    error("Local Window Controller is not yet ready")
 | 
					    error("Local Window Controller is not yet ready")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -24,23 +39,47 @@ val LocalWindowController = compositionLocalOf<WindowController> {
 | 
				
			||||||
data class WindowController(
 | 
					data class WindowController(
 | 
				
			||||||
    private val onCloseRequest: () -> Unit
 | 
					    private val onCloseRequest: () -> Unit
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    fun close() = onCloseRequest()
 | 
					    val sheet: State<Set<CharacterUio>> get() = _sheet
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val create: State<Set<Int>> get() = _create
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun showCreateCharacterSheet() {
 | 
				
			||||||
 | 
					        _create.value = _create.value.toMutableSet().apply { add(size) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun hideCreateCharacterSheet(id: Int) {
 | 
				
			||||||
 | 
					        _create.value = _create.value.toMutableSet().apply { remove(id) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun showCharacterSheet(sheet: CharacterUio) {
 | 
				
			||||||
 | 
					        _sheet.value = _sheet.value.toMutableSet().apply { add(sheet) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun hideCharacterSheet(sheet: CharacterUio) {
 | 
				
			||||||
 | 
					        _sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun closeWindows() = onCloseRequest()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    companion object {
 | 
				
			||||||
 | 
					        private val _sheet = mutableStateOf<Set<CharacterUio>>(emptySet())
 | 
				
			||||||
 | 
					        private val _create = mutableStateOf<Set<Int>>(emptySet())
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
@Preview
 | 
					@Preview
 | 
				
			||||||
fun ApplicationScope.App() {
 | 
					fun ApplicationScope.App() {
 | 
				
			||||||
    val controller = remember {
 | 
					    val controller = remember { WindowController(onCloseRequest = ::exitApplication) }
 | 
				
			||||||
        WindowController(
 | 
					    val snackHostState = remember { SnackbarHostState() }
 | 
				
			||||||
            onCloseRequest = ::exitApplication
 | 
					
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    CompositionLocalProvider(
 | 
					    CompositionLocalProvider(
 | 
				
			||||||
        LocalWindowController provides controller,
 | 
					        LocalWindowController provides controller,
 | 
				
			||||||
 | 
					        LocalSnackHost provides snackHostState,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        Window(
 | 
					        Window(
 | 
				
			||||||
            onCloseRequest = {
 | 
					            onCloseRequest = {
 | 
				
			||||||
                controller.close()
 | 
					                controller.closeWindows()
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
            state = rememberWindowState(
 | 
					            state = rememberWindowState(
 | 
				
			||||||
                width = 320.dp + 64.dp,
 | 
					                width = 320.dp + 64.dp,
 | 
				
			||||||
| 
						 | 
					@ -52,8 +91,85 @@ fun ApplicationScope.App() {
 | 
				
			||||||
                Surface(
 | 
					                Surface(
 | 
				
			||||||
                    modifier = Modifier.fillMaxSize()
 | 
					                    modifier = Modifier.fillMaxSize()
 | 
				
			||||||
                ) {
 | 
					                ) {
 | 
				
			||||||
 | 
					                    Scaffold(
 | 
				
			||||||
 | 
					                        snackbarHost = {
 | 
				
			||||||
 | 
					                            SnackbarHost(
 | 
				
			||||||
 | 
					                                hostState = snackHostState,
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        },
 | 
				
			||||||
 | 
					                        content = {
 | 
				
			||||||
                            MainNavHost()
 | 
					                            MainNavHost()
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    HandleCharacterSheet(
 | 
				
			||||||
 | 
					                        sheets = controller.sheet,
 | 
				
			||||||
 | 
					                        onCloseRequest = { controller.hideCharacterSheet(sheet = it) }
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                    HandleCharacterSheetCreation(
 | 
				
			||||||
 | 
					                        sheets = controller.create,
 | 
				
			||||||
 | 
					                        onCloseRequest = { controller.hideCreateCharacterSheet(id = it) },
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					fun HandleCharacterSheet(
 | 
				
			||||||
 | 
					    sheets: State<Set<CharacterUio>>,
 | 
				
			||||||
 | 
					    onCloseRequest: (id: CharacterUio) -> Unit,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    sheets.value.forEach { sheet ->
 | 
				
			||||||
 | 
					        val controller = remember {
 | 
				
			||||||
 | 
					            WindowController(
 | 
				
			||||||
 | 
					                onCloseRequest = { onCloseRequest(sheet) }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        CompositionLocalProvider(
 | 
				
			||||||
 | 
					            LocalWindowController provides controller,
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Window(
 | 
				
			||||||
 | 
					                onCloseRequest = { onCloseRequest(sheet) },
 | 
				
			||||||
 | 
					                state = rememberWindowState(
 | 
				
			||||||
 | 
					                    width = 400.dp + 64.dp,
 | 
				
			||||||
 | 
					                    height = 900.dp,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                title = sheet.name,
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                CharacterSheetMainNavHost(
 | 
				
			||||||
 | 
					                    startDestination = CharacterSheetDestination.navigationRoute(id = sheet.id)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					fun HandleCharacterSheetCreation(
 | 
				
			||||||
 | 
					    sheets: State<Set<Int>>,
 | 
				
			||||||
 | 
					    onCloseRequest: (id: Int) -> Unit,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    sheets.value.forEach { sheet ->
 | 
				
			||||||
 | 
					        val controller = remember {
 | 
				
			||||||
 | 
					            WindowController(
 | 
				
			||||||
 | 
					                onCloseRequest = { onCloseRequest(sheet) }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        CompositionLocalProvider(
 | 
				
			||||||
 | 
					            LocalWindowController provides controller,
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					            Window(
 | 
				
			||||||
 | 
					                onCloseRequest = { controller.closeWindows() },
 | 
				
			||||||
 | 
					                state = rememberWindowState(
 | 
				
			||||||
 | 
					                    width = 400.dp + 64.dp,
 | 
				
			||||||
 | 
					                    height = 900.dp,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                title = stringResource(Res.string.character_sheet_edit__title),
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                CharacterSheetMainNavHost(
 | 
				
			||||||
 | 
					                    startDestination = CharacterSheetEditDestination.navigationRoute(id = null)
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -9,6 +9,8 @@ import androidx.navigation.compose.rememberNavController
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.MainDestination
 | 
					import com.pixelized.desktop.lwa.navigation.destination.MainDestination
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
 | 
					import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.composableNetworkPage
 | 
					import com.pixelized.desktop.lwa.navigation.destination.composableNetworkPage
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.navigation.destination.composableRollHistory
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.screen.main.MainPageViewModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
val LocalScreenController = compositionLocalOf<NavHostController> {
 | 
					val LocalScreenController = compositionLocalOf<NavHostController> {
 | 
				
			||||||
    error("MainNavHost controller is not yet ready")
 | 
					    error("MainNavHost controller is not yet ready")
 | 
				
			||||||
| 
						 | 
					@ -28,6 +30,7 @@ fun MainNavHost(
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            composableMainPage()
 | 
					            composableMainPage()
 | 
				
			||||||
            composableNetworkPage()
 | 
					            composableNetworkPage()
 | 
				
			||||||
 | 
					            composableRollHistory()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import androidx.navigation.NavGraphBuilder
 | 
				
			||||||
import androidx.navigation.NavHostController
 | 
					import androidx.navigation.NavHostController
 | 
				
			||||||
import androidx.navigation.compose.composable
 | 
					import androidx.navigation.compose.composable
 | 
				
			||||||
import com.pixelized.desktop.lwa.screen.main.MainPage
 | 
					import com.pixelized.desktop.lwa.screen.main.MainPage
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.screen.main.MainPageViewModel
 | 
				
			||||||
 | 
					
 | 
				
			||||||
object MainDestination {
 | 
					object MainDestination {
 | 
				
			||||||
    private const val ROUTE = "main"
 | 
					    private const val ROUTE = "main"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,26 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.navigation.destination
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.navigation.NavGraphBuilder
 | 
				
			||||||
 | 
					import androidx.navigation.NavHostController
 | 
				
			||||||
 | 
					import androidx.navigation.compose.composable
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryPage
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object RollHistoryDestination {
 | 
				
			||||||
 | 
					    private const val ROUTE = "roll_history"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    fun baseRoute() = ROUTE
 | 
				
			||||||
 | 
					    fun navigationRoute() = ROUTE
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun NavGraphBuilder.composableRollHistory() {
 | 
				
			||||||
 | 
					    composable(
 | 
				
			||||||
 | 
					        route = RollHistoryDestination.baseRoute()
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        RollHistoryPage()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fun NavHostController.navigateToRollHistory() {
 | 
				
			||||||
 | 
					    val route = RollHistoryDestination.navigationRoute()
 | 
				
			||||||
 | 
					    navigate(route = route)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4,6 +4,7 @@ import com.pixelized.desktop.lwa.repository.network.helper.client
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
 | 
					import com.pixelized.desktop.lwa.repository.network.helper.connectWebSocket
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.helper.server
 | 
					import com.pixelized.desktop.lwa.repository.network.helper.server
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
 | 
					import com.pixelized.desktop.lwa.repository.network.protocol.Message
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.network.protocol.MessageContent
 | 
				
			||||||
import io.ktor.client.HttpClient
 | 
					import io.ktor.client.HttpClient
 | 
				
			||||||
import io.ktor.server.engine.EmbeddedServer
 | 
					import io.ktor.server.engine.EmbeddedServer
 | 
				
			||||||
import io.ktor.server.netty.NettyApplicationEngine
 | 
					import io.ktor.server.netty.NettyApplicationEngine
 | 
				
			||||||
| 
						 | 
					@ -15,8 +16,8 @@ import kotlinx.coroutines.Job
 | 
				
			||||||
import kotlinx.coroutines.channels.consumeEach
 | 
					import kotlinx.coroutines.channels.consumeEach
 | 
				
			||||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
					import kotlinx.coroutines.flow.MutableSharedFlow
 | 
				
			||||||
import kotlinx.coroutines.flow.MutableStateFlow
 | 
					import kotlinx.coroutines.flow.MutableStateFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
import kotlinx.coroutines.flow.StateFlow
 | 
					import kotlinx.coroutines.flow.StateFlow
 | 
				
			||||||
import kotlinx.coroutines.flow.asSharedFlow
 | 
					 | 
				
			||||||
import kotlinx.coroutines.launch
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
import kotlinx.coroutines.runBlocking
 | 
					import kotlinx.coroutines.runBlocking
 | 
				
			||||||
import kotlinx.serialization.json.Json
 | 
					import kotlinx.serialization.json.Json
 | 
				
			||||||
| 
						 | 
					@ -31,8 +32,9 @@ object NetworkRepository {
 | 
				
			||||||
    private var server: Server? = null
 | 
					    private var server: Server? = null
 | 
				
			||||||
    private var client: Client? = null
 | 
					    private var client: Client? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val messageResponseFlow = MutableSharedFlow<String>()
 | 
					    private val outgoingMessageBuffer = MutableSharedFlow<Message>()
 | 
				
			||||||
    private val sharedFlow = messageResponseFlow.asSharedFlow()
 | 
					    private val incomingMessageBuffer = MutableSharedFlow<Message>()
 | 
				
			||||||
 | 
					    val data: SharedFlow<Message> get() = incomingMessageBuffer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _player = MutableStateFlow("")
 | 
					    private val _player = MutableStateFlow("")
 | 
				
			||||||
    val player: StateFlow<String> get() = _player
 | 
					    val player: StateFlow<String> get() = _player
 | 
				
			||||||
| 
						 | 
					@ -57,17 +59,19 @@ object NetworkRepository {
 | 
				
			||||||
            println("Server launched")
 | 
					            println("Server launched")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            val job = launch {
 | 
					            val job = launch {
 | 
				
			||||||
                sharedFlow.collect { message ->
 | 
					                // send local message to the clients
 | 
				
			||||||
                    println("Broadcast: $message")
 | 
					                outgoingMessageBuffer.collect { message ->
 | 
				
			||||||
                    send(Frame.Text(message))
 | 
					                    send(Json.encodeToFrame(message = message))
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
            runCatching {
 | 
					            runCatching {
 | 
				
			||||||
 | 
					                // watching for clients incoming message
 | 
				
			||||||
                incoming.consumeEach { frame ->
 | 
					                incoming.consumeEach { frame ->
 | 
				
			||||||
                    if (frame is Frame.Text) {
 | 
					                    if (frame is Frame.Text) {
 | 
				
			||||||
                        val receivedText = frame.readText()
 | 
					                        val message = Json.decodeFromFrame(frame = frame)
 | 
				
			||||||
                        messageResponseFlow.emit(receivedText)
 | 
					                        incomingMessageBuffer.emit(message)
 | 
				
			||||||
 | 
					                        // broadcast to clients the message
 | 
				
			||||||
 | 
					                        outgoingMessageBuffer.emit(message)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }.onFailure { exception ->
 | 
					            }.onFailure { exception ->
 | 
				
			||||||
| 
						 | 
					@ -108,17 +112,17 @@ object NetworkRepository {
 | 
				
			||||||
                    println("Client launched")
 | 
					                    println("Client launched")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    val job = launch {
 | 
					                    val job = launch {
 | 
				
			||||||
                        sharedFlow.collect { message ->
 | 
					                        // send message to the server
 | 
				
			||||||
                            println("Send: $message")
 | 
					                        outgoingMessageBuffer.collect { message ->
 | 
				
			||||||
                            send(Frame.Text(message))
 | 
					                            send(Json.encodeToFrame(message = message))
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
                    runBlocking {
 | 
					                    runBlocking {
 | 
				
			||||||
 | 
					                        // watching for server incoming message
 | 
				
			||||||
                        incoming.consumeEach { frame ->
 | 
					                        incoming.consumeEach { frame ->
 | 
				
			||||||
                            if (frame is Frame.Text) {
 | 
					                            if (frame is Frame.Text) {
 | 
				
			||||||
                                val receivedText = frame.readText()
 | 
					                                val message = Json.decodeFromFrame(frame = frame)
 | 
				
			||||||
                                println("client received: $receivedText")
 | 
					                                incomingMessageBuffer.emit(message)
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        }
 | 
					                        }
 | 
				
			||||||
                    }.also {
 | 
					                    }.also {
 | 
				
			||||||
| 
						 | 
					@ -144,15 +148,19 @@ object NetworkRepository {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun share(
 | 
					    suspend fun share(
 | 
				
			||||||
        type: String,
 | 
					        content: MessageContent,
 | 
				
			||||||
        value: String,
 | 
					 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        if (status.value == Status.CONNECTED) {
 | 
					        if (status.value == Status.CONNECTED) {
 | 
				
			||||||
            scope.launch {
 | 
					            val message = Message(
 | 
				
			||||||
                val message = Message(from = player.value, type = type, value = value)
 | 
					                from = player.value,
 | 
				
			||||||
                val json = Json.encodeToJsonElement(message)
 | 
					                value = content,
 | 
				
			||||||
                messageResponseFlow.emit(json.toString())
 | 
					            )
 | 
				
			||||||
 | 
					            // emit the message into the outgoing buffer
 | 
				
			||||||
 | 
					            outgoingMessageBuffer.emit(message)
 | 
				
			||||||
 | 
					            // emit the message into the incoming buffer IF we are the server
 | 
				
			||||||
 | 
					            if (type.value == Type.SERVER) {
 | 
				
			||||||
 | 
					                incomingMessageBuffer.emit(message)
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -168,3 +176,13 @@ object NetworkRepository {
 | 
				
			||||||
        NONE,
 | 
					        NONE,
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private fun Json.decodeFromFrame(frame: Frame.Text): Message {
 | 
				
			||||||
 | 
					    val json = frame.readText()
 | 
				
			||||||
 | 
					    return decodeFromString<Message>(json)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private fun Json.encodeToFrame(message: Message): Frame {
 | 
				
			||||||
 | 
					    val json = encodeToJsonElement(message)
 | 
				
			||||||
 | 
					    return Frame.Text(text = json.toString())
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,8 +3,7 @@ package com.pixelized.desktop.lwa.repository.network.protocol
 | 
				
			||||||
import kotlinx.serialization.Serializable
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Serializable
 | 
					@Serializable
 | 
				
			||||||
class Message(
 | 
					data class Message(
 | 
				
			||||||
    val from: String,
 | 
					    val from: String,
 | 
				
			||||||
    val type: String,
 | 
					    val value: MessageContent,
 | 
				
			||||||
    val value: String,
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,6 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.repository.network.protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
 | 
					sealed interface MessageContent
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,9 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.repository.network.protocol
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import kotlinx.serialization.Serializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Serializable
 | 
				
			||||||
 | 
					data class RollMessage(
 | 
				
			||||||
 | 
					    val label: String,
 | 
				
			||||||
 | 
					    val roll: Int,
 | 
				
			||||||
 | 
					): MessageContent
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,41 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.repository.roll
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.network.protocol.Message
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.Dispatchers
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.SharedFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.SharingStarted
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.mapNotNull
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.shareIn
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					object RollHistoryRepository {
 | 
				
			||||||
 | 
					    private val scope = CoroutineScope(Dispatchers.IO)
 | 
				
			||||||
 | 
					    private val network = NetworkRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val rolls: SharedFlow<Message> = network.data
 | 
				
			||||||
 | 
					        .mapNotNull { it.takeIf { it.value is RollMessage } }
 | 
				
			||||||
 | 
					        .shareIn(
 | 
				
			||||||
 | 
					            scope = scope,
 | 
				
			||||||
 | 
					            started = SharingStarted.Eagerly,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					        scope.launch {
 | 
				
			||||||
 | 
					            network.data.collect {
 | 
				
			||||||
 | 
					                println(it)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    suspend fun share(
 | 
				
			||||||
 | 
					        label: String,
 | 
				
			||||||
 | 
					        roll: Int,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        network.share(
 | 
				
			||||||
 | 
					            content = RollMessage(label = label, roll = roll)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,6 @@ import androidx.compose.material.Surface
 | 
				
			||||||
import androidx.compose.material.Text
 | 
					import androidx.compose.material.Text
 | 
				
			||||||
import androidx.compose.material.TopAppBar
 | 
					import androidx.compose.material.TopAppBar
 | 
				
			||||||
import androidx.compose.material.icons.Icons
 | 
					import androidx.compose.material.icons.Icons
 | 
				
			||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
 | 
					 | 
				
			||||||
import androidx.compose.material.icons.filled.Delete
 | 
					import androidx.compose.material.icons.filled.Delete
 | 
				
			||||||
import androidx.compose.material.icons.filled.Edit
 | 
					import androidx.compose.material.icons.filled.Edit
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
| 
						 | 
					@ -124,7 +123,7 @@ fun CharacterSheetPage(
 | 
				
			||||||
                            scope.launch {
 | 
					                            scope.launch {
 | 
				
			||||||
                                viewModel.deleteCharacter(id = sheet.id)
 | 
					                                viewModel.deleteCharacter(id = sheet.id)
 | 
				
			||||||
                                if (screen.popBackStack().not()) {
 | 
					                                if (screen.popBackStack().not()) {
 | 
				
			||||||
                                    window.close()
 | 
					                                    window.closeWindows()
 | 
				
			||||||
                                }
 | 
					                                }
 | 
				
			||||||
                            }
 | 
					                            }
 | 
				
			||||||
                        },
 | 
					                        },
 | 
				
			||||||
| 
						 | 
					@ -191,16 +190,6 @@ fun CharacterSheetPageContent(
 | 
				
			||||||
                        )
 | 
					                        )
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                navigationIcon = {
 | 
					 | 
				
			||||||
                    IconButton(
 | 
					 | 
				
			||||||
                        onClick = onBack,
 | 
					 | 
				
			||||||
                    ) {
 | 
					 | 
				
			||||||
                        Icon(
 | 
					 | 
				
			||||||
                            imageVector = Icons.AutoMirrored.Filled.ArrowBack,
 | 
					 | 
				
			||||||
                            contentDescription = null,
 | 
					 | 
				
			||||||
                        )
 | 
					 | 
				
			||||||
                    }
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
        content = { paddingValues ->
 | 
					        content = { paddingValues ->
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -92,7 +92,7 @@ fun CharacterSheetEditPage(
 | 
				
			||||||
                scope.launch {
 | 
					                scope.launch {
 | 
				
			||||||
                    viewModel.save()
 | 
					                    viewModel.save()
 | 
				
			||||||
                    if (screen.popBackStack().not()) {
 | 
					                    if (screen.popBackStack().not()) {
 | 
				
			||||||
                        window.close()
 | 
					                        window.closeWindows()
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa.screen.main
 | 
				
			||||||
import androidx.compose.foundation.layout.Arrangement
 | 
					import androidx.compose.foundation.layout.Arrangement
 | 
				
			||||||
import androidx.compose.foundation.layout.Box
 | 
					import androidx.compose.foundation.layout.Box
 | 
				
			||||||
import androidx.compose.foundation.layout.Column
 | 
					import androidx.compose.foundation.layout.Column
 | 
				
			||||||
import androidx.compose.foundation.layout.Spacer
 | 
					 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxSize
 | 
					import androidx.compose.foundation.layout.fillMaxSize
 | 
				
			||||||
import androidx.compose.foundation.layout.fillMaxWidth
 | 
					import androidx.compose.foundation.layout.fillMaxWidth
 | 
				
			||||||
import androidx.compose.foundation.layout.padding
 | 
					import androidx.compose.foundation.layout.padding
 | 
				
			||||||
| 
						 | 
					@ -27,16 +26,17 @@ import androidx.compose.ui.window.rememberWindowState
 | 
				
			||||||
import androidx.lifecycle.viewmodel.compose.viewModel
 | 
					import androidx.lifecycle.viewmodel.compose.viewModel
 | 
				
			||||||
import com.pixelized.desktop.lwa.LocalWindowController
 | 
					import com.pixelized.desktop.lwa.LocalWindowController
 | 
				
			||||||
import com.pixelized.desktop.lwa.WindowController
 | 
					import com.pixelized.desktop.lwa.WindowController
 | 
				
			||||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
 | 
					 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.LocalScreenController
 | 
					import com.pixelized.desktop.lwa.navigation.LocalScreenController
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
 | 
					import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
 | 
					import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
 | 
				
			||||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToNetwork
 | 
					import com.pixelized.desktop.lwa.navigation.destination.navigateToNetwork
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.navigation.destination.navigateToRollHistory
 | 
				
			||||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
 | 
					import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.Res
 | 
					import lwacharactersheet.composeapp.generated.resources.Res
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
 | 
					import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
 | 
					import lwacharactersheet.composeapp.generated.resources.main_page__create_action
 | 
				
			||||||
import lwacharactersheet.composeapp.generated.resources.main_page__network_action
 | 
					import lwacharactersheet.composeapp.generated.resources.main_page__network_action
 | 
				
			||||||
 | 
					import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
 | 
				
			||||||
import org.jetbrains.compose.resources.stringResource
 | 
					import org.jetbrains.compose.resources.stringResource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Stable
 | 
					@Stable
 | 
				
			||||||
| 
						 | 
					@ -49,6 +49,7 @@ data class CharacterUio(
 | 
				
			||||||
fun MainPage(
 | 
					fun MainPage(
 | 
				
			||||||
    viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
 | 
					    viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
 | 
					    val window = LocalWindowController.current
 | 
				
			||||||
    val screen = LocalScreenController.current
 | 
					    val screen = LocalScreenController.current
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Surface(
 | 
					    Surface(
 | 
				
			||||||
| 
						 | 
					@ -64,10 +65,13 @@ fun MainPage(
 | 
				
			||||||
            MainPageContent(
 | 
					            MainPageContent(
 | 
				
			||||||
                characters = viewModel.characters,
 | 
					                characters = viewModel.characters,
 | 
				
			||||||
                onCharacter = {
 | 
					                onCharacter = {
 | 
				
			||||||
                    viewModel.showCharacterSheet(sheet = it)
 | 
					                    window.showCharacterSheet(sheet = it)
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                onCreateCharacter = {
 | 
					                onCreateCharacter = {
 | 
				
			||||||
                    viewModel.showCreateCharacterSheet()
 | 
					                    window.showCreateCharacterSheet()
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                onRollHistory = {
 | 
				
			||||||
 | 
					                    screen.navigateToRollHistory()
 | 
				
			||||||
                },
 | 
					                },
 | 
				
			||||||
                onNetwork = {
 | 
					                onNetwork = {
 | 
				
			||||||
                    screen.navigateToNetwork()
 | 
					                    screen.navigateToNetwork()
 | 
				
			||||||
| 
						 | 
					@ -75,97 +79,20 @@ fun MainPage(
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    HandleCharacterSheet(
 | 
					 | 
				
			||||||
        sheets = viewModel.sheet,
 | 
					 | 
				
			||||||
        onCloseRequest = { viewModel.hideCharacterSheet(sheet = it) }
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    HandleCharacterSheetCreation(
 | 
					 | 
				
			||||||
        sheets = viewModel.create,
 | 
					 | 
				
			||||||
        onCloseRequest = { viewModel.hideCreateCharacterSheet(id = it) },
 | 
					 | 
				
			||||||
    )
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Composable
 | 
					 | 
				
			||||||
fun HandleCharacterSheet(
 | 
					 | 
				
			||||||
    sheets: State<Set<CharacterUio>>,
 | 
					 | 
				
			||||||
    onCloseRequest: (id: CharacterUio) -> Unit,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    sheets.value.forEach { sheet ->
 | 
					 | 
				
			||||||
        val controller = remember {
 | 
					 | 
				
			||||||
            WindowController(
 | 
					 | 
				
			||||||
                onCloseRequest = { onCloseRequest(sheet) }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        CompositionLocalProvider(
 | 
					 | 
				
			||||||
            LocalWindowController provides controller,
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            Window(
 | 
					 | 
				
			||||||
                onCloseRequest = { onCloseRequest(sheet) },
 | 
					 | 
				
			||||||
                state = rememberWindowState(
 | 
					 | 
				
			||||||
                    width = 400.dp + 64.dp,
 | 
					 | 
				
			||||||
                    height = 900.dp,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                title = sheet.name,
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                CharacterSheetMainNavHost(
 | 
					 | 
				
			||||||
                    startDestination = CharacterSheetDestination.navigationRoute(id = sheet.id)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Composable
 | 
					 | 
				
			||||||
fun HandleCharacterSheetCreation(
 | 
					 | 
				
			||||||
    sheets: State<Set<Int>>,
 | 
					 | 
				
			||||||
    onCloseRequest: (id: Int) -> Unit,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
    sheets.value.forEach { sheet ->
 | 
					 | 
				
			||||||
        val controller = remember {
 | 
					 | 
				
			||||||
            WindowController(
 | 
					 | 
				
			||||||
                onCloseRequest = { onCloseRequest(sheet) }
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        CompositionLocalProvider(
 | 
					 | 
				
			||||||
            LocalWindowController provides controller,
 | 
					 | 
				
			||||||
        ) {
 | 
					 | 
				
			||||||
            Window(
 | 
					 | 
				
			||||||
                onCloseRequest = { controller.close() },
 | 
					 | 
				
			||||||
                state = rememberWindowState(
 | 
					 | 
				
			||||||
                    width = 400.dp + 64.dp,
 | 
					 | 
				
			||||||
                    height = 900.dp,
 | 
					 | 
				
			||||||
                ),
 | 
					 | 
				
			||||||
                title = stringResource(Res.string.character_sheet_edit__title),
 | 
					 | 
				
			||||||
            ) {
 | 
					 | 
				
			||||||
                CharacterSheetMainNavHost(
 | 
					 | 
				
			||||||
                    startDestination = CharacterSheetEditDestination.navigationRoute(id = null)
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Composable
 | 
					@Composable
 | 
				
			||||||
fun MainPageContent(
 | 
					fun MainPageContent(
 | 
				
			||||||
    modifier: Modifier = Modifier,
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
    characters: State<List<CharacterUio>>,
 | 
					    characters: State<List<CharacterUio>>,
 | 
				
			||||||
    onCharacter: (CharacterUio) -> Unit,
 | 
					    onCharacter: (CharacterUio) -> Unit,
 | 
				
			||||||
    onCreateCharacter: () -> Unit,
 | 
					    onCreateCharacter: () -> Unit,
 | 
				
			||||||
 | 
					    onRollHistory: () -> Unit,
 | 
				
			||||||
    onNetwork: () -> Unit,
 | 
					    onNetwork: () -> Unit,
 | 
				
			||||||
) {
 | 
					) {
 | 
				
			||||||
    Column(
 | 
					    Column(
 | 
				
			||||||
        modifier = modifier,
 | 
					        modifier = modifier,
 | 
				
			||||||
    ) {
 | 
					        verticalArrangement = Arrangement.spacedBy(16.dp),
 | 
				
			||||||
        Spacer(
 | 
					 | 
				
			||||||
            modifier = Modifier.weight(weight = 1f)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        DecoratedBox {
 | 
					 | 
				
			||||||
            Column(
 | 
					 | 
				
			||||||
                modifier = Modifier.padding(horizontal = 8.dp),
 | 
					 | 
				
			||||||
                verticalArrangement = Arrangement.spacedBy(space = 32.dp),
 | 
					 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        Column {
 | 
					        Column {
 | 
				
			||||||
            characters.value.forEach { sheet ->
 | 
					            characters.value.forEach { sheet ->
 | 
				
			||||||
| 
						 | 
					@ -184,7 +111,7 @@ fun MainPageContent(
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        TextButton(
 | 
					        TextButton(
 | 
				
			||||||
                    onClick = { onCreateCharacter() },
 | 
					            onClick = onCreateCharacter,
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
            Text(
 | 
					            Text(
 | 
				
			||||||
                modifier = Modifier.fillMaxWidth(),
 | 
					                modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
| 
						 | 
					@ -194,21 +121,30 @@ fun MainPageContent(
 | 
				
			||||||
                text = stringResource(Res.string.main_page__create_action),
 | 
					                text = stringResource(Res.string.main_page__create_action),
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
            }
 | 
					
 | 
				
			||||||
        }
 | 
					        Column {
 | 
				
			||||||
        Spacer(
 | 
					 | 
				
			||||||
            modifier = Modifier.weight(weight = 1f)
 | 
					 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
            TextButton(
 | 
					            TextButton(
 | 
				
			||||||
            onClick = { onNetwork() },
 | 
					                onClick = onRollHistory,
 | 
				
			||||||
            ) {
 | 
					            ) {
 | 
				
			||||||
                Text(
 | 
					                Text(
 | 
				
			||||||
                    modifier = Modifier.fillMaxWidth(),
 | 
					                    modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
                    maxLines = 1,
 | 
					                    maxLines = 1,
 | 
				
			||||||
                    overflow = TextOverflow.Ellipsis,
 | 
					                    overflow = TextOverflow.Ellipsis,
 | 
				
			||||||
                    textAlign = TextAlign.Start,
 | 
					                    textAlign = TextAlign.Start,
 | 
				
			||||||
 | 
					                    text = stringResource(Res.string.main_page__roll_history_action),
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            TextButton(
 | 
				
			||||||
 | 
					                onClick = onNetwork,
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                Text(
 | 
				
			||||||
 | 
					                    modifier = Modifier.fillMaxWidth(),
 | 
				
			||||||
 | 
					                    overflow = TextOverflow.Ellipsis,
 | 
				
			||||||
 | 
					                    textAlign = TextAlign.Start,
 | 
				
			||||||
 | 
					                    maxLines = 1,
 | 
				
			||||||
                    text = stringResource(Res.string.main_page__network_action),
 | 
					                    text = stringResource(Res.string.main_page__network_action),
 | 
				
			||||||
                )
 | 
					                )
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa.screen.main
 | 
				
			||||||
import androidx.compose.runtime.Composable
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
import androidx.compose.runtime.Stable
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
import androidx.compose.runtime.State
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
import androidx.compose.runtime.mutableStateOf
 | 
					 | 
				
			||||||
import androidx.lifecycle.ViewModel
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
					import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
				
			||||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
 | 
					import com.pixelized.desktop.lwa.utils.extention.collectAsState
 | 
				
			||||||
| 
						 | 
					@ -12,12 +11,6 @@ class MainPageViewModel : ViewModel() {
 | 
				
			||||||
    // using a variable to help with later injection.
 | 
					    // using a variable to help with later injection.
 | 
				
			||||||
    private val repository = CharacterSheetRepository
 | 
					    private val repository = CharacterSheetRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _sheet = mutableStateOf<Set<CharacterUio>>(emptySet())
 | 
					 | 
				
			||||||
    val sheet: State<Set<CharacterUio>> get() = _sheet
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private val _create = mutableStateOf<Set<Int>>(emptySet())
 | 
					 | 
				
			||||||
    val create: State<Set<Int>> get() = _create
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    val characters: State<List<CharacterUio>>
 | 
					    val characters: State<List<CharacterUio>>
 | 
				
			||||||
        @Composable
 | 
					        @Composable
 | 
				
			||||||
        @Stable
 | 
					        @Stable
 | 
				
			||||||
| 
						 | 
					@ -31,20 +24,4 @@ class MainPageViewModel : ViewModel() {
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun showCreateCharacterSheet() {
 | 
					 | 
				
			||||||
        _create.value = _create.value.toMutableSet().apply { add(size) }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun hideCreateCharacterSheet(id: Int) {
 | 
					 | 
				
			||||||
        _create.value = _create.value.toMutableSet().apply { remove(id) }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun showCharacterSheet(sheet: CharacterUio) {
 | 
					 | 
				
			||||||
        _sheet.value = _sheet.value.toMutableSet().apply { add(sheet) }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    fun hideCharacterSheet(sheet: CharacterUio) {
 | 
					 | 
				
			||||||
        _sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -10,7 +10,7 @@ import com.pixelized.desktop.lwa.business.RollUseCase
 | 
				
			||||||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
 | 
					import com.pixelized.desktop.lwa.business.SkillStepUseCase
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
 | 
					import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
					import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
 | 
				
			||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
 | 
					import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository
 | 
				
			||||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
 | 
					import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
 | 
				
			||||||
import kotlinx.coroutines.Job
 | 
					import kotlinx.coroutines.Job
 | 
				
			||||||
import kotlinx.coroutines.coroutineScope
 | 
					import kotlinx.coroutines.coroutineScope
 | 
				
			||||||
| 
						 | 
					@ -26,7 +26,7 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__success
 | 
				
			||||||
import org.jetbrains.compose.resources.getString
 | 
					import org.jetbrains.compose.resources.getString
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RollViewModel : ViewModel() {
 | 
					class RollViewModel : ViewModel() {
 | 
				
			||||||
    private val network = NetworkRepository
 | 
					    private val repository = RollHistoryRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val _roll = mutableStateOf(RollUio(label = "", value = 0))
 | 
					    private val _roll = mutableStateOf(RollUio(label = "", value = 0))
 | 
				
			||||||
    val roll: State<RollUio> get() = _roll
 | 
					    val roll: State<RollUio> get() = _roll
 | 
				
			||||||
| 
						 | 
					@ -138,13 +138,11 @@ class RollViewModel : ViewModel() {
 | 
				
			||||||
                        value = roll,
 | 
					                        value = roll,
 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    share(roll = roll)
 | 
					                    launch {
 | 
				
			||||||
 | 
					                        repository.share(label = _roll.value.label, roll = roll)
 | 
				
			||||||
                    }
 | 
					                    }
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun share(roll: Int) {
 | 
					 | 
				
			||||||
        network.share(type = "roll", value = "$roll")
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,135 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.screen.rollhistory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.Arrangement
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.PaddingValues
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.Row
 | 
				
			||||||
 | 
					import androidx.compose.foundation.layout.fillMaxSize
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.LazyColumn
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.items
 | 
				
			||||||
 | 
					import androidx.compose.foundation.lazy.rememberLazyListState
 | 
				
			||||||
 | 
					import androidx.compose.material.Icon
 | 
				
			||||||
 | 
					import androidx.compose.material.IconButton
 | 
				
			||||||
 | 
					import androidx.compose.material.MaterialTheme
 | 
				
			||||||
 | 
					import androidx.compose.material.Scaffold
 | 
				
			||||||
 | 
					import androidx.compose.material.Surface
 | 
				
			||||||
 | 
					import androidx.compose.material.Text
 | 
				
			||||||
 | 
					import androidx.compose.material.TopAppBar
 | 
				
			||||||
 | 
					import androidx.compose.material.icons.Icons
 | 
				
			||||||
 | 
					import androidx.compose.material.icons.automirrored.filled.ArrowBack
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Composable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.Stable
 | 
				
			||||||
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
 | 
					import androidx.compose.ui.Modifier
 | 
				
			||||||
 | 
					import androidx.compose.ui.text.font.FontWeight
 | 
				
			||||||
 | 
					import androidx.compose.ui.text.style.TextOverflow
 | 
				
			||||||
 | 
					import androidx.compose.ui.unit.dp
 | 
				
			||||||
 | 
					import androidx.lifecycle.viewmodel.compose.viewModel
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.navigation.LocalScreenController
 | 
				
			||||||
 | 
					import lwacharactersheet.composeapp.generated.resources.Res
 | 
				
			||||||
 | 
					import lwacharactersheet.composeapp.generated.resources.roll_history__title
 | 
				
			||||||
 | 
					import org.jetbrains.compose.resources.stringResource
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Stable
 | 
				
			||||||
 | 
					data class RollItemUio(
 | 
				
			||||||
 | 
					    val from: String,
 | 
				
			||||||
 | 
					    val label: String,
 | 
				
			||||||
 | 
					    val roll: Int,
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					fun RollHistoryPage(
 | 
				
			||||||
 | 
					    viewModel: RollHistoryViewModel = viewModel { RollHistoryViewModel() }
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    val screen = LocalScreenController.current
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Surface(
 | 
				
			||||||
 | 
					        modifier = Modifier.fillMaxSize(),
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        RollHistoryContent(
 | 
				
			||||||
 | 
					            modifier = Modifier.fillMaxSize(),
 | 
				
			||||||
 | 
					            rolls = viewModel.rolls,
 | 
				
			||||||
 | 
					            onBack = {
 | 
				
			||||||
 | 
					                screen.popBackStack()
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					private fun RollHistoryContent(
 | 
				
			||||||
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
 | 
					    rolls: State<List<RollItemUio>>,
 | 
				
			||||||
 | 
					    onBack: () -> Unit,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    Scaffold(
 | 
				
			||||||
 | 
					        modifier = modifier,
 | 
				
			||||||
 | 
					        topBar = {
 | 
				
			||||||
 | 
					            TopAppBar(
 | 
				
			||||||
 | 
					                title = {
 | 
				
			||||||
 | 
					                    Text(
 | 
				
			||||||
 | 
					                        overflow = TextOverflow.Ellipsis,
 | 
				
			||||||
 | 
					                        maxLines = 1,
 | 
				
			||||||
 | 
					                        text = stringResource(Res.string.roll_history__title),
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                navigationIcon = {
 | 
				
			||||||
 | 
					                    IconButton(
 | 
				
			||||||
 | 
					                        onClick = onBack,
 | 
				
			||||||
 | 
					                    ) {
 | 
				
			||||||
 | 
					                        Icon(
 | 
				
			||||||
 | 
					                            imageVector = Icons.AutoMirrored.Filled.ArrowBack,
 | 
				
			||||||
 | 
					                            contentDescription = null,
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        content = {
 | 
				
			||||||
 | 
					            val state = rememberLazyListState()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            LazyColumn(
 | 
				
			||||||
 | 
					                modifier = Modifier.fillMaxSize(),
 | 
				
			||||||
 | 
					                state = state,
 | 
				
			||||||
 | 
					                reverseLayout = true,
 | 
				
			||||||
 | 
					                contentPadding = PaddingValues(all = 24.dp),
 | 
				
			||||||
 | 
					                verticalArrangement = Arrangement.spacedBy(space = 8.dp)
 | 
				
			||||||
 | 
					            ) {
 | 
				
			||||||
 | 
					                items(items = rolls.value) {
 | 
				
			||||||
 | 
					                    RollItem(
 | 
				
			||||||
 | 
					                        roll = it
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Composable
 | 
				
			||||||
 | 
					private fun RollItem(
 | 
				
			||||||
 | 
					    modifier: Modifier = Modifier,
 | 
				
			||||||
 | 
					    roll: RollItemUio,
 | 
				
			||||||
 | 
					) {
 | 
				
			||||||
 | 
					    Row(
 | 
				
			||||||
 | 
					        modifier = modifier,
 | 
				
			||||||
 | 
					        horizontalArrangement = Arrangement.spacedBy(4.dp),
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        Text(
 | 
				
			||||||
 | 
					            modifier = Modifier.alignByBaseline(),
 | 
				
			||||||
 | 
					            style = MaterialTheme.typography.body1,
 | 
				
			||||||
 | 
					            fontWeight = FontWeight.Thin,
 | 
				
			||||||
 | 
					            text = roll.from,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        Text(
 | 
				
			||||||
 | 
					            modifier = Modifier.alignByBaseline(),
 | 
				
			||||||
 | 
					            style = MaterialTheme.typography.body1,
 | 
				
			||||||
 | 
					            fontWeight = FontWeight.Light,
 | 
				
			||||||
 | 
					            text = roll.label,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        Text(
 | 
				
			||||||
 | 
					            modifier = Modifier.alignByBaseline(),
 | 
				
			||||||
 | 
					            style = MaterialTheme.typography.body1,
 | 
				
			||||||
 | 
					            fontWeight = FontWeight.Bold,
 | 
				
			||||||
 | 
					            text = "${roll.roll}",
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,36 @@
 | 
				
			||||||
 | 
					package com.pixelized.desktop.lwa.screen.rollhistory
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import androidx.compose.runtime.State
 | 
				
			||||||
 | 
					import androidx.compose.runtime.mutableStateOf
 | 
				
			||||||
 | 
					import androidx.lifecycle.ViewModel
 | 
				
			||||||
 | 
					import androidx.lifecycle.viewModelScope
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.network.protocol.RollMessage
 | 
				
			||||||
 | 
					import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class RollHistoryViewModel : ViewModel() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val repository = RollHistoryRepository
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val _rolls = mutableStateOf((emptyList<RollItemUio>()))
 | 
				
			||||||
 | 
					    val rolls: State<List<RollItemUio>> get() = _rolls
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					        viewModelScope.launch {
 | 
				
			||||||
 | 
					            repository.rolls.collect {
 | 
				
			||||||
 | 
					                (it.value as? RollMessage)?.let { content ->
 | 
				
			||||||
 | 
					                    _rolls.value = _rolls.value.toMutableList().apply {
 | 
				
			||||||
 | 
					                        add(
 | 
				
			||||||
 | 
					                            index = 0,
 | 
				
			||||||
 | 
					                            element = RollItemUio(
 | 
				
			||||||
 | 
					                                from = it.from,
 | 
				
			||||||
 | 
					                                label = content.label,
 | 
				
			||||||
 | 
					                                roll = content.roll
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                        )
 | 
				
			||||||
 | 
					                    }
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue