Add static resource managment to (image) to the client.

This commit is contained in:
Thomas Andres Gomez 2025-12-11 11:02:35 +01:00
parent 1c9b1c49b1
commit 9f0198ce53
12 changed files with 67 additions and 46 deletions

View file

@ -34,7 +34,7 @@ import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogFa
import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.item.ItemDetailDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogFactory import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogFactory
import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel import com.pixelized.desktop.lwa.ui.composable.character.purse.PurseDialogViewModel
import com.pixelized.desktop.lwa.ui.composable.image.ImagerModelConverter import com.pixelized.desktop.lwa.usecase.ImagerModelLogic
import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel import com.pixelized.desktop.lwa.ui.overlay.portrait.PortraitOverlayViewModel
import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel import com.pixelized.desktop.lwa.ui.overlay.roll.RollViewModel
import com.pixelized.desktop.lwa.ui.screen.admin.AdminViewModel import com.pixelized.desktop.lwa.ui.screen.admin.AdminViewModel
@ -77,7 +77,7 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.toolbar.network.NetworkViewM
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpFactory
import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel import com.pixelized.desktop.lwa.ui.screen.levelup.LevelUpViewModel
import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel import com.pixelized.desktop.lwa.ui.screen.settings.SettingsViewModel
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory import com.pixelized.shared.lwa.model.campaign.factory.CampaignJsonFactory
import com.pixelized.shared.lwa.utils.PathProvider import com.pixelized.shared.lwa.utils.PathProvider
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
@ -122,7 +122,7 @@ val toolsDependencies
single { single {
PathProvider(appName = "LwaClient") PathProvider(appName = "LwaClient")
} }
singleOf(::ImagerModelConverter) singleOf(::ImagerModelLogic)
} }
val storeDependencies val storeDependencies
@ -218,6 +218,6 @@ val viewModelDependencies
val useCaseDependencies val useCaseDependencies
get() = module { get() = module {
factoryOf(::SettingsUseCase) factoryOf(::SettingsLogic)
factoryOf(::GMActionUseCase) factoryOf(::GMActionUseCase)
} }

View file

@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.repository.resources
import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.graphics.ImageBitmap
import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.graphics.toComposeImageBitmap
import com.pixelized.desktop.lwa.ui.composable.image.ImagerModelConverter import com.pixelized.desktop.lwa.usecase.ImagerModelLogic
import io.ktor.client.HttpClient import io.ktor.client.HttpClient
import io.ktor.client.request.get import io.ktor.client.request.get
import io.ktor.client.statement.readRawBytes import io.ktor.client.statement.readRawBytes
@ -10,11 +10,11 @@ import org.jetbrains.skia.Image
class ImageResourcesRepository( class ImageResourcesRepository(
private val httpClient: HttpClient, private val httpClient: HttpClient,
private val googleImageConverter: ImagerModelConverter, private val imageConverter: ImagerModelLogic,
) { ) {
suspend fun load(url: String): ImageBitmap { suspend fun load(url: String): ImageBitmap {
try { try {
val unwrapUri = googleImageConverter.unwrap(model = url) val unwrapUri = imageConverter.unwrap(model = url)
val byteArray = httpClient.get(unwrapUri).readRawBytes() val byteArray = httpClient.get(unwrapUri).readRawBytes()
val skiaImage = Image.makeFromEncoded(byteArray) val skiaImage = Image.makeFromEncoded(byteArray)
return skiaImage.toComposeImageBitmap() return skiaImage.toComposeImageBitmap()

View file

@ -4,11 +4,11 @@ import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV1 import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV1
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV2 import com.pixelized.desktop.lwa.repository.settings.model.SettingsJsonV2
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
class SettingsFactory( class SettingsFactory(
private val useCase: SettingsUseCase, private val useCase: SettingsLogic,
) { ) {
fun convertToJson( fun convertToJson(
settings: Settings, settings: Settings,

View file

@ -1,12 +1,12 @@
package com.pixelized.desktop.lwa.repository.settings package com.pixelized.desktop.lwa.repository.settings
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
class SettingsRepository( class SettingsRepository(
private val store: SettingsStore, private val store: SettingsStore,
private val useCase: SettingsUseCase, private val useCase: SettingsLogic,
) { ) {
fun settingsFlow(): StateFlow<Settings> = store.settingsFlow fun settingsFlow(): StateFlow<Settings> = store.settingsFlow

View file

@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.repository.settings
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson import com.pixelized.desktop.lwa.repository.settings.model.SettingsJson
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
import com.pixelized.shared.lwa.utils.PathProvider import com.pixelized.shared.lwa.utils.PathProvider
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -13,7 +13,7 @@ import java.io.File
class SettingsStore( class SettingsStore(
private val pathProvider: PathProvider, private val pathProvider: PathProvider,
private val factory: SettingsFactory, private val factory: SettingsFactory,
private val useCase: SettingsUseCase, private val useCase: SettingsLogic,
private val jsonFormatter: Json, private val jsonFormatter: Json,
) { ) {
private val _settingsFlow = MutableStateFlow(value = useCase.defaultSettings()) private val _settingsFlow = MutableStateFlow(value = useCase.defaultSettings())

View file

@ -1,25 +0,0 @@
package com.pixelized.desktop.lwa.ui.composable.image
class ImagerModelConverter {
val googleDriveUrlRegex = Regex("""drive\.google\.com/file/d/([^/]*)""")
val workingGoogleDriveUri = "https://drive.google.com/uc?export=view&id="
fun convert(
model: Any?,
): Any? {
return when (model) {
is String -> unwrap(model = model)
else -> model
}
}
fun unwrap(
model: String,
): String {
return googleDriveUrlRegex.find(model)?.let {
val id = it.groupValues.getOrNull(1)
"$workingGoogleDriveUri$id"
} ?: model
}
}

View file

@ -11,12 +11,13 @@ import androidx.compose.ui.layout.ContentScale
import coil3.compose.AsyncImage import coil3.compose.AsyncImage
import coil3.compose.AsyncImagePainter.Companion.DefaultTransform import coil3.compose.AsyncImagePainter.Companion.DefaultTransform
import coil3.compose.AsyncImagePainter.State import coil3.compose.AsyncImagePainter.State
import com.pixelized.desktop.lwa.usecase.ImagerModelLogic
import org.koin.compose.koinInject import org.koin.compose.koinInject
@Composable @Composable
fun LwaAsyncImage( fun LwaAsyncImage(
model: Any?, model: Any?,
modelConverter: ImagerModelConverter? = koinInject<ImagerModelConverter?>(), modelConverter: ImagerModelLogic? = koinInject<ImagerModelLogic?>(),
contentDescription: String?, contentDescription: String?,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
transform: (State) -> State = DefaultTransform, transform: (State) -> State = DefaultTransform,

View file

@ -52,7 +52,7 @@ import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMes
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMessageUio import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.RollTextMessageUio
import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.TextMessage import com.pixelized.desktop.lwa.ui.screen.campaign.chatbox.messages.TextMessage
import com.pixelized.desktop.lwa.ui.theme.lwa import com.pixelized.desktop.lwa.ui.theme.lwa
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_more_down_24dp import lwacharactersheet.composeapp.generated.resources.ic_more_down_24dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
@ -64,7 +64,7 @@ data class ChatSettingsUio(
val autoScroll: Boolean, val autoScroll: Boolean,
) { ) {
companion object { companion object {
fun default() = with(SettingsUseCase().defaultSettings()) { fun default() = with(SettingsLogic().defaultSettings()) {
ChatSettingsUio( ChatSettingsUio(
show = chat.showChat, show = chat.showChat,
autoScroll = chat.autoScrollChat, autoScroll = chat.autoScrollChat,

View file

@ -8,7 +8,7 @@ import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingNumberItemUio import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingNumberItemUio
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingSectionUio import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingSectionUio
import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingToggleItemUio import com.pixelized.desktop.lwa.ui.screen.settings.composable.SettingToggleItemUio
import com.pixelized.desktop.lwa.usecase.SettingsUseCase import com.pixelized.desktop.lwa.usecase.SettingsLogic
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import lwacharactersheet.composeapp.generated.resources.Res import lwacharactersheet.composeapp.generated.resources.Res
import lwacharactersheet.composeapp.generated.resources.ic_fan_focus_24dp import lwacharactersheet.composeapp.generated.resources.ic_fan_focus_24dp
@ -29,7 +29,7 @@ import lwacharactersheet.composeapp.generated.resources.settings__player_portrai
class SettingsViewModel( class SettingsViewModel(
private val settingsRepository: SettingsRepository, private val settingsRepository: SettingsRepository,
private val settingsUseCase: SettingsUseCase, private val settingsLogic: SettingsLogic,
) : ViewModel() { ) : ViewModel() {
private val settings = settingsRepository.settingsFlow() private val settings = settingsRepository.settingsFlow()
private val booleanStates = hashMapOf<String, MutableState<Boolean>>() private val booleanStates = hashMapOf<String, MutableState<Boolean>>()
@ -132,7 +132,7 @@ class SettingsViewModel(
fun onReset() { fun onReset() {
val current = settingsRepository.settings() val current = settingsRepository.settings()
settingsRepository.update( settingsRepository.update(
settings = settingsUseCase.defaultSettings().copy( settings = settingsLogic.defaultSettings().copy(
playerName = current.playerName, playerName = current.playerName,
) )
) )

View file

@ -0,0 +1,46 @@
package com.pixelized.desktop.lwa.usecase
import com.pixelized.desktop.lwa.repository.settings.SettingsRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
class ImagerModelLogic(
settingsRepo: SettingsRepository, // not sure about those import into a Logic class.
settingsLogic: SettingsLogic,
) {
val googleDriveUrlRegex = Regex("""drive\.google\.com/file/d/([^/]*)""")
val workingGoogleDriveUri = "https://drive.google.com/uc?export=view&id="
val localUrlRegex = Regex("""^/resources/.*$""")
val workingLocalUrl = settingsRepo.settingsFlow()
.map { it.network.root }
.stateIn(
scope = CoroutineScope(Dispatchers.IO + Job()),
started = SharingStarted.Eagerly,
initialValue = settingsLogic.defaultSettings(),
)
fun convert(
model: Any?,
): Any? {
return when (model) {
is String -> unwrap(model = model)
else -> model
}
}
fun unwrap(
model: String,
): String {
return googleDriveUrlRegex.find(model)?.let {
val id = it.groupValues.getOrNull(1)
"$workingGoogleDriveUri$id"
} ?: localUrlRegex.find(model)?.let {
"${workingLocalUrl.value}$model"
} ?: model
}
}

View file

@ -2,7 +2,7 @@ package com.pixelized.desktop.lwa.usecase
import com.pixelized.desktop.lwa.repository.settings.model.Settings import com.pixelized.desktop.lwa.repository.settings.model.Settings
class SettingsUseCase { class SettingsLogic {
fun defaultSettings(): Settings = Settings( fun defaultSettings(): Settings = Settings(
playerName = "", playerName = "",

View file

@ -148,7 +148,6 @@ class LocalServer {
staticFiles( staticFiles(
remotePath = "/resources", remotePath = "/resources",
dir = File(pathProvider.resourcesPath()), dir = File(pathProvider.resourcesPath()),
index = null,
) )
route(path = "/campaign") { route(path = "/campaign") {
get( get(