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>
|
||||
<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__roll_history_action">Consulter l'historique des lancés</string>
|
||||
|
||||
<string name="roll_page__critical_success">Réussite critique</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_client">Client</string>
|
||||
<string name="network__socket__type_none">Aucun</string>
|
||||
|
||||
<string name="roll_history__title">Historique des lancés</string>
|
||||
</resources>
|
||||
|
|
@ -1,11 +1,16 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.SnackbarHost
|
||||
import androidx.compose.material.SnackbarHostState
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
|
@ -13,9 +18,19 @@ import androidx.compose.ui.window.ApplicationScope
|
|||
import androidx.compose.ui.window.Window
|
||||
import androidx.compose.ui.window.rememberWindowState
|
||||
import com.pixelized.desktop.lwa.navigation.MainNavHost
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
|
||||
import com.pixelized.desktop.lwa.screen.main.CharacterUio
|
||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
val LocalSnackHost = compositionLocalOf<SnackbarHostState> {
|
||||
error("Local Snack Controller is not yet ready")
|
||||
}
|
||||
val LocalWindowController = compositionLocalOf<WindowController> {
|
||||
error("Local Window Controller is not yet ready")
|
||||
}
|
||||
|
|
@ -24,23 +39,47 @@ val LocalWindowController = compositionLocalOf<WindowController> {
|
|||
data class WindowController(
|
||||
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
|
||||
@Preview
|
||||
fun ApplicationScope.App() {
|
||||
val controller = remember {
|
||||
WindowController(
|
||||
onCloseRequest = ::exitApplication
|
||||
)
|
||||
}
|
||||
val controller = remember { WindowController(onCloseRequest = ::exitApplication) }
|
||||
val snackHostState = remember { SnackbarHostState() }
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalWindowController provides controller,
|
||||
LocalSnackHost provides snackHostState,
|
||||
) {
|
||||
Window(
|
||||
onCloseRequest = {
|
||||
controller.close()
|
||||
controller.closeWindows()
|
||||
},
|
||||
state = rememberWindowState(
|
||||
width = 320.dp + 64.dp,
|
||||
|
|
@ -52,9 +91,86 @@ fun ApplicationScope.App() {
|
|||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
MainNavHost()
|
||||
Scaffold(
|
||||
snackbarHost = {
|
||||
SnackbarHost(
|
||||
hostState = snackHostState,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
MainNavHost()
|
||||
}
|
||||
)
|
||||
HandleCharacterSheet(
|
||||
sheets = controller.sheet,
|
||||
onCloseRequest = { controller.hideCharacterSheet(sheet = it) }
|
||||
)
|
||||
HandleCharacterSheetCreation(
|
||||
sheets = controller.create,
|
||||
onCloseRequest = { controller.hideCreateCharacterSheet(id = it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleCharacterSheet(
|
||||
sheets: State<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.composableMainPage
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableNetworkPage
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableRollHistory
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPageViewModel
|
||||
|
||||
val LocalScreenController = compositionLocalOf<NavHostController> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
|
|
@ -28,6 +30,7 @@ fun MainNavHost(
|
|||
) {
|
||||
composableMainPage()
|
||||
composableNetworkPage()
|
||||
composableRollHistory()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import androidx.navigation.NavGraphBuilder
|
|||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPage
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPageViewModel
|
||||
|
||||
object MainDestination {
|
||||
private const val ROUTE = "main"
|
||||
|
|
|
|||
|
|
@ -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.server
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.Message
|
||||
import com.pixelized.desktop.lwa.repository.network.protocol.MessageContent
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.server.engine.EmbeddedServer
|
||||
import io.ktor.server.netty.NettyApplicationEngine
|
||||
|
|
@ -15,8 +16,8 @@ import kotlinx.coroutines.Job
|
|||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.serialization.json.Json
|
||||
|
|
@ -31,8 +32,9 @@ object NetworkRepository {
|
|||
private var server: Server? = null
|
||||
private var client: Client? = null
|
||||
|
||||
private val messageResponseFlow = MutableSharedFlow<String>()
|
||||
private val sharedFlow = messageResponseFlow.asSharedFlow()
|
||||
private val outgoingMessageBuffer = MutableSharedFlow<Message>()
|
||||
private val incomingMessageBuffer = MutableSharedFlow<Message>()
|
||||
val data: SharedFlow<Message> get() = incomingMessageBuffer
|
||||
|
||||
private val _player = MutableStateFlow("")
|
||||
val player: StateFlow<String> get() = _player
|
||||
|
|
@ -57,17 +59,19 @@ object NetworkRepository {
|
|||
println("Server launched")
|
||||
|
||||
val job = launch {
|
||||
sharedFlow.collect { message ->
|
||||
println("Broadcast: $message")
|
||||
send(Frame.Text(message))
|
||||
// send local message to the clients
|
||||
outgoingMessageBuffer.collect { message ->
|
||||
send(Json.encodeToFrame(message = message))
|
||||
}
|
||||
}
|
||||
|
||||
runCatching {
|
||||
// watching for clients incoming message
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val receivedText = frame.readText()
|
||||
messageResponseFlow.emit(receivedText)
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
incomingMessageBuffer.emit(message)
|
||||
// broadcast to clients the message
|
||||
outgoingMessageBuffer.emit(message)
|
||||
}
|
||||
}
|
||||
}.onFailure { exception ->
|
||||
|
|
@ -108,17 +112,17 @@ object NetworkRepository {
|
|||
println("Client launched")
|
||||
|
||||
val job = launch {
|
||||
sharedFlow.collect { message ->
|
||||
println("Send: $message")
|
||||
send(Frame.Text(message))
|
||||
// send message to the server
|
||||
outgoingMessageBuffer.collect { message ->
|
||||
send(Json.encodeToFrame(message = message))
|
||||
}
|
||||
}
|
||||
|
||||
runBlocking {
|
||||
// watching for server incoming message
|
||||
incoming.consumeEach { frame ->
|
||||
if (frame is Frame.Text) {
|
||||
val receivedText = frame.readText()
|
||||
println("client received: $receivedText")
|
||||
val message = Json.decodeFromFrame(frame = frame)
|
||||
incomingMessageBuffer.emit(message)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
|
|
@ -144,15 +148,19 @@ object NetworkRepository {
|
|||
}
|
||||
}
|
||||
|
||||
fun share(
|
||||
type: String,
|
||||
value: String,
|
||||
suspend fun share(
|
||||
content: MessageContent,
|
||||
) {
|
||||
if (status.value == Status.CONNECTED) {
|
||||
scope.launch {
|
||||
val message = Message(from = player.value, type = type, value = value)
|
||||
val json = Json.encodeToJsonElement(message)
|
||||
messageResponseFlow.emit(json.toString())
|
||||
val message = Message(
|
||||
from = player.value,
|
||||
value = content,
|
||||
)
|
||||
// emit the message into the outgoing buffer
|
||||
outgoingMessageBuffer.emit(message)
|
||||
// emit the message into the incoming buffer IF we are the server
|
||||
if (type.value == Type.SERVER) {
|
||||
incomingMessageBuffer.emit(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -167,4 +175,14 @@ object NetworkRepository {
|
|||
SERVER,
|
||||
NONE,
|
||||
}
|
||||
}
|
||||
|
||||
private fun Json.decodeFromFrame(frame: Frame.Text): Message {
|
||||
val json = frame.readText()
|
||||
return decodeFromString<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
|
||||
|
||||
@Serializable
|
||||
class Message(
|
||||
data class Message(
|
||||
val from: String,
|
||||
val type: String,
|
||||
val value: String,
|
||||
val value: MessageContent,
|
||||
)
|
||||
|
|
@ -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.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -124,7 +123,7 @@ fun CharacterSheetPage(
|
|||
scope.launch {
|
||||
viewModel.deleteCharacter(id = sheet.id)
|
||||
if (screen.popBackStack().not()) {
|
||||
window.close()
|
||||
window.closeWindows()
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
@ -191,16 +190,6 @@ fun CharacterSheetPageContent(
|
|||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ fun CharacterSheetEditPage(
|
|||
scope.launch {
|
||||
viewModel.save()
|
||||
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.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
|
|
@ -27,16 +26,17 @@ import androidx.compose.ui.window.rememberWindowState
|
|||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.LocalWindowController
|
||||
import com.pixelized.desktop.lwa.WindowController
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreenController
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToNetwork
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToRollHistory
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.character_sheet_edit__title
|
||||
import lwacharactersheet.composeapp.generated.resources.main_page__create_action
|
||||
import lwacharactersheet.composeapp.generated.resources.main_page__network_action
|
||||
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
|
||||
import org.jetbrains.compose.resources.stringResource
|
||||
|
||||
@Stable
|
||||
|
|
@ -49,6 +49,7 @@ data class CharacterUio(
|
|||
fun MainPage(
|
||||
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
|
||||
) {
|
||||
val window = LocalWindowController.current
|
||||
val screen = LocalScreenController.current
|
||||
|
||||
Surface(
|
||||
|
|
@ -64,10 +65,13 @@ fun MainPage(
|
|||
MainPageContent(
|
||||
characters = viewModel.characters,
|
||||
onCharacter = {
|
||||
viewModel.showCharacterSheet(sheet = it)
|
||||
window.showCharacterSheet(sheet = it)
|
||||
},
|
||||
onCreateCharacter = {
|
||||
viewModel.showCreateCharacterSheet()
|
||||
window.showCreateCharacterSheet()
|
||||
},
|
||||
onRollHistory = {
|
||||
screen.navigateToRollHistory()
|
||||
},
|
||||
onNetwork = {
|
||||
screen.navigateToNetwork()
|
||||
|
|
@ -75,140 +79,72 @@ fun MainPage(
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
HandleCharacterSheet(
|
||||
sheets = viewModel.sheet,
|
||||
onCloseRequest = { viewModel.hideCharacterSheet(sheet = it) }
|
||||
)
|
||||
|
||||
HandleCharacterSheetCreation(
|
||||
sheets = viewModel.create,
|
||||
onCloseRequest = { viewModel.hideCreateCharacterSheet(id = it) },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun HandleCharacterSheet(
|
||||
sheets: State<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
|
||||
fun MainPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
characters: State<List<CharacterUio>>,
|
||||
onCharacter: (CharacterUio) -> Unit,
|
||||
onCreateCharacter: () -> Unit,
|
||||
onRollHistory: () -> Unit,
|
||||
onNetwork: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||
) {
|
||||
Spacer(
|
||||
modifier = Modifier.weight(weight = 1f)
|
||||
)
|
||||
DecoratedBox {
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 32.dp),
|
||||
) {
|
||||
Column {
|
||||
characters.value.forEach { sheet ->
|
||||
TextButton(
|
||||
onClick = { onCharacter(sheet) },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 1,
|
||||
text = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
characters.value.forEach { sheet ->
|
||||
TextButton(
|
||||
onClick = { onCreateCharacter() },
|
||||
onClick = { onCharacter(sheet) },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
text = stringResource(Res.string.main_page__create_action),
|
||||
maxLines = 1,
|
||||
text = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Spacer(
|
||||
modifier = Modifier.weight(weight = 1f)
|
||||
)
|
||||
|
||||
TextButton(
|
||||
onClick = { onNetwork() },
|
||||
onClick = onCreateCharacter,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
text = stringResource(Res.string.main_page__network_action),
|
||||
text = stringResource(Res.string.main_page__create_action),
|
||||
)
|
||||
}
|
||||
|
||||
Column {
|
||||
TextButton(
|
||||
onClick = onRollHistory,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
text = stringResource(Res.string.main_page__roll_history_action),
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
onClick = onNetwork,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
maxLines = 1,
|
||||
text = stringResource(Res.string.main_page__network_action),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa.screen.main
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
||||
|
|
@ -12,12 +11,6 @@ class MainPageViewModel : ViewModel() {
|
|||
// using a variable to help with later injection.
|
||||
private val repository = CharacterSheetRepository
|
||||
|
||||
private val _sheet = mutableStateOf<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>>
|
||||
@Composable
|
||||
@Stable
|
||||
|
|
@ -31,20 +24,4 @@ class MainPageViewModel : ViewModel() {
|
|||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun showCreateCharacterSheet() {
|
||||
_create.value = _create.value.toMutableSet().apply { add(size) }
|
||||
}
|
||||
|
||||
fun hideCreateCharacterSheet(id: Int) {
|
||||
_create.value = _create.value.toMutableSet().apply { remove(id) }
|
||||
}
|
||||
|
||||
fun showCharacterSheet(sheet: CharacterUio) {
|
||||
_sheet.value = _sheet.value.toMutableSet().apply { add(sheet) }
|
||||
}
|
||||
|
||||
fun hideCharacterSheet(sheet: CharacterUio) {
|
||||
_sheet.value = _sheet.value.toMutableSet().apply { remove(sheet) }
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ import com.pixelized.desktop.lwa.business.RollUseCase
|
|||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||
import com.pixelized.desktop.lwa.repository.roll.RollHistoryRepository
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
|
|
@ -26,7 +26,7 @@ import lwacharactersheet.composeapp.generated.resources.roll_page__success
|
|||
import org.jetbrains.compose.resources.getString
|
||||
|
||||
class RollViewModel : ViewModel() {
|
||||
private val network = NetworkRepository
|
||||
private val repository = RollHistoryRepository
|
||||
|
||||
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
|
||||
val roll: State<RollUio> get() = _roll
|
||||
|
|
@ -138,13 +138,11 @@ class RollViewModel : ViewModel() {
|
|||
value = roll,
|
||||
)
|
||||
|
||||
share(roll = roll)
|
||||
launch {
|
||||
repository.share(label = _roll.value.label, roll = roll)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun share(roll: Int) {
|
||||
network.share(type = "roll", value = "$roll")
|
||||
}
|
||||
}
|
||||
|
|
@ -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