Add georgia font to help with heavy reading screen.

This commit is contained in:
Andres Gomez, Thomas (ITDV RL) 2024-06-18 17:04:52 +02:00
parent 861ec795b1
commit 532f5810d7
10 changed files with 176 additions and 54 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1,
"database": {
"version": 2,
"identityHash": "10e1da384a3b4f07ee4bba8c16828742",
"identityHash": "ad9094e2a7611443722a5415154015bf",
"entities": [
{
"tableName": "lexicon",
@ -378,7 +378,7 @@
},
{
"tableName": "adventuresStories",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookTitle` TEXT NOT NULL, `adventureTitle` TEXT NOT NULL, `index` INTEGER NOT NULL, `text` TEXT NOT NULL, `format` TEXT NOT NULL, PRIMARY KEY(`bookTitle`, `adventureTitle`, `index`))",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookTitle` TEXT NOT NULL, `adventureTitle` TEXT NOT NULL, `index` INTEGER NOT NULL, `text` TEXT NOT NULL, `format` TEXT NOT NULL, PRIMARY KEY(`bookTitle`, `adventureTitle`, `index`), FOREIGN KEY(`bookTitle`, `adventureTitle`) REFERENCES `adventures`(`bookTitle`, `adventureTitle`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [
{
"fieldPath": "bookTitle",
@ -420,13 +420,27 @@
]
},
"indices": [],
"foreignKeys": []
"foreignKeys": [
{
"table": "adventures",
"onDelete": "CASCADE",
"onUpdate": "NO ACTION",
"columns": [
"bookTitle",
"adventureTitle"
],
"referencedColumns": [
"bookTitle",
"adventureTitle"
]
}
]
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '10e1da384a3b4f07ee4bba8c16828742')"
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ad9094e2a7611443722a5415154015bf')"
]
}
}

View file

@ -60,14 +60,9 @@ interface AdventureDao {
adventure: List<Pair<AdventureDbo, List<AdventureStoryDbo>>>
) {
// First clean the database from old unused data.
booksToRemove.forEach {
deleteBook(id = it)
deleteStory(id = it)
}
storiesToRemove.forEach {
deleteBook(id = it)
deleteStory(id = it)
}
// story lines are remove with cascading foreign key.
booksToRemove.forEach { deleteBook(id = it) }
storiesToRemove.forEach { deleteBook(id = it) }
// then update the data.
adventure.forEach { (adventure, lines) ->
// then remove the story lines before inserting them again (easier than to keep track of them all)

View file

@ -1,6 +1,7 @@
package com.pixelized.rplexicon.data.database.adventure
import androidx.room.Entity
import androidx.room.ForeignKey
@Entity(
tableName = "adventures",
@ -19,6 +20,14 @@ data class AdventureDbo(
@Entity(
tableName = "adventuresStories",
primaryKeys = ["bookTitle", "adventureTitle", "index"],
foreignKeys = [
ForeignKey(
entity = AdventureDbo::class,
parentColumns = ["bookTitle", "adventureTitle"],
childColumns = ["bookTitle", "adventureTitle"],
onDelete = ForeignKey.CASCADE,
),
],
)
data class AdventureStoryDbo(
val bookTitle: String,

View file

@ -6,6 +6,7 @@ data class AdventureLine(
) {
enum class Format {
TITLE,
SUB_TITLE,
CHAPTER,
PARAGRAPH,
DIALOGUE,

View file

@ -22,19 +22,24 @@ class AdventureStoryLineParser @Inject constructor() {
}
}
private fun ArrayList<*>.parseFormat(): AdventureLine.Format? = when (this[0] as? String) {
TITLE -> AdventureLine.Format.TITLE
CHAPTER -> AdventureLine.Format.CHAPTER
PARAGRAPH -> AdventureLine.Format.PARAGRAPH
DIALOGUE -> AdventureLine.Format.DIALOGUE
ANNEX -> AdventureLine.Format.ANNEX
LEGEND -> AdventureLine.Format.LEGEND
else -> null
}
private fun ArrayList<*>.parseFormat(): AdventureLine.Format? =
when (this.getOrNull(0) as? String) {
TITLE -> AdventureLine.Format.TITLE
SUB_TITLE -> AdventureLine.Format.SUB_TITLE
CHAPTER -> AdventureLine.Format.CHAPTER
PARAGRAPH -> AdventureLine.Format.PARAGRAPH
DIALOGUE -> AdventureLine.Format.DIALOGUE
ANNEX -> AdventureLine.Format.ANNEX
LEGEND -> AdventureLine.Format.LEGEND
else -> null
}
private fun ArrayList<*>.parseText(): String? = this[1] as? String
private fun ArrayList<*>.parseText(): String? =
this.getOrNull(1) as? String
private fun <T> ValueRange.mapNotNull(lambda: (row: ArrayList<*>) -> T?): List<T> {
private inline fun <T> ValueRange.mapNotNull(
crossinline lambda: (row: ArrayList<*>) -> T?
): List<T> {
return values.sheet()?.mapNotNull { row: Any? ->
when (row) {
is ArrayList<*> -> lambda(row)
@ -45,6 +50,7 @@ class AdventureStoryLineParser @Inject constructor() {
companion object {
private const val TITLE = "Titre"
private const val SUB_TITLE = "Sous titre"
private const val CHAPTER = "Chapitre"
private const val PARAGRAPH = "Paragraphe"
private const val DIALOGUE = "Dialogue"

View file

@ -23,11 +23,14 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.mapSaver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
@ -41,6 +44,7 @@ import androidx.compose.ui.layout.positionInParent
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
@ -48,9 +52,12 @@ import androidx.core.net.toUri
import androidx.hilt.navigation.compose.hiltViewModel
import com.pixelized.rplexicon.R
import com.pixelized.rplexicon.ui.composable.images.BackgroundImage
import com.pixelized.rplexicon.ui.composable.images.rememberSaturationFilter
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.CHAPTER
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.LEGEND
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.PARAGRAPH
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.SUB_TITLE
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.TITLE
import com.pixelized.rplexicon.ui.theme.LexiconTheme
@ -75,6 +82,12 @@ fun AdventureDetailScreen(
}
}
@Stable
data class TitleLayoutInfo(
val position: Float,
val height: Float,
)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun AdventureDetailContent(
@ -92,9 +105,9 @@ private fun AdventureDetailContent(
onBack: () -> Unit,
) {
val nestedScrollOffset = rememberSaveable { mutableFloatStateOf(0f) }
val titlePosition = rememberSaveable { mutableFloatStateOf(0f) }
val titleHeight = rememberSaveable { mutableFloatStateOf(0f) }
val titleLayoutInfo = rememberSaveable(stateSaver = TitleLayoutInfoSaver) {
mutableStateOf(null)
}
val nestedScrollConnexion = remember {
object : NestedScrollConnection {
override fun onPostScroll(
@ -109,19 +122,21 @@ private fun AdventureDetailContent(
}
val topAppBarAlpha = remember {
derivedStateOf {
when {
-nestedScrollOffset.floatValue < titlePosition.floatValue -> 0f
-nestedScrollOffset.floatValue > titlePosition.floatValue + titleHeight.floatValue -> 1f
titleHeight.floatValue != 0f -> (-nestedScrollOffset.floatValue - titlePosition.floatValue) / titleHeight.floatValue
else -> 0f
}.coerceIn(minimumValue = 0f, maximumValue = 1f)
titleLayoutInfo.value?.let { info ->
when {
-nestedScrollOffset.floatValue < info.position -> 0f
-nestedScrollOffset.floatValue > info.position + info.height -> 1f
info.height != 0f -> (-nestedScrollOffset.floatValue - info.position) / info.height
else -> 0f
}.coerceIn(minimumValue = 0f, maximumValue = 1f)
} ?: 0f
}
}
val backgroundAlpha = remember {
derivedStateOf {
(titlePosition.floatValue).let {
(it + nestedScrollOffset.floatValue) / it
}
titleLayoutInfo.value?.let { info ->
(info.position + nestedScrollOffset.floatValue) / info.position
} ?: 0f
}
}
@ -161,6 +176,8 @@ private fun AdventureDetailContent(
)
Text(
modifier = Modifier.alpha(alpha = alpha.value),
overflow = TextOverflow.Ellipsis,
maxLines = 1,
text = adventureTitle.value ?: "",
)
},
@ -174,15 +191,16 @@ private fun AdventureDetailContent(
) { index, adventure ->
val previous = adventures.value.getOrNull(index - 1)
AdventureLine(
modifier = when (index) {
0 -> Modifier.onGloballyPositioned { coordinate ->
if (titlePosition.floatValue == 0f) {
titlePosition.floatValue = coordinate.positionInParent().y
}
if (titleHeight.floatValue == 0f) {
titleHeight.floatValue = coordinate.size.height.toFloat()
modifier = when {
titleLayoutInfo.value == null && adventure.style == TITLE -> {
Modifier.onGloballyPositioned { coordinate ->
titleLayoutInfo.value = TitleLayoutInfo(
position = coordinate.positionInParent().y,
height = coordinate.size.height.toFloat(),
)
}
}
else -> Modifier
},
paddingValues = rememberPaddingValues(
@ -197,6 +215,33 @@ private fun AdventureDetailContent(
}
}
val TitleLayoutInfoSaver: Saver<TitleLayoutInfo?, Any> = run {
val positionKey = "position"
val heightKey = "height"
mapSaver(
save = {
it?.let {
mapOf(
positionKey to it.position,
heightKey to it.height,
)
} ?: emptyMap()
},
restore = {
val position = it[positionKey] as? Float
val height = it[heightKey] as? Float
if (position != null && height != null) {
TitleLayoutInfo(
position = it[positionKey] as Float,
height = it[heightKey] as Float,
)
} else {
null
}
}
)
}
@Composable
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@ -215,10 +260,18 @@ private fun AdventureDetailPreview() {
adventures = remember {
mutableStateOf(
listOf(
AdventureUio(
text = "Péripétie",
style = SUB_TITLE,
),
AdventureUio(
text = "La traque",
style = TITLE,
),
AdventureUio(
text = "Fake Chapter",
style = CHAPTER,
),
AdventureUio(
text = "Il était temps pour moi de partir à la chasse. Il ne restait déjà plus grand chose du loup dil y a quelques jours. J'éteignis les dernières braises de mon feu, récupérai dagues, javelot et mon outre deau puis dissimulai le reste de mes biens au fond de la caverne me servant d'abri…",
style = PARAGRAPH,

View file

@ -33,6 +33,7 @@ class AdventureDetailViewModel @Inject constructor(
text = line.text,
style = when (line.format) {
Format.TITLE -> Style.TITLE
Format.SUB_TITLE -> Style.SUB_TITLE
Format.CHAPTER -> Style.CHAPTER
Format.PARAGRAPH -> Style.PARAGRAPH
Format.DIALOGUE -> Style.DIALOGUE

View file

@ -15,7 +15,9 @@ import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.CH
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.DIALOGUE
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.LEGEND
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.PARAGRAPH
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.SUB_TITLE
import com.pixelized.rplexicon.ui.screens.adventure.detail.AdventureUio.Style.TITLE
import com.pixelized.rplexicon.utilitary.annotateMajWithDropCap
import com.pixelized.rplexicon.utilitary.annotateWithDropCap
import com.pixelized.rplexicon.utilitary.extentions.lexicon
@ -26,6 +28,7 @@ data class AdventureUio(
) {
enum class Style {
TITLE,
SUB_TITLE,
CHAPTER,
PARAGRAPH,
DIALOGUE,
@ -47,6 +50,7 @@ fun AdventureLine(
.then(other = modifier),
style = when (item.style) {
TITLE -> MaterialTheme.lexicon.typography.adventure.title
SUB_TITLE -> MaterialTheme.lexicon.typography.adventure.subTitle
CHAPTER -> MaterialTheme.lexicon.typography.adventure.chapter
PARAGRAPH -> MaterialTheme.lexicon.typography.adventure.paragraph
DIALOGUE -> MaterialTheme.lexicon.typography.adventure.dialogue
@ -54,12 +58,12 @@ fun AdventureLine(
LEGEND -> MaterialTheme.lexicon.typography.adventure.legend
},
text = when (item.style) {
TITLE -> annotateWithDropCap(
TITLE -> annotateMajWithDropCap(
text = item.text,
style = MaterialTheme.lexicon.typography.adventure.dropCap.title
)
CHAPTER -> annotateWithDropCap(
CHAPTER -> annotateMajWithDropCap(
text = item.text,
style = MaterialTheme.lexicon.typography.adventure.dropCap.chapter
)
@ -81,13 +85,22 @@ fun rememberPaddingValues(
): PaddingValues {
return PaddingValues(
top = when (current.style) {
TITLE -> 32.dp
TITLE -> when (previous?.style) {
TITLE -> 0.dp
SUB_TITLE -> 0.dp
else -> 32.dp
}
CHAPTER -> 32.dp
SUB_TITLE -> 0.dp
CHAPTER -> when (previous?.style) {
TITLE -> 64.dp
else -> 32.dp
}
PARAGRAPH -> when (previous?.style) {
PARAGRAPH -> 8.dp
TITLE -> 32.dp
TITLE -> 64.dp
else -> 16.dp
}
@ -96,7 +109,10 @@ fun rememberPaddingValues(
else -> 16.dp
}
ANNEX -> 16.dp
ANNEX -> when (previous?.style) {
ANNEX -> 8.dp
else -> 16.dp
}
LEGEND -> when (previous?.style) {
LEGEND -> 8.dp

View file

@ -25,6 +25,11 @@ val stampFontFamily = FontFamily(
Font(resId = R.font.rubber_stamp, weight = FontWeight.Normal),
)
@Stable
val georgia = FontFamily(
Font(resId = R.font.georgia),
)
@Suppress("MemberVisibilityCanBePrivate")
@Stable
data class LexiconTypography(
@ -55,6 +60,7 @@ data class LexiconTypography(
data class Adventure(
val dropCap: DropCap,
val title: TextStyle,
val subTitle: TextStyle,
val chapter: TextStyle,
val paragraph: TextStyle,
val dialogue: TextStyle,
@ -204,25 +210,46 @@ fun lexiconTypography(
).toSpanStyle(),
),
adventure: LexiconTypography.Adventure = LexiconTypography.Adventure(
title = base.displaySmall.copy(
title = base.displayMedium.copy(
textAlign = TextAlign.Center,
fontWeight = FontWeight.SemiBold,
),
subTitle = base.bodyMedium.copy(
fontFamily = georgia,
fontSize = base.bodyMedium.fontSize.times(other = 1.1f),
textAlign = TextAlign.Center,
fontWeight = FontWeight.Normal,
),
chapter = base.titleLarge.copy(
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
),
chapter = base.titleLarge,
paragraph = base.bodyMedium.copy(
fontFamily = georgia,
fontSize = base.bodyMedium.fontSize.times(other = 1.1f),
textAlign = TextAlign.Justify,
fontWeight = FontWeight.Normal,
),
dialogue = base.bodyMedium,
legend = base.labelMedium.copy(
fontWeight = FontWeight.Light,
dialogue = base.bodyMedium.copy(
fontFamily = georgia,
fontSize = base.bodyMedium.fontSize.times(other = 1.1f),
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
),
legend = base.labelSmall.copy(
fontFamily = georgia,
textAlign = TextAlign.End,
fontWeight = FontWeight.Light,
fontStyle = FontStyle.Italic,
),
annex = base.labelMedium.copy(
fontFamily = georgia,
textAlign = TextAlign.Start,
fontWeight = FontWeight.Normal,
fontStyle = FontStyle.Italic,
),
dropCap = LexiconTypography.Adventure.DropCap(
title = dropCap.displaySmall,
title = dropCap.displayMedium,
chapter = dropCap.titleLarge,
paragraph = dropCap.bodyMedium,
)

Binary file not shown.