Link the CharacterSheet model to the UI.
This commit is contained in:
parent
65aa53890f
commit
b71645a7a2
30 changed files with 1113 additions and 961 deletions
|
|
@ -0,0 +1,21 @@
|
|||
package com.pixelized.desktop.lwa
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.pixelized.desktop.lwa.navigation.MainNavHost
|
||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
fun App() {
|
||||
LwaTheme {
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
MainNavHost()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.pixelized.desktop.lwa.composable.decoratedBox
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.RectangleShape
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.pixelized.desktop.lwa.theme.LwaTheme
|
||||
import org.jetbrains.compose.ui.tooling.preview.Preview
|
||||
|
||||
@Composable
|
||||
fun DecoratedBox(
|
||||
modifier: Modifier = Modifier,
|
||||
border: Color = Color(0xFFDFDFDF),
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.border(width = 1.dp, color = border, shape = RoundedCornerShape(size = 16.dp))
|
||||
.padding(all = 2.dp)
|
||||
.border(width = 1.dp, color = border, shape = RectangleShape)
|
||||
.then(other = modifier),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
@Preview
|
||||
private fun DecoratedBoxPreview() {
|
||||
LwaTheme {
|
||||
Surface {
|
||||
DecoratedBox {
|
||||
Text("test")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.pixelized.desktop.lwa.composable.overlay
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.BoxScope
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.blur
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
|
||||
@Stable
|
||||
data class BlurOverlayTransitionUio(
|
||||
val blur: State<Dp>,
|
||||
val background: State<Color>,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun BlurOverlay(
|
||||
viewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
|
||||
overlay: @Composable BoxScope.() -> Unit,
|
||||
content: @Composable BoxScope.() -> Unit,
|
||||
) {
|
||||
val transition = viewModel.transition
|
||||
Box {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.blur(radius = transition.blur.value)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
drawRect(color = transition.background.value)
|
||||
},
|
||||
content = content,
|
||||
)
|
||||
|
||||
AnimatedContent(
|
||||
targetState = viewModel.overlay.value,
|
||||
transitionSpec = {
|
||||
val enter = fadeIn() + slideInVertically { 64 }
|
||||
val exit = fadeOut() + slideOutVertically { 64 }
|
||||
enter togetherWith exit
|
||||
},
|
||||
) { roll ->
|
||||
when (roll) {
|
||||
true -> Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
content = overlay,
|
||||
)
|
||||
|
||||
else -> Box(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
package com.pixelized.desktop.lwa.composable.overlay
|
||||
|
||||
import androidx.compose.animation.animateColor
|
||||
import androidx.compose.animation.core.animateDp
|
||||
import androidx.compose.animation.core.updateTransition
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class BlurOverlayViewModel : ViewModel() {
|
||||
private val _overlay = mutableStateOf(false)
|
||||
val overlay: State<Boolean>
|
||||
get() = _overlay
|
||||
|
||||
val transition: BlurOverlayTransitionUio
|
||||
@Composable
|
||||
get() {
|
||||
val transition = updateTransition(_overlay.value)
|
||||
val blur = transition.animateDp {
|
||||
when (it) {
|
||||
true -> 8.dp
|
||||
else -> 0.dp
|
||||
}
|
||||
}
|
||||
val background = transition.animateColor {
|
||||
when (it) {
|
||||
true -> Color.Black.copy(alpha = 0.6f)
|
||||
else -> Color.Black.copy(alpha = 0f)
|
||||
}
|
||||
}
|
||||
return remember {
|
||||
BlurOverlayTransitionUio(
|
||||
blur = blur,
|
||||
background = background,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun show() {
|
||||
_overlay.value = true
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
_overlay.value = false
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.pixelized.desktop.lwa.navigation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.rememberNavController
|
||||
import com.pixelized.desktop.lwa.navigation.destination.MainDestination
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetEditPage
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableCharacterSheetPage
|
||||
import com.pixelized.desktop.lwa.navigation.destination.composableMainPage
|
||||
|
||||
val LocalScreen = compositionLocalOf<NavHostController> {
|
||||
error("MainNavHost controller is not yet ready")
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainNavHost(
|
||||
controller: NavHostController = rememberNavController(),
|
||||
startDestination: String = MainDestination.navigationRoute(),
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalScreen provides controller,
|
||||
) {
|
||||
NavHost(
|
||||
navController = controller,
|
||||
startDestination = startDestination,
|
||||
) {
|
||||
composableMainPage()
|
||||
|
||||
composableCharacterSheetPage()
|
||||
composableCharacterSheetEditPage()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.pixelized.desktop.lwa.navigation.destination
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPage
|
||||
import com.pixelized.desktop.lwa.utils.extention.ARG
|
||||
|
||||
object CharacterSheetDestination {
|
||||
private const val ROUTE = "character.sheet"
|
||||
private const val CHARACTER_ID = "id"
|
||||
|
||||
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
||||
|
||||
fun navigationRoute(id: String) = "$ROUTE?$CHARACTER_ID=$id"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_ID) {
|
||||
nullable = true
|
||||
}
|
||||
)
|
||||
|
||||
data class Argument(
|
||||
val id: String,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
id = savedStateHandle.get<String>(CHARACTER_ID) ?: error("missing character id")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableCharacterSheetPage() {
|
||||
composable(
|
||||
route = CharacterSheetDestination.baseRoute(),
|
||||
arguments = CharacterSheetDestination.arguments(),
|
||||
) {
|
||||
CharacterSheetPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToCharacterSheet(
|
||||
id: String,
|
||||
) {
|
||||
val route = CharacterSheetDestination.navigationRoute(id = id)
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.pixelized.desktop.lwa.navigation.destination
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPage
|
||||
import com.pixelized.desktop.lwa.utils.extention.ARG
|
||||
|
||||
object CharacterSheetEditDestination {
|
||||
private const val ROUTE = "character.sheet.edit"
|
||||
private const val CHARACTER_ID = "id"
|
||||
|
||||
fun baseRoute() = "$ROUTE?${CHARACTER_ID.ARG}"
|
||||
|
||||
fun navigationRoute(id: String?) = "$ROUTE?$CHARACTER_ID=$id"
|
||||
|
||||
fun arguments() = listOf(
|
||||
navArgument(CHARACTER_ID) {
|
||||
nullable = true
|
||||
}
|
||||
)
|
||||
|
||||
data class Argument(
|
||||
val id: String?,
|
||||
) {
|
||||
constructor(savedStateHandle: SavedStateHandle) : this(
|
||||
id = savedStateHandle.get<String>(CHARACTER_ID)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableCharacterSheetEditPage() {
|
||||
composable(
|
||||
route = CharacterSheetEditDestination.baseRoute(),
|
||||
arguments = CharacterSheetEditDestination.arguments(),
|
||||
) {
|
||||
CharacterSheetEditPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToCharacterSheetEdit(
|
||||
id: String? = null,
|
||||
) {
|
||||
val route = CharacterSheetEditDestination.navigationRoute(id = id)
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.desktop.lwa.navigation.destination
|
||||
|
||||
import androidx.navigation.NavGraphBuilder
|
||||
import androidx.navigation.NavHostController
|
||||
import androidx.navigation.compose.composable
|
||||
import com.pixelized.desktop.lwa.screen.main.MainPage
|
||||
|
||||
object MainDestination {
|
||||
private const val ROUTE = "main"
|
||||
|
||||
fun baseRoute() = ROUTE
|
||||
fun navigationRoute() = ROUTE
|
||||
}
|
||||
|
||||
fun NavGraphBuilder.composableMainPage() {
|
||||
composable(
|
||||
route = MainDestination.baseRoute(),
|
||||
) {
|
||||
MainPage()
|
||||
}
|
||||
}
|
||||
|
||||
fun NavHostController.navigateToMainPage() {
|
||||
val route = MainDestination.navigationRoute()
|
||||
navigate(route = route)
|
||||
}
|
||||
|
|
@ -10,28 +10,31 @@ import kotlinx.coroutines.flow.stateIn
|
|||
|
||||
object CharacterSheetRepository {
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
private val preferences = CharacterSheetPreference(
|
||||
dataStore = createDataStore { "characterssheet.preferences_pb" }
|
||||
)
|
||||
|
||||
fun characterSheet(): StateFlow<List<CharacterSheet>> {
|
||||
return preferences.loadFlow()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
private val sheets = preferences.loadFlow()
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = emptyList()
|
||||
)
|
||||
|
||||
fun characterSheetFlow(): StateFlow<List<CharacterSheet>> {
|
||||
return sheets
|
||||
}
|
||||
|
||||
fun characterSheet(id: String): StateFlow<CharacterSheet?> {
|
||||
return preferences.loadFlow()
|
||||
fun characterSheetFlow(id: String?): StateFlow<CharacterSheet?> {
|
||||
return sheets
|
||||
.map { sheets ->
|
||||
sheets.firstOrNull { sheet -> sheet.id == id }
|
||||
}
|
||||
.stateIn(
|
||||
scope = scope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = null
|
||||
initialValue = sheets.value.firstOrNull { it.id == id }
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -48,5 +51,11 @@ object CharacterSheetRepository {
|
|||
// save the list of characters sheet.
|
||||
preferences.save(sheets = savedSheets)
|
||||
}
|
||||
|
||||
suspend fun delete(id: String) {
|
||||
val savedSheets = preferences.load().toMutableList()
|
||||
savedSheets.removeIf { it.id == id }
|
||||
preferences.save(sheets = savedSheets)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,70 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.detail
|
||||
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Characteristic
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio.Node
|
||||
|
||||
class CharacterSheetFactory {
|
||||
|
||||
fun convertToUio(model: CharacterSheet): CharacterSheetPageUio {
|
||||
return CharacterSheetPageUio(
|
||||
id = model.id,
|
||||
name = model.name,
|
||||
characteristics = listOf(
|
||||
Characteristic(label = "Force", value = "${model.strength}"),
|
||||
Characteristic(label = "Dextérité", value = "${model.dexterity}"),
|
||||
Characteristic(label = "Constitution", value = "${model.constitution}"),
|
||||
Characteristic(label = "Taille", value = "${model.height}"),
|
||||
Characteristic(label = "Intelligence", value = "${model.intelligence}"),
|
||||
Characteristic(label = "Pouvoir", value = "${model.power}"),
|
||||
Characteristic(label = "Charisme", value = "${model.charisma}"),
|
||||
),
|
||||
subCharacteristics = listOf(
|
||||
Characteristic(label = "Déplacement ", value = "${model.movement}"),
|
||||
Characteristic(
|
||||
label = "Points de vie",
|
||||
value = "${model.currentHp}/${model.maxHp}"
|
||||
),
|
||||
Characteristic(
|
||||
label = "Points de pouvoir",
|
||||
value = "${model.currentPP}/${model.maxPP}"
|
||||
),
|
||||
Characteristic(label = "Bonus aux dégâts", value = model.damageBonus),
|
||||
Characteristic(label = "Armure", value = "${model.armor}"),
|
||||
),
|
||||
skills = model.skills.mapNotNull {
|
||||
if (it.value > 0) {
|
||||
Node(
|
||||
type = Node.Type.SKILLS,
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
occupations = model.occupations.mapNotNull {
|
||||
if (it.value > 0) {
|
||||
Node(
|
||||
type = Node.Type.OCCUPATIONS,
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
magics = model.magics.mapNotNull {
|
||||
if (it.value > 0) {
|
||||
Node(
|
||||
type = Node.Type.MAGICS,
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,377 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.detail
|
||||
|
||||
import androidx.compose.foundation.ScrollState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
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.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Checkbox
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlay
|
||||
import com.pixelized.desktop.lwa.composable.overlay.BlurOverlayViewModel
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
||||
import com.pixelized.desktop.lwa.screen.roll.RollPage
|
||||
import com.pixelized.desktop.lwa.screen.roll.RollViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetPageUio(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val characteristics: List<Characteristic>,
|
||||
val subCharacteristics: List<Characteristic>,
|
||||
val skills: List<Node>,
|
||||
val occupations: List<Node>,
|
||||
val magics: List<Node>,
|
||||
) {
|
||||
@Stable
|
||||
data class Characteristic(
|
||||
val label: String,
|
||||
val value: String,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class Node(
|
||||
val type: Type,
|
||||
val label: String,
|
||||
val value: Int,
|
||||
) {
|
||||
@Stable
|
||||
enum class Type {
|
||||
SKILLS, OCCUPATIONS, MAGICS,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetPage(
|
||||
viewModel: CharacterSheetViewModel = viewModel {
|
||||
CharacterSheetViewModel(savedStateHandle = createSavedStateHandle())
|
||||
},
|
||||
overlayViewModel: BlurOverlayViewModel = viewModel { BlurOverlayViewModel() },
|
||||
rollViewModel: RollViewModel = viewModel { RollViewModel() },
|
||||
) {
|
||||
val screen = LocalScreen.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
BlurOverlay(
|
||||
viewModel = overlayViewModel,
|
||||
overlay = {
|
||||
RollPage(
|
||||
viewModel = rollViewModel,
|
||||
onDismissRequest = overlayViewModel::hide,
|
||||
)
|
||||
},
|
||||
content = {
|
||||
viewModel.sheet.value?.let {
|
||||
CharacterSheetPageContent(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
characterSheet = it,
|
||||
onBack = {
|
||||
screen.popBackStack()
|
||||
},
|
||||
onEdit = {
|
||||
screen.navigateToCharacterSheetEdit(id = it.id)
|
||||
},
|
||||
onDelete = {
|
||||
scope.launch {
|
||||
viewModel.deleteCharacter(id = it.id)
|
||||
screen.popBackStack()
|
||||
}
|
||||
},
|
||||
onCharacteristic = { characteristic ->
|
||||
rollViewModel.prepareRoll(characteristic = characteristic)
|
||||
overlayViewModel.show()
|
||||
},
|
||||
onSkill = { node ->
|
||||
rollViewModel.prepareRoll(node = node)
|
||||
overlayViewModel.show()
|
||||
},
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
fun CharacterSheetPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
scrollState: ScrollState = rememberScrollState(),
|
||||
width: Dp = 320.dp,
|
||||
characterSheet: CharacterSheetPageUio,
|
||||
onBack: () -> Unit,
|
||||
onEdit: () -> Unit,
|
||||
onDelete: () -> Unit,
|
||||
onCharacteristic: (characteristic: CharacterSheetPageUio.Characteristic) -> Unit,
|
||||
onSkill: (skill: CharacterSheetPageUio.Node) -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = characterSheet.name,
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
IconButton(
|
||||
onClick = onEdit,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Edit,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
IconButton(
|
||||
onClick = onDelete,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Default.Delete,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = scrollState).padding(all = 16.dp)
|
||||
.padding(paddingValues)
|
||||
.then(other = modifier),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
FlowRow(
|
||||
maxItemsInEachRow = 3,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 16.dp,
|
||||
alignment = Alignment.CenterHorizontally,
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp),
|
||||
) {
|
||||
characterSheet.characteristics.forEach {
|
||||
Stat(
|
||||
modifier = Modifier.width(width = width / 3 - 32.dp)
|
||||
.height(height = 112.dp),
|
||||
characteristic = it,
|
||||
onClick = { onCharacteristic(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
DecoratedBox(
|
||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = "Charactéristiques dérivées"
|
||||
)
|
||||
characterSheet.subCharacteristics.forEach {
|
||||
Characteristics(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
characteristic = it,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DecoratedBox(
|
||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = "Compétences",
|
||||
)
|
||||
characterSheet.skills.forEach {
|
||||
Skill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
onClick = { onSkill(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DecoratedBox(
|
||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = "Occupations"
|
||||
)
|
||||
characterSheet.occupations.forEach {
|
||||
Skill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
onClick = { onSkill(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
DecoratedBox(
|
||||
modifier = Modifier.width(width = width).padding(vertical = 8.dp),
|
||||
) {
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
text = "Compétences magiques"
|
||||
)
|
||||
characterSheet.magics.forEach {
|
||||
Skill(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
label = it.label,
|
||||
value = it.value,
|
||||
onClick = { onSkill(it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Stat(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(all = 8.dp),
|
||||
characteristic: CharacterSheetPageUio.Characteristic,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.clickable(onClick = onClick).padding(paddingValues = paddingValues)
|
||||
.then(other = modifier),
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.align(alignment = Alignment.TopCenter),
|
||||
style = MaterialTheme.typography.caption,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = characteristic.label,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.align(alignment = Alignment.Center),
|
||||
style = MaterialTheme.typography.h4,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = characteristic.value
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Characteristics(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
|
||||
characteristic: CharacterSheetPageUio.Characteristic,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(paddingValues = paddingValues).then(other = modifier),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.body1,
|
||||
text = characteristic.label
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.alignByBaseline(),
|
||||
style = MaterialTheme.typography.body1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = characteristic.value,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Skill(
|
||||
modifier: Modifier = Modifier,
|
||||
paddingValues: PaddingValues = PaddingValues(horizontal = 8.dp),
|
||||
label: String,
|
||||
value: Any,
|
||||
onClick: () -> Unit,
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.clickable(onClick = onClick).padding(paddingValues = paddingValues)
|
||||
.then(other = modifier),
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
style = MaterialTheme.typography.body1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = label
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.body1,
|
||||
fontWeight = FontWeight.Bold,
|
||||
text = "$value",
|
||||
)
|
||||
Checkbox(modifier = Modifier.size(size = 32.dp), checked = false, onCheckedChange = { })
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.detail
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetDestination
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
||||
|
||||
class CharacterSheetViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val argument = CharacterSheetDestination.Argument(savedStateHandle)
|
||||
private val repository = CharacterSheetRepository
|
||||
private val factory = CharacterSheetFactory()
|
||||
|
||||
val sheet: State<CharacterSheetPageUio?>
|
||||
@Composable
|
||||
@Stable
|
||||
get() = repository
|
||||
.characterSheetFlow(id = argument.id)
|
||||
.collectAsState { sheet ->
|
||||
sheet?.let { model -> factory.convertToUio(model = model) }
|
||||
}
|
||||
|
||||
suspend fun deleteCharacter(id: String) {
|
||||
repository.delete(id = id)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.IconButton
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Scaffold
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.material.TopAppBar
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.createSavedStateHandle
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.composable.decoratedBox.DecoratedBox
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.Form
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Stable
|
||||
data class CharacterSheetEditPageUio(
|
||||
val id: String,
|
||||
val name: FieldUio,
|
||||
val skills: List<SkillGroup>,
|
||||
) {
|
||||
@Stable
|
||||
data class SkillGroup(
|
||||
val title: String,
|
||||
val type: Type,
|
||||
val editable: Boolean = false,
|
||||
val fields: List<FieldUio>,
|
||||
) {
|
||||
@Stable
|
||||
enum class Type {
|
||||
CHARACTERISTICS,
|
||||
SUB_CHARACTERISTICS,
|
||||
SKILLS,
|
||||
OCCUPATIONS,
|
||||
MAGICS,
|
||||
OTHER,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetEditPage(
|
||||
viewModel: CharacterSheetEditViewModel = viewModel {
|
||||
CharacterSheetEditViewModel(
|
||||
savedStateHandle = createSavedStateHandle()
|
||||
)
|
||||
},
|
||||
) {
|
||||
val screen = LocalScreen.current
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
CharacterSheetEdit(
|
||||
form = viewModel.characterSheet.value,
|
||||
onSkill = viewModel::onSkill,
|
||||
onBack = { screen.popBackStack() },
|
||||
onSave = {
|
||||
scope.launch {
|
||||
viewModel.save()
|
||||
screen.popBackStack()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun CharacterSheetEdit(
|
||||
form: CharacterSheetEditPageUio,
|
||||
onSkill: (CharacterSheetEditPageUio.SkillGroup) -> Unit,
|
||||
onBack: () -> Unit,
|
||||
onSave: () -> Unit,
|
||||
) {
|
||||
Scaffold(
|
||||
topBar = {
|
||||
TopAppBar(
|
||||
title = {
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
text = "Création de personnage",
|
||||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
IconButton(
|
||||
onClick = onBack,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.ArrowBack,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
content = { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.verticalScroll(state = rememberScrollState())
|
||||
.padding(paddingValues = paddingValues)
|
||||
.padding(all = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 16.dp)
|
||||
) {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = form.name,
|
||||
)
|
||||
|
||||
form.skills.forEach {
|
||||
DecoratedBox(
|
||||
modifier = Modifier.animateContentSize(),
|
||||
) {
|
||||
Column(
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
style = MaterialTheme.typography.caption,
|
||||
text = it.title,
|
||||
)
|
||||
it.fields.forEach {
|
||||
Form(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
field = it,
|
||||
)
|
||||
}
|
||||
if (it.editable) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(
|
||||
space = 8.dp,
|
||||
alignment = Alignment.End
|
||||
)
|
||||
) {
|
||||
TextButton(
|
||||
onClick = { onSkill(it) },
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = "Ajouter une ligne",
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Add,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End,
|
||||
) {
|
||||
TextButton(
|
||||
onClick = onSave,
|
||||
) {
|
||||
Text(text = "Sauvegarder")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.navigation.destination.CharacterSheetEditDestination
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
|
||||
class CharacterSheetEditViewModel(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val argument = CharacterSheetEditDestination.Argument(savedStateHandle)
|
||||
private val repository = CharacterSheetRepository
|
||||
private val factory = CharacterSheetFactory()
|
||||
|
||||
private val _characterSheet = repository
|
||||
.characterSheetFlow(id = argument.id).value
|
||||
.let { sheet -> mutableStateOf(factory.convertToUio(sheet = sheet)) }
|
||||
val characterSheet: State<CharacterSheetEditPageUio> get() = _characterSheet
|
||||
|
||||
fun onSkill(skill: SkillGroup) {
|
||||
val sheet = _characterSheet.value
|
||||
|
||||
_characterSheet.value = sheet.copy(
|
||||
skills = sheet.skills.map { group ->
|
||||
if (skill.title == group.title) {
|
||||
group.copy(
|
||||
fields = mutableListOf<FieldUio>().apply {
|
||||
addAll(group.fields)
|
||||
add(
|
||||
FieldUio.create(
|
||||
label = "",
|
||||
isLabelEditable = true,
|
||||
valuePlaceHolder = {
|
||||
when (group.type) {
|
||||
SkillGroup.Type.CHARACTERISTICS -> ""
|
||||
SkillGroup.Type.SUB_CHARACTERISTICS -> ""
|
||||
SkillGroup.Type.SKILLS -> "0"
|
||||
SkillGroup.Type.OCCUPATIONS -> "40"
|
||||
SkillGroup.Type.MAGICS -> "0"
|
||||
SkillGroup.Type.OTHER -> ""
|
||||
}
|
||||
},
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
group
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun save() {
|
||||
val sheet = _characterSheet.value
|
||||
val model = factory.convertToModel(sheet = sheet)
|
||||
repository.save(characterSheet = model)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit
|
||||
|
||||
import com.pixelized.desktop.lwa.business.SkillNormalizerUseCase.normalize
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheet
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.CharacterSheetEditPageUio.SkillGroup
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.edit.composable.FieldUio
|
||||
import java.util.UUID
|
||||
import kotlin.math.max
|
||||
|
||||
class CharacterSheetFactory {
|
||||
|
||||
fun convertToModel(sheet: CharacterSheetEditPageUio): CharacterSheet {
|
||||
return CharacterSheet(
|
||||
id = sheet.id,
|
||||
name = sheet.name.value.value,
|
||||
strength = sheet.skills[0].fields[0].unpack(),
|
||||
dexterity = sheet.skills[0].fields[1].unpack(),
|
||||
constitution = sheet.skills[0].fields[2].unpack(),
|
||||
height = sheet.skills[0].fields[3].unpack(),
|
||||
intelligence = sheet.skills[0].fields[4].unpack(),
|
||||
power = sheet.skills[0].fields[5].unpack(),
|
||||
charisma = sheet.skills[0].fields[6].unpack(),
|
||||
movement = sheet.skills[1].fields[0].unpack(),
|
||||
currentHp = sheet.skills[1].fields[1].unpack(),
|
||||
maxHp = sheet.skills[1].fields[1].unpack(),
|
||||
currentPP = sheet.skills[1].fields[2].unpack(),
|
||||
maxPP = sheet.skills[1].fields[2].unpack(),
|
||||
damageBonus = sheet.skills[1].fields[3].unpack(),
|
||||
armor = sheet.skills[1].fields[4].unpack(),
|
||||
skills = sheet.skills[2].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.unpack(),
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
occupations = sheet.skills[3].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.unpack(),
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
magics = sheet.skills[4].fields.map {
|
||||
CharacterSheet.Skill(
|
||||
label = it.label.value,
|
||||
value = it.unpack(),
|
||||
used = false,
|
||||
)
|
||||
},
|
||||
attacks = emptyList(),
|
||||
)
|
||||
}
|
||||
|
||||
fun convertToUio(
|
||||
sheet: CharacterSheet?,
|
||||
): CharacterSheetEditPageUio {
|
||||
val str = FieldUio.create(
|
||||
label = "Force",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.strength?.toString() ?: ""
|
||||
)
|
||||
val dex = FieldUio.create(
|
||||
label = "Dextérité",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.dexterity?.toString() ?: ""
|
||||
)
|
||||
val con = FieldUio.create(
|
||||
label = "Constitution",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.constitution?.toString() ?: ""
|
||||
)
|
||||
val hei = FieldUio.create(
|
||||
label = "Taille",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.height?.toString() ?: ""
|
||||
)
|
||||
val int = FieldUio.create(
|
||||
label = "Intelligence",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.intelligence?.toString() ?: ""
|
||||
)
|
||||
val pow = FieldUio.create(
|
||||
label = "Pouvoir",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.power?.toString() ?: ""
|
||||
)
|
||||
val cha = FieldUio.create(
|
||||
label = "Charisme",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.charisma?.toString() ?: ""
|
||||
)
|
||||
|
||||
fun str(): Int = str.unpack() ?: 0
|
||||
fun dex(): Int = dex.unpack() ?: 0
|
||||
fun con(): Int = con.unpack() ?: 0
|
||||
fun hei(): Int = hei.unpack() ?: 0
|
||||
fun int(): Int = int.unpack() ?: 0
|
||||
fun pow(): Int = pow.unpack() ?: 0
|
||||
fun cha(): Int = cha.unpack() ?: 0
|
||||
|
||||
return CharacterSheetEditPageUio(
|
||||
id = sheet?.id ?: UUID.randomUUID().toString(),
|
||||
name = FieldUio.create(
|
||||
useLabelAsPlaceholder = true,
|
||||
label = "Name",
|
||||
initialValue = sheet?.name ?: ""
|
||||
),
|
||||
skills = listOf(
|
||||
SkillGroup(
|
||||
title = "Charactéristiques",
|
||||
type = SkillGroup.Type.CHARACTERISTICS,
|
||||
fields = listOf(str, dex, con, hei, int, pow, cha),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Charactéristiques dérivées",
|
||||
type = SkillGroup.Type.SUB_CHARACTERISTICS,
|
||||
fields = listOf(
|
||||
FieldUio.create(
|
||||
label = "Déplacement",
|
||||
valuePlaceHolder = { "10" },
|
||||
initialValue = sheet?.movement?.toString() ?: ""
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de vie",
|
||||
valuePlaceHolder = { "${(con() + hei()) / 2}" },
|
||||
initialValue = sheet?.maxHp?.toString() ?: ""
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Points de pouvoir",
|
||||
valuePlaceHolder = { "${pow()}" },
|
||||
initialValue = sheet?.maxPP?.toString() ?: ""
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Bonus aux dégats",
|
||||
valuePlaceHolder = {
|
||||
val bonus = str() + hei()
|
||||
when {
|
||||
bonus < 12 -> "-1d6"
|
||||
bonus in 12..17 -> "-1d4"
|
||||
bonus in 18..22 -> "-0"
|
||||
bonus in 23..29 -> "1d4"
|
||||
bonus in 30..39 -> "1d6"
|
||||
else -> "2d6"
|
||||
}
|
||||
},
|
||||
initialValue = sheet?.damageBonus ?: ""
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Armure",
|
||||
valuePlaceHolder = { "0" },
|
||||
initialValue = sheet?.armor?.toString() ?: ""
|
||||
),
|
||||
),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Compétances",
|
||||
type = SkillGroup.Type.SKILLS,
|
||||
editable = true,
|
||||
fields = sheet?.skills?.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
valuePlaceHolder = { "" },
|
||||
initialValue = it.value.toString(),
|
||||
)
|
||||
} ?: listOf(
|
||||
FieldUio.create(
|
||||
label = "Bagarre",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Esquive",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Saisie",
|
||||
valuePlaceHolder = { "${normalize(str() + hei())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Lancer",
|
||||
valuePlaceHolder = { "${normalize(str() + dex())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Athlétisme",
|
||||
valuePlaceHolder = { "${normalize(str() + con() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Acrobatie",
|
||||
valuePlaceHolder = { "${normalize(dex() + con() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Perception",
|
||||
valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Recherche",
|
||||
valuePlaceHolder = { "${normalize(10 + int() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Empathie",
|
||||
valuePlaceHolder = { "${normalize(cha() + int())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Persuasion",
|
||||
valuePlaceHolder = { "${normalize(cha() * 3)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Intimidation",
|
||||
valuePlaceHolder = { "${normalize(cha() + max(pow(), hei()) * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Baratin",
|
||||
valuePlaceHolder = { "${normalize(cha() * 2 + int())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Marchandage",
|
||||
valuePlaceHolder = { "${normalize(cha() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Discrétion",
|
||||
valuePlaceHolder = { "${normalize(cha() + dex() * 2 - hei())}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Escamotage",
|
||||
valuePlaceHolder = { "${normalize(dex() * 2)}" },
|
||||
),
|
||||
FieldUio.create(
|
||||
label = "Premiers soins",
|
||||
valuePlaceHolder = { "${normalize(int() + dex())}" },
|
||||
),
|
||||
),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Occupations",
|
||||
type = SkillGroup.Type.OCCUPATIONS,
|
||||
editable = true,
|
||||
fields = sheet?.occupations?.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
valuePlaceHolder = { "40" },
|
||||
initialValue = it.value.toString()
|
||||
)
|
||||
} ?: emptyList(),
|
||||
),
|
||||
SkillGroup(
|
||||
title = "Compétences magiques",
|
||||
type = SkillGroup.Type.MAGICS,
|
||||
editable = true,
|
||||
fields = sheet?.magics?.map {
|
||||
FieldUio.create(
|
||||
label = it.label,
|
||||
valuePlaceHolder = { "" },
|
||||
initialValue = it.value.toString()
|
||||
)
|
||||
} ?: emptyList(),
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private inline fun <reified T> FieldUio.unpack(): T {
|
||||
val tmp = value.value.ifBlank { valuePlaceHolder.value }
|
||||
return when (T::class) {
|
||||
Int::class -> (tmp.toIntOrNull() ?: 0) as T
|
||||
else -> tmp as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
package com.pixelized.desktop.lwa.screen.characterSheet.edit.composable
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusDirection
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Stable
|
||||
open class FieldUio(
|
||||
val useLabelAsPlaceholder: Boolean,
|
||||
val isLabelEditable: Boolean,
|
||||
val label: State<String>,
|
||||
val onLabelChange: (String) -> Unit,
|
||||
val valuePlaceHolder: State<String>,
|
||||
val value: State<String>,
|
||||
val onValueChange: (String) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
@Stable
|
||||
fun create(
|
||||
useLabelAsPlaceholder: Boolean = false,
|
||||
isLabelEditable: Boolean = false,
|
||||
label: String = "",
|
||||
initialValue: String = "",
|
||||
valuePlaceHolder: () -> String = { "" },
|
||||
): FieldUio {
|
||||
val labelState = mutableStateOf(label)
|
||||
val valueState = mutableStateOf(initialValue)
|
||||
return FieldUio(
|
||||
useLabelAsPlaceholder = useLabelAsPlaceholder,
|
||||
isLabelEditable = useLabelAsPlaceholder.not() && isLabelEditable,
|
||||
label = labelState,
|
||||
onLabelChange = { labelState.value = it },
|
||||
valuePlaceHolder = derivedStateOf(valuePlaceHolder),
|
||||
value = valueState,
|
||||
onValueChange = { valueState.value = it },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Form(
|
||||
modifier: Modifier = Modifier,
|
||||
field: FieldUio,
|
||||
) {
|
||||
val focus = LocalFocusManager.current
|
||||
|
||||
AnimatedContent(
|
||||
targetState = field.useLabelAsPlaceholder,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() }
|
||||
) {
|
||||
when (it) {
|
||||
true -> {
|
||||
TextField(
|
||||
modifier = modifier,
|
||||
value = field.value.value,
|
||||
label = { Text(text = field.label.value) },
|
||||
singleLine = true,
|
||||
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
|
||||
onValueChange = field.onValueChange,
|
||||
)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
AnimatedContent(
|
||||
modifier = Modifier.weight(weight = 1f),
|
||||
targetState = field.isLabelEditable,
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
||||
) { editable ->
|
||||
when (editable) {
|
||||
true -> TextField(
|
||||
value = field.label.value,
|
||||
placeholder = { Text(text = "Nom") },
|
||||
singleLine = true,
|
||||
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
|
||||
onValueChange = field.onLabelChange,
|
||||
)
|
||||
|
||||
else -> Text(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
style = MaterialTheme.typography.body1,
|
||||
text = field.label.value
|
||||
)
|
||||
}
|
||||
}
|
||||
TextField(
|
||||
modifier = Modifier.width(width = 80.dp),
|
||||
value = field.value.value,
|
||||
placeholder = { Text(text = field.valuePlaceHolder.value) },
|
||||
singleLine = true,
|
||||
keyboardActions = KeyboardActions { focus.moveFocus(FocusDirection.Next) },
|
||||
onValueChange = field.onValueChange,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
package com.pixelized.desktop.lwa.screen.main
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Surface
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.material.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.pixelized.desktop.lwa.navigation.LocalScreen
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheet
|
||||
import com.pixelized.desktop.lwa.navigation.destination.navigateToCharacterSheetEdit
|
||||
|
||||
@Stable
|
||||
data class CharacterUio(
|
||||
val id: String,
|
||||
val name: String,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun MainPage(
|
||||
viewModel: MainPageViewModel = viewModel { MainPageViewModel() },
|
||||
) {
|
||||
val screen = LocalScreen.current
|
||||
|
||||
Surface {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
MainPageContent(
|
||||
characters = viewModel.characters,
|
||||
onCharacter = {
|
||||
screen.navigateToCharacterSheet(id = it.id)
|
||||
},
|
||||
onCreateCharacter = {
|
||||
screen.navigateToCharacterSheetEdit()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun MainPageContent(
|
||||
modifier: Modifier = Modifier,
|
||||
characters: State<List<CharacterUio>>,
|
||||
onCharacter: (CharacterUio) -> Unit,
|
||||
onCreateCharacter: () -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier.padding(horizontal = 24.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(space = 32.dp),
|
||||
) {
|
||||
Column {
|
||||
characters.value.forEach { sheet ->
|
||||
TextButton(
|
||||
onClick = { onCharacter(sheet) }
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
text = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TextButton(
|
||||
onClick = { onCreateCharacter() },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
textAlign = TextAlign.Start,
|
||||
text = "Créer une feuille de personnage",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.pixelized.desktop.lwa.screen.main
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
|
||||
import com.pixelized.desktop.lwa.utils.extention.collectAsState
|
||||
|
||||
class MainPageViewModel : ViewModel() {
|
||||
// using a variable to help with later injection.
|
||||
private val characterSheetRepository = CharacterSheetRepository
|
||||
|
||||
val characters: State<List<CharacterUio>>
|
||||
@Composable
|
||||
@Stable
|
||||
get() = characterSheetRepository
|
||||
.characterSheetFlow()
|
||||
.collectAsState { sheets ->
|
||||
sheets.map { sheet ->
|
||||
CharacterUio(
|
||||
id = sheet.id,
|
||||
name = sheet.name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
package com.pixelized.desktop.lwa.screen.roll
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.animation.togetherWith
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
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.MaterialTheme
|
||||
import androidx.compose.material.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.remember
|
||||
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.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Shadow
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import lwacharactersheet.composeapp.generated.resources.Res
|
||||
import lwacharactersheet.composeapp.generated.resources.ic_d20_32dp
|
||||
import org.jetbrains.compose.resources.painterResource
|
||||
|
||||
@Stable
|
||||
data class RollUio(
|
||||
val label: String,
|
||||
val value: Int,
|
||||
)
|
||||
|
||||
@Stable
|
||||
data class RollResultUio(
|
||||
val label: String,
|
||||
val value: Int,
|
||||
)
|
||||
|
||||
@Composable
|
||||
fun RollPage(
|
||||
viewModel: RollViewModel = viewModel { RollViewModel() },
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
.clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null,
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
.padding(all = 32.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = "Jet de :",
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.h5,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = viewModel.roll.value.label,
|
||||
)
|
||||
Text(
|
||||
style = MaterialTheme.typography.caption,
|
||||
textAlign = TextAlign.Center,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
text = "Réussite en dessous de : ${viewModel.roll.value.value}",
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(
|
||||
space = 24.dp,
|
||||
alignment = Alignment.CenterVertically,
|
||||
),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Box(
|
||||
contentAlignment = Alignment.Center
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.clip(shape = CircleShape)
|
||||
.clickable { scope.launch { viewModel.roll() } }
|
||||
.padding(all = 24.dp)
|
||||
.size(size = 128.dp)
|
||||
.graphicsLayer {
|
||||
this.alpha = 0.8f
|
||||
this.rotationZ = viewModel.rollRotation.value
|
||||
},
|
||||
tint = MaterialTheme.colors.onSurface,
|
||||
painter = painterResource(Res.drawable.ic_d20_32dp),
|
||||
contentDescription = null,
|
||||
)
|
||||
AnimatedContent(
|
||||
targetState = viewModel.result.value?.value?.toString() ?: "",
|
||||
transitionSpec = {
|
||||
val enter = fadeIn() + slideInVertically { 32 }
|
||||
val exit = fadeOut() + slideOutVertically { -32 }
|
||||
enter togetherWith exit
|
||||
}
|
||||
) { label ->
|
||||
Text(
|
||||
modifier = Modifier.width(width = 128.dp),
|
||||
style = MaterialTheme.typography.h3.copy(
|
||||
shadow = Shadow(
|
||||
color = MaterialTheme.colors.surface,
|
||||
offset = Offset.Zero,
|
||||
blurRadius = 8f,
|
||||
)
|
||||
),
|
||||
textAlign = TextAlign.Center,
|
||||
color = MaterialTheme.colors.onSurface,
|
||||
text = label,
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedContent(
|
||||
targetState = viewModel.result.value?.label ?: "",
|
||||
transitionSpec = { fadeIn() togetherWith fadeOut() },
|
||||
) { value ->
|
||||
Text(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
style = MaterialTheme.typography.h4,
|
||||
textAlign = TextAlign.Center,
|
||||
text = value,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
package com.pixelized.desktop.lwa.screen.roll
|
||||
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.Spring
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.pixelized.desktop.lwa.business.RollUseCase
|
||||
import com.pixelized.desktop.lwa.business.SkillStepUseCase
|
||||
import com.pixelized.desktop.lwa.screen.characterSheet.detail.CharacterSheetPageUio
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
|
||||
class RollViewModel : ViewModel() {
|
||||
private val _roll = mutableStateOf(RollUio(label = "", value = 0))
|
||||
val roll: State<RollUio> get() = _roll
|
||||
|
||||
private var rollJob: Job? = null
|
||||
private lateinit var rollStep: SkillStepUseCase.SkillStep
|
||||
val rollRotation = Animatable(0f)
|
||||
|
||||
private val _result = mutableStateOf<RollResultUio?>(null)
|
||||
val result: State<RollResultUio?> get() = _result
|
||||
|
||||
fun prepareRoll(node: CharacterSheetPageUio.Node) {
|
||||
val step = SkillStepUseCase.computeSkillStep(
|
||||
skill = node.value,
|
||||
)
|
||||
prepareRoll(
|
||||
label = node.label,
|
||||
step = step,
|
||||
)
|
||||
}
|
||||
|
||||
fun prepareRoll(characteristic: CharacterSheetPageUio.Characteristic) {
|
||||
val step = SkillStepUseCase.computeSkillStep(
|
||||
skill = (characteristic.value.toIntOrNull() ?: 0) * 5
|
||||
)
|
||||
prepareRoll(
|
||||
label = characteristic.label,
|
||||
step = step,
|
||||
)
|
||||
}
|
||||
|
||||
private fun prepareRoll(
|
||||
label: String,
|
||||
step: SkillStepUseCase.SkillStep,
|
||||
) {
|
||||
runBlocking { rollRotation.snapTo(0f) }
|
||||
rollStep = step
|
||||
_result.value = null
|
||||
_roll.value = RollUio(
|
||||
label = label,
|
||||
value = step.successRange.last
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun roll() {
|
||||
coroutineScope {
|
||||
_result.value = null
|
||||
|
||||
rollJob?.cancel()
|
||||
rollJob = launch {
|
||||
launch {
|
||||
rollRotation.animateTo(
|
||||
targetValue = rollRotation.value.let { it - it % 360 } + 360f * 3,
|
||||
animationSpec = spring(
|
||||
dampingRatio = Spring.DampingRatioNoBouncy,
|
||||
stiffness = Spring.StiffnessLow,
|
||||
)
|
||||
)
|
||||
}
|
||||
launch {
|
||||
delay(500)
|
||||
|
||||
val d100 = RollUseCase.rollD100()
|
||||
|
||||
_result.value = RollResultUio(
|
||||
label = when (d100) {
|
||||
// TODO wording
|
||||
in rollStep.criticalSuccessRange -> "Réussite critique"
|
||||
in rollStep.specialSuccessRange -> "Réussite spéciale"
|
||||
in rollStep.successRange -> "Réussite"
|
||||
in rollStep.failureRange -> "Échec"
|
||||
in rollStep.criticalFailureRange -> "Échec critique"
|
||||
else -> ""
|
||||
},
|
||||
value = d100,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.pixelized.desktop.lwa.theme
|
||||
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.material.darkColors
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun LwaTheme(
|
||||
content: @Composable () -> Unit
|
||||
) {
|
||||
MaterialTheme(
|
||||
colors = darkColors(),
|
||||
typography = MaterialTheme.typography,
|
||||
shapes = MaterialTheme.shapes,
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
package com.pixelized.desktop.lwa.utils.extention
|
||||
|
||||
val String.ARG: String get() = "$this={$this}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue