New UI.
This commit is contained in:
parent
d84bc9bbef
commit
0c613c5b72
25 changed files with 947 additions and 23 deletions
|
|
@ -29,6 +29,9 @@ kotlin {
|
||||||
api(libs.koin.core)
|
api(libs.koin.core)
|
||||||
implementation(libs.koin.compose)
|
implementation(libs.koin.compose)
|
||||||
implementation(libs.koin.compose.viewmodel)
|
implementation(libs.koin.compose.viewmodel)
|
||||||
|
// composable component.
|
||||||
|
implementation(libs.coil.compose)
|
||||||
|
implementation(libs.coil.network)
|
||||||
// common
|
// common
|
||||||
implementation(projects.shared)
|
implementation(projects.shared)
|
||||||
// network
|
// network
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M0 0h24v24H0z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41 0.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M0 0h24v24H0V0z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M21 3L3 10.53v0.98l6.84 2.65L12.48 21h0.98L21 3z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
|
||||||
|
<path
|
||||||
|
android:pathData="M 0 0 H 24 V 24 H 0 V 0 Z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M12,2c-5.33,4.55-8,8.48-8,11.8c0,4.98,3.8,8.2,8,8.2s8-3.22,8-8.2C20,10.48,17.33,6.55,12,2z M7.83,14 c0.37,0,0.67,0.26,0.74,0.62c0.41,2.22,2.28,2.98,3.64,2.87c0.43-0.02,0.79,0.32,0.79,0.75c0,0.4-0.32,0.73-0.72,0.75 c-2.13,0.13-4.62-1.09-5.19-4.12C7.01,14.42,7.37,14,7.83,14z" />
|
||||||
|
</vector>
|
||||||
|
|
@ -25,16 +25,12 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.compose.ui.unit.min
|
|
||||||
import androidx.compose.ui.window.ApplicationScope
|
import androidx.compose.ui.window.ApplicationScope
|
||||||
import androidx.compose.ui.window.Window
|
import androidx.compose.ui.window.Window
|
||||||
import androidx.compose.ui.window.rememberWindowState
|
|
||||||
import com.pixelized.desktop.lwa.composable.key.KeyEventHandler
|
import com.pixelized.desktop.lwa.composable.key.KeyEventHandler
|
||||||
import com.pixelized.desktop.lwa.composable.key.LocalKeyEventHandlers
|
import com.pixelized.desktop.lwa.composable.key.LocalKeyEventHandlers
|
||||||
import com.pixelized.desktop.lwa.navigation.screen.MainNavHost
|
|
||||||
import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetDestination
|
import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetDestination
|
||||||
import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetEditDestination
|
import com.pixelized.desktop.lwa.navigation.screen.destination.CharacterSheetEditDestination
|
||||||
import com.pixelized.desktop.lwa.navigation.window.WindowController
|
import com.pixelized.desktop.lwa.navigation.window.WindowController
|
||||||
|
|
@ -45,6 +41,7 @@ import com.pixelized.desktop.lwa.navigation.window.destination.RollHistoryWindow
|
||||||
import com.pixelized.desktop.lwa.navigation.window.rememberMaxWindowHeight
|
import com.pixelized.desktop.lwa.navigation.window.rememberMaxWindowHeight
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
|
||||||
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.CampaignScreen
|
||||||
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
|
import com.pixelized.desktop.lwa.screen.characterSheet.CharacterSheetMainNavHost
|
||||||
import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryPage
|
import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryPage
|
||||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||||
|
|
@ -55,7 +52,6 @@ import lwacharactersheet.composeapp.generated.resources.network__disconnect__mes
|
||||||
import org.jetbrains.compose.resources.getString
|
import org.jetbrains.compose.resources.getString
|
||||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||||
import org.koin.compose.koinInject
|
import org.koin.compose.koinInject
|
||||||
import java.awt.Toolkit
|
|
||||||
|
|
||||||
val LocalWindowController = compositionLocalOf<WindowController> {
|
val LocalWindowController = compositionLocalOf<WindowController> {
|
||||||
error("Local Window Controller is not yet ready")
|
error("Local Window Controller is not yet ready")
|
||||||
|
|
@ -87,10 +83,10 @@ fun ApplicationScope.App() {
|
||||||
) {
|
) {
|
||||||
Window(
|
Window(
|
||||||
onCloseRequest = ::exitApplication,
|
onCloseRequest = ::exitApplication,
|
||||||
state = rememberWindowState(
|
// state = rememberWindowState(
|
||||||
width = 320.dp + 64.dp,
|
// width = 320.dp + 64.dp,
|
||||||
height = 900.dp,
|
// height = 900.dp,
|
||||||
),
|
// ),
|
||||||
title = "LwaCharacterSheet",
|
title = "LwaCharacterSheet",
|
||||||
onKeyEvent = { event ->
|
onKeyEvent = { event ->
|
||||||
keyEventHandlers.reversed().any { it(event) }
|
keyEventHandlers.reversed().any { it(event) }
|
||||||
|
|
@ -128,7 +124,8 @@ fun ApplicationScope.App() {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
MainNavHost()
|
// MainNavHost()
|
||||||
|
CampaignScreen()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
NetworkSnackHandler(
|
NetworkSnackHandler(
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ import com.pixelized.desktop.lwa.screen.rollhistory.RollHistoryViewModel
|
||||||
import com.pixelized.desktop.lwa.parser.dice.DiceParser
|
import com.pixelized.desktop.lwa.parser.dice.DiceParser
|
||||||
import com.pixelized.desktop.lwa.parser.word.WordParser
|
import com.pixelized.desktop.lwa.parser.word.WordParser
|
||||||
import com.pixelized.desktop.lwa.parser.expression.ExpressionParser
|
import com.pixelized.desktop.lwa.parser.expression.ExpressionParser
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetailViewModel
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDiminishedViewModel
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.ribbon.PlayerRibbonViewModel
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import org.koin.core.module.dsl.factoryOf
|
import org.koin.core.module.dsl.factoryOf
|
||||||
import org.koin.core.module.dsl.singleOf
|
import org.koin.core.module.dsl.singleOf
|
||||||
|
|
@ -86,6 +89,9 @@ val viewModelDependencies
|
||||||
viewModelOf(::RollViewModel)
|
viewModelOf(::RollViewModel)
|
||||||
viewModelOf(::RollHistoryViewModel)
|
viewModelOf(::RollHistoryViewModel)
|
||||||
viewModelOf(::NetworkViewModel)
|
viewModelOf(::NetworkViewModel)
|
||||||
|
viewModelOf(::PlayerRibbonViewModel)
|
||||||
|
viewModelOf(::CharacterDetailViewModel)
|
||||||
|
viewModelOf(::CharacterDiminishedViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parserDependencies
|
val parserDependencies
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ import androidx.compose.ui.draw.drawWithContent
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.unit.Dp
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.pixelized.desktop.lwa.theme.LwaColorPalette
|
import com.pixelized.desktop.lwa.theme.color.LwaColorPalette
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
class BlurContentController(
|
class BlurContentController(
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,8 @@ class CharacterSheetJsonFactory(
|
||||||
val json = CharacterSheetJsonV1(
|
val json = CharacterSheetJsonV1(
|
||||||
id = sheet.id,
|
id = sheet.id,
|
||||||
name = sheet.name,
|
name = sheet.name,
|
||||||
|
thumbnail = sheet.thumbnail,
|
||||||
|
portrait = sheet.portrait,
|
||||||
strength = sheet.strength,
|
strength = sheet.strength,
|
||||||
dexterity = sheet.dexterity,
|
dexterity = sheet.dexterity,
|
||||||
constitution = sheet.constitution,
|
constitution = sheet.constitution,
|
||||||
|
|
@ -93,6 +95,8 @@ class CharacterSheetJsonFactory(
|
||||||
CharacterSheet(
|
CharacterSheet(
|
||||||
id = json.id,
|
id = json.id,
|
||||||
name = json.name,
|
name = json.name,
|
||||||
|
portrait = json.portrait,
|
||||||
|
thumbnail = json.thumbnail,
|
||||||
strength = json.strength,
|
strength = json.strength,
|
||||||
dexterity = json.dexterity,
|
dexterity = json.dexterity,
|
||||||
constitution = json.constitution,
|
constitution = json.constitution,
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package com.pixelized.desktop.lwa.repository.characterSheet.model
|
||||||
data class CharacterSheet(
|
data class CharacterSheet(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val portrait: String?,
|
||||||
|
val thumbnail: String?,
|
||||||
// characteristics
|
// characteristics
|
||||||
val strength: Int,
|
val strength: Int,
|
||||||
val dexterity: Int,
|
val dexterity: Int,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import kotlinx.serialization.Serializable
|
||||||
data class CharacterSheetJsonV1(
|
data class CharacterSheetJsonV1(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
|
val portrait: String?,
|
||||||
|
val thumbnail: String?,
|
||||||
// characteristics
|
// characteristics
|
||||||
val strength: Int,
|
val strength: Int,
|
||||||
val dexterity: Int,
|
val dexterity: Int,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,177 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign
|
||||||
|
|
||||||
|
import androidx.compose.desktop.ui.tooling.preview.Preview
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.input.key.KeyEventType
|
||||||
|
import androidx.compose.ui.input.key.key
|
||||||
|
import androidx.compose.ui.input.key.type
|
||||||
|
import androidx.compose.ui.layout.onSizeChanged
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.unit.Density
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.IntSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.desktop.lwa.composable.key.KeyHandler
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetail
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDetailViewModel
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.detail.CharacterDiminishedViewModel
|
||||||
|
import com.pixelized.desktop.lwa.screen.campaign.player.ribbon.PlayerRibbon
|
||||||
|
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CampaignScreen(
|
||||||
|
characterDetailViewModel: CharacterDetailViewModel = koinViewModel(),
|
||||||
|
dismissedViewModel: CharacterDiminishedViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
KeyHandler {
|
||||||
|
when {
|
||||||
|
it.type == KeyEventType.KeyUp && it.key == Key.Escape -> {
|
||||||
|
characterDetailViewModel.hideCharacter()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Surface {
|
||||||
|
CampaignScreenLayout(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
top = {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(32.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
elevation = 1.dp,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bottom = {
|
||||||
|
Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(48.dp)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
elevation = 1.dp,
|
||||||
|
) {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
main = {
|
||||||
|
|
||||||
|
},
|
||||||
|
leftOverlay = {
|
||||||
|
PlayerRibbon(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
onCharacter = {
|
||||||
|
characterDetailViewModel.showCharacter(id = it)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
rightOverlay = {
|
||||||
|
CharacterDetail(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 8.dp)
|
||||||
|
.fillMaxHeight(),
|
||||||
|
viewModel = characterDetailViewModel,
|
||||||
|
dismissedViewModel = dismissedViewModel,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
DiminishedStatDialog(
|
||||||
|
dialog = dismissedViewModel.diminishedDialog,
|
||||||
|
onConfirm = { diminished ->
|
||||||
|
dismissedViewModel.changeDiminished(
|
||||||
|
dialog = diminished
|
||||||
|
)
|
||||||
|
dismissedViewModel.hideDiminishedDialog()
|
||||||
|
},
|
||||||
|
onDismissRequest = {
|
||||||
|
dismissedViewModel.hideDiminishedDialog()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@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,
|
||||||
|
) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
val leftOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
|
val rightOverlayState = remember { mutableStateOf(DpSize.Unspecified) }
|
||||||
|
val scope = remember {
|
||||||
|
CampaignScreenScope(
|
||||||
|
leftOverlay = leftOverlayState,
|
||||||
|
rightOverlay = rightOverlayState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
with(scope) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
top()
|
||||||
|
Box(
|
||||||
|
modifier = Modifier.weight(1f, fill = true),
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.Center)
|
||||||
|
.fillMaxSize(),
|
||||||
|
) {
|
||||||
|
main()
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.CenterStart)
|
||||||
|
.onSizeChanged { leftOverlayState.value = it.toDp(density) },
|
||||||
|
) {
|
||||||
|
leftOverlay()
|
||||||
|
}
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.align(alignment = Alignment.CenterEnd)
|
||||||
|
.onSizeChanged { rightOverlayState.value = it.toDp(density) },
|
||||||
|
) {
|
||||||
|
rightOverlay()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bottom()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CampaignScreenScope(
|
||||||
|
val leftOverlay: State<DpSize>,
|
||||||
|
val rightOverlay: State<DpSize>,
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun IntSize.toDp(density: Density) = with(density) {
|
||||||
|
DpSize(
|
||||||
|
width = width.toDp(),
|
||||||
|
height = height.toDp(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,279 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.detail
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.slideInHorizontally
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
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.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.IconButton
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Surface
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
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.screen.characterSheet.detail.dialog.DiminishedStatDialog
|
||||||
|
import com.pixelized.desktop.lwa.theme.lwa
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_close_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_near_me
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_skull_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class CharacterDetailUio(
|
||||||
|
val id: String,
|
||||||
|
val portrait: String?,
|
||||||
|
val name: String,
|
||||||
|
val hp: String,
|
||||||
|
val pp: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class CharacterDynDetailUio(
|
||||||
|
val hp: String,
|
||||||
|
val pp: String,
|
||||||
|
val mov: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CharacterDetail(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
dismissedViewModel: CharacterDiminishedViewModel,
|
||||||
|
viewModel: CharacterDetailViewModel = koinViewModel(),
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val detail = viewModel.detail.collectAsState()
|
||||||
|
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = modifier,
|
||||||
|
targetState = detail.value,
|
||||||
|
transitionSpec = {
|
||||||
|
(fadeIn() + slideInHorizontally { it / 2 }).togetherWith(fadeOut())
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
when (it) {
|
||||||
|
null -> Box(
|
||||||
|
modifier = Modifier.fillMaxHeight(),
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> {
|
||||||
|
val dynDetail = viewModel.collectDynamicDetailAsState(id = it.id)
|
||||||
|
|
||||||
|
CharacterDetailContent(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.width(width = 128.dp * 4),
|
||||||
|
character = it,
|
||||||
|
dynDetail = dynDetail,
|
||||||
|
onDismissRequest = { viewModel.hideCharacter() },
|
||||||
|
onDiminished = {
|
||||||
|
scope.launch {
|
||||||
|
dismissedViewModel.showDiminishedDialog(id = it.id)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onHp = { },
|
||||||
|
onPp = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun CharacterDetailContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
character: CharacterDetailUio,
|
||||||
|
dynDetail: State<CharacterDynDetailUio>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDiminished: () -> Unit,
|
||||||
|
onHp: () -> Unit,
|
||||||
|
onPp: () -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Background(
|
||||||
|
character = character,
|
||||||
|
)
|
||||||
|
Column {
|
||||||
|
CharacterHeader(
|
||||||
|
modifier = Modifier.padding(start = 16.dp).fillMaxWidth(),
|
||||||
|
character = character,
|
||||||
|
onDismissRequest = onDismissRequest,
|
||||||
|
dynDetail = dynDetail,
|
||||||
|
onDiminished = onDiminished,
|
||||||
|
onHp = onHp,
|
||||||
|
onPp = onPp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Background(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
character: CharacterDetailUio,
|
||||||
|
) {
|
||||||
|
Surface(
|
||||||
|
modifier = modifier.fillMaxSize(),
|
||||||
|
color = MaterialTheme.lwa.color.elevatedSurface,
|
||||||
|
) {
|
||||||
|
// Image(
|
||||||
|
// modifier = Modifier.fillMaxSize().drawWithContent {
|
||||||
|
// drawContent()
|
||||||
|
// drawRect(
|
||||||
|
// brush = Brush.verticalGradient(
|
||||||
|
// listOfNotNull(
|
||||||
|
// color?.copy(alpha = 0.7f),
|
||||||
|
// color,
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// )
|
||||||
|
// },
|
||||||
|
// painter = rememberAsyncImagePainter(model = character.portrait),
|
||||||
|
// contentDescription = null,
|
||||||
|
// contentScale = ContentScale.Crop,
|
||||||
|
// alignment = Alignment.TopCenter,
|
||||||
|
// )
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CharacterHeader(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
character: CharacterDetailUio,
|
||||||
|
dynDetail: State<CharacterDynDetailUio>,
|
||||||
|
onDismissRequest: () -> Unit,
|
||||||
|
onDiminished: () -> Unit,
|
||||||
|
onHp: () -> Unit,
|
||||||
|
onPp: () -> Unit,
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Row {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.weight(1f)
|
||||||
|
.align(alignment = Alignment.CenterVertically),
|
||||||
|
style = MaterialTheme.typography.h5,
|
||||||
|
text = character.name,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
IconButton(
|
||||||
|
onClick = onDiminished,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(size = 24.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_skull_24dp),
|
||||||
|
tint = MaterialTheme.colors.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
IconButton(
|
||||||
|
onClick = onDismissRequest,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(Res.drawable.ic_close_24dp),
|
||||||
|
tint = MaterialTheme.lwa.color.base.primary,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.clip(shape = CircleShape).clickable { onHp() },
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_heart_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
color = MaterialTheme.lwa.color.base.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = dynDetail.value.hp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
fontWeight = FontWeight.Thin,
|
||||||
|
text = "/${character.hp}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.clip(shape = CircleShape).clickable { onPp() },
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_water_drop_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
color = MaterialTheme.lwa.color.base.primary,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = dynDetail.value.pp,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
fontWeight = FontWeight.Thin,
|
||||||
|
text = "/${character.pp}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.Bottom,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.padding(bottom = 4.dp, end = 2.dp).size(12.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_near_me),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.h6,
|
||||||
|
text = dynDetail.value.mov,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
text = "m",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.detail
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.pixelized.desktop.lwa.repository.alteration.AlterationRepository
|
||||||
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class CharacterDetailViewModel(
|
||||||
|
private val repository: CharacterSheetRepository,
|
||||||
|
private val alteration: AlterationRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val displayedCharacterId = MutableStateFlow<String?>(null)
|
||||||
|
|
||||||
|
val detail: StateFlow<CharacterDetailUio?> = combine(
|
||||||
|
displayedCharacterId,
|
||||||
|
repository.characterSheetFlow(),
|
||||||
|
) { id, sheets ->
|
||||||
|
val sheet = sheets.firstOrNull { it.id == id }
|
||||||
|
if (sheet == null) return@combine null
|
||||||
|
CharacterDetailUio(
|
||||||
|
id = sheet.id,
|
||||||
|
portrait = sheet.portrait,
|
||||||
|
name = sheet.name,
|
||||||
|
hp = "${sheet.maxHp}",
|
||||||
|
pp = "${sheet.maxPp}",
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = null,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun collectDynamicDetailAsState(id: String): State<CharacterDynDetailUio> {
|
||||||
|
val flow = remember(id) {
|
||||||
|
repository.characterSheetFlow(id = id)
|
||||||
|
}
|
||||||
|
return remember(id) {
|
||||||
|
flow.mapNotNull { sheet ->
|
||||||
|
if (sheet == null) return@mapNotNull null
|
||||||
|
CharacterDynDetailUio(
|
||||||
|
hp = sheet.currentHp.toString(),
|
||||||
|
pp = sheet.currentPp.toString(),
|
||||||
|
mov = sheet.movement.toString(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.collectAsState(
|
||||||
|
initial = CharacterDynDetailUio(
|
||||||
|
hp = flow.value?.maxHp?.toString() ?: "",
|
||||||
|
pp = flow.value?.maxPp?.toString() ?: "",
|
||||||
|
mov = flow.value?.movement?.toString() ?: "",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showCharacter(id: String) {
|
||||||
|
displayedCharacterId.value = id
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideCharacter() {
|
||||||
|
displayedCharacterId.value = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.detail
|
||||||
|
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
|
import com.pixelized.desktop.lwa.screen.characterSheet.detail.dialog.DiminishedStatDialogUio
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.character_sheet__diminished__label
|
||||||
|
import org.jetbrains.compose.resources.getString
|
||||||
|
|
||||||
|
class CharacterDiminishedViewModel(
|
||||||
|
private val repository: CharacterSheetRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _diminishedDialog = mutableStateOf<DiminishedStatDialogUio?>(null)
|
||||||
|
val diminishedDialog: State<DiminishedStatDialogUio?> get() = _diminishedDialog
|
||||||
|
|
||||||
|
suspend fun showDiminishedDialog(id: String) {
|
||||||
|
val diminished = repository.characterDiminishedFlow(id = id).value
|
||||||
|
val textFieldValue =
|
||||||
|
mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
|
||||||
|
_diminishedDialog.value = DiminishedStatDialogUio(
|
||||||
|
id = id,
|
||||||
|
label = getString(resource = Res.string.character_sheet__diminished__label),
|
||||||
|
value = { textFieldValue.value },
|
||||||
|
onValueChange = { value ->
|
||||||
|
textFieldValue.value = when (value.text.toIntOrNull()?.takeIf { it >= 0 }) {
|
||||||
|
null -> TextFieldValue("0", selection = TextRange(index = 0))
|
||||||
|
else -> value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun hideDiminishedDialog() {
|
||||||
|
_diminishedDialog.value = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeDiminished(dialog: DiminishedStatDialogUio) {
|
||||||
|
val value = dialog.value().text.toIntOrNull() ?: 0
|
||||||
|
repository.setDiminishedForCharacter(
|
||||||
|
id = dialog.id,
|
||||||
|
diminished = value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,125 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.ribbon
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.Icon
|
||||||
|
import androidx.compose.material.MaterialTheme
|
||||||
|
import androidx.compose.material.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
|
import androidx.compose.ui.graphics.Brush
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.FilterQuality
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.unit.DpSize
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil3.compose.AsyncImage
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.Res
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_heart_24dp
|
||||||
|
import lwacharactersheet.composeapp.generated.resources.ic_water_drop_24dp
|
||||||
|
import org.jetbrains.compose.resources.painterResource
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class PlayerPortraitUio(
|
||||||
|
val id: String,
|
||||||
|
val portrait: String?,
|
||||||
|
val hp: Int,
|
||||||
|
val maxHp: Int,
|
||||||
|
val pp: Int,
|
||||||
|
val maxPp: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
object PlayerPortrait {
|
||||||
|
object Default {
|
||||||
|
val size = DpSize(96.dp, 128.dp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlayerPortrait(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
size: DpSize = PlayerPortrait.Default.size,
|
||||||
|
character: PlayerPortraitUio,
|
||||||
|
onCharacter: (id: String) -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier
|
||||||
|
.size(size = size)
|
||||||
|
.clip(shape = remember { RoundedCornerShape(8.dp) })
|
||||||
|
.clickable { onCharacter(character.id) },
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
model = character.portrait,
|
||||||
|
contentScale = ContentScale.Crop,
|
||||||
|
alignment = Alignment.TopCenter,
|
||||||
|
filterQuality = FilterQuality.High,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.drawWithContent {
|
||||||
|
drawRect(
|
||||||
|
brush = Brush.verticalGradient(
|
||||||
|
listOf(
|
||||||
|
Color.Black.copy(alpha = 0.0f),
|
||||||
|
Color.Black.copy(alpha = 0.0f),
|
||||||
|
Color.Black.copy(alpha = 0.0f),
|
||||||
|
Color.Black.copy(alpha = 0.5f),
|
||||||
|
Color.Black.copy(alpha = 0.8f),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
drawContent()
|
||||||
|
}
|
||||||
|
.padding(all = 2.dp),
|
||||||
|
verticalArrangement = Arrangement.aligned(alignment = Alignment.Bottom),
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_heart_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(bottom = 2.dp),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
text = "${character.hp}/${character.maxHp}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(space = 2.dp),
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
modifier = Modifier.size(12.dp),
|
||||||
|
painter = painterResource(Res.drawable.ic_water_drop_24dp),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(bottom = 2.dp),
|
||||||
|
style = MaterialTheme.typography.caption,
|
||||||
|
text = "${character.pp}/${character.maxPp}",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.ribbon
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import org.koin.compose.viewmodel.koinViewModel
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun PlayerRibbon(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
playerRibbonViewModel: PlayerRibbonViewModel = koinViewModel(),
|
||||||
|
onCharacter: (id: String) -> Unit,
|
||||||
|
) {
|
||||||
|
val characters = playerRibbonViewModel.characters.collectAsState()
|
||||||
|
|
||||||
|
LazyColumn(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(space = 8.dp)
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = characters.value,
|
||||||
|
key = { it.id },
|
||||||
|
) {
|
||||||
|
PlayerPortrait(
|
||||||
|
character = it,
|
||||||
|
onCharacter = onCharacter,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.pixelized.desktop.lwa.screen.campaign.player.ribbon
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
|
||||||
|
class PlayerRibbonViewModel(
|
||||||
|
repository: CharacterSheetRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
val characters = repository.characterSheetFlow()
|
||||||
|
.map { sheets ->
|
||||||
|
sheets.map { sheet ->
|
||||||
|
PlayerPortraitUio(
|
||||||
|
id = sheet.id,
|
||||||
|
portrait = sheet.thumbnail,
|
||||||
|
hp = sheet.currentHp,
|
||||||
|
maxHp = sheet.maxHp,
|
||||||
|
pp = sheet.currentPp,
|
||||||
|
maxPp = sheet.maxPp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.stateIn(
|
||||||
|
scope = viewModelScope,
|
||||||
|
started = SharingStarted.Eagerly,
|
||||||
|
initialValue = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -197,6 +197,7 @@ class CharacterSheetViewModel(
|
||||||
val textFieldValue =
|
val textFieldValue =
|
||||||
mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
|
mutableStateOf(TextFieldValue("$diminished", selection = TextRange(index = 0)))
|
||||||
_diminishedDialog.value = DiminishedStatDialogUio(
|
_diminishedDialog.value = DiminishedStatDialogUio(
|
||||||
|
id = argument.id,
|
||||||
label = getString(resource = Res.string.character_sheet__diminished__label),
|
label = getString(resource = Res.string.character_sheet__diminished__label),
|
||||||
value = { textFieldValue.value },
|
value = { textFieldValue.value },
|
||||||
onValueChange = { value ->
|
onValueChange = { value ->
|
||||||
|
|
@ -215,7 +216,7 @@ class CharacterSheetViewModel(
|
||||||
fun changeDiminished(dialog: DiminishedStatDialogUio) {
|
fun changeDiminished(dialog: DiminishedStatDialogUio) {
|
||||||
val value = dialog.value().text.toIntOrNull() ?: 0
|
val value = dialog.value().text.toIntOrNull() ?: 0
|
||||||
repository.setDiminishedForCharacter(
|
repository.setDiminishedForCharacter(
|
||||||
id = argument.id,
|
id = dialog.id,
|
||||||
diminished = value,
|
diminished = value,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ import org.jetbrains.compose.resources.stringResource
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class DiminishedStatDialogUio(
|
data class DiminishedStatDialogUio(
|
||||||
|
val id: String,
|
||||||
val label: String,
|
val label: String,
|
||||||
val value: () -> TextFieldValue,
|
val value: () -> TextFieldValue,
|
||||||
val onValueChange: (TextFieldValue) -> Unit,
|
val onValueChange: (TextFieldValue) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,8 @@ class CharacterSheetEditFactory(
|
||||||
return CharacterSheet(
|
return CharacterSheet(
|
||||||
id = editedSheet.id,
|
id = editedSheet.id,
|
||||||
name = editedSheet.name.value.value,
|
name = editedSheet.name.value.value,
|
||||||
|
portrait = currentSheet?.portrait,
|
||||||
|
thumbnail = currentSheet?.thumbnail,
|
||||||
strength = editedSheet.strength.unpack()?.toIntOrNull()
|
strength = editedSheet.strength.unpack()?.toIntOrNull()
|
||||||
?: currentSheet?.strength
|
?: currentSheet?.strength
|
||||||
?: 0,
|
?: 0,
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,47 @@
|
||||||
package com.pixelized.desktop.lwa.theme
|
package com.pixelized.desktop.lwa.theme
|
||||||
|
|
||||||
import androidx.compose.material.MaterialTheme
|
import androidx.compose.material.MaterialTheme
|
||||||
import androidx.compose.material.darkColors
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
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.theme.color.LwaColorTheme
|
||||||
|
import com.pixelized.desktop.lwa.theme.color.darkLwaColorTheme
|
||||||
|
|
||||||
|
val LocalLwaTheme = compositionLocalOf<LwaTheme> {
|
||||||
|
error("Local Snack Controller is not yet ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
val MaterialTheme.lwa: LwaTheme
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
get() = LocalLwaTheme.current
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class LwaTheme(
|
||||||
|
val color: LwaColorTheme,
|
||||||
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun LwaTheme(
|
fun LwaTheme(
|
||||||
content: @Composable () -> Unit
|
content: @Composable () -> Unit,
|
||||||
) {
|
) {
|
||||||
MaterialTheme(
|
val lwaColorTheme = darkLwaColorTheme()
|
||||||
colors = darkColors(),
|
val theme = remember {
|
||||||
typography = MaterialTheme.typography,
|
LwaTheme(
|
||||||
shapes = MaterialTheme.shapes,
|
color = lwaColorTheme,
|
||||||
content = content,
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalLwaTheme provides theme
|
||||||
|
) {
|
||||||
|
MaterialTheme(
|
||||||
|
colors = lwaColorTheme.base,
|
||||||
|
typography = MaterialTheme.typography,
|
||||||
|
shapes = MaterialTheme.shapes,
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.desktop.lwa.theme
|
package com.pixelized.desktop.lwa.theme.color
|
||||||
|
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
|
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package com.pixelized.desktop.lwa.theme.color
|
||||||
|
|
||||||
|
import androidx.compose.material.Colors
|
||||||
|
import androidx.compose.material.darkColors
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.ReadOnlyComposable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.compositeOver
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import kotlin.math.ln
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class LwaColorTheme(
|
||||||
|
val base: Colors,
|
||||||
|
val elevatedSurface: Color,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
fun darkLwaColorTheme(
|
||||||
|
base: Colors = darkColors(),
|
||||||
|
elevatedSurface: Color = base.calculateElevatedColor(
|
||||||
|
color = base.surface,
|
||||||
|
onColor = base.onSurface,
|
||||||
|
elevation = 1.dp,
|
||||||
|
),
|
||||||
|
): LwaColorTheme = LwaColorTheme(
|
||||||
|
base = base,
|
||||||
|
elevatedSurface = elevatedSurface,
|
||||||
|
)
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
private fun Colors.calculateElevatedColor(color: Color, onColor: Color, elevation: Dp): Color {
|
||||||
|
return if (elevation > 0.dp && !isLight) {
|
||||||
|
val foregroundColor = calculateForegroundColor(onColor, elevation)
|
||||||
|
foregroundColor.compositeOver(color)
|
||||||
|
} else {
|
||||||
|
color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ReadOnlyComposable
|
||||||
|
@Composable
|
||||||
|
private fun calculateForegroundColor(color: Color, elevation: Dp): Color {
|
||||||
|
val alpha = ((4.5f * ln(elevation.value + 1)) + 2f) / 100f
|
||||||
|
return color.copy(alpha = alpha)
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ ktor = "3.0.1"
|
||||||
koin = "4.0.0"
|
koin = "4.0.0"
|
||||||
turtle = "0.5.0"
|
turtle = "0.5.0"
|
||||||
logback = "1.5.11"
|
logback = "1.5.11"
|
||||||
|
coil = "3.1.0"
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
|
||||||
|
|
@ -42,4 +42,7 @@ ktor-server-netty = { group = 'io.ktor', name = "ktor-server-netty", version.ref
|
||||||
ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" }
|
ktor-server-websockets = { group = 'io.ktor', name = "ktor-server-websockets", version.ref = "ktor" }
|
||||||
|
|
||||||
turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" }
|
turtle = { group = "com.lordcodes.turtle", name = "turtle", version.ref = "turtle" }
|
||||||
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
logback = { module = "ch.qos.logback:logback-classic", version.ref = "logback" }
|
||||||
|
|
||||||
|
coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" }
|
||||||
|
coil-network = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" }
|
||||||
Loading…
Add table
Add a link
Reference in a new issue