Add illustration to the maps.

This commit is contained in:
Thomas Andres Gomez 2023-11-21 10:31:04 +01:00
parent 8410d11799
commit 30b3de2681
11 changed files with 100 additions and 24 deletions

View file

@ -9,5 +9,6 @@ data class Location(
val category: String?,
val uri: Uri?,
val description: String?,
val illustrations: List<Uri>,
val child: List<Pair<Offset, Location>> = emptyList(),
)

View file

@ -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<Uri> {
return value?.split(SEPARATOR)?.mapNotNull { it.toUriOrNull() } ?: emptyList()

View file

@ -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<Lexicon> = 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,

View file

@ -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<Quest> = parserScope {
val entries = hashMapOf<String, MutableList<QuestEntry>>()
@ -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,
)
}

View file

@ -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 }

View file

@ -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<MapDto> = 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<Uri>,
)
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)
}
}

View file

@ -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<Boolean>,
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 = { },
)
}
}

View file

@ -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<Uri>,
val marquees: List<MarqueeUio>,
)
@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<Int?>,
mapHighlight: State<Offset>,
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 = { },

View file

@ -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,

View file

@ -58,11 +58,13 @@
<string name="search_lexicon_item_description">Description :</string>
<string name="search_lexicon_item_history">Histoire :</string>
<string name="search_lexicon_item_tags">Mots clés :</string>
<string name="search_quest_item_owner">Owner :</string>
<string name="search_quest_item_location">Location :</string>
<string name="search_quest_item_individualReward">Individual reward :</string>
<string name="search_quest_item_groupReward">Group reward :</string>
<string name="search_quest_item_owner">Commanditaire :</string>
<string name="search_quest_item_location">Lieu :</string>
<string name="search_quest_item_individualReward">Récompense individuelle :</string>
<string name="search_quest_item_groupReward">Récompense de groupe :</string>
<string name="search_quest_item_description">Description :</string>
<string name="search_location_item_description">Description :</string>
<string name="search_location_item_destination">Destination :</string>
<string name="quest_detail_title">Détails de quête</string>
<string name="quest_detail_completed">Complétée</string>
@ -74,6 +76,7 @@
<string name="map_title">Carte</string>
<string name="map_label">Coordonnées</string>
<string name="map_destination">Destinations :</string>
<string name="map_illustrations">Illustrations :</string>
<string name="character_sheet_tab_proficiency">Talents</string>
<string name="character_sheet_tab_inventory">Inventaire</string>

View file

@ -76,6 +76,7 @@
<string name="map_title">Map</string>
<string name="map_label">Coordinates</string>
<string name="map_destination">Destinations:</string>
<string name="map_illustrations">Illustrations:</string>
<string name="character_sheet_tab_proficiency">Proficiencies</string>
<string name="character_sheet_tab_inventory">Inventory</string>