Add directories to distingish between pixelized apps.

This commit is contained in:
Thomas Andres Gomez 2025-03-02 10:20:52 +01:00
parent 03df369e0b
commit 3f67e342a7
18 changed files with 178 additions and 108 deletions

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="packageReleaseDmg" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<configuration default="false" name="appPackageReleaseDmg" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="packageReleaseMsi" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<configuration default="false" name="appPackageReleaseMsi" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="runDebug" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<configuration default="false" name="appRunDebug" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />

View file

@ -1,5 +1,5 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="runRelease" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<configuration default="false" name="appRunRelease" type="GradleRunConfiguration" factoryName="Gradle" folderName="App">
<ExternalSystemSettings>
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />

View file

@ -31,6 +31,9 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.ApplicationScope
import androidx.compose.ui.window.Window
import androidx.compose.ui.window.rememberWindowState
import coil3.ImageLoader
import coil3.compose.setSingletonImageLoaderFactory
import coil3.request.crossfade
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository.Status
import com.pixelized.desktop.lwa.ui.composable.key.KeyEventHandler
@ -85,6 +88,13 @@ fun ApplicationScope.App() {
val windowController = remember { WindowController(maxWindowHeight) }
val keyEventHandlers = remember { mutableStateListOf<KeyEventHandler>() }
// Coil configuration
setSingletonImageLoaderFactory { context ->
ImageLoader.Builder(context)
.crossfade(enable = false)
.build()
}
CompositionLocalProvider(
LocalSnackHost provides snackHostState,
LocalErrorSnackHost provides errorSnackHostState,

View file

@ -33,6 +33,7 @@ import com.pixelized.desktop.lwa.ui.screen.roll.RollViewModel
import com.pixelized.desktop.lwa.ui.screen.rollhistory.RollHistoryViewModel
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider
import io.ktor.client.HttpClient
import io.ktor.client.engine.okhttp.OkHttp
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
@ -67,7 +68,12 @@ val toolsDependencies
}
}
}
single<LwaClient> { LwaClientImpl(get(), get()) }
single<LwaClient> {
LwaClientImpl(get(), get())
}
single {
PathProvider(appName = "LwaClient")
}
}
val storeDependencies

View file

@ -1,9 +1,9 @@
package com.pixelized.desktop.lwa.repository.settings
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson
import com.pixelized.shared.lwa.storePath
import com.pixelized.desktop.lwa.usecase.SettingsUseCase
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -14,6 +14,7 @@ import kotlinx.serialization.json.Json
import java.io.File
class SettingsStore(
private val pathProvider: PathProvider,
private val factory: SettingsFactory,
private val useCase: SettingsUseCase,
private val jsonFormatter: Json,
@ -24,7 +25,7 @@ class SettingsStore(
init {
// create the directory
File(storePath()).also { it.mkdirs() }
File(pathProvider.storePath()).also { it.mkdirs() }
// load the data.
val scope = CoroutineScope(Dispatchers.IO + Job())
scope.launch {
@ -80,7 +81,7 @@ class SettingsStore(
}
private fun settingsFile(): File {
return File("${storePath()}settings.json")
return File("${pathProvider.storePath()}settings.json")
}
sealed class SettingsStoreException(root: Exception) : Exception(root)

View file

@ -1,5 +1,12 @@
package com.pixelized.desktop.lwa.ui.screen.campaign.player.ribbon
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.core.EaseOutCirc
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
@ -7,7 +14,9 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
@ -23,8 +32,8 @@ 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.Color
import androidx.compose.ui.graphics.FilterQuality
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.DpSize
@ -36,6 +45,8 @@ 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
import kotlin.math.max
import kotlin.math.min
@Stable
data class PlayerPortraitUio(
@ -48,10 +59,17 @@ data class PlayerPortraitUio(
val maxPp: Int,
)
object PlayerPortrait {
object Default {
val bloodColor = Color(0xbb0a1e).copy(alpha = 0.25f)
}
}
@Composable
fun PlayerPortrait(
modifier: Modifier = Modifier,
size: DpSize,
bloodColor: Color = PlayerPortrait.Default.bloodColor,
character: PlayerPortraitUio,
onCharacter: (id: Campaign.CharacterInstance.Id) -> Unit,
) {
@ -64,14 +82,26 @@ fun PlayerPortrait(
.background(color = colorScheme.elevated.base1dp)
.clickable { onCharacter(character.id) },
) {
AsyncImage(
modifier = Modifier.fillMaxSize(),
model = character.portrait,
contentScale = ContentScale.Crop,
alignment = Alignment.TopCenter,
filterQuality = FilterQuality.High,
contentDescription = null,
AnimatedContent(
targetState = character.portrait,
transitionSpec = { fadeIn() togetherWith fadeOut() },
) { portrait ->
AsyncImage(
modifier = Modifier.fillMaxSize(),
model = portrait,
contentScale = ContentScale.Crop,
alignment = Alignment.TopCenter,
filterQuality = FilterQuality.High,
contentDescription = null,
)
}
BloodOverlay(
bloodColor = bloodColor,
maxHp = character.maxHp.toFloat(),
hp = character.hp.toFloat(),
)
Column(
modifier = Modifier
.fillMaxSize()
@ -129,3 +159,22 @@ fun PlayerPortrait(
}
}
}
@Composable
private fun BloodOverlay(
modifier: Modifier = Modifier,
maxHp: Float,
hp: Float,
bloodColor: Color = PlayerPortrait.Default.bloodColor,
) {
val animatedRatio = animateFloatAsState(
targetValue = min(maxHp, max(0f, (maxHp - hp) / maxHp)),
animationSpec = tween(durationMillis = 350, easing = EaseOutCirc)
)
Box(
modifier = modifier
.fillMaxWidth()
.fillMaxHeight(fraction = animatedRatio.value)
.background(color = bloodColor)
)
}

View file

@ -6,8 +6,8 @@ import com.lordcodes.turtle.shellRun
import com.pixelized.desktop.lwa.repository.campaign.CampaignRepository
import com.pixelized.desktop.lwa.repository.characterSheet.CharacterSheetRepository
import com.pixelized.desktop.lwa.repository.network.NetworkRepository
import com.pixelized.shared.lwa.OperatingSystem
import com.pixelized.shared.lwa.storePath
import com.pixelized.shared.lwa.utils.OperatingSystem
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
@ -18,6 +18,7 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class MainPageViewModel(
private val pathProvider: PathProvider,
private val characterSheetRepository: CharacterSheetRepository,
private val campaignRepository: CampaignRepository,
networkRepository: NetworkRepository,
@ -86,9 +87,10 @@ class MainPageViewModel(
fun openSaveDirectory(
os: OperatingSystem = OperatingSystem.current,
) {
val path = pathProvider.storePath(os = os)
when (os) {
OperatingSystem.Windows -> shellRun("explorer.exe", listOf(storePath(os = os)))
OperatingSystem.Macintosh -> shellRun("open", listOf(storePath(os = os)))
OperatingSystem.Windows -> shellRun("explorer.exe", listOf(path))
OperatingSystem.Macintosh -> shellRun("open", listOf(path))
}
}
}

View file

@ -3,7 +3,6 @@ package com.pixelized.desktop.lwa
import androidx.compose.ui.window.application
import com.pixelized.shared.lwa.sharedModuleDependencies
import org.koin.compose.KoinContext
import org.koin.core.context.loadKoinModules
import org.koin.core.context.startKoin
import javax.swing.UIManager

View file

@ -5,20 +5,26 @@ import com.pixelized.server.lwa.model.campaign.CampaignStore
import com.pixelized.server.lwa.model.character.CharacterSheetService
import com.pixelized.server.lwa.model.character.CharacterSheetStore
import com.pixelized.server.lwa.server.Engine
import com.pixelized.shared.lwa.utils.PathProvider
import org.koin.core.module.dsl.createdAtStart
import org.koin.core.module.dsl.singleOf
import org.koin.dsl.module
val serverModuleDependencies
get() = listOf(
parserDependencies,
factoryDependencies,
useCaseDependencies,
toolsDependencies,
engineDependencies,
storeDependencies,
serviceDependencies,
engineDependencies,
)
val toolsDependencies
get() = module {
single {
PathProvider(appName = "LwaServer")
}
}
val engineDependencies
get() = module {
singleOf(constructor = ::Engine, options = { createdAtStart() })
@ -37,18 +43,3 @@ val serviceDependencies
singleOf(::CampaignService)
singleOf(::AlterationService)
}
val factoryDependencies
get() = module {
}
val parserDependencies
get() = module {
}
val useCaseDependencies
get() = module {
}

View file

@ -1,9 +1,9 @@
package com.pixelized.server.lwa.model.alteration
import com.pixelized.shared.lwa.alterationsPath
import com.pixelized.shared.lwa.model.alteration.AlterationJson
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -14,10 +14,11 @@ import kotlinx.serialization.json.Json
import java.io.File
class AlterationStore(
private val pathProvider: PathProvider,
private val campaignJsonFactory: CampaignJsonFactory,
private val json: Json,
) {
private val directory = File(alterationsPath()).also { it.mkdirs() }
private val directory = File(pathProvider.alterationsPath()).also { it.mkdirs() }
private val alterationsFlow = MutableStateFlow<List<AlterationJson>>(emptyList())
private val activeFlow = MutableStateFlow<Map<String, List<String>>>(emptyMap())
@ -118,13 +119,13 @@ class AlterationStore(
}
private fun file(): File {
return File("${alterationsPath()}alterations.json")
return File("${pathProvider.alterationsPath()}alterations.json")
}
private fun file(
id: String,
): File {
return File("${alterationsPath()}$id.json")
return File("${pathProvider.alterationsPath()}$id.json")
}
private fun MutableList<String>.toggle(alterationId: String): MutableList<String> {

View file

@ -1,9 +1,9 @@
package com.pixelized.server.lwa.model.campaign
import com.pixelized.shared.lwa.campaignPath
import com.pixelized.shared.lwa.model.campaign.Campaign
import com.pixelized.shared.lwa.model.campaign.CampaignJson
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
@ -14,6 +14,7 @@ import kotlinx.serialization.json.Json
import java.io.File
class CampaignStore(
private val pathProvider: PathProvider,
private val factory: CampaignJsonFactory,
private val json: Json,
) {
@ -21,7 +22,7 @@ class CampaignStore(
init {
// create the directory if needed.
File(campaignPath()).also { it.mkdirs() }
File(pathProvider.campaignPath()).also { it.mkdirs() }
// build a coroutine scope for async calls
val scope = CoroutineScope(Dispatchers.IO + Job())
// load the initial data
@ -73,6 +74,6 @@ class CampaignStore(
class FileWriteException(root: Exception) : CampaignStoreException(root)
private fun file(): File {
return File("${campaignPath()}campaign.json")
return File("${pathProvider.campaignPath()}campaign.json")
}
}

View file

@ -1,25 +1,25 @@
package com.pixelized.server.lwa.model.character
import com.pixelized.shared.lwa.characterStorePath
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheet
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJson
import com.pixelized.shared.lwa.model.characterSheet.CharacterSheetJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.io.File
import java.text.Collator
class CharacterSheetStore(
private val pathProvider: PathProvider,
private val factory: CharacterSheetJsonFactory,
private val json: Json,
) {
private val directory = File(characterStorePath()).also { it.mkdirs() }
private val directory = File(pathProvider.characterStorePath()).also { it.mkdirs() }
private val flow = MutableStateFlow<List<CharacterSheet>>(value = emptyList())
init {
@ -112,7 +112,7 @@ class CharacterSheetStore(
}
private fun characterSheetFile(id: String): File {
return File("${characterStorePath()}${id}.json")
return File("${pathProvider.characterStorePath()}${id}.json")
}
sealed class CharacterSheetStoreException(root: Exception) : Exception(root)

View file

@ -1,55 +0,0 @@
package com.pixelized.shared.lwa
enum class OperatingSystem(
val home: String = System.getProperty("user.home"),
) {
Windows,
Macintosh;
companion object {
val current: OperatingSystem = run {
val name = System.getProperty("os.name")
when {
name.contains(other = "win", ignoreCase = true) -> Windows
name.contains(other = "mac", ignoreCase = true) -> Macintosh
else -> error("Unsupported operating system: $name")
}
}
}
}
fun storePath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${os.home}\\AppData\\Roaming\\Pixelized\\"
OperatingSystem.Macintosh -> "${os.home}/Library/Pixelized/"
}
}
fun characterStorePath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os)}characters\\"
OperatingSystem.Macintosh -> "${storePath(os = os)}characters/"
}
}
fun campaignPath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os)}campaign\\"
OperatingSystem.Macintosh -> "${storePath(os = os)}campaign/"
}
}
fun alterationsPath(
os: OperatingSystem = OperatingSystem.current,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os)}alterations\\"
OperatingSystem.Macintosh -> "${storePath(os = os)}alterations/"
}
}

View file

@ -111,7 +111,7 @@ class CharacterSheetJsonFactory(
CharacterSheetJsonV1.Skill(
id = it.id,
label = it.label,
description = null,
description = it.description,
base = it.base,
bonus = it.bonus,
level = it.level,

View file

@ -0,0 +1,19 @@
package com.pixelized.shared.lwa.utils
enum class OperatingSystem(
val home: String = System.getProperty("user.home"),
) {
Windows,
Macintosh;
companion object {
val current: OperatingSystem = run {
val name = System.getProperty("os.name")
when {
name.contains(other = "win", ignoreCase = true) -> Windows
name.contains(other = "mac", ignoreCase = true) -> Macintosh
else -> error("Unsupported operating system: $name")
}
}
}
}

View file

@ -0,0 +1,46 @@
package com.pixelized.shared.lwa.utils
class PathProvider(
private val operatingSystem: OperatingSystem = OperatingSystem.current,
private val appName: String,
) {
fun storePath(
os: OperatingSystem = this.operatingSystem,
app: String = this.appName,
): String {
return when (os) {
OperatingSystem.Windows -> "${os.home}\\AppData\\Roaming\\Pixelized\\$app\\"
OperatingSystem.Macintosh -> "${os.home}/Library/Pixelized/$app/"
}
}
fun characterStorePath(
os: OperatingSystem = this.operatingSystem,
app: String = this.appName,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os, app = app)}characters\\"
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}characters/"
}
}
fun campaignPath(
os: OperatingSystem = this.operatingSystem,
app: String = this.appName,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os, app = app)}campaign\\"
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}campaign/"
}
}
fun alterationsPath(
os: OperatingSystem = this.operatingSystem,
app: String = this.appName,
): String {
return when (os) {
OperatingSystem.Windows -> "${storePath(os = os, app = app)}alterations\\"
OperatingSystem.Macintosh -> "${storePath(os = os, app = app)}alterations/"
}
}
}