Add a download link to a portrait.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2025-04-12 10:19:48 +02:00
parent 7dd5ade1f5
commit ca5d01c0ca
3 changed files with 85 additions and 22 deletions

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="960"
android:viewportHeight="960">
<path
android:fillColor="#000000"
android:pathData="M440,680L280,680Q197,680 138.5,621.5Q80,563 80,480Q80,397 138.5,338.5Q197,280 280,280L440,280L440,360L280,360Q230,360 195,395Q160,430 160,480Q160,530 195,565Q230,600 280,600L440,600L440,680ZM320,520L320,440L640,440L640,520L320,520ZM520,680L520,600L680,600Q730,600 765,565Q800,530 800,480Q800,430 765,395Q730,360 680,360L520,360L520,280L680,280Q763,280 821.5,338.5Q880,397 880,480Q880,563 821.5,621.5Q763,680 680,680L520,680Z" />
</vector>

View file

@ -2,15 +2,18 @@ package com.pixelized.desktop.lwa.ui.overlay.portrait
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.Icon import androidx.compose.material.Icon
import androidx.compose.material.IconButton import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme import androidx.compose.material.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
@ -23,9 +26,16 @@ import com.pixelized.desktop.lwa.ui.theme.lwa
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_cancel_24dp import lwacharactersheet.composeapp.generated.resources.ic_cancel_24dp
import lwacharactersheet.composeapp.generated.resources.ic_link_24dp
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.koin.compose.viewmodel.koinViewModel import org.koin.compose.viewmodel.koinViewModel
@Stable
data class PortraitOptionUio(
val isBrowserAvailable: Boolean,
val isGameMaster: Boolean,
)
@Composable @Composable
fun PortraitOverlay( fun PortraitOverlay(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -33,13 +43,16 @@ fun PortraitOverlay(
) { ) {
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
val portrait = viewModel.portrait.collectAsState() val portrait = viewModel.portrait.collectAsState()
val isGameMaster = viewModel.isGameMaster.collectAsState() val options = viewModel.options.collectAsState()
PortraitContent( PortraitContent(
modifier = modifier, modifier = modifier,
portrait = portrait, portrait = portrait,
isGameMaster = isGameMaster, options = options,
onGameMaster = { onDownload = { url ->
viewModel.browse(url)
},
onClose = {
scope.launch { scope.launch {
viewModel.dismissPortrait() viewModel.dismissPortrait()
} }
@ -51,8 +64,9 @@ fun PortraitOverlay(
private fun PortraitContent( private fun PortraitContent(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
portrait: State<String?>, portrait: State<String?>,
isGameMaster: State<Boolean>, options: State<PortraitOptionUio>,
onGameMaster: () -> Unit, onDownload: (url: String) -> Unit,
onClose: () -> Unit,
) { ) {
AnimatedContent( AnimatedContent(
modifier = Modifier modifier = Modifier
@ -65,11 +79,11 @@ private fun PortraitContent(
) { ) {
when (it) { when (it) {
null -> Box( null -> Box(
modifier = Modifier.size(MaterialTheme.lwa.size.portrait.maximized) modifier = Modifier.size(size = MaterialTheme.lwa.size.portrait.maximized)
) )
else -> Box( else -> Box(
modifier = Modifier.size(MaterialTheme.lwa.size.portrait.maximized) modifier = Modifier.size(size = MaterialTheme.lwa.size.portrait.maximized)
) { ) {
AsyncImage( AsyncImage(
modifier = Modifier.matchParentSize(), modifier = Modifier.matchParentSize(),
@ -78,14 +92,33 @@ private fun PortraitContent(
contentDescription = null contentDescription = null
) )
Row(
modifier = Modifier
.align(alignment = Alignment.TopEnd)
.animateContentSize(),
) {
AnimatedVisibility( AnimatedVisibility(
modifier = Modifier.align(alignment = Alignment.TopEnd), visible = options.value.isBrowserAvailable,
visible = isGameMaster.value,
enter = fadeIn(), enter = fadeIn(),
exit = fadeOut(), exit = fadeOut(),
) { ) {
IconButton( IconButton(
onClick = onGameMaster, onClick = { onDownload(it) },
) {
Icon(
painter = painterResource(Res.drawable.ic_link_24dp),
tint = MaterialTheme.lwa.colorScheme.base.primary,
contentDescription = null
)
}
}
AnimatedVisibility(
visible = options.value.isGameMaster,
enter = fadeIn(),
exit = fadeOut(),
) {
IconButton(
onClick = onClose,
) { ) {
Icon( Icon(
painter = painterResource(Res.drawable.ic_cancel_24dp), painter = painterResource(Res.drawable.ic_cancel_24dp),
@ -98,3 +131,4 @@ private fun PortraitContent(
} }
} }
} }
}

View file

@ -13,6 +13,8 @@ import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapNotNull import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import java.awt.Desktop
import java.net.URI
class PortraitOverlayViewModel( class PortraitOverlayViewModel(
private val networkRepository: NetworkRepository, private val networkRepository: NetworkRepository,
@ -32,12 +34,21 @@ class PortraitOverlayViewModel(
initialValue = null initialValue = null
) )
val isGameMaster = settingsRepository.settingsFlow() val options = settingsRepository.settingsFlow()
.map { settings -> settings.isGameMaster ?: false } .map { settings ->
PortraitOptionUio(
isGameMaster = settings.isGameMaster ?: false,
isBrowserAvailable = Desktop.isDesktopSupported()
&& Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)
)
}
.stateIn( .stateIn(
scope = viewModelScope, scope = viewModelScope,
started = SharingStarted.Eagerly, started = SharingStarted.Eagerly,
initialValue = false initialValue = PortraitOptionUio(
isGameMaster = false,
isBrowserAvailable = false,
)
) )
suspend fun dismissPortrait() { suspend fun dismissPortrait() {
@ -48,4 +59,13 @@ class PortraitOverlayViewModel(
) )
) )
} }
fun browse(url: String) {
if (Desktop.isDesktopSupported()) {
val desktop = Desktop.getDesktop()
if (desktop.isSupported(Desktop.Action.BROWSE)) {
desktop.browse(URI.create(url))
}
}
}
} }