From 30b3de26818b42a6bd9916b4063a7bf84e03838a Mon Sep 17 00:00:00 2001 From: Thomas Andres Gomez Date: Tue, 21 Nov 2023 10:31:04 +0100 Subject: [PATCH] Add illustration to the maps. --- .../rplexicon/data/model/Location.kt | 1 + ...ortraitParser.kt => IllustrationParser.kt} | 2 +- .../rplexicon/data/parser/LexiconParser.kt | 8 +-- .../rplexicon/data/parser/QuestParser.kt | 8 +-- .../data/parser/map/LocationParser.kt | 1 + .../rplexicon/data/parser/map/MapParser.kt | 10 ++- .../lexicon/detail/LexiconDetailScreen.kt | 11 ++- .../location/detail/LocationDetailScreen.kt | 69 ++++++++++++++++++- .../detail/LocationDetailViewModel.kt | 2 +- app/src/main/res/values-fr/strings.xml | 11 +-- app/src/main/res/values/strings.xml | 1 + 11 files changed, 100 insertions(+), 24 deletions(-) rename app/src/main/java/com/pixelized/rplexicon/data/parser/{PortraitParser.kt => IllustrationParser.kt} (88%) diff --git a/app/src/main/java/com/pixelized/rplexicon/data/model/Location.kt b/app/src/main/java/com/pixelized/rplexicon/data/model/Location.kt index 7422501..ff94f1c 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/model/Location.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/model/Location.kt @@ -9,5 +9,6 @@ data class Location( val category: String?, val uri: Uri?, val description: String?, + val illustrations: List, val child: List> = emptyList(), ) \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/PortraitParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/IllustrationParser.kt similarity index 88% rename from app/src/main/java/com/pixelized/rplexicon/data/parser/PortraitParser.kt rename to app/src/main/java/com/pixelized/rplexicon/data/parser/IllustrationParser.kt index 7d5c6e2..5e8ca4f 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/PortraitParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/IllustrationParser.kt @@ -4,7 +4,7 @@ import android.net.Uri import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull import javax.inject.Inject -class PortraitParser @Inject constructor() { +class IllustrationParser @Inject constructor() { fun parse(value: String?): List { return value?.split(SEPARATOR)?.mapNotNull { it.toUriOrNull() } ?: emptyList() diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt index a94c60d..d8f8de6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/LexiconParser.kt @@ -6,7 +6,7 @@ import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import javax.inject.Inject class LexiconParser @Inject constructor( - private val portraitParser: PortraitParser, + private val illustrationParser: IllustrationParser, ) { @Throws(IncompatibleSheetStructure::class) fun parse(sheet: ValueRange): List = parserScope { @@ -30,7 +30,7 @@ class LexiconParser @Inject constructor( race = row.parse(column = RACE), status = row.parse(column = STATUS), location = row.parse(column = LOCATION), - portrait = portraitParser.parse(row.parse(column = PORTRAIT)), + portrait = illustrationParser.parse(row.parse(column = ILLUSTRATIONS)), description = row.parse(column = DESCRIPTION), history = row.parse(column = HISTORY), tags = row.parse(column = TAGS), @@ -52,7 +52,7 @@ class LexiconParser @Inject constructor( private val RACE = column("Race") private val STATUS = column("Statut") private val LOCATION = column("Localisation") - private val PORTRAIT = column("Portrait") + private val ILLUSTRATIONS = column("Portrait", "Illustrations") private val DESCRIPTION = column("Description") private val HISTORY = column("Histoire") private val TAGS = column("Mots clés") @@ -66,7 +66,7 @@ class LexiconParser @Inject constructor( RACE, STATUS, LOCATION, - PORTRAIT, + ILLUSTRATIONS, DESCRIPTION, HISTORY, TAGS, diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt index b4b565b..59ac5d6 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/QuestParser.kt @@ -7,7 +7,7 @@ import com.pixelized.rplexicon.data.model.QuestEntry import javax.inject.Inject class QuestParser @Inject constructor( - private val imageParser: PortraitParser + private val imageParser: IllustrationParser ) { fun parse(sheet: ValueRange): List = parserScope { val entries = hashMapOf>() @@ -30,7 +30,7 @@ class QuestParser @Inject constructor( groupReward = item.parse(column = GROUP_REWARD), individualReward = item.parse(column = INDIVIDUAL_REWARD), description = description, - images = imageParser.parse(item.parse(column = IMAGE)), + images = imageParser.parse(item.parse(column = ILLUSTRATIONS)), background = item.parseUri(column = BACKGROUND), ) entries.getOrPut(quest) { mutableListOf() }.add(entry) @@ -62,7 +62,7 @@ class QuestParser @Inject constructor( private val GROUP_REWARD = column("Récompense de groupe") private val INDIVIDUAL_REWARD = column("Récompense individuelle") private val DESCRIPTION = column("Description") - private val IMAGE = column("Image") + private val ILLUSTRATIONS = column("Image", "Illustrations") // TODO remove Image after 0.9.0 release private val BACKGROUND = column("fond", "Fond") // TODO remove "fond" after 0.7.0 release private val COLUMNS @@ -76,7 +76,7 @@ class QuestParser @Inject constructor( GROUP_REWARD, INDIVIDUAL_REWARD, DESCRIPTION, - IMAGE, + ILLUSTRATIONS, BACKGROUND, ) } diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/map/LocationParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/map/LocationParser.kt index 7588e20..b733d91 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/map/LocationParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/map/LocationParser.kt @@ -22,6 +22,7 @@ class LocationParser @Inject constructor( category = localMap.category, uri = localMap.uri, description = localMap.description, + illustrations = localMap.illustrations, child = emptyList(), ) }.associateBy { it.name } diff --git a/app/src/main/java/com/pixelized/rplexicon/data/parser/map/MapParser.kt b/app/src/main/java/com/pixelized/rplexicon/data/parser/map/MapParser.kt index 79011f7..bdda382 100644 --- a/app/src/main/java/com/pixelized/rplexicon/data/parser/map/MapParser.kt +++ b/app/src/main/java/com/pixelized/rplexicon/data/parser/map/MapParser.kt @@ -2,12 +2,15 @@ package com.pixelized.rplexicon.data.parser.map import android.net.Uri import com.google.api.services.sheets.v4.model.ValueRange +import com.pixelized.rplexicon.data.parser.IllustrationParser import com.pixelized.rplexicon.data.parser.column import com.pixelized.rplexicon.data.parser.parserScope import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure import javax.inject.Inject -class MapParser @Inject constructor() { +class MapParser @Inject constructor( + private val illustrationParser: IllustrationParser, +) { @Throws(IncompatibleSheetStructure::class) fun parse(sheet: ValueRange): List = parserScope { @@ -21,6 +24,7 @@ class MapParser @Inject constructor() { category = it.parse(column = CATEGORY), uri = it.parseUri(column = URI), description = it.parse(column = DESCRIPTION), + illustrations = illustrationParser.parse(it.parse(column = ILLUSTRATIONS)), ) maps.add(map) } @@ -34,6 +38,7 @@ class MapParser @Inject constructor() { val category: String?, val uri: Uri?, val description: String?, + val illustrations: List, ) companion object { @@ -41,6 +46,7 @@ class MapParser @Inject constructor() { private val CATEGORY = column("Catégorie") private val URI = column("Carte") private val DESCRIPTION = column("Description") - private val COLUMNS get() = listOf(NAME, CATEGORY, URI, DESCRIPTION) + private val ILLUSTRATIONS = column("Illustrations") + private val COLUMNS get() = listOf(NAME, CATEGORY, URI, DESCRIPTION, ILLUSTRATIONS) } } \ No newline at end of file diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt index 5ab170e..e203986 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/lexicon/detail/LexiconDetailScreen.kt @@ -1,6 +1,5 @@ package com.pixelized.rplexicon.ui.screens.lexicon.detail -import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_NO import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.net.Uri @@ -33,8 +32,6 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionLocal -import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.Stable import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf @@ -100,7 +97,7 @@ fun LexiconDetailScreen( haveCharacterSheet = viewModel.haveCharacterSheet, onBack = { screen.popBackStack() }, onCharacterSheet = { screen.navigateToCharacterSheet(name = it) }, - onImage = { imageViewModel.showDetail(it) } + onIllustration = { imageViewModel.showDetail(it) } ) FullScreenImageHandler( @@ -119,7 +116,7 @@ private fun LexiconDetailContent( haveCharacterSheet: State, onBack: () -> Unit, onCharacterSheet: (String) -> Unit, - onImage: (Uri) -> Unit, + onIllustration: (Uri) -> Unit, ) { val typography = MaterialTheme.lexicon.typography val highlightRegex = remember(highlight) { highlightRegex(terms = highlight.searchCriterion()) } @@ -334,7 +331,7 @@ private fun LexiconDetailContent( items(items = item.portrait) { AsyncImage( modifier = Modifier - .clickable { onImage(it) } + .clickable { onIllustration(it) } .sizeIn( minWidth = maxSize / 2, maxWidth = maxSize, @@ -408,7 +405,7 @@ private fun LexiconDetailPreview() { haveCharacterSheet = remember { mutableStateOf(true) }, onBack = { }, onCharacterSheet = { }, - onImage = { }, + onIllustration = { }, ) } } diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt index 0204947..694e36b 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailScreen.kt @@ -14,12 +14,16 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column +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.heightIn import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.sizeIn +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api @@ -53,18 +57,24 @@ import androidx.compose.ui.graphics.RectangleShape import androidx.compose.ui.input.nestedscroll.NestedScrollConnection import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.platform.LocalView import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.pixelized.rplexicon.LocalSnack import com.pixelized.rplexicon.R +import com.pixelized.rplexicon.ui.composable.AsyncImage +import com.pixelized.rplexicon.ui.composable.FullScreenImageHandler +import com.pixelized.rplexicon.ui.composable.FullScreenImageViewModel import com.pixelized.rplexicon.ui.composable.rememberBackgroundGradient import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost import com.pixelized.rplexicon.ui.navigation.screens.navigateToLocationDetail @@ -86,12 +96,14 @@ data class LocationDetailUio( val name: String, val map: Uri?, val description: String?, + val illustrations: List, val marquees: List, ) @Composable fun LocationDetailScreen( - viewModel: LocationDetailViewModel = hiltViewModel() + viewModel: LocationDetailViewModel = hiltViewModel(), + imageViewModel: FullScreenImageViewModel = hiltViewModel(), ) { val uriHandler = LocalUriHandler.current val screen = LocalScreenNavHost.current @@ -121,6 +133,7 @@ fun LocationDetailScreen( onBack = { screen.popBackStack() }, + onIllustration = { imageViewModel.showDetail(it) }, onMarquee = viewModel::onSelectMarquee, onDestination = { screen.navigateToLocationDetail(id = it.id) @@ -189,6 +202,10 @@ fun LocationDetailScreen( } }, ) + + FullScreenImageHandler( + viewModel = imageViewModel, + ) } } @@ -203,6 +220,7 @@ private fun LocationContent( selectedIndex: State, mapHighlight: State, onBack: () -> Unit, + onIllustration: (Uri) -> Unit, onMarquee: (MarqueeUio) -> Unit, onDestination: (MarqueeUio) -> Unit, onMapTap: (Offset) -> Unit, @@ -398,6 +416,40 @@ private fun LocationContent( ), ) } + item.value?.illustrations?.takeIf { it.isNotEmpty() }?.let { + Column { + val maxSize = rememberMaxtWidth() + Text( + modifier = Modifier + .padding(bottom = 8.dp) + .padding(horizontal = 16.dp), + style = MaterialTheme.typography.titleMedium, + text = annotateWithDropCap( + text = stringResource(id = R.string.map_illustrations), + style = MaterialTheme.lexicon.typography.titleMediumDropCap, + ) + ) + LazyRow( + contentPadding = PaddingValues(horizontal = 16.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(items = it) { + AsyncImage( + modifier = Modifier + .clickable { onIllustration(it) } + .sizeIn( + minWidth = maxSize / 2, + maxWidth = maxSize, + minHeight = maxSize, + maxHeight = maxSize, + ), + contentScale = ContentScale.FillHeight, + model = it, + ) + } + } + } + } if (item.value?.marquees?.isNotEmpty() == true) { Column { Text( @@ -467,6 +519,19 @@ private fun Modifier.offset(scrollState: ScrollState) = this.offset { IntOffset(x = 0, y = scrollState.value / 2) } +@Composable +private fun rememberMaxtWidth(): Dp { + val configuration = LocalConfiguration.current + val view = LocalView.current + return remember(configuration, view) { + if (view.isInEditMode) { + 300.dp + } else { + (configuration.screenWidthDp.dp - 16.dp * 2) + } + } +} + @Composable @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -490,6 +555,7 @@ private fun LocationPreview() { name = "Barovie", map = Uri.parse("https://cdn.discordapp.com/attachments/1123326578508714106/1161014438736969759/Map_of_Barovia_-_4k_-_Names_-_No_Special_Locations_-_Without_Hex.jpg?ex=6536c1f4&is=65244cf4&hm=671ddc88d073e5559bd37af14dc731e497b78457dc37a3ea5620a5342004b733&"), description = "Contrée sombre et maudite soumise au joug de Stradh von Zarovith", + illustrations = emptyList(), marquees = listOf( MarqueeUio( id = "Barovie (village)", @@ -524,6 +590,7 @@ private fun LocationPreview() { selectedIndex = remember { mutableIntStateOf(0) }, mapHighlight = remember { mutableStateOf(Offset(0.5f, 0.5f)) }, onBack = { }, + onIllustration = { }, onMarquee = { }, onDestination = { }, onMapTap = { }, diff --git a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailViewModel.kt b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailViewModel.kt index 24537ed..4bbbece 100644 --- a/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailViewModel.kt +++ b/app/src/main/java/com/pixelized/rplexicon/ui/screens/location/detail/LocationDetailViewModel.kt @@ -6,7 +6,6 @@ import android.content.ClipboardManager import android.content.Context.CLIPBOARD_SERVICE import androidx.compose.runtime.State import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.geometry.Offset import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.SavedStateHandle @@ -53,6 +52,7 @@ class LocationDetailViewModel @Inject constructor( name = it.name, map = it.uri, description = it.description, + illustrations = it.illustrations, marquees = it.child.map { child -> MarqueeUio( id = child.second.id, diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 75f35a1..c33a1b2 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -58,11 +58,13 @@ Description : Histoire : Mots clés : - Owner : - Location : - Individual reward : - Group reward : + Commanditaire : + Lieu : + Récompense individuelle : + Récompense de groupe : Description : + Description : + Destination : Détails de quête Complétée @@ -74,6 +76,7 @@ Carte Coordonnées Destinations : + Illustrations : Talents Inventaire diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 43e4cc9..d73bd14 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -76,6 +76,7 @@ Map Coordinates Destinations: + Illustrations: Proficiencies Inventory