Add illustration to the maps.
This commit is contained in:
parent
8410d11799
commit
30b3de2681
11 changed files with 100 additions and 24 deletions
|
|
@ -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(),
|
||||
)
|
||||
|
|
@ -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()
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = { },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 = { },
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue