diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_wifi_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_wifi_24dp.xml
new file mode 100644
index 0000000..261089d
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_wifi_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/drawable/ic_wifi_off_24dp.xml b/composeApp/src/commonMain/composeResources/drawable/ic_wifi_off_24dp.xml
new file mode 100644
index 0000000..facc091
--- /dev/null
+++ b/composeApp/src/commonMain/composeResources/drawable/ic_wifi_off_24dp.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/composeApp/src/commonMain/composeResources/font/consola_mono_bold.ttf b/composeApp/src/commonMain/composeResources/font/consola_mono_bold.ttf
new file mode 100644
index 0000000..7e289b3
Binary files /dev/null and b/composeApp/src/commonMain/composeResources/font/consola_mono_bold.ttf differ
diff --git a/composeApp/src/commonMain/composeResources/font/consola_mono_book.ttf b/composeApp/src/commonMain/composeResources/font/consola_mono_book.ttf
new file mode 100644
index 0000000..78ca9da
Binary files /dev/null and b/composeApp/src/commonMain/composeResources/font/consola_mono_book.ttf differ
diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml
index 7c6d410..f17faa1 100644
--- a/composeApp/src/commonMain/composeResources/values/strings.xml
+++ b/composeApp/src/commonMain/composeResources/values/strings.xml
@@ -9,6 +9,7 @@
Ouvrir le dossier de sauvegarde
Historique des lancers
Configuration de la table
+ Configuration de l'application
Jet de :
Réussite si lancer inférieur ou égal à : %1$s
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
index 1de11d9..10bc5ac 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/App.kt
@@ -28,6 +28,8 @@ import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.compose.ui.unit.min
import androidx.compose.ui.window.ApplicationScope
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.rememberWindowState
@@ -41,6 +43,7 @@ import com.pixelized.desktop.lwa.ui.composable.key.LocalKeyEventHandlers
import com.pixelized.desktop.lwa.ui.navigation.screen.MainNavHost
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetDestination
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.CharacterSheetEditDestination
+import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
import com.pixelized.desktop.lwa.ui.navigation.window.WindowController
import com.pixelized.desktop.lwa.ui.navigation.window.WindowsNavHost
import com.pixelized.desktop.lwa.ui.navigation.window.destination.CharacterSheetEditWindow
@@ -49,6 +52,8 @@ import com.pixelized.desktop.lwa.ui.navigation.window.destination.NetworkWindows
import com.pixelized.desktop.lwa.ui.navigation.window.destination.RollHistoryWindow
import com.pixelized.desktop.lwa.ui.navigation.window.rememberMaxWindowHeight
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
import com.pixelized.desktop.lwa.ui.screen.characterSheet.CharacterSheetMainNavHost
import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage
import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
@@ -78,15 +83,27 @@ val LocalErrorSnackHost = compositionLocalOf {
error("Local Snack Controller is not yet ready")
}
+val LocalApplicationScope = compositionLocalOf {
+ error("Local application scope is not yet ready")
+}
+
@Composable
@Preview
fun ApplicationScope.App() {
-
val maxWindowHeight = rememberMaxWindowHeight()
val snackHostState = remember { SnackbarHostState() }
val errorSnackHostState = remember { SnackbarHostState() }
val windowController = remember { WindowController(maxWindowHeight) }
val keyEventHandlers = remember { mutableStateListOf() }
+ val windowsState = rememberWindowState(
+ size = DpSize(
+ width = 800.dp,
+ height = min(
+ a = 56.dp + PlayerRibbon.Default.size.height * 6 + 8.dp * 7 + 40.dp,
+ b = maxWindowHeight,
+ ),
+ ),
+ )
// Coil configuration
setSingletonImageLoaderFactory { context ->
@@ -96,14 +113,16 @@ fun ApplicationScope.App() {
}
CompositionLocalProvider(
+ LocalApplicationScope provides this,
LocalSnackHost provides snackHostState,
LocalErrorSnackHost provides errorSnackHostState,
LocalWindowController provides windowController,
LocalKeyEventHandlers provides keyEventHandlers,
+ LocalWindowState provides windowsState,
) {
Window(
onCloseRequest = ::exitApplication,
- state = rememberWindowState(size = DpSize(width = 800.dp, height = maxWindowHeight)),
+ state = windowsState,
title = runBlocking { getString(Res.string.app_name) },
onKeyEvent = { event ->
keyEventHandlers.reversed().any { it(event) }
@@ -118,6 +137,7 @@ fun ApplicationScope.App() {
private fun MainWindowScreen(
campaignViewModel: CampaignViewModel = koinViewModel(),
networkViewModel: NetworkViewModel = koinViewModel(),
+ campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
rollViewModel: RollHistoryViewModel = koinViewModel(),
) {
LaunchedEffect(Unit) {
@@ -161,7 +181,11 @@ private fun MainWindowScreen(
}
},
content = {
- MainNavHost()
+ MainNavHost(
+ campaignViewModel = campaignViewModel,
+ networkViewModel = networkViewModel,
+ campaignChatViewModel = campaignChatViewModel,
+ )
}
)
NetworkSnackHandler(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
index e020579..c509289 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/Module.kt
@@ -16,6 +16,8 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsStore
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialogFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.TextMessageFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailFactory
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
@@ -105,6 +107,7 @@ val factoryDependencies
factoryOf(::PlayerRibbonFactory)
factoryOf(::CharacterDetailFactory)
factoryOf(::CharacterSheetCharacteristicDialogFactory)
+ factoryOf(::TextMessageFactory)
}
val viewModelDependencies
@@ -120,6 +123,7 @@ val viewModelDependencies
viewModelOf(::CharacterDetailViewModel)
viewModelOf(::CharacterDiminishedViewModel)
viewModelOf(::CharacterDetailCharacteristicDialogViewModel)
+ viewModelOf(::CampaignChatViewModel)
}
val useCaseDependencies
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt
index 0103989..2fd1f09 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/MainNavHost.kt
@@ -10,6 +10,10 @@ import com.pixelized.desktop.lwa.ui.navigation.screen.destination.MainDestinatio
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableMainPage
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableNetworkPage
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableOldMainPage
+import com.pixelized.desktop.lwa.ui.navigation.screen.destination.composableSettingsPage
+import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
+import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
val LocalScreenController = compositionLocalOf {
error("MainNavHost controller is not yet ready")
@@ -18,6 +22,9 @@ val LocalScreenController = compositionLocalOf {
@Composable
fun MainNavHost(
controller: NavHostController = rememberNavController(),
+ campaignViewModel: CampaignViewModel,
+ networkViewModel: NetworkViewModel,
+ campaignChatViewModel: CampaignChatViewModel,
startDestination: String = MainDestination.navigationRoute(),
) {
CompositionLocalProvider(
@@ -27,9 +34,14 @@ fun MainNavHost(
navController = controller,
startDestination = startDestination,
) {
- composableMainPage()
+ composableMainPage(
+ campaignViewModel = campaignViewModel,
+ networkViewModel = networkViewModel,
+ campaignChatViewModel = campaignChatViewModel,
+ )
composableOldMainPage()
composableNetworkPage()
+ composableSettingsPage()
}
}
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt
index dc0dd26..f6d5817 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/MainDestination.kt
@@ -3,7 +3,10 @@ package com.pixelized.desktop.lwa.ui.navigation.screen.destination
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController
import androidx.navigation.compose.composable
+import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.MainPage
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
+import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
object MainDestination {
private const val ROUTE = "main"
@@ -12,11 +15,19 @@ object MainDestination {
fun navigationRoute() = ROUTE
}
-fun NavGraphBuilder.composableMainPage() {
+fun NavGraphBuilder.composableMainPage(
+ campaignViewModel: CampaignViewModel,
+ networkViewModel: NetworkViewModel,
+ campaignChatViewModel: CampaignChatViewModel,
+) {
composable(
route = MainDestination.baseRoute(),
) {
- MainPage()
+ MainPage(
+ campaignViewModel = campaignViewModel,
+ networkViewModel = networkViewModel,
+ campaignChatViewModel = campaignChatViewModel,
+ )
}
}
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/SettingsDestination.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/SettingsDestination.kt
new file mode 100644
index 0000000..eda1551
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/navigation/screen/destination/SettingsDestination.kt
@@ -0,0 +1,26 @@
+package com.pixelized.desktop.lwa.ui.navigation.screen.destination
+
+import androidx.navigation.NavGraphBuilder
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.composable
+import com.pixelized.desktop.lwa.ui.screen.settings.SettingsScreen
+
+object SettingsDestination {
+ private const val ROUTE = "settings"
+
+ fun baseRoute() = ROUTE
+ fun navigationRoute() = ROUTE
+}
+
+fun NavGraphBuilder.composableSettingsPage() {
+ composable(
+ route = SettingsDestination.baseRoute(),
+ ) {
+ SettingsScreen()
+ }
+}
+
+fun NavHostController.navigateToSettings() {
+ val route = SettingsDestination.navigationRoute()
+ navigate(route = route)
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
index e04a78d..f6afe13 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignScreen.kt
@@ -10,12 +10,14 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.material.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.runtime.rememberCoroutineScope
@@ -36,22 +38,32 @@ import com.pixelized.desktop.lwa.ui.composable.blur.rememberBlurContentControlle
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterDetailCharacteristicDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.characteristic.CharacterSheetCharacteristicDialog
import com.pixelized.desktop.lwa.ui.composable.key.KeyHandler
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChat
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.CampaignChatViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailPanel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDetailViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.detail.CharacterDiminishedViewModel
import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.CampaignToolbar
import com.pixelized.desktop.lwa.ui.screen.characterSheet.detail.dialog.DiminishedStatDialog
+import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
import com.pixelized.desktop.lwa.ui.screen.roll.RollPage
import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel
import kotlinx.coroutines.launch
import org.koin.compose.viewmodel.koinViewModel
+val LocalCampaignLayoutScope = compositionLocalOf {
+ error("LocalCampaignLayoutScope is not yet ready.")
+}
+
@Composable
fun MainPage(
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
characteristicDialogViewModel: CharacterDetailCharacteristicDialogViewModel = koinViewModel(),
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
+ campaignViewModel: CampaignViewModel = koinViewModel(),
+ networkViewModel: NetworkViewModel = koinViewModel(),
+ campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
rollViewModel: RollViewModel = koinViewModel(),
) {
KeyHandler {
@@ -64,7 +76,7 @@ fun MainPage(
else -> false
}
}
-
+
val scope = rememberCoroutineScope()
val blurController = rememberBlurContentController()
@@ -78,20 +90,21 @@ fun MainPage(
CampaignScreenLayout(
modifier = Modifier.fillMaxSize(),
top = {
- CampaignToolbar()
+ CampaignToolbar(
+ campaignViewModel = campaignViewModel,
+ )
},
bottom = {
- Surface(
- modifier = Modifier
-// .height(48.dp)
- .fillMaxWidth(),
- elevation = 1.dp,
- ) {
- }
},
main = {
+ },
+ chat = {
+ CampaignChat(
+ modifier = Modifier.padding(all = 8.dp),
+ campaignChatViewModel = campaignChatViewModel,
+ )
},
leftOverlay = {
PlayerRibbon(
@@ -104,9 +117,9 @@ fun MainPage(
rightOverlay = {
CharacterDetailPanel(
modifier = Modifier
+ .padding(all = 8.dp)
.width(width = 128.dp * 4)
- .fillMaxHeight()
- .padding(all = 8.dp),
+ .fillMaxHeight(),
blurController = blurController,
detailViewModel = characterDetailViewModel,
rollViewModel = rollViewModel,
@@ -182,22 +195,27 @@ fun MainPage(
@Composable
private fun CampaignScreenLayout(
modifier: Modifier = Modifier,
- top: @Composable CampaignScreenScope.() -> Unit,
- bottom: @Composable CampaignScreenScope.() -> Unit,
- main: @Composable CampaignScreenScope.() -> Unit,
- leftOverlay: @Composable CampaignScreenScope.() -> Unit,
- rightOverlay: @Composable CampaignScreenScope.() -> Unit,
+ top: @Composable () -> Unit,
+ bottom: @Composable () -> Unit,
+ main: @Composable () -> Unit,
+ chat: @Composable () -> Unit,
+ leftOverlay: @Composable () -> Unit,
+ rightOverlay: @Composable () -> Unit,
) {
val density = LocalDensity.current
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
+ val chatOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
val scope = remember {
- CampaignScreenScope(
+ CampaignLayoutScope(
leftOverlay = leftOverlayState,
rightOverlay = rightOverlayState,
+ chatOverlay = chatOverlayState,
)
}
- with(scope) {
+ CompositionLocalProvider(
+ LocalCampaignLayoutScope provides scope,
+ ) {
Column(
modifier = modifier,
) {
@@ -212,6 +230,13 @@ private fun CampaignScreenLayout(
) {
main()
}
+ Box(
+ modifier = Modifier
+ .align(alignment = Alignment.BottomEnd)
+ .onSizeChanged { }
+ ) {
+ chat()
+ }
Box(
modifier = Modifier
.align(alignment = Alignment.CenterStart)
@@ -232,9 +257,11 @@ private fun CampaignScreenLayout(
}
}
-private class CampaignScreenScope(
+@Stable
+data class CampaignLayoutScope(
val leftOverlay: State,
val rightOverlay: State,
+ val chatOverlay: State,
)
private fun IntSize.toDp(density: Density) = with(density) {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt
index c901665..02447fa 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/CampaignViewModel.kt
@@ -22,6 +22,8 @@ class CampaignViewModel(
val title: Flow = campaignRepository.campaignFlow
.map { it.scene.name }
+ val networkStatus = network.status
+
fun init() {
viewModelScope.launch {
combine(
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChat.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChat.kt
new file mode 100644
index 0000000..13e4fa3
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChat.kt
@@ -0,0 +1,147 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.chat
+
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.input.pointer.PointerEventType
+import androidx.compose.ui.input.pointer.onPointerEvent
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.max
+import androidx.compose.ui.unit.min
+import androidx.compose.ui.window.WindowState
+import com.pixelized.desktop.lwa.ui.navigation.window.LocalWindowState
+import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignLayoutScope
+import com.pixelized.desktop.lwa.ui.screen.campaign.LocalCampaignLayoutScope
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessage
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
+import com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon.PlayerRibbon
+import com.pixelized.desktop.lwa.ui.theme.lwa
+import kotlinx.coroutines.launch
+import org.koin.compose.viewmodel.koinViewModel
+
+@OptIn(ExperimentalComposeUiApi::class)
+@Composable
+fun CampaignChat(
+ modifier: Modifier = Modifier,
+ campaignChatViewModel: CampaignChatViewModel = koinViewModel(),
+) {
+ val scope = rememberCoroutineScope()
+ val lazyState = rememberLazyListState()
+ val animatedChatWidth = rememberAnimatedChatWidth()
+ val colorScheme = MaterialTheme.lwa.colorScheme
+ val messages = campaignChatViewModel.messages.collectAsState()
+
+ ChatScrollDownEffect(
+ lazyState = lazyState,
+ messages = messages,
+ displayChat = campaignChatViewModel::displayChat,
+ hideChat = campaignChatViewModel::hideChat,
+ )
+
+ Box(
+ modifier = modifier
+ .size(
+ width = animatedChatWidth.value,
+ height = PlayerRibbon.Default.size.height * 2 + 8.dp,
+ )
+ .graphicsLayer {
+ alpha = campaignChatViewModel.chatAnimatedVisibility.value
+ }
+ .background(
+ shape = remember { RoundedCornerShape(8.dp) },
+ color = remember { colorScheme.elevated.base1dp.copy(alpha = 0.5f) },
+ )
+ .onPointerEvent(eventType = PointerEventType.Enter) {
+ scope.launch { campaignChatViewModel.displayChat() }
+ }
+ .onPointerEvent(eventType = PointerEventType.Exit) {
+ scope.launch { campaignChatViewModel.hideChat() }
+ },
+ ) {
+ LazyColumn(
+ modifier = Modifier.fillMaxSize(),
+ state = lazyState,
+ verticalArrangement = Arrangement.spacedBy(
+ space = 4.dp,
+ alignment = Alignment.Bottom,
+ ),
+ contentPadding = remember { PaddingValues(all = 8.dp) },
+ ) {
+ items(
+ items = messages.value,
+ key = { it.id },
+ contentType = { it.javaClass.simpleName }
+ ) {
+ when (it) {
+ is RollTextMessageUio -> RollTextMessage(message = it)
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ChatScrollDownEffect(
+ lazyState: LazyListState,
+ messages: State>,
+ displayChat: suspend () -> Unit,
+ hideChat: suspend () -> Unit,
+) {
+ LaunchedEffect(
+ key1 = messages.value.lastOrNull()?.id,
+ ) {
+ if (messages.value.isNotEmpty()) {
+ displayChat()
+ lazyState.animateScrollToItem(
+ index = messages.value.lastIndex + 1,
+ )
+ hideChat()
+ }
+ }
+}
+
+@Composable
+@Stable
+private fun rememberAnimatedChatWidth(
+ campaignScreenScope: CampaignLayoutScope = LocalCampaignLayoutScope.current,
+ windowsState: WindowState = LocalWindowState.current,
+): State {
+ val chatWidth = remember(windowsState, campaignScreenScope) {
+ derivedStateOf {
+ val minChatWidth = 64.dp * 8
+ val maxChatWidth = 64.dp * 12
+ val windowWidth = windowsState.size.width
+ if (windowWidth != Dp.Unspecified) {
+ val width = windowWidth - campaignScreenScope.leftOverlay.value.width - 16.dp
+ min(max(width, minChatWidth), maxChatWidth)
+ } else {
+ minChatWidth
+ }
+ }
+ }
+ return animateDpAsState(targetValue = chatWidth.value)
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChatViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChatViewModel.kt
new file mode 100644
index 0000000..160843a
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/CampaignChatViewModel.kt
@@ -0,0 +1,47 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.chat
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.pixelized.desktop.lwa.repository.network.NetworkRepository
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.stateIn
+
+
+class CampaignChatViewModel(
+ networkRepository: NetworkRepository,
+ textMessageFactory: TextMessageFactory,
+) : ViewModel() {
+
+ val chatAnimatedVisibility = Animatable(0f)
+
+ private var _messages = emptyList()
+ val messages: StateFlow> = networkRepository.data
+ .mapNotNull { message ->
+ val text = textMessageFactory
+ .convertToTextMessage(message = message)
+ ?: return@mapNotNull _messages
+
+ _messages = _messages.toMutableList().also {
+ it.add(index = it.lastIndex + 1, element = text)
+ }
+ _messages
+ }
+ .stateIn(
+ scope = viewModelScope,
+ started = SharingStarted.Eagerly,
+ initialValue = _messages,
+ )
+
+ suspend fun displayChat() {
+ chatAnimatedVisibility.animateTo(1f)
+ }
+
+ suspend fun hideChat() {
+ chatAnimatedVisibility.animateTo(0f, animationSpec = tween(2000, delayMillis = 8000))
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/TextMessageFactory.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/TextMessageFactory.kt
new file mode 100644
index 0000000..bc8d5df
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/TextMessageFactory.kt
@@ -0,0 +1,59 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.chat
+
+import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.RollTextMessageUio
+import com.pixelized.desktop.lwa.ui.screen.campaign.chat.text.TextMessage
+import com.pixelized.shared.lwa.protocol.websocket.Message
+import com.pixelized.shared.lwa.protocol.websocket.payload.CampaignMessage
+import com.pixelized.shared.lwa.protocol.websocket.payload.RestSynchronisation
+import com.pixelized.shared.lwa.protocol.websocket.payload.RollMessage
+import com.pixelized.shared.lwa.protocol.websocket.payload.UpdateSkillUsageMessage
+import java.text.SimpleDateFormat
+
+class TextMessageFactory(
+ private val characterSheetRepository: CharacterSheetRepository,
+) {
+ private val formatId = SimpleDateFormat("yyyy/MM/dd-HH:mm:ss:SSS")
+ private val formatTime = SimpleDateFormat("HH:mm:ss")
+
+ fun convertToTextMessage(
+ message: Message,
+ ): TextMessage? {
+ val time = System.currentTimeMillis()
+ val id = formatId.format(time)
+ return when (val payload = message.value) {
+ is RollMessage -> {
+ val sheetPreview = characterSheetRepository
+ .characterPreview(characterId = payload.characterId)
+ ?: return null
+
+ RollTextMessageUio(
+ id = id,
+ timestamp = formatTime.format(time),
+ character = sheetPreview.name,
+ skillLabel = payload.skillLabel,
+ rollDifficulty = payload.rollDifficulty,
+ rollValue = payload.rollValue,
+ rollSuccessLimit = payload.rollSuccessLimit,
+ resultLabel = payload.resultLabel,
+ resultType = when (payload.critical) {
+ RollMessage.Critical.CRITICAL_SUCCESS -> RollTextMessageUio.Critical.CRITICAL_SUCCESS
+ RollMessage.Critical.SPECIAL_SUCCESS -> RollTextMessageUio.Critical.SPECIAL_SUCCESS
+ RollMessage.Critical.SUCCESS -> RollTextMessageUio.Critical.SUCCESS
+ RollMessage.Critical.FAILURE -> RollTextMessageUio.Critical.FAILURE
+ RollMessage.Critical.CRITICAL_FAILURE -> RollTextMessageUio.Critical.CRITICAL_FAILURE
+ null -> null
+ }
+ )
+ }
+
+ is CampaignMessage.UpdateCharacteristic -> null
+ is CampaignMessage.UpdateDiminished -> null
+ RestSynchronisation.Campaign -> null
+ is RestSynchronisation.CharacterDelete -> null
+ is RestSynchronisation.CharacterUpdate -> null
+ is RestSynchronisation.ToggleActiveAlteration -> null
+ is UpdateSkillUsageMessage -> null
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/RollText.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/RollText.kt
new file mode 100644
index 0000000..e3c9954
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/RollText.kt
@@ -0,0 +1,153 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.ui.Alignment
+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 com.pixelized.desktop.lwa.ui.theme.lwa
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.roll_history__item__difficulty
+import lwacharactersheet.composeapp.generated.resources.roll_history__item__throw
+import org.jetbrains.compose.resources.stringResource
+
+@Stable
+data class RollTextMessageUio(
+ override val id: String,
+ override val timestamp: String,
+ val character: String,
+ val skillLabel: String,
+ val rollDifficulty: String?,
+ val rollValue: Int,
+ val rollSuccessLimit: Int?,
+ val resultLabel: String?,
+ val resultType: Critical?,
+) : TextMessage {
+ enum class Critical {
+ CRITICAL_SUCCESS,
+ SPECIAL_SUCCESS,
+ SUCCESS,
+ FAILURE,
+ CRITICAL_FAILURE
+ }
+}
+
+@Composable
+fun RollTextMessage(
+ modifier: Modifier = Modifier,
+ message: RollTextMessageUio,
+) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.spacedBy(space = 3.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.timestamp,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = message.timestamp,
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.timestamp,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = ">",
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = message.character,
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ fontWeight = FontWeight.ExtraLight,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.roll_history__item__throw),
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = message.skillLabel,
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "-",
+ )
+ message.resultLabel?.let {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ color = when (message.resultType) {
+ RollTextMessageUio.Critical.CRITICAL_SUCCESS -> MaterialTheme.lwa.colorScheme.chat.criticalSuccess
+ RollTextMessageUio.Critical.SPECIAL_SUCCESS -> MaterialTheme.lwa.colorScheme.chat.spacialSuccess
+ RollTextMessageUio.Critical.SUCCESS -> MaterialTheme.lwa.colorScheme.chat.success
+ RollTextMessageUio.Critical.FAILURE -> MaterialTheme.lwa.colorScheme.chat.failure
+ RollTextMessageUio.Critical.CRITICAL_FAILURE -> MaterialTheme.lwa.colorScheme.chat.criticalFailure
+ null -> MaterialTheme.lwa.colorScheme.chat.text
+ },
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = it,
+ )
+ }
+ Row {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "${message.rollValue}",
+ )
+ message.rollSuccessLimit?.let {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "/$it",
+ )
+ }
+ }
+ message.rollDifficulty?.let {
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = "-",
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = stringResource(Res.string.roll_history__item__difficulty),
+ )
+ Text(
+ modifier = Modifier.alignByBaseline(),
+ style = MaterialTheme.lwa.typography.chat.text,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ text = it,
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/TextMessage.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/TextMessage.kt
new file mode 100644
index 0000000..9ede501
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/chat/text/TextMessage.kt
@@ -0,0 +1,6 @@
+package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
+
+sealed interface TextMessage {
+ val id : String
+ val timestamp: String
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt
index 4a64c17..22f5bd0 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/detail/CharacterDetail.kt
@@ -118,6 +118,7 @@ fun CharacterDetailPanel(
blurController.show()
}
)
+
}
@Composable
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt
index 56e4199..4966f6b 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/player/ribbon/PlayerPortraitRoll.kt
@@ -99,7 +99,8 @@ fun PlayerPortraitRoll(
.onClick(
matcher = PointerMatcher.mouse(PointerButton.Secondary),
onClick = { onRightClick(it) },
- ).clickable {
+ )
+ .clickable {
onLeftClick(it)
}
.padding(all = 8.dp),
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt
index d301dce..809d2d9 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/campaign/toolbar/CampaignToolbar.kt
@@ -1,6 +1,7 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.toolbar
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.material.DropdownMenu
import androidx.compose.material.DropdownMenuItem
import androidx.compose.material.Icon
@@ -16,19 +17,24 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp
import com.pixelized.desktop.lwa.LocalWindowController
+import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToOldMainPage
-import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToNetwork
+import com.pixelized.desktop.lwa.ui.navigation.screen.destination.navigateToSettings
import com.pixelized.desktop.lwa.ui.navigation.window.destination.navigateToRollHistory
import com.pixelized.desktop.lwa.ui.screen.campaign.CampaignViewModel
+import com.pixelized.desktop.lwa.ui.screen.network.NetworkPage
+import com.pixelized.desktop.lwa.ui.screen.network.NetworkViewModel
import com.pixelized.desktop.lwa.ui.theme.lwa
import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_settings_24dp
import lwacharactersheet.composeapp.generated.resources.ic_table_24dp
import lwacharactersheet.composeapp.generated.resources.ic_timeline_24dp
-import lwacharactersheet.composeapp.generated.resources.main_page__network_action
+import lwacharactersheet.composeapp.generated.resources.ic_wifi_24dp
+import lwacharactersheet.composeapp.generated.resources.ic_wifi_off_24dp
import lwacharactersheet.composeapp.generated.resources.main_page__roll_history_action
import org.jetbrains.compose.resources.painterResource
import org.jetbrains.compose.resources.stringResource
@@ -37,30 +43,76 @@ import org.koin.compose.viewmodel.koinViewModel
@Composable
fun CampaignToolbar(
campaignViewModel: CampaignViewModel = koinViewModel(),
+ networkViewModel: NetworkViewModel = koinViewModel(),
) {
val windows = LocalWindowController.current
val screen = LocalScreenController.current
val isOverflowMenuOpen = remember { mutableStateOf(false) }
+ val isNetworkMenuOpen = remember { mutableStateOf(false) }
CampaignToolbarContent(
title = campaignViewModel.title.collectAsState(initial = ""),
+ networkStatus = campaignViewModel.networkStatus.collectAsState(),
+ isNetworkMenuOpen = isNetworkMenuOpen,
isOverflowMenuOpen = isOverflowMenuOpen,
+ networkMenu = {
+ NetworkPage(
+ modifier = Modifier.size(384.dp, 240.dp),
+ viewModel = networkViewModel
+ )
+ },
+ overflowMenu = {
+ DropdownMenuItem(
+ onClick = {
+ isOverflowMenuOpen.value = false
+ windows.navigateToRollHistory()
+ },
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_timeline_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ color = MaterialTheme.colors.primary,
+ text = stringResource(Res.string.main_page__roll_history_action),
+ )
+ }
+ DropdownMenuItem(
+ onClick = {
+ isOverflowMenuOpen.value = false
+ screen.navigateToOldMainPage()
+ },
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_table_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ Text(
+ modifier = Modifier.padding(start = 8.dp),
+ color = MaterialTheme.colors.primary,
+ text = "Ancienne interface utilisateur",
+ )
+ }
+ },
+ onNetwork = {
+ isNetworkMenuOpen.value = true
+ },
onOverflow = {
isOverflowMenuOpen.value = isOverflowMenuOpen.value.not()
},
+ onSettings = {
+ screen.navigateToSettings()
+ },
+ onDismissNetworkMenu = {
+ isNetworkMenuOpen.value = false
+ },
onDismissOverflowMenu = {
isOverflowMenuOpen.value = false
},
- onRollHistory = {
- windows.navigateToRollHistory()
- },
- onNetwork = {
- windows.navigateToNetwork()
- },
- onOlUi = {
- screen.navigateToOldMainPage()
- },
)
}
@@ -68,12 +120,16 @@ fun CampaignToolbar(
private fun CampaignToolbarContent(
modifier: Modifier = Modifier,
title: State,
+ networkStatus: State,
+ isNetworkMenuOpen: State,
isOverflowMenuOpen: State,
- onOverflow: () -> Unit,
- onDismissOverflowMenu: () -> Unit,
- onRollHistory: () -> Unit,
+ networkMenu: @Composable () -> Unit,
+ overflowMenu: @Composable () -> Unit,
onNetwork: () -> Unit,
- onOlUi: () -> Unit,
+ onOverflow: () -> Unit,
+ onSettings: () -> Unit,
+ onDismissNetworkMenu: () -> Unit,
+ onDismissOverflowMenu: () -> Unit,
) {
TopAppBar(
modifier = modifier,
@@ -83,6 +139,38 @@ private fun CampaignToolbarContent(
)
},
actions = {
+ IconButton(
+ onClick = onNetwork
+ ) {
+ Icon(
+ painter = painterResource(
+ when (networkStatus.value) {
+ NetworkRepository.Status.CONNECTED -> Res.drawable.ic_wifi_24dp
+ NetworkRepository.Status.DISCONNECTED -> Res.drawable.ic_wifi_off_24dp
+ }
+ ),
+ tint = when (networkStatus.value) {
+ NetworkRepository.Status.CONNECTED -> MaterialTheme.lwa.colorScheme.base.primary
+ NetworkRepository.Status.DISCONNECTED -> MaterialTheme.lwa.colorScheme.base.error
+ },
+ contentDescription = null,
+ )
+ }
+ DropdownMenu(
+ offset = remember { DpOffset(x = -(48.dp * 2 + 8.dp), y = 8.dp) },
+ expanded = isNetworkMenuOpen.value,
+ onDismissRequest = onDismissNetworkMenu,
+ content = { networkMenu() },
+ )
+ IconButton(
+ onClick = onSettings
+ ) {
+ Icon(
+ painter = painterResource(Res.drawable.ic_settings_24dp),
+ tint = MaterialTheme.lwa.colorScheme.base.primary,
+ contentDescription = null,
+ )
+ }
IconButton(
onClick = onOverflow,
) {
@@ -92,54 +180,12 @@ private fun CampaignToolbarContent(
contentDescription = null,
)
}
-
DropdownMenu(
+ offset = remember { DpOffset(x = (-8).dp, y = 8.dp) },
expanded = isOverflowMenuOpen.value,
onDismissRequest = onDismissOverflowMenu,
- ) {
- DropdownMenuItem(
- onClick = { onDismissOverflowMenu(); onRollHistory() },
- ) {
- Icon(
- painter = painterResource(Res.drawable.ic_timeline_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null,
- )
- Text(
- modifier = Modifier.padding(start = 8.dp),
- color = MaterialTheme.colors.primary,
- text = stringResource(Res.string.main_page__roll_history_action),
- )
- }
- DropdownMenuItem(
- onClick = { onDismissOverflowMenu(); onNetwork() },
- ) {
- Icon(
- painter = painterResource(Res.drawable.ic_settings_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null,
- )
- Text(
- modifier = Modifier.padding(start = 8.dp),
- color = MaterialTheme.colors.primary,
- text = stringResource(Res.string.main_page__network_action),
- )
- }
- DropdownMenuItem(
- onClick = { onDismissOverflowMenu(); onOlUi() },
- ) {
- Icon(
- painter = painterResource(Res.drawable.ic_table_24dp),
- tint = MaterialTheme.lwa.colorScheme.base.primary,
- contentDescription = null,
- )
- Text(
- modifier = Modifier.padding(start = 8.dp),
- color = MaterialTheme.colors.primary,
- text = "Ancienne interface utilisateur",
- )
- }
- }
+ content = { overflowMenu() },
+ )
},
)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt
index 69ac12e..c098b3f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkScreen.kt
@@ -64,6 +64,7 @@ data class NetworkPageUio(
val enableCancel: Boolean,
) {
companion object {
+ @Stable
fun empty(
player: String = "",
host: String = "",
@@ -167,11 +168,14 @@ fun NetworkScreen(
@Composable
fun NetworkPage(
+ modifier: Modifier = Modifier,
viewModel: NetworkViewModel = koinViewModel(),
) {
val snack = LocalSnackHost.current
- Surface {
+ Surface(
+ modifier = modifier,
+ ) {
BlurContent(
modifier = Modifier.fillMaxSize(),
controller = viewModel.blurController,
@@ -267,6 +271,7 @@ private fun NetworkContent(
}
TextButton(
+ modifier = Modifier.align(alignment = Alignment.End),
enabled = player.value.enableActions,
onClick = onConnect,
) {
@@ -274,6 +279,7 @@ private fun NetworkContent(
}
TextButton(
+ modifier = Modifier.align(alignment = Alignment.End),
enabled = player.value.enableCancel,
onClick = onDisconnect,
) {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt
index 78418fb..dd2b7a7 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/network/NetworkViewModel.kt
@@ -64,11 +64,11 @@ class NetworkViewModel(
}
fun onPortChange(port: String) {
- this.portFlow.value = port.toIntOrNull() ?: settings.port
+ portFlow.value = port.toIntOrNull() ?: settings.port
}
fun onHostChange(host: String) {
- this.hostFlow.value = host
+ hostFlow.value = host
}
fun connect() {
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt
index 2287c75..7c6fdfa 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/roll/RollViewModel.kt
@@ -196,6 +196,16 @@ class RollViewModel(
rollValue = roll,
rollSuccessLimit = rollStep?.success?.last,
resultLabel = success,
+ critical = rollStep?.let {
+ when (roll) {
+ in it.criticalSuccess -> RollMessage.Critical.CRITICAL_SUCCESS
+ in it.specialSuccess -> RollMessage.Critical.SPECIAL_SUCCESS
+ in it.success -> RollMessage.Critical.SUCCESS
+ in it.failure -> RollMessage.Critical.FAILURE
+ in it.criticalFailure -> RollMessage.Critical.CRITICAL_FAILURE
+ else -> null
+ }
+ }
)
networkRepository.share(
payload = payload,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsScreen.kt
new file mode 100644
index 0000000..abfd139
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/screen/settings/SettingsScreen.kt
@@ -0,0 +1,61 @@
+package com.pixelized.desktop.lwa.ui.screen.settings
+
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Icon
+import androidx.compose.material.IconButton
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Surface
+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.ui.Modifier
+import com.pixelized.desktop.lwa.ui.navigation.screen.LocalScreenController
+
+@Composable
+fun SettingsScreen() {
+ val screen = LocalScreenController.current
+
+ Surface {
+ SettingsContent(
+ modifier = Modifier.fillMaxSize(),
+ onBack = {
+ screen.popBackStack()
+ },
+ )
+ }
+}
+
+@Composable
+private fun SettingsContent(
+ modifier: Modifier = Modifier,
+ onBack: () -> Unit,
+) {
+ Scaffold(
+ modifier = modifier,
+ topBar = {
+ TopAppBar(
+ title = { },
+ navigationIcon = {
+ IconButton(
+ onClick = onBack,
+ ) {
+ Icon(
+ imageVector = Icons.AutoMirrored.Default.ArrowBack,
+ contentDescription = null,
+ )
+ }
+ }
+ )
+ },
+ content = { paddingValues ->
+ Column(
+ modifier = Modifier.padding(paddingValues = paddingValues),
+ ) {
+
+ }
+ }
+ )
+}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt
index 8337488..73ca61e 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/LwaTheme.kt
@@ -6,8 +6,10 @@ import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.Stable
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.remember
-import com.pixelized.desktop.lwa.ui.theme.color.LwaColorTheme
+import com.pixelized.desktop.lwa.ui.theme.color.LwaColors
import com.pixelized.desktop.lwa.ui.theme.color.darkLwaColorTheme
+import com.pixelized.desktop.lwa.ui.theme.typography.LwaTypography
+import com.pixelized.desktop.lwa.ui.theme.typography.lwaTypography
val LocalLwaTheme = compositionLocalOf {
error("Local Snack Controller is not yet ready")
@@ -20,17 +22,21 @@ val MaterialTheme.lwa: LwaTheme
@Stable
data class LwaTheme(
- val colorScheme: LwaColorTheme,
+ val colorScheme: LwaColors,
+ val typography: LwaTypography,
)
@Composable
fun LwaTheme(
content: @Composable () -> Unit,
) {
- val lwaColorTheme = darkLwaColorTheme()
+ val lwaColors = darkLwaColorTheme()
+ val lwaTypography = lwaTypography(colors = lwaColors)
+
val theme = remember {
LwaTheme(
- colorScheme = lwaColorTheme,
+ colorScheme = lwaColors,
+ typography = lwaTypography,
)
}
@@ -38,7 +44,7 @@ fun LwaTheme(
LocalLwaTheme provides theme
) {
MaterialTheme(
- colors = lwaColorTheme.base,
+ colors = lwaColors.base,
typography = MaterialTheme.typography,
shapes = MaterialTheme.shapes,
content = content,
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorPalette.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorPalette.kt
index 06a24fc..c9f0d7f 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorPalette.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorPalette.kt
@@ -4,4 +4,10 @@ import androidx.compose.ui.graphics.Color
object LwaColorPalette {
val DefaultScrimColor = Color.Black.copy(alpha = 0.4f)
+
+ val Orange400 = Color(0xFFFFA726)
+ val Red400 = Color(0xFFFF7043)
+ val LightGreen400 = Color(0xFF9CCC65)
+ val Green400 = Color(0xFF66BB6A)
+ val Teal400 = Color(0xFF26A69A)
}
\ No newline at end of file
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
similarity index 64%
rename from composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt
rename to composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
index 22a4a39..73f4fd0 100644
--- a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColorTheme.kt
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/color/LwaColors.kt
@@ -13,15 +13,29 @@ import androidx.compose.ui.unit.dp
import kotlin.math.ln
@Stable
-data class LwaColorTheme(
+data class LwaColors(
val base: Colors,
val elevated: Elevated,
val portraitBackgroundBrush: Brush,
+ val chat: Chat,
) {
@Stable
data class Elevated(
val base1dp: Color,
val base2dp: Color,
+ val base3dp: Color,
+ val base4dp: Color,
+ )
+
+ @Stable
+ data class Chat(
+ val timestamp: Color,
+ val text: Color,
+ val criticalSuccess: Color,
+ val spacialSuccess: Color,
+ val success: Color,
+ val failure: Color,
+ val criticalFailure: Color,
)
}
@@ -29,7 +43,7 @@ data class LwaColorTheme(
@Stable
fun darkLwaColorTheme(
base: Colors = darkColors(),
- elevated: LwaColorTheme.Elevated = LwaColorTheme.Elevated(
+ elevated: LwaColors.Elevated = LwaColors.Elevated(
base1dp = base.calculateElevatedColor(
color = base.surface,
onColor = base.onSurface,
@@ -40,6 +54,16 @@ fun darkLwaColorTheme(
onColor = base.onSurface,
elevation = 2.dp,
),
+ base3dp = base.calculateElevatedColor(
+ color = base.surface,
+ onColor = base.onSurface,
+ elevation = 3.dp,
+ ),
+ base4dp = base.calculateElevatedColor(
+ color = base.surface,
+ onColor = base.onSurface,
+ elevation = 4.dp,
+ ),
),
portraitBackgroundBrush: Brush = Brush.verticalGradient(
listOf(
@@ -49,11 +73,21 @@ fun darkLwaColorTheme(
elevated.base1dp.copy(alpha = 0.5f),
elevated.base1dp.copy(alpha = 0.8f),
)
- )
-): LwaColorTheme = LwaColorTheme(
+ ),
+ chat: LwaColors.Chat = LwaColors.Chat(
+ timestamp = base.secondary,
+ text = base.onSurface.copy(alpha = 0.7f),
+ criticalSuccess = LwaColorPalette.Teal400,
+ spacialSuccess = LwaColorPalette.Green400,
+ success = LwaColorPalette.LightGreen400,
+ failure = LwaColorPalette.Orange400,
+ criticalFailure = LwaColorPalette.Red400,
+ ),
+): LwaColors = LwaColors(
base = base,
elevated = elevated,
portraitBackgroundBrush = portraitBackgroundBrush,
+ chat = chat,
)
@ReadOnlyComposable
diff --git a/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/typography/LwaTypography.kt b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/typography/LwaTypography.kt
new file mode 100644
index 0000000..affabe4
--- /dev/null
+++ b/composeApp/src/commonMain/kotlin/com/pixelized/desktop/lwa/ui/theme/typography/LwaTypography.kt
@@ -0,0 +1,68 @@
+package com.pixelized.desktop.lwa.ui.theme.typography
+
+import androidx.compose.material.Typography
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.unit.sp
+import com.pixelized.desktop.lwa.ui.theme.color.LwaColors
+import lwacharactersheet.composeapp.generated.resources.Res
+import lwacharactersheet.composeapp.generated.resources.consola_mono_bold
+import lwacharactersheet.composeapp.generated.resources.consola_mono_book
+import org.jetbrains.compose.resources.Font
+
+@Stable
+data class LwaTypography(
+ val base: Typography,
+ val chat: Chat,
+) {
+ @Stable
+ data class Chat(
+ val timestamp: TextStyle,
+ val text: TextStyle,
+ )
+}
+
+@Composable
+@Stable
+fun ConsolaFontFamily() = FontFamily(
+ Font(resource = Res.font.consola_mono_book, weight = FontWeight.Normal),
+ Font(resource = Res.font.consola_mono_bold, weight = FontWeight.Bold),
+)
+
+@Composable
+@Stable
+fun lwaTypography(
+ base: Typography = Typography(),
+ colors: LwaColors,
+): LwaTypography {
+ val jack = ConsolaFontFamily()
+
+ return remember(
+ jack,
+ ) {
+ LwaTypography(
+ base = base,
+ chat = LwaTypography.Chat(
+ timestamp = base.body1.copy(
+ fontFamily = jack,
+ fontWeight = FontWeight.Normal,
+ fontSize = 12.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.sp,
+ color = colors.chat.timestamp,
+ ),
+ text = base.body1.copy(
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp,
+ lineHeight = 16.sp,
+ letterSpacing = 0.4.sp,
+ color = colors.chat.text,
+ ),
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/RollMessage.kt b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/RollMessage.kt
index 0b56471..34a44d8 100644
--- a/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/RollMessage.kt
+++ b/shared/src/commonMain/kotlin/com/pixelized/shared/lwa/protocol/websocket/payload/RollMessage.kt
@@ -10,4 +10,13 @@ data class RollMessage(
val rollDifficulty: String? = null,
val rollValue: Int,
val rollSuccessLimit: Int? = null,
-) : MessagePayload
\ No newline at end of file
+ val critical: Critical?,
+) : MessagePayload {
+ enum class Critical {
+ CRITICAL_SUCCESS,
+ SPECIAL_SUCCESS,
+ SUCCESS,
+ FAILURE,
+ CRITICAL_FAILURE
+ }
+}
\ No newline at end of file