Add chat to the campaign screen + change a bit the network UI.
This commit is contained in:
parent
3f67e342a7
commit
7a9dd97123
29 changed files with 885 additions and 100 deletions
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M480,840q-42,0 -71,-29t-29,-71q0,-42 29,-71t71,-29q42,0 71,29t29,71q0,42 -29,71t-71,29ZM254,614l-84,-86q59,-59 138.5,-93.5T480,400q92,0 171.5,35T790,530l-84,84q-44,-44 -102,-69t-124,-25q-66,0 -124,25t-102,69ZM84,444 L0,360q92,-94 215,-147t265,-53q142,0 265,53t215,147l-84,84q-77,-77 -178.5,-120.5T480,280q-116,0 -217.5,43.5T84,444Z"
|
||||
android:fillColor="#5f6368"/>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:pathData="M790,904 L414,526q-47,11 -87.5,33T254,614l-84,-86q32,-32 69,-56t79,-42l-90,-90q-41,21 -76.5,46.5T84,444L0,358q32,-32 66.5,-57.5T140,252l-84,-84 56,-56 736,736 -58,56ZM480,840q-42,0 -71,-29.5T380,740q0,-42 29,-71t71,-29q42,0 71,29t29,71q0,41 -29,70.5T480,840ZM716,602 L687,573 658,544 514,400q81,8 151.5,41T790,528l-74,74ZM876,444q-77,-77 -178.5,-120.5T480,280q-21,0 -40.5,1.5T400,286L298,184q44,-12 89.5,-18t92.5,-6q142,0 265,53t215,145l-84,86Z"
|
||||
android:fillColor="#5f6368"/>
|
||||
</vector>
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -9,6 +9,7 @@
|
|||
<string name="main_page__open_save_directory">Ouvrir le dossier de sauvegarde</string>
|
||||
<string name="main_page__roll_history_action">Historique des lancers</string>
|
||||
<string name="main_page__network_action">Configuration de la table</string>
|
||||
<string name="main_page__settings_action">Configuration de l'application</string>
|
||||
|
||||
<string name="roll_page__roll__label">Jet de :</string>
|
||||
<string name="roll_page__roll__success_label">Réussite si lancer inférieur ou égal à : %1$s</string>
|
||||
|
|
|
|||
|
|
@ -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<SnackbarHostState> {
|
|||
error("Local Snack Controller is not yet ready")
|
||||
}
|
||||
|
||||
val LocalApplicationScope = compositionLocalOf<ApplicationScope> {
|
||||
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<KeyEventHandler>() }
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<NavHostController> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
|
|
@ -18,6 +22,9 @@ val LocalScreenController = compositionLocalOf<NavHostController> {
|
|||
@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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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<CampaignLayoutScope> {
|
||||
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<DpSize>,
|
||||
val rightOverlay: State<DpSize>,
|
||||
val chatOverlay: State<DpSize>,
|
||||
)
|
||||
|
||||
private fun IntSize.toDp(density: Density) = with(density) {
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ class CampaignViewModel(
|
|||
val title: Flow<String> = campaignRepository.campaignFlow
|
||||
.map { it.scene.name }
|
||||
|
||||
val networkStatus = network.status
|
||||
|
||||
fun init() {
|
||||
viewModelScope.launch {
|
||||
combine(
|
||||
|
|
|
|||
|
|
@ -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<List<TextMessage>>,
|
||||
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<Dp> {
|
||||
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)
|
||||
}
|
||||
|
|
@ -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<TextMessage>()
|
||||
val messages: StateFlow<List<TextMessage>> = 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))
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.pixelized.desktop.lwa.ui.screen.campaign.chat.text
|
||||
|
||||
sealed interface TextMessage {
|
||||
val id : String
|
||||
val timestamp: String
|
||||
}
|
||||
|
|
@ -118,6 +118,7 @@ fun CharacterDetailPanel(
|
|||
blurController.show()
|
||||
}
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -99,7 +99,8 @@ fun PlayerPortraitRoll(
|
|||
.onClick(
|
||||
matcher = PointerMatcher.mouse(PointerButton.Secondary),
|
||||
onClick = { onRightClick(it) },
|
||||
).clickable {
|
||||
)
|
||||
.clickable {
|
||||
onLeftClick(it)
|
||||
}
|
||||
.padding(all = 8.dp),
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
networkStatus: State<NetworkRepository.Status>,
|
||||
isNetworkMenuOpen: State<Boolean>,
|
||||
isOverflowMenuOpen: State<Boolean>,
|
||||
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() },
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -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,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
) {
|
||||
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -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<LwaTheme> {
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -10,4 +10,13 @@ data class RollMessage(
|
|||
val rollDifficulty: String? = null,
|
||||
val rollValue: Int,
|
||||
val rollSuccessLimit: Int? = null,
|
||||
) : MessagePayload
|
||||
val critical: Critical?,
|
||||
) : MessagePayload {
|
||||
enum class Critical {
|
||||
CRITICAL_SUCCESS,
|
||||
SPECIAL_SUCCESS,
|
||||
SUCCESS,
|
||||
FAILURE,
|
||||
CRITICAL_FAILURE
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue