Add a quest log feature
|
|
@ -11,6 +11,7 @@
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:colorMode="wideColorGamut"
|
android:colorMode="wideColorGamut"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
|
android:enableOnBackInvokedCallback="true"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|
|
||||||
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 36 KiB |
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.pixelized.rplexicon.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class Quest(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val entries: List<QuestEntry>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class QuestEntry(
|
||||||
|
val sheetIndex: Int,
|
||||||
|
val title: String,
|
||||||
|
val subtitle: String?,
|
||||||
|
val complete: Boolean,
|
||||||
|
val questGiver: String?,
|
||||||
|
val area: String?,
|
||||||
|
val groupReward: String?,
|
||||||
|
val individualReward: String?,
|
||||||
|
val description: String,
|
||||||
|
)
|
||||||
|
|
@ -9,6 +9,10 @@ import com.google.api.client.json.gson.GsonFactory
|
||||||
import com.google.api.services.sheets.v4.Sheets
|
import com.google.api.services.sheets.v4.Sheets
|
||||||
import com.google.api.services.sheets.v4.model.ValueRange
|
import com.google.api.services.sheets.v4.model.ValueRange
|
||||||
import com.pixelized.rplexicon.model.Lexicon
|
import com.pixelized.rplexicon.model.Lexicon
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
@ -59,7 +63,7 @@ class LexiconRepository @Inject constructor(
|
||||||
val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
|
val lexicon: List<Lexicon> = sheet?.mapIndexedNotNull { index, row ->
|
||||||
when {
|
when {
|
||||||
index == 0 -> {
|
index == 0 -> {
|
||||||
sheetStructure = checkSheetStructure(firstRow = row)
|
sheetStructure = row.checkSheetStructure(model = Sheet.COLUMNS)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,34 +83,6 @@ class LexiconRepository @Inject constructor(
|
||||||
_data.tryEmit(lexicon)
|
_data.tryEmit(lexicon)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(IncompatibleSheetStructure::class)
|
|
||||||
private fun checkSheetStructure(firstRow: Any?): HashMap<String, Int> {
|
|
||||||
// check if the row is a list
|
|
||||||
if (firstRow !is ArrayList<*>) {
|
|
||||||
throw IncompatibleSheetStructure("First row is not a List: $firstRow")
|
|
||||||
}
|
|
||||||
// parse the first line to find element that we recognize.
|
|
||||||
val sheetStructure = hashMapOf<String, Int>()
|
|
||||||
|
|
||||||
firstRow.forEachIndexed { index, cell ->
|
|
||||||
if (cell is String && Sheet.COLUMNS.contains(cell)) {
|
|
||||||
sheetStructure[cell] = index
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// check if we found everything we need.
|
|
||||||
when {
|
|
||||||
sheetStructure.size < Sheet.COLUMNS.size -> throw IncompatibleSheetStructure(
|
|
||||||
message = "Sheet header row does not have enough column: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
|
|
||||||
)
|
|
||||||
|
|
||||||
sheetStructure.size > Sheet.COLUMNS.size -> throw IncompatibleSheetStructure(
|
|
||||||
message = "Sheet header row does have too mush columns: ${firstRow.size}.\nstructure: $firstRow\nheader: $sheetStructure"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return sheetStructure
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun parseCharacterRow(
|
private fun parseCharacterRow(
|
||||||
sheetStructure: Map<String, Int>?,
|
sheetStructure: Map<String, Int>?,
|
||||||
id: Int,
|
id: Int,
|
||||||
|
|
@ -159,13 +135,6 @@ class LexiconRepository @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun MutableCollection<Any>?.sheet(): List<*>? {
|
|
||||||
return this?.firstOrNull {
|
|
||||||
val sheet = it as? ArrayList<*>
|
|
||||||
sheet != null
|
|
||||||
} as List<*>?
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String?.toUriOrNull(): Uri? = try {
|
private fun String?.toUriOrNull(): Uri? = try {
|
||||||
this?.takeIf { it.isNotBlank() }?.toUri()
|
this?.takeIf { it.isNotBlank() }?.toUri()
|
||||||
} catch (_: Exception) {
|
} catch (_: Exception) {
|
||||||
|
|
@ -181,10 +150,6 @@ class LexiconRepository @Inject constructor(
|
||||||
private val Map<String, Int>?.history: Int get() = this?.getValue(Sheet.HISTORY) ?: 6
|
private val Map<String, Int>?.history: Int get() = this?.getValue(Sheet.HISTORY) ?: 6
|
||||||
private val Map<String, Int>?.tags: Int get() = this?.getValue(Sheet.TAGS) ?: 7
|
private val Map<String, Int>?.tags: Int get() = this?.getValue(Sheet.TAGS) ?: 7
|
||||||
|
|
||||||
class ServiceNotReady : Exception()
|
|
||||||
|
|
||||||
class IncompatibleSheetStructure(message: String?) : Exception(message)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TAG = "LexiconRepository"
|
const val TAG = "LexiconRepository"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,156 @@
|
||||||
|
package com.pixelized.rplexicon.repository
|
||||||
|
|
||||||
|
import androidx.compose.runtime.derivedStateOf
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import com.google.api.client.extensions.android.http.AndroidHttp
|
||||||
|
import com.google.api.client.json.gson.GsonFactory
|
||||||
|
import com.google.api.services.sheets.v4.Sheets
|
||||||
|
import com.google.api.services.sheets.v4.model.ValueRange
|
||||||
|
import com.pixelized.rplexicon.model.Quest
|
||||||
|
import com.pixelized.rplexicon.model.QuestEntry
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.ServiceNotReady
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.checkSheetStructure
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class QuestRepository @Inject constructor(
|
||||||
|
private val authenticationRepository: AuthenticationRepository,
|
||||||
|
) {
|
||||||
|
private val sheetService: Sheets? by derivedStateOf {
|
||||||
|
when (authenticationRepository.isAuthenticated.value) {
|
||||||
|
true -> Sheets
|
||||||
|
.Builder(
|
||||||
|
AndroidHttp.newCompatibleTransport(),
|
||||||
|
GsonFactory(),
|
||||||
|
authenticationRepository.credential,
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _data = MutableStateFlow<List<Quest>>(emptyList())
|
||||||
|
val data: StateFlow<List<Quest>> get() = _data
|
||||||
|
|
||||||
|
@Throws(ServiceNotReady::class, IncompatibleSheetStructure::class, Exception::class)
|
||||||
|
suspend fun fetchQuests() {
|
||||||
|
val service = sheetService
|
||||||
|
if (service == null) {
|
||||||
|
throw ServiceNotReady()
|
||||||
|
} else {
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
val request = service.spreadsheets().values().get(Sheet.ID, Sheet.QUEST_JOURNAL)
|
||||||
|
val data = request.execute()
|
||||||
|
updateData(data = data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(IncompatibleSheetStructure::class)
|
||||||
|
private fun updateData(data: ValueRange?) {
|
||||||
|
val sheet = data?.values?.sheet()
|
||||||
|
var sheetStructure: Map<String, Int>? = null
|
||||||
|
|
||||||
|
val questEntries: List<QuestEntry> = sheet?.mapIndexedNotNull { index, row ->
|
||||||
|
when {
|
||||||
|
index == 0 -> {
|
||||||
|
sheetStructure = row.checkSheetStructure(model = Sheet.COLUMNS)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
row is List<*> -> parseQuestRow(
|
||||||
|
sheetStructure = sheetStructure,
|
||||||
|
sheetIndex = index,
|
||||||
|
row = row,
|
||||||
|
)
|
||||||
|
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
} ?: emptyList()
|
||||||
|
|
||||||
|
val questMap = questEntries.groupBy { it.title }
|
||||||
|
|
||||||
|
val quests = questMap.keys.mapIndexed { index, item ->
|
||||||
|
Quest(
|
||||||
|
id = index,
|
||||||
|
title = item,
|
||||||
|
entries = questMap[item] ?: emptyList(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
_data.tryEmit(quests)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseQuestRow(
|
||||||
|
sheetStructure: Map<String, Int>?,
|
||||||
|
sheetIndex: Int,
|
||||||
|
row: List<*>?,
|
||||||
|
): QuestEntry? {
|
||||||
|
val title = row?.getOrNull(sheetStructure.title) as? String
|
||||||
|
val subtitle = row?.getOrNull(sheetStructure.subtitle) as? String?
|
||||||
|
val complete = row?.getOrNull(sheetStructure.complete) as? Boolean? ?: false
|
||||||
|
val questGiver = row?.getOrNull(sheetStructure.questGiver) as? String?
|
||||||
|
val area = row?.getOrNull(sheetStructure.area) as? String?
|
||||||
|
val groupReward = row?.getOrNull(sheetStructure.groupReward) as? String?
|
||||||
|
val individualReward = row?.getOrNull(sheetStructure.individualReward) as? String?
|
||||||
|
val description = row?.getOrNull(sheetStructure.description) as? String
|
||||||
|
|
||||||
|
return if (title?.isNotEmpty() == true && description?.isNotEmpty() == true) {
|
||||||
|
QuestEntry(
|
||||||
|
sheetIndex = sheetIndex,
|
||||||
|
title = title,
|
||||||
|
subtitle = subtitle?.takeIf { it.isNotBlank() },
|
||||||
|
complete = complete,
|
||||||
|
questGiver = questGiver?.takeIf { it.isNotBlank() },
|
||||||
|
area = area?.takeIf { it.isNotBlank() },
|
||||||
|
groupReward = groupReward?.takeIf { it.isNotBlank() },
|
||||||
|
individualReward = individualReward?.takeIf { it.isNotBlank() },
|
||||||
|
description = description,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Map<String, Int>?.title: Int get() = this?.getValue(Sheet.TITLE) ?: 0
|
||||||
|
private val Map<String, Int>?.subtitle: Int get() = this?.getValue(Sheet.SUBTITLE) ?: 1
|
||||||
|
private val Map<String, Int>?.complete: Int get() = this?.getValue(Sheet.COMPLETE) ?: 2
|
||||||
|
private val Map<String, Int>?.questGiver: Int get() = this?.getValue(Sheet.QUEST_GIVER) ?: 3
|
||||||
|
private val Map<String, Int>?.area: Int get() = this?.getValue(Sheet.AREA) ?: 4
|
||||||
|
private val Map<String, Int>?.groupReward: Int get() = this?.getValue(Sheet.G_REWARD) ?: 5
|
||||||
|
private val Map<String, Int>?.individualReward: Int get() = this?.getValue(Sheet.I_REWARD) ?: 6
|
||||||
|
private val Map<String, Int>?.description: Int get() = this?.getValue(Sheet.DESCRIPTION) ?: 7
|
||||||
|
|
||||||
|
private object Sheet {
|
||||||
|
const val ID = "1sDAay8DjbRYKM39MvEXWs-RuvyxjOFpOfRZLAEWjIUY"
|
||||||
|
|
||||||
|
const val QUEST_JOURNAL = "Journal de quêtes"
|
||||||
|
|
||||||
|
val COLUMNS = listOf(
|
||||||
|
"Titre",
|
||||||
|
"Sous Titre",
|
||||||
|
"Compléter",
|
||||||
|
"Commanditaire",
|
||||||
|
"Lieu",
|
||||||
|
"Récompense de groupe",
|
||||||
|
"Récompense individuelle",
|
||||||
|
"Description",
|
||||||
|
)
|
||||||
|
val TITLE = COLUMNS[0]
|
||||||
|
val SUBTITLE = COLUMNS[1]
|
||||||
|
val COMPLETE = COLUMNS[2]
|
||||||
|
val QUEST_GIVER = COLUMNS[3]
|
||||||
|
val AREA = COLUMNS[4]
|
||||||
|
val G_REWARD = COLUMNS[5]
|
||||||
|
val I_REWARD = COLUMNS[6]
|
||||||
|
val DESCRIPTION = COLUMNS[7]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
package com.pixelized.rplexicon.ui.composable
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||||
|
import androidx.compose.material3.LinearProgressIndicator
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.clip
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun Loader(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
refreshState: PullRefreshState,
|
||||||
|
refreshing: State<Boolean>,
|
||||||
|
) {
|
||||||
|
if (refreshing.value) {
|
||||||
|
LinearProgressIndicator(
|
||||||
|
modifier = modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clip(shape = CircleShape)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
PullRefreshIndicator(
|
||||||
|
modifier = modifier,
|
||||||
|
refreshing = false,
|
||||||
|
state = refreshState,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package com.pixelized.rplexicon.ui.navigation
|
||||||
|
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.material3.BottomAppBar
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.NavigationBarItem
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||||
|
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.pages.LEXICON_ROUTE
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.pages.composableLexicon
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.pages.composableQuestList
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.pages.navigateToLexicon
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.pages.navigateToQuestList
|
||||||
|
|
||||||
|
val LocalPageNavHost = staticCompositionLocalOf<NavHostController> {
|
||||||
|
error("LocalScreenNavHost not ready")
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
fun HomeNavHost(
|
||||||
|
lexiconListState: LazyListState,
|
||||||
|
navHostController: NavHostController = rememberAnimatedNavController(),
|
||||||
|
bottomBarItems: List<BottomBarItem> = rememberBottomBarItems(navHostController = navHostController),
|
||||||
|
startDestination: String = LEXICON_ROUTE
|
||||||
|
) {
|
||||||
|
Scaffold(
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(id = R.string.app_name))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
bottomBar = {
|
||||||
|
BottomAppBar(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
tonalElevation = 0.dp,
|
||||||
|
) {
|
||||||
|
bottomBarItems.forEach {
|
||||||
|
NavigationBarItem(
|
||||||
|
selected = it.selected.value,
|
||||||
|
onClick = it.onClick,
|
||||||
|
label = {
|
||||||
|
Text(text = stringResource(id = it.label))
|
||||||
|
},
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = it.icon),
|
||||||
|
contentDescription = "",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
content = { padding ->
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalPageNavHost provides navHostController,
|
||||||
|
) {
|
||||||
|
AnimatedNavHost(
|
||||||
|
modifier = Modifier.padding(padding),
|
||||||
|
navController = navHostController,
|
||||||
|
startDestination = startDestination,
|
||||||
|
) {
|
||||||
|
composableLexicon(lazyListState = lexiconListState)
|
||||||
|
composableQuestList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
class BottomBarItem(
|
||||||
|
val selected: State<Boolean>,
|
||||||
|
val icon: Int,
|
||||||
|
val label: Int,
|
||||||
|
val onClick: () -> Unit,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
private fun rememberBottomBarItems(
|
||||||
|
navHostController: NavHostController,
|
||||||
|
): List<BottomBarItem> {
|
||||||
|
return remember(navHostController) {
|
||||||
|
listOf(
|
||||||
|
BottomBarItem(
|
||||||
|
selected = mutableStateOf(false),
|
||||||
|
icon = R.drawable.ic_outline_account_circle_24,
|
||||||
|
label = R.string.home_lexicon,
|
||||||
|
onClick = { navHostController.navigateToLexicon(navHostController.pageOption()) }
|
||||||
|
),
|
||||||
|
BottomBarItem(
|
||||||
|
selected = mutableStateOf(false),
|
||||||
|
icon = R.drawable.ic_outline_map_24,
|
||||||
|
label = R.string.home_quest_log,
|
||||||
|
onClick = { navHostController.navigateToQuestList(navHostController.pageOption()) }
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,22 @@
|
||||||
package com.pixelized.rplexicon.ui.navigation
|
package com.pixelized.rplexicon.ui.navigation
|
||||||
|
|
||||||
import androidx.compose.animation.ExperimentalAnimationApi
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
|
import androidx.navigation.NavGraph.Companion.findStartDestination
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavOptionsBuilder
|
import androidx.navigation.NavOptionsBuilder
|
||||||
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
import com.google.accompanist.navigation.animation.AnimatedNavHost
|
||||||
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
import com.google.accompanist.navigation.animation.rememberAnimatedNavController
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
import com.pixelized.rplexicon.ui.navigation.screens.AUTHENTICATION_ROUTE
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
import com.pixelized.rplexicon.ui.navigation.screens.composableAuthentication
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableCharacterDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.composableHome
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableLexicon
|
import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconDetail
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.composableSearch
|
import com.pixelized.rplexicon.ui.navigation.screens.composableLexiconSearch
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.composableQuestDetail
|
||||||
|
|
||||||
val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> {
|
val LocalScreenNavHost = staticCompositionLocalOf<NavHostController> {
|
||||||
error("LocalScreenNavHost not ready")
|
error("LocalScreenNavHost not ready")
|
||||||
|
|
@ -25,7 +28,7 @@ fun ScreenNavHost(
|
||||||
navHostController: NavHostController = rememberAnimatedNavController(),
|
navHostController: NavHostController = rememberAnimatedNavController(),
|
||||||
startDestination: String = AUTHENTICATION_ROUTE,
|
startDestination: String = AUTHENTICATION_ROUTE,
|
||||||
) {
|
) {
|
||||||
val lexiconListState = rememberLazyListState()
|
val lexiconListState: LazyListState = rememberLazyListState()
|
||||||
|
|
||||||
CompositionLocalProvider(
|
CompositionLocalProvider(
|
||||||
LocalScreenNavHost provides navHostController,
|
LocalScreenNavHost provides navHostController,
|
||||||
|
|
@ -35,9 +38,12 @@ fun ScreenNavHost(
|
||||||
startDestination = startDestination,
|
startDestination = startDestination,
|
||||||
) {
|
) {
|
||||||
composableAuthentication()
|
composableAuthentication()
|
||||||
composableLexicon(lazyListState = lexiconListState)
|
composableHome(lexiconListState)
|
||||||
composableCharacterDetail()
|
|
||||||
composableSearch()
|
composableLexiconDetail()
|
||||||
|
composableLexiconSearch()
|
||||||
|
|
||||||
|
composableQuestDetail()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -50,3 +56,15 @@ fun rootOption(): NavOptionsBuilder.() -> Unit = {
|
||||||
inclusive = true
|
inclusive = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun NavHostController.pageOption(): NavOptionsBuilder.() -> Unit = {
|
||||||
|
// Pop up to the start destination of the graph to avoid building up a
|
||||||
|
// large stack of destinations on the back stack as users select items
|
||||||
|
popUpTo(graph.findStartDestination().id) {
|
||||||
|
saveState = true
|
||||||
|
}
|
||||||
|
// Avoid multiple copies of the same destination when re-selecting the same item
|
||||||
|
launchSingleTop = true
|
||||||
|
// Restore state when re-selecting a previously selected item
|
||||||
|
restoreState = true
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.navigation.screens
|
package com.pixelized.rplexicon.ui.navigation.pages
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
|
@ -6,7 +6,7 @@ import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavOptionsBuilder
|
import androidx.navigation.NavOptionsBuilder
|
||||||
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconScreen
|
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconScreen
|
||||||
|
|
||||||
private const val ROUTE = "lexicon"
|
private const val ROUTE = "lexicon"
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ fun NavGraphBuilder.composableLexicon(
|
||||||
) {
|
) {
|
||||||
animatedComposable(
|
animatedComposable(
|
||||||
route = LEXICON_ROUTE,
|
route = LEXICON_ROUTE,
|
||||||
animation = NavigationAnimation.Fade,
|
animation = NavigationAnimation.NONE,
|
||||||
) {
|
) {
|
||||||
LexiconScreen(
|
LexiconScreen(
|
||||||
lazyListState = lazyListState
|
lazyListState = lazyListState
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.pixelized.rplexicon.ui.navigation.pages
|
||||||
|
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavOptionsBuilder
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
|
import com.pixelized.rplexicon.ui.screens.quest.list.QuestListScreen
|
||||||
|
|
||||||
|
private const val ROUTE = "quests"
|
||||||
|
|
||||||
|
const val QUEST_LIST_ROUTE = ROUTE
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableQuestList() {
|
||||||
|
animatedComposable(
|
||||||
|
route = QUEST_LIST_ROUTE,
|
||||||
|
animation = NavigationAnimation.NONE,
|
||||||
|
) {
|
||||||
|
QuestListScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToQuestList(
|
||||||
|
option: NavOptionsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
navigate(route = ROUTE, builder = option)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.pixelized.rplexicon.ui.navigation.screens
|
||||||
|
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavOptionsBuilder
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.HomeNavHost
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
|
|
||||||
|
private const val ROUTE = "home"
|
||||||
|
|
||||||
|
const val HOME_ROUTE = ROUTE
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableHome(
|
||||||
|
lexiconListState: LazyListState,
|
||||||
|
) {
|
||||||
|
animatedComposable(
|
||||||
|
route = HOME_ROUTE,
|
||||||
|
animation = NavigationAnimation.Fade,
|
||||||
|
) {
|
||||||
|
HomeNavHost(lexiconListState = lexiconListState)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToHome(
|
||||||
|
option: NavOptionsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
navigate(route = ROUTE, builder = option)
|
||||||
|
}
|
||||||
|
|
@ -11,10 +11,10 @@ import androidx.navigation.navArgument
|
||||||
import com.pixelized.rplexicon.model.Lexicon
|
import com.pixelized.rplexicon.model.Lexicon
|
||||||
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
import com.pixelized.rplexicon.ui.screens.detail.CharacterDetailScreen
|
import com.pixelized.rplexicon.ui.screens.lexicon.detail.LexiconDetailScreen
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.ARG
|
import com.pixelized.rplexicon.utilitary.extentions.ARG
|
||||||
|
|
||||||
private const val ROUTE = "CharacterDetail"
|
private const val ROUTE = "LexiconDetail"
|
||||||
private const val ARG_ID = "id"
|
private const val ARG_ID = "id"
|
||||||
private const val ARG_HIGHLIGHT = "highlight"
|
private const val ARG_HIGHLIGHT = "highlight"
|
||||||
private const val ARG_RACE = "race"
|
private const val ARG_RACE = "race"
|
||||||
|
|
@ -22,16 +22,7 @@ private const val ARG_HIGHLIGHT_RACE = "highlightRace"
|
||||||
private const val ARG_GENDER = "gender"
|
private const val ARG_GENDER = "gender"
|
||||||
private const val ARG_HIGHLIGHT_GENDER = "highlightGender"
|
private const val ARG_HIGHLIGHT_GENDER = "highlightGender"
|
||||||
|
|
||||||
//CharacterDetail
|
val LEXICON_DETAIL_ROUTE = ROUTE +
|
||||||
// ?id=0
|
|
||||||
// &highlight=null
|
|
||||||
// &race=false
|
|
||||||
// &highlightRace=UNDETERMINED
|
|
||||||
// &gender=false
|
|
||||||
// &highlightGender=UNDETERMINED
|
|
||||||
|
|
||||||
|
|
||||||
val CHARACTER_DETAIL_ROUTE = ROUTE +
|
|
||||||
"?${ARG_ID.ARG}" +
|
"?${ARG_ID.ARG}" +
|
||||||
"&${ARG_HIGHLIGHT.ARG}" +
|
"&${ARG_HIGHLIGHT.ARG}" +
|
||||||
"&${ARG_RACE.ARG}" +
|
"&${ARG_RACE.ARG}" +
|
||||||
|
|
@ -41,7 +32,7 @@ val CHARACTER_DETAIL_ROUTE = ROUTE +
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
@Immutable
|
@Immutable
|
||||||
data class CharacterDetailArgument(
|
data class LexiconDetailArgument(
|
||||||
val id: Int,
|
val id: Int,
|
||||||
val highlight: String?,
|
val highlight: String?,
|
||||||
val race: Lexicon.Race,
|
val race: Lexicon.Race,
|
||||||
|
|
@ -50,8 +41,8 @@ data class CharacterDetailArgument(
|
||||||
val highlightGender: Boolean,
|
val highlightGender: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
val SavedStateHandle.characterDetailArgument: CharacterDetailArgument
|
val SavedStateHandle.lexiconDetailArgument: LexiconDetailArgument
|
||||||
get() = CharacterDetailArgument(
|
get() = LexiconDetailArgument(
|
||||||
id = get(ARG_ID)
|
id = get(ARG_ID)
|
||||||
?: error("CharacterDetailArgument argument: $ARG_ID"),
|
?: error("CharacterDetailArgument argument: $ARG_ID"),
|
||||||
race = get(ARG_RACE)
|
race = get(ARG_RACE)
|
||||||
|
|
@ -65,9 +56,9 @@ val SavedStateHandle.characterDetailArgument: CharacterDetailArgument
|
||||||
highlight = get(ARG_HIGHLIGHT),
|
highlight = get(ARG_HIGHLIGHT),
|
||||||
)
|
)
|
||||||
|
|
||||||
fun NavGraphBuilder.composableCharacterDetail() {
|
fun NavGraphBuilder.composableLexiconDetail() {
|
||||||
animatedComposable(
|
animatedComposable(
|
||||||
route = CHARACTER_DETAIL_ROUTE,
|
route = LEXICON_DETAIL_ROUTE,
|
||||||
arguments = listOf(
|
arguments = listOf(
|
||||||
navArgument(name = ARG_ID) {
|
navArgument(name = ARG_ID) {
|
||||||
type = NavType.IntType
|
type = NavType.IntType
|
||||||
|
|
@ -91,11 +82,11 @@ fun NavGraphBuilder.composableCharacterDetail() {
|
||||||
),
|
),
|
||||||
animation = NavigationAnimation.Push,
|
animation = NavigationAnimation.Push,
|
||||||
) {
|
) {
|
||||||
CharacterDetailScreen()
|
LexiconDetailScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NavHostController.navigateToCharacterDetail(
|
fun NavHostController.navigateToLexiconDetail(
|
||||||
id: Int,
|
id: Int,
|
||||||
highlight: String? = null,
|
highlight: String? = null,
|
||||||
race: Lexicon.Race? = null,
|
race: Lexicon.Race? = null,
|
||||||
|
|
@ -5,13 +5,13 @@ import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.NavOptionsBuilder
|
import androidx.navigation.NavOptionsBuilder
|
||||||
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
import com.pixelized.rplexicon.ui.screens.search.SearchScreen
|
import com.pixelized.rplexicon.ui.screens.lexicon.search.SearchScreen
|
||||||
|
|
||||||
private const val ROUTE = "search"
|
private const val ROUTE = "search"
|
||||||
|
|
||||||
const val SEARCH_ROUTE = ROUTE
|
const val SEARCH_ROUTE = ROUTE
|
||||||
|
|
||||||
fun NavGraphBuilder.composableSearch() {
|
fun NavGraphBuilder.composableLexiconSearch() {
|
||||||
animatedComposable(
|
animatedComposable(
|
||||||
route = SEARCH_ROUTE,
|
route = SEARCH_ROUTE,
|
||||||
animation = NavigationAnimation.Push,
|
animation = NavigationAnimation.Push,
|
||||||
|
|
@ -20,7 +20,7 @@ fun NavGraphBuilder.composableSearch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun NavHostController.navigateToSearch(
|
fun NavHostController.navigateToLexiconSearch(
|
||||||
option: NavOptionsBuilder.() -> Unit = {},
|
option: NavOptionsBuilder.() -> Unit = {},
|
||||||
) {
|
) {
|
||||||
navigate(route = ROUTE, builder = option)
|
navigate(route = ROUTE, builder = option)
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
package com.pixelized.rplexicon.ui.navigation.screens
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavOptionsBuilder
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.navArgument
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.NavigationAnimation
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.animatedComposable
|
||||||
|
import com.pixelized.rplexicon.ui.screens.quest.detail.QuestDetailScreen
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.ARG
|
||||||
|
|
||||||
|
private const val ROUTE = "QuestDetail"
|
||||||
|
private const val ARG_ID = "id"
|
||||||
|
|
||||||
|
val QUEST_DETAIL_ROUTE = "$ROUTE?${ARG_ID.ARG}"
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
@Immutable
|
||||||
|
data class QuestDetailArgument(
|
||||||
|
val id: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
val SavedStateHandle.questDetailArgument: QuestDetailArgument
|
||||||
|
get() = QuestDetailArgument(
|
||||||
|
id = get(ARG_ID) ?: error("CharacterDetailArgument argument: $ARG_ID"),
|
||||||
|
)
|
||||||
|
|
||||||
|
fun NavGraphBuilder.composableQuestDetail() {
|
||||||
|
animatedComposable(
|
||||||
|
route = QUEST_DETAIL_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(name = ARG_ID) {
|
||||||
|
type = NavType.IntType
|
||||||
|
},
|
||||||
|
),
|
||||||
|
animation = NavigationAnimation.Push,
|
||||||
|
) {
|
||||||
|
QuestDetailScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavHostController.navigateToQuestDetail(
|
||||||
|
id: Int,
|
||||||
|
option: NavOptionsBuilder.() -> Unit = {},
|
||||||
|
) {
|
||||||
|
val route = "$ROUTE?$ARG_ID=$id"
|
||||||
|
|
||||||
|
navigate(route = route, builder = option)
|
||||||
|
}
|
||||||
|
|
@ -52,7 +52,7 @@ import com.pixelized.rplexicon.LocalActivity
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.navigation.rootOption
|
import com.pixelized.rplexicon.ui.navigation.rootOption
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexicon
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToHome
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
|
import com.pixelized.rplexicon.ui.theme.colors.GoogleColorPalette
|
||||||
import com.pixelized.rplexicon.utilitary.sensor.Gyroscope
|
import com.pixelized.rplexicon.utilitary.sensor.Gyroscope
|
||||||
|
|
@ -88,7 +88,7 @@ fun AuthenticationScreen(
|
||||||
HandleAuthenticationState(
|
HandleAuthenticationState(
|
||||||
state = state,
|
state = state,
|
||||||
onSignIn = {
|
onSignIn = {
|
||||||
screen.navigateToLexicon(option = rootOption())
|
screen.navigateToHome(option = rootOption())
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,132 +0,0 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.lexicon
|
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
|
||||||
import androidx.annotation.StringRes
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Box
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
|
||||||
import androidx.compose.foundation.layout.Row
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.runtime.Stable
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.res.stringResource
|
|
||||||
import androidx.compose.ui.text.font.FontStyle
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.pixelized.rplexicon.R
|
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.placeholder
|
|
||||||
|
|
||||||
@Stable
|
|
||||||
data class LexiconItemUio(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val diminutive: String?,
|
|
||||||
@StringRes val gender: Int,
|
|
||||||
@StringRes val race: Int,
|
|
||||||
val placeholder: Boolean = false,
|
|
||||||
) {
|
|
||||||
companion object {
|
|
||||||
val Brulkhai = LexiconItemUio(
|
|
||||||
id = 0,
|
|
||||||
name = "Brulkhai",
|
|
||||||
diminutive = "Bru",
|
|
||||||
gender = R.string.gender_female_short,
|
|
||||||
race = R.string.race_half_orc,
|
|
||||||
placeholder = true,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class)
|
|
||||||
@Composable
|
|
||||||
fun LexiconItem(
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
item: LexiconItemUio,
|
|
||||||
) {
|
|
||||||
val typography = MaterialTheme.typography
|
|
||||||
|
|
||||||
Box(
|
|
||||||
modifier = modifier,
|
|
||||||
contentAlignment = Alignment.Center,
|
|
||||||
) {
|
|
||||||
Column(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(vertical = 8.dp, horizontal = 16.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
FlowRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.alignByBaseline()
|
|
||||||
.placeholder { item.placeholder },
|
|
||||||
style = remember { typography.bodyLarge.copy(fontWeight = FontWeight.Bold) },
|
|
||||||
maxLines = 1,
|
|
||||||
text = item.name,
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.alignByBaseline()
|
|
||||||
.placeholder { item.placeholder },
|
|
||||||
style = typography.labelMedium,
|
|
||||||
maxLines = 1,
|
|
||||||
text = item.diminutive ?: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Row(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.alignByBaseline()
|
|
||||||
.placeholder { item.placeholder },
|
|
||||||
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
|
|
||||||
maxLines = 1,
|
|
||||||
text = stringResource(id = item.gender)
|
|
||||||
)
|
|
||||||
Text(
|
|
||||||
modifier = Modifier
|
|
||||||
.alignByBaseline()
|
|
||||||
.placeholder { item.placeholder },
|
|
||||||
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
|
|
||||||
maxLines = 1,
|
|
||||||
text = stringResource(id = item.race)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
|
||||||
private fun LexiconItemContentPreview() {
|
|
||||||
LexiconTheme {
|
|
||||||
Surface {
|
|
||||||
LexiconItem(
|
|
||||||
item = LexiconItemUio(
|
|
||||||
id = 0,
|
|
||||||
name = "Brulkhai",
|
|
||||||
diminutive = "Bru",
|
|
||||||
gender = R.string.gender_female_short,
|
|
||||||
race = R.string.race_half_orc,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.detail
|
package com.pixelized.rplexicon.ui.screens.lexicon.detail
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
@ -69,7 +69,7 @@ import com.pixelized.rplexicon.utilitary.extentions.highlightRegex
|
||||||
import com.skydoves.landscapist.ImageOptions
|
import com.skydoves.landscapist.ImageOptions
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class CharacterDetailUio(
|
data class LexiconDetailUio(
|
||||||
val name: String,
|
val name: String,
|
||||||
val diminutive: String?,
|
val diminutive: String?,
|
||||||
val gender: Lexicon.Gender,
|
val gender: Lexicon.Gender,
|
||||||
|
|
@ -84,7 +84,7 @@ data class CharacterDetailUio(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Stable
|
@Stable
|
||||||
data class AnnotatedCharacterDetailUio(
|
data class AnnotatedLexiconDetailUio(
|
||||||
val name: AnnotatedString,
|
val name: AnnotatedString,
|
||||||
val diminutive: AnnotatedString?,
|
val diminutive: AnnotatedString?,
|
||||||
val gender: AnnotatedString,
|
val gender: AnnotatedString,
|
||||||
|
|
@ -97,7 +97,7 @@ data class AnnotatedCharacterDetailUio(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Stable
|
@Stable
|
||||||
fun CharacterDetailUio.annotated(): AnnotatedCharacterDetailUio {
|
fun LexiconDetailUio.annotate(): AnnotatedLexiconDetailUio {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
val highlight = remember { SpanStyle(color = colorScheme.primary) }
|
||||||
val highlightRegex = remember(search) { search.highlightRegex }
|
val highlightRegex = remember(search) { search.highlightRegex }
|
||||||
|
|
@ -105,7 +105,7 @@ fun CharacterDetailUio.annotated(): AnnotatedCharacterDetailUio {
|
||||||
val race = stringResource(id = race)
|
val race = stringResource(id = race)
|
||||||
|
|
||||||
return remember(search, race, highlightRace, gender, highlightGender) {
|
return remember(search, race, highlightRace, gender, highlightGender) {
|
||||||
AnnotatedCharacterDetailUio(
|
AnnotatedLexiconDetailUio(
|
||||||
portrait = portrait,
|
portrait = portrait,
|
||||||
name = AnnotatedString(
|
name = AnnotatedString(
|
||||||
text = name,
|
text = name,
|
||||||
|
|
@ -148,13 +148,13 @@ fun CharacterDetailUio.annotated(): AnnotatedCharacterDetailUio {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CharacterDetailScreen(
|
fun LexiconDetailScreen(
|
||||||
viewModel: CharacterDetailViewModel = hiltViewModel(),
|
viewModel: LexiconDetailViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val screen = LocalScreenNavHost.current
|
val screen = LocalScreenNavHost.current
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
CharacterDetailScreenContent(
|
LexiconDetailContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
item = viewModel.character,
|
item = viewModel.character,
|
||||||
onBack = { screen.popBackStack() },
|
onBack = { screen.popBackStack() },
|
||||||
|
|
@ -164,15 +164,15 @@ fun CharacterDetailScreen(
|
||||||
|
|
||||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalLayoutApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
private fun CharacterDetailScreenContent(
|
private fun LexiconDetailContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
state: ScrollState = rememberScrollState(),
|
state: ScrollState = rememberScrollState(),
|
||||||
item: State<CharacterDetailUio>,
|
item: State<LexiconDetailUio>,
|
||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val colorScheme = MaterialTheme.colorScheme
|
val colorScheme = MaterialTheme.colorScheme
|
||||||
val typography = MaterialTheme.typography
|
val typography = MaterialTheme.typography
|
||||||
val annotatedItem = item.value.annotated()
|
val annotatedItem = item.value.annotate()
|
||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -244,7 +244,8 @@ private fun CharacterDetailScreenContent(
|
||||||
annotatedItem.diminutive?.let {
|
annotatedItem.diminutive?.let {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.alignByBaseline(),
|
modifier = Modifier.alignByBaseline(),
|
||||||
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
|
style = typography.labelMedium,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -255,11 +256,13 @@ private fun CharacterDetailScreenContent(
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
|
style = typography.labelMedium,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
text = annotatedItem.gender,
|
text = annotatedItem.gender,
|
||||||
)
|
)
|
||||||
Text(
|
Text(
|
||||||
style = remember { typography.labelMedium.copy(fontStyle = FontStyle.Italic) },
|
style = typography.labelMedium,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
text = annotatedItem.race,
|
text = annotatedItem.race,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -329,7 +332,8 @@ private fun CharacterDetailScreenContent(
|
||||||
annotatedItem.tags?.let {
|
annotatedItem.tags?.let {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
|
modifier = Modifier.padding(start = 16.dp, top = 24.dp, end = 16.dp),
|
||||||
style = remember { typography.labelSmall.copy(fontStyle = FontStyle.Italic) },
|
style = typography.labelSmall,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
text = it,
|
text = it,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -374,14 +378,14 @@ private fun Modifier.scrollOffset(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
@Preview(uiMode = UI_MODE_NIGHT_NO, heightDp = 980)
|
||||||
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
@Preview(uiMode = UI_MODE_NIGHT_YES, heightDp = 980)
|
||||||
private fun CharacterDetailScreenContentPreview() {
|
private fun LexiconDetailPreview() {
|
||||||
LexiconTheme {
|
LexiconTheme {
|
||||||
Surface {
|
Surface {
|
||||||
val character = remember {
|
val character = remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
CharacterDetailUio(
|
LexiconDetailUio(
|
||||||
name = "Brulkhai",
|
name = "Brulkhai",
|
||||||
diminutive = "./ Bru",
|
diminutive = "./ Bru",
|
||||||
gender = Lexicon.Gender.FEMALE,
|
gender = Lexicon.Gender.FEMALE,
|
||||||
|
|
@ -391,14 +395,14 @@ private fun CharacterDetailScreenContentPreview() {
|
||||||
),
|
),
|
||||||
description = "Brulkhai, ou plus simplement Bru, est solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux qu’elle considère plus faibles qu’elle. D’une nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale. Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle nomme foshnu (bébé, chouineur en commun).",
|
description = "Brulkhai, ou plus simplement Bru, est solidement bâti. Elle mesure 192 cm pour 110 kg de muscles lorsqu’elle est en bonne santé. Elle a les cheveux châtains, les yeux noisettes et la peau couleur gris-vert typique de son espèce. D’un tempérament taciturne, elle parle peu et de façon concise. Elle est parfois brutale, aussi bien physiquement que verbalement, Elle ne prend cependant aucun plaisir à malmener ceux qu’elle considère plus faibles qu’elle. D’une nature simple et honnête, elle ne mâche pas ses mots et ne dissimule généralement pas ses pensées. Son intelligence modeste est plus le reflet d’un manque d’éducation et d’une capacité limitée à gérer ses émotions qu’à une débilité congénitale. Elle voue à la force un culte car c’est par son expression qu’elle se sent vraiment vivante et éprouve de grandes difficultés vis à vis de ceux qu’elle nomme foshnu (bébé, chouineur en commun).",
|
||||||
history = null,
|
history = null,
|
||||||
tags = null,
|
tags = "protagoniste, brute",
|
||||||
search = "Bru",
|
search = "Bru",
|
||||||
highlightGender = true,
|
highlightGender = true,
|
||||||
highlightRace = true,
|
highlightRace = true,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
CharacterDetailScreenContent(
|
LexiconDetailContent(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
item = character,
|
item = character,
|
||||||
onBack = { },
|
onBack = { },
|
||||||
|
|
@ -1,27 +1,27 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.detail
|
package com.pixelized.rplexicon.ui.screens.lexicon.detail
|
||||||
|
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import com.pixelized.rplexicon.repository.LexiconRepository
|
import com.pixelized.rplexicon.repository.LexiconRepository
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.characterDetailArgument
|
import com.pixelized.rplexicon.ui.navigation.screens.lexiconDetailArgument
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class CharacterDetailViewModel @Inject constructor(
|
class LexiconDetailViewModel @Inject constructor(
|
||||||
savedStateHandle: SavedStateHandle,
|
savedStateHandle: SavedStateHandle,
|
||||||
repository: LexiconRepository,
|
repository: LexiconRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val character: State<CharacterDetailUio>
|
val character: State<LexiconDetailUio>
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val argument = savedStateHandle.characterDetailArgument
|
val argument = savedStateHandle.lexiconDetailArgument
|
||||||
val source = repository.data.value[argument.id]
|
val source = repository.data.value[argument.id]
|
||||||
|
|
||||||
character = mutableStateOf(
|
character = mutableStateOf(
|
||||||
CharacterDetailUio(
|
LexiconDetailUio(
|
||||||
name = source.name,
|
name = source.name,
|
||||||
diminutive = source.diminutive?.let { "./ $it" },
|
diminutive = source.diminutive?.let { "./ $it" },
|
||||||
gender = source.gender,
|
gender = source.gender,
|
||||||
|
|
@ -0,0 +1,182 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.lexicon.list
|
||||||
|
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||||
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontStyle
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.cell
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.placeholder
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class LexiconItemUio(
|
||||||
|
val id: Int,
|
||||||
|
val name: String,
|
||||||
|
val diminutive: String?,
|
||||||
|
@StringRes val gender: Int,
|
||||||
|
@StringRes val race: Int,
|
||||||
|
val placeholder: Boolean = false,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
@Stable
|
||||||
|
fun preview(
|
||||||
|
id: Int = 0,
|
||||||
|
name: String = "Brulkhai",
|
||||||
|
diminutive: String? = null,
|
||||||
|
@StringRes gender: Int = R.string.gender_female_short,
|
||||||
|
@StringRes race: Int = R.string.race_half_orc,
|
||||||
|
placeholder: Boolean = false,
|
||||||
|
) = LexiconItemUio(
|
||||||
|
id = id,
|
||||||
|
name = name,
|
||||||
|
diminutive = diminutive,
|
||||||
|
gender = gender,
|
||||||
|
race = race,
|
||||||
|
placeholder = placeholder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalLayoutApi::class)
|
||||||
|
@Composable
|
||||||
|
fun LexiconItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
item: LexiconItemUio,
|
||||||
|
) {
|
||||||
|
val typography = MaterialTheme.lexicon.typography
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = modifier,
|
||||||
|
contentAlignment = Alignment.CenterStart,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
text = "◊",
|
||||||
|
)
|
||||||
|
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.alignByBaseline()
|
||||||
|
.placeholder { item.placeholder },
|
||||||
|
style = typography.base.bodyLarge,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
text = AnnotatedString(
|
||||||
|
text = item.name,
|
||||||
|
spanStyles = when (item.placeholder) {
|
||||||
|
true -> emptyList()
|
||||||
|
else -> listOf(
|
||||||
|
AnnotatedString.Range(
|
||||||
|
item = typography.dropCapMediumSpan,
|
||||||
|
start = 0,
|
||||||
|
end = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
item.diminutive?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.alignByBaseline()
|
||||||
|
.placeholder { item.placeholder },
|
||||||
|
style = typography.base.labelMedium,
|
||||||
|
maxLines = 1,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.alignByBaseline(),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.alignByBaseline()
|
||||||
|
.placeholder { item.placeholder },
|
||||||
|
style = typography.base.labelMedium,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
maxLines = 1,
|
||||||
|
text = stringResource(id = item.gender)
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier
|
||||||
|
.alignByBaseline()
|
||||||
|
.placeholder { item.placeholder },
|
||||||
|
style = typography.base.labelMedium,
|
||||||
|
fontStyle = FontStyle.Italic,
|
||||||
|
maxLines = 1,
|
||||||
|
text = stringResource(id = item.race)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = UI_MODE_NIGHT_YES)
|
||||||
|
private fun LexiconItemPreview(
|
||||||
|
@PreviewParameter(LexiconItemPreviewProvider::class) preview: LexiconItemUio
|
||||||
|
) {
|
||||||
|
LexiconTheme {
|
||||||
|
Surface {
|
||||||
|
LexiconItem(
|
||||||
|
modifier = Modifier.cell(),
|
||||||
|
item = preview
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LexiconItemPreviewProvider : PreviewParameterProvider<LexiconItemUio> {
|
||||||
|
override val values: Sequence<LexiconItemUio> = sequenceOf(
|
||||||
|
LexiconItemUio.preview(
|
||||||
|
name = "Mundas-Naltum-Brulkhai-Arauishi",
|
||||||
|
diminutive = "Mun-Nalt-Bru-Arahi",
|
||||||
|
placeholder = false,
|
||||||
|
),
|
||||||
|
LexiconItemUio.preview(
|
||||||
|
name = "Brulkhai",
|
||||||
|
diminutive = "Bru",
|
||||||
|
placeholder = false,
|
||||||
|
),
|
||||||
|
LexiconItemUio.preview(
|
||||||
|
placeholder = true,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.lexicon
|
package com.pixelized.rplexicon.ui.screens.lexicon.list
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -16,29 +16,20 @@ import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyListState
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
|
||||||
import androidx.compose.material.ExperimentalMaterialApi
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
import androidx.compose.material.LinearProgressIndicator
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshIndicator
|
|
||||||
import androidx.compose.material.pullrefresh.PullRefreshState
|
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||||
import androidx.compose.material.pullrefresh.pullRefresh
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ButtonDefaults
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Scaffold
|
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBar
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
|
|
@ -49,7 +40,6 @@ import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
@ -57,17 +47,17 @@ import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import com.pixelized.rplexicon.LocalSnack
|
import com.pixelized.rplexicon.LocalSnack
|
||||||
import com.pixelized.rplexicon.NO_WINDOW_INSETS
|
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
|
import com.pixelized.rplexicon.ui.composable.FloatingActionButton
|
||||||
|
import com.pixelized.rplexicon.ui.composable.Loader
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToSearch
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconSearch
|
||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Default
|
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Default
|
||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Permission
|
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Permission
|
||||||
import com.pixelized.rplexicon.ui.screens.lexicon.LexiconErrorUio.Structure
|
import com.pixelized.rplexicon.ui.screens.lexicon.list.LexiconErrorUio.Structure
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.cell
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
@ -109,17 +99,16 @@ fun LexiconScreen(
|
||||||
|
|
||||||
Surface {
|
Surface {
|
||||||
LexiconScreenContent(
|
LexiconScreenContent(
|
||||||
modifier = Modifier.systemBarsPadding(),
|
|
||||||
items = viewModel.items,
|
items = viewModel.items,
|
||||||
lazyColumnState = lazyListState,
|
lazyColumnState = lazyListState,
|
||||||
refreshState = refresh,
|
refreshState = refresh,
|
||||||
refreshing = viewModel.isLoading,
|
refreshing = viewModel.isLoading,
|
||||||
isFabExpended = isFabExpended,
|
isFabExpended = isFabExpended,
|
||||||
onSearch = {
|
onSearch = {
|
||||||
screen.navigateToSearch()
|
screen.navigateToLexiconSearch()
|
||||||
},
|
},
|
||||||
onItem = {
|
onItem = {
|
||||||
screen.navigateToCharacterDetail(id = it.id)
|
screen.navigateToLexiconDetail(id = it.id)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -133,7 +122,6 @@ fun LexiconScreen(
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(
|
@OptIn(
|
||||||
ExperimentalMaterial3Api::class,
|
|
||||||
ExperimentalMaterialApi::class,
|
ExperimentalMaterialApi::class,
|
||||||
ExperimentalAnimationApi::class,
|
ExperimentalAnimationApi::class,
|
||||||
)
|
)
|
||||||
|
|
@ -141,6 +129,10 @@ fun LexiconScreen(
|
||||||
private fun LexiconScreenContent(
|
private fun LexiconScreenContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
lazyColumnState: LazyListState,
|
lazyColumnState: LazyListState,
|
||||||
|
paddingValues: PaddingValues = PaddingValues(
|
||||||
|
top = 6.dp,
|
||||||
|
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
||||||
|
),
|
||||||
refreshState: PullRefreshState,
|
refreshState: PullRefreshState,
|
||||||
refreshing: State<Boolean>,
|
refreshing: State<Boolean>,
|
||||||
isFabExpended: State<Boolean>,
|
isFabExpended: State<Boolean>,
|
||||||
|
|
@ -148,127 +140,85 @@ private fun LexiconScreenContent(
|
||||||
onSearch: () -> Unit,
|
onSearch: () -> Unit,
|
||||||
onItem: (LexiconItemUio) -> Unit,
|
onItem: (LexiconItemUio) -> Unit,
|
||||||
) {
|
) {
|
||||||
Scaffold(
|
Box(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentWindowInsets = NO_WINDOW_INSETS,
|
) {
|
||||||
topBar = {
|
AnimatedContent(
|
||||||
TopAppBar(
|
targetState = items.value.isEmpty(),
|
||||||
windowInsets = NO_WINDOW_INSETS,
|
transitionSpec = { fadeIn() with fadeOut() },
|
||||||
title = {
|
label = "AnimatedLexicon"
|
||||||
Text(text = stringResource(id = R.string.app_name))
|
) { empty ->
|
||||||
|
when (empty) {
|
||||||
|
true -> LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state = refreshState),
|
||||||
|
state = lazyColumnState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
) {
|
||||||
|
items(count = 6) {
|
||||||
|
LexiconItem(
|
||||||
|
modifier = Modifier.cell(),
|
||||||
|
item = LexiconItemUio.preview(placeholder = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state = refreshState),
|
||||||
|
state = lazyColumnState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = items.value,
|
||||||
|
key = { it.id },
|
||||||
|
contentType = { "Lexicon" },
|
||||||
|
) {
|
||||||
|
LexiconItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { onItem(it) }
|
||||||
|
.cell(),
|
||||||
|
item = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility(
|
||||||
|
modifier = Modifier
|
||||||
|
.padding(all = 16.dp)
|
||||||
|
.align(Alignment.BottomEnd),
|
||||||
|
visible = items.value.isNotEmpty(),
|
||||||
|
enter = fadeIn(),
|
||||||
|
exit = fadeOut(),
|
||||||
|
) {
|
||||||
|
FloatingActionButton(
|
||||||
|
expended = isFabExpended.value,
|
||||||
|
onClick = onSearch,
|
||||||
|
colors = ButtonDefaults.outlinedButtonColors(
|
||||||
|
containerColor = MaterialTheme.colorScheme.surface,
|
||||||
|
),
|
||||||
|
icon = {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_baseline_search_24),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
text = {
|
||||||
|
Text(text = stringResource(id = R.string.lexicon_search))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
|
||||||
floatingActionButton = {
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.height(56.dp)
|
|
||||||
.padding(start = 32.dp), // `Fix` Scaffold content size for FAB.
|
|
||||||
contentAlignment = Alignment.CenterEnd,
|
|
||||||
) {
|
|
||||||
AnimatedVisibility(
|
|
||||||
visible = items.value.isNotEmpty(),
|
|
||||||
enter = fadeIn(),
|
|
||||||
exit = fadeOut(),
|
|
||||||
) {
|
|
||||||
FloatingActionButton(
|
|
||||||
expended = isFabExpended.value,
|
|
||||||
onClick = onSearch,
|
|
||||||
icon = {
|
|
||||||
Icon(
|
|
||||||
painter = painterResource(id = R.drawable.ic_baseline_search_24),
|
|
||||||
contentDescription = null,
|
|
||||||
)
|
|
||||||
},
|
|
||||||
text = {
|
|
||||||
Text(text = stringResource(id = R.string.lexicon_search))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
) { padding ->
|
|
||||||
Box(
|
|
||||||
modifier = Modifier.padding(paddingValues = padding),
|
|
||||||
contentAlignment = Alignment.TopCenter,
|
|
||||||
) {
|
|
||||||
AnimatedContent(
|
|
||||||
targetState = items.value.isEmpty(),
|
|
||||||
transitionSpec = { fadeIn() with fadeOut() },
|
|
||||||
label = "AnimatedLexicon"
|
|
||||||
) { empty ->
|
|
||||||
when (empty) {
|
|
||||||
true -> LazyColumn(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.pullRefresh(state = refreshState),
|
|
||||||
state = lazyColumnState,
|
|
||||||
contentPadding = PaddingValues(
|
|
||||||
top = 8.dp,
|
|
||||||
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
items(count = 6) {
|
|
||||||
LexiconItem(
|
|
||||||
modifier = Modifier.heightIn(min = MaterialTheme.lexicon.dimens.item),
|
|
||||||
item = LexiconItemUio.Brulkhai,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> LazyColumn(
|
Loader(
|
||||||
modifier = Modifier
|
modifier = Modifier.align(Alignment.TopCenter),
|
||||||
.fillMaxSize()
|
refreshState = refreshState,
|
||||||
.pullRefresh(state = refreshState),
|
refreshing = refreshing,
|
||||||
state = lazyColumnState,
|
|
||||||
contentPadding = PaddingValues(
|
|
||||||
top = 8.dp,
|
|
||||||
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
|
||||||
),
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = items.value,
|
|
||||||
key = { it.id },
|
|
||||||
contentType = { "Lexicon" },
|
|
||||||
) {
|
|
||||||
LexiconItem(
|
|
||||||
modifier = Modifier
|
|
||||||
.clickable { onItem(it) }
|
|
||||||
.heightIn(min = MaterialTheme.lexicon.dimens.item),
|
|
||||||
item = it,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader(
|
|
||||||
refreshState = refreshState,
|
|
||||||
refreshing = refreshing,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterialApi::class)
|
|
||||||
@Composable
|
|
||||||
private fun Loader(
|
|
||||||
refreshState: PullRefreshState,
|
|
||||||
refreshing: State<Boolean>,
|
|
||||||
) {
|
|
||||||
if (refreshing.value) {
|
|
||||||
LinearProgressIndicator(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(shape = CircleShape)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
PullRefreshIndicator(
|
|
||||||
refreshing = false,
|
|
||||||
state = refreshState,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.lexicon
|
package com.pixelized.rplexicon.ui.screens.lexicon.list
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
|
|
@ -9,7 +9,7 @@ import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecovera
|
||||||
import com.pixelized.rplexicon.R
|
import com.pixelized.rplexicon.R
|
||||||
import com.pixelized.rplexicon.model.Lexicon
|
import com.pixelized.rplexicon.model.Lexicon
|
||||||
import com.pixelized.rplexicon.repository.LexiconRepository
|
import com.pixelized.rplexicon.repository.LexiconRepository
|
||||||
import com.pixelized.rplexicon.repository.LexiconRepository.IncompatibleSheetStructure
|
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.SharedFlow
|
import kotlinx.coroutines.flow.SharedFlow
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.search
|
package com.pixelized.rplexicon.ui.screens.lexicon.search
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.search
|
package com.pixelized.rplexicon.ui.screens.lexicon.search
|
||||||
|
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
import android.content.res.Configuration.UI_MODE_NIGHT_NO
|
||||||
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
import android.content.res.Configuration.UI_MODE_NIGHT_YES
|
||||||
|
|
@ -47,7 +47,7 @@ import com.pixelized.rplexicon.ui.composable.form.DropDownFieldUio
|
||||||
import com.pixelized.rplexicon.ui.composable.form.TextField
|
import com.pixelized.rplexicon.ui.composable.form.TextField
|
||||||
import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
|
import com.pixelized.rplexicon.ui.composable.form.TextFieldUio
|
||||||
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
import com.pixelized.rplexicon.ui.navigation.screens.navigateToCharacterDetail
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToLexiconDetail
|
||||||
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
import com.pixelized.rplexicon.utilitary.composable.stringResource
|
import com.pixelized.rplexicon.utilitary.composable.stringResource
|
||||||
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
|
@ -77,7 +77,7 @@ fun SearchScreen(
|
||||||
form = viewModel.form,
|
form = viewModel.form,
|
||||||
onItem = { item ->
|
onItem = { item ->
|
||||||
val form = viewModel.form
|
val form = viewModel.form
|
||||||
screen.navigateToCharacterDetail(
|
screen.navigateToLexiconDetail(
|
||||||
id = item.id,
|
id = item.id,
|
||||||
highlight = form.search.value.value.takeIf { it.isNotEmpty() },
|
highlight = form.search.value.value.takeIf { it.isNotEmpty() },
|
||||||
race = form.race.value.value,
|
race = form.race.value.value,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.pixelized.rplexicon.ui.screens.search
|
package com.pixelized.rplexicon.ui.screens.lexicon.search
|
||||||
|
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.quest.detail
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.Image
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
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.height
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Scaffold
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TopAppBar
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.ColorFilter
|
||||||
|
import androidx.compose.ui.graphics.graphicsLayer
|
||||||
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
import java.lang.Integer.min
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class QuestDetailUio(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val steps: List<QuestStep>,
|
||||||
|
) {
|
||||||
|
@Stable
|
||||||
|
data class QuestStep(
|
||||||
|
val completed: Boolean,
|
||||||
|
val subtitle: String?,
|
||||||
|
val giver: String?,
|
||||||
|
val place: String?,
|
||||||
|
val individualReward: String?,
|
||||||
|
val globalReward: String?,
|
||||||
|
val description: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class AnnotatedQuestDetailUio(
|
||||||
|
val title: String,
|
||||||
|
val steps: List<AnnotatedQuestStep>,
|
||||||
|
) {
|
||||||
|
@Stable
|
||||||
|
data class AnnotatedQuestStep(
|
||||||
|
val completed: Boolean,
|
||||||
|
val subtitle: String?,
|
||||||
|
val giver: String?,
|
||||||
|
val place: String?,
|
||||||
|
val individualReward: String?,
|
||||||
|
val globalReward: String?,
|
||||||
|
val description: AnnotatedString,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
private fun QuestDetailUio.annotate(): AnnotatedQuestDetailUio {
|
||||||
|
val annotatedSteps = steps.map { it.annotate() }
|
||||||
|
return remember {
|
||||||
|
AnnotatedQuestDetailUio(
|
||||||
|
title = title,
|
||||||
|
steps = annotatedSteps,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Stable
|
||||||
|
private fun QuestDetailUio.QuestStep.annotate(): AnnotatedQuestDetailUio.AnnotatedQuestStep {
|
||||||
|
val typography = MaterialTheme.lexicon.typography
|
||||||
|
|
||||||
|
return remember {
|
||||||
|
AnnotatedQuestDetailUio.AnnotatedQuestStep(
|
||||||
|
completed = completed,
|
||||||
|
subtitle = subtitle,
|
||||||
|
giver = giver,
|
||||||
|
place = place,
|
||||||
|
individualReward = individualReward,
|
||||||
|
globalReward = globalReward,
|
||||||
|
description = AnnotatedString(
|
||||||
|
text = description,
|
||||||
|
spanStyles = listOf(
|
||||||
|
AnnotatedString.Range(
|
||||||
|
item = typography.dropCapLargeSpan,
|
||||||
|
start = 0,
|
||||||
|
end = min(1, description.length),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun QuestDetailScreen(
|
||||||
|
viewModel: QuestDetailViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val screen = LocalScreenNavHost.current
|
||||||
|
|
||||||
|
Surface {
|
||||||
|
QuestDetailContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
item = viewModel.quest,
|
||||||
|
onBack = { screen.popBackStack() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@Composable
|
||||||
|
private fun QuestDetailContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
item: State<QuestDetailUio>,
|
||||||
|
onBack: () -> Unit,
|
||||||
|
) {
|
||||||
|
val annotatedQuest = item.value.annotate()
|
||||||
|
|
||||||
|
Scaffold(
|
||||||
|
modifier = modifier,
|
||||||
|
containerColor = Color.Transparent,
|
||||||
|
topBar = {
|
||||||
|
TopAppBar(
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(
|
||||||
|
painter = painterResource(id = R.drawable.ic_arrow_back_ios_new_24),
|
||||||
|
contentDescription = null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title = {
|
||||||
|
Text(text = stringResource(id = R.string.quest_detail_title))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
content = { padding ->
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(padding),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
top = 40.dp,
|
||||||
|
bottom = 16.dp,
|
||||||
|
start = 16.dp,
|
||||||
|
end = 16.dp
|
||||||
|
),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(40.dp),
|
||||||
|
) {
|
||||||
|
item {
|
||||||
|
Column {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.displaySmall,
|
||||||
|
text = annotatedQuest.title,
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
modifier = Modifier
|
||||||
|
.height(24.dp)
|
||||||
|
.graphicsLayer { rotationZ = 180f }
|
||||||
|
.align(Alignment.CenterHorizontally),
|
||||||
|
painter = painterResource(id = R.drawable.art_divider_1),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
alignment = Alignment.Center,
|
||||||
|
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items(annotatedQuest.steps) { quest ->
|
||||||
|
Column {
|
||||||
|
quest.subtitle?.let { subtitle ->
|
||||||
|
Row(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.Center,
|
||||||
|
) {
|
||||||
|
Image(
|
||||||
|
modifier = Modifier.graphicsLayer { rotationY = 180f },
|
||||||
|
painter = painterResource(id = R.drawable.art_clip_1),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
alignment = Alignment.Center,
|
||||||
|
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(all = 8.dp),
|
||||||
|
textAlign = TextAlign.Center,
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
text = subtitle,
|
||||||
|
)
|
||||||
|
Image(
|
||||||
|
painter = painterResource(id = R.drawable.art_clip_1),
|
||||||
|
contentScale = ContentScale.FillWidth,
|
||||||
|
alignment = Alignment.Center,
|
||||||
|
colorFilter = ColorFilter.tint(color = MaterialTheme.colorScheme.onSurface),
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quest.giver?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = "Commanditaire",
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
quest.place?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = "Lieu",
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
quest.globalReward?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = "Récompense de groupe",
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
quest.individualReward?.let {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 8.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
text = "Récompense individuelle",
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.padding(top = 24.dp),
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
text = quest.description,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun QuestDetailPreview(
|
||||||
|
@PreviewParameter(QuestDetailPreviewProvider::class) preview: State<QuestDetailUio>,
|
||||||
|
) {
|
||||||
|
LexiconTheme {
|
||||||
|
Surface {
|
||||||
|
QuestDetailContent(
|
||||||
|
item = preview,
|
||||||
|
onBack = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class QuestDetailPreviewProvider : PreviewParameterProvider<State<QuestDetailUio>> {
|
||||||
|
override val values: Sequence<State<QuestDetailUio>> = sequenceOf(
|
||||||
|
mutableStateOf(
|
||||||
|
QuestDetailUio(
|
||||||
|
id = 0,
|
||||||
|
title = "La chasse aux loups",
|
||||||
|
steps = listOf(
|
||||||
|
QuestDetailUio.QuestStep(
|
||||||
|
completed = false,
|
||||||
|
subtitle = "Partie 1",
|
||||||
|
giver = "Sergent d'arme",
|
||||||
|
place = "DaggerFord",
|
||||||
|
individualReward = "5po",
|
||||||
|
globalReward = null,
|
||||||
|
description = "Des nobles participant aux festivités de DaggerFord aurait entendu des loups dans la forêt proche. Sur ordre du baron, cette forêt doit être fouillée bien que depuis 300 ans, aucun loup n'y ait été vu.",
|
||||||
|
),
|
||||||
|
QuestDetailUio.QuestStep(
|
||||||
|
completed = false,
|
||||||
|
subtitle = "Partie 2",
|
||||||
|
giver = "Sergent d'arme",
|
||||||
|
place = "DaggerFord",
|
||||||
|
individualReward = "5po. Bonus de 1po par loup.",
|
||||||
|
globalReward = null,
|
||||||
|
description = "Nous devons rapporter la dépouille d'un loup pour prouver au sergent que nous avons tué ces bêtes.",
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
mutableStateOf(
|
||||||
|
QuestDetailUio(
|
||||||
|
id = 1,
|
||||||
|
title = "Les enfants de la caravanes",
|
||||||
|
steps = listOf(
|
||||||
|
QuestDetailUio.QuestStep(
|
||||||
|
completed = false,
|
||||||
|
subtitle = null,
|
||||||
|
giver = null,
|
||||||
|
place = null,
|
||||||
|
individualReward = "Pouvoir se regarder dans une glace",
|
||||||
|
globalReward = null,
|
||||||
|
description = "Une meute de lycan a massacré une caravane marchande quittant DaggerFord. Leur dessin n'est pas encore clair, mais ils ont enlevé les enfants. Nous les pourchassons afin de les sauver.",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.quest.detail
|
||||||
|
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.pixelized.rplexicon.repository.QuestRepository
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.questDetailArgument
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class QuestDetailViewModel @Inject constructor(
|
||||||
|
savedStateHandle: SavedStateHandle,
|
||||||
|
repository: QuestRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
val quest: State<QuestDetailUio>
|
||||||
|
|
||||||
|
init {
|
||||||
|
val argument = savedStateHandle.questDetailArgument
|
||||||
|
val source = repository.data.value[argument.id]
|
||||||
|
|
||||||
|
quest = mutableStateOf(
|
||||||
|
QuestDetailUio(
|
||||||
|
id = source.id,
|
||||||
|
title = source.title,
|
||||||
|
steps = source.entries.map { entry ->
|
||||||
|
QuestDetailUio.QuestStep(
|
||||||
|
completed = entry.complete,
|
||||||
|
subtitle = entry.subtitle,
|
||||||
|
giver = entry.questGiver,
|
||||||
|
place = entry.area,
|
||||||
|
individualReward = entry.individualReward,
|
||||||
|
globalReward = entry.groupReward,
|
||||||
|
description = entry.description,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.quest.list
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.cell
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.placeholder
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
data class QuestItemUio(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val complete: Boolean,
|
||||||
|
val placeholder: Boolean = false,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
fun preview(
|
||||||
|
id: Int = 0,
|
||||||
|
title: String = "La chasse aux loups",
|
||||||
|
complete: Boolean = false,
|
||||||
|
placeHolder: Boolean = false,
|
||||||
|
): QuestItemUio {
|
||||||
|
return QuestItemUio(
|
||||||
|
id = id,
|
||||||
|
title = title,
|
||||||
|
complete = complete,
|
||||||
|
placeholder = placeHolder,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun QuestItem(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
item: QuestItemUio,
|
||||||
|
) {
|
||||||
|
val typography = MaterialTheme.lexicon.typography
|
||||||
|
|
||||||
|
Row(
|
||||||
|
modifier = modifier,
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
modifier = when (item.placeholder) {
|
||||||
|
true -> Modifier.placeholder { true }
|
||||||
|
else -> Modifier.alignByBaseline()
|
||||||
|
},
|
||||||
|
text = if (item.complete) "⧫" else "◊",
|
||||||
|
)
|
||||||
|
Text(
|
||||||
|
modifier = when (item.placeholder) {
|
||||||
|
true -> Modifier.placeholder { true }
|
||||||
|
else -> Modifier.alignByBaseline()
|
||||||
|
},
|
||||||
|
text = remember(item.placeholder) {
|
||||||
|
AnnotatedString(
|
||||||
|
text = item.title,
|
||||||
|
spanStyles = when (item.placeholder) {
|
||||||
|
true -> emptyList()
|
||||||
|
else -> listOf(
|
||||||
|
AnnotatedString.Range(
|
||||||
|
item = typography.dropCapMediumSpan,
|
||||||
|
start = 0,
|
||||||
|
end = 1,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun QuestItemPreview(
|
||||||
|
@PreviewParameter(QuestItemPreviewProvider::class) preview: QuestItemUio,
|
||||||
|
) {
|
||||||
|
LexiconTheme {
|
||||||
|
Surface {
|
||||||
|
QuestItem(
|
||||||
|
modifier = Modifier.cell(),
|
||||||
|
item = preview,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class QuestItemPreviewProvider : PreviewParameterProvider<QuestItemUio> {
|
||||||
|
override val values: Sequence<QuestItemUio> = sequenceOf(
|
||||||
|
QuestItemUio.preview(complete = false, placeHolder = false),
|
||||||
|
QuestItemUio.preview(complete = true, placeHolder = false),
|
||||||
|
QuestItemUio.preview(complete = true, placeHolder = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,172 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.quest.list
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.ExperimentalAnimationApi
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.with
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
|
import androidx.compose.foundation.lazy.items
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.material.ExperimentalMaterialApi
|
||||||
|
import androidx.compose.material.pullrefresh.PullRefreshState
|
||||||
|
import androidx.compose.material.pullrefresh.pullRefresh
|
||||||
|
import androidx.compose.material.pullrefresh.rememberPullRefreshState
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import com.pixelized.rplexicon.ui.composable.Loader
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.LocalScreenNavHost
|
||||||
|
import com.pixelized.rplexicon.ui.navigation.screens.navigateToQuestDetail
|
||||||
|
import com.pixelized.rplexicon.ui.theme.LexiconTheme
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.cell
|
||||||
|
import com.pixelized.rplexicon.utilitary.extentions.lexicon
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
fun QuestListScreen(
|
||||||
|
viewModel: QuestListViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val scope = rememberCoroutineScope()
|
||||||
|
val lazyListState = rememberLazyListState()
|
||||||
|
val screen = LocalScreenNavHost.current
|
||||||
|
|
||||||
|
val refresh = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = {
|
||||||
|
scope.launch {
|
||||||
|
viewModel.fetchQuests()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
Surface {
|
||||||
|
QuestListContent(
|
||||||
|
items = viewModel.items,
|
||||||
|
lazyColumnState = lazyListState,
|
||||||
|
refreshState = refresh,
|
||||||
|
refreshing = viewModel.isLoading,
|
||||||
|
onItem = {
|
||||||
|
screen.navigateToQuestDetail(id = it.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalAnimationApi::class, ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
private fun QuestListContent(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
lazyColumnState: LazyListState,
|
||||||
|
paddingValues: PaddingValues = PaddingValues(
|
||||||
|
top = 6.dp,
|
||||||
|
bottom = 8.dp + 16.dp + 56.dp + 16.dp,
|
||||||
|
),
|
||||||
|
refreshState: PullRefreshState,
|
||||||
|
refreshing: State<Boolean>,
|
||||||
|
items: State<List<QuestItemUio>>,
|
||||||
|
onItem: (QuestItemUio) -> Unit,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier,
|
||||||
|
contentAlignment = Alignment.TopCenter,
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = items.value.isEmpty(),
|
||||||
|
transitionSpec = { fadeIn() with fadeOut() },
|
||||||
|
label = "AnimatedQuests"
|
||||||
|
) { empty ->
|
||||||
|
when (empty) {
|
||||||
|
true -> LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state = refreshState),
|
||||||
|
state = lazyColumnState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
) {
|
||||||
|
items(count = 2) {
|
||||||
|
QuestItem(
|
||||||
|
modifier = Modifier.cell(),
|
||||||
|
item = QuestItemUio.preview(placeHolder = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> LazyColumn(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.pullRefresh(state = refreshState),
|
||||||
|
state = lazyColumnState,
|
||||||
|
contentPadding = paddingValues,
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = items.value,
|
||||||
|
key = { it.id },
|
||||||
|
contentType = { "Lexicon" },
|
||||||
|
) {
|
||||||
|
QuestItem(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable { onItem(it) }
|
||||||
|
.cell(),
|
||||||
|
item = it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Loader(
|
||||||
|
refreshState = refreshState,
|
||||||
|
refreshing = refreshing,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterialApi::class)
|
||||||
|
@Composable
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
private fun QuestListPreview() {
|
||||||
|
LexiconTheme {
|
||||||
|
Surface {
|
||||||
|
QuestListContent(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
lazyColumnState = rememberLazyListState(),
|
||||||
|
refreshState = rememberPullRefreshState(
|
||||||
|
refreshing = false,
|
||||||
|
onRefresh = {},
|
||||||
|
),
|
||||||
|
refreshing = remember { mutableStateOf(false) },
|
||||||
|
items = remember {
|
||||||
|
mutableStateOf(
|
||||||
|
listOf(
|
||||||
|
QuestItemUio.preview(id = 0, title = "La chasse aux loups"),
|
||||||
|
QuestItemUio.preview(id = 1, title = "Les enfants de la caravanes"),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onItem = { },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,75 @@
|
||||||
|
package com.pixelized.rplexicon.ui.screens.quest.list
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.compose.runtime.State
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException
|
||||||
|
import com.pixelized.rplexicon.repository.QuestRepository
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class QuestListViewModel @Inject constructor(
|
||||||
|
private val repository: QuestRepository,
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val _isLoading = mutableStateOf(false)
|
||||||
|
val isLoading: State<Boolean> get() = _isLoading
|
||||||
|
|
||||||
|
private val _items = mutableStateOf<List<QuestItemUio>>(emptyList())
|
||||||
|
val items: State<List<QuestItemUio>> get() = _items
|
||||||
|
|
||||||
|
// private val _error = MutableSharedFlow<LexiconErrorUio>()
|
||||||
|
// val error: SharedFlow<LexiconErrorUio> get() = _error
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launch {
|
||||||
|
launch {
|
||||||
|
repository.data.collect { items ->
|
||||||
|
_items.value = items.map { item ->
|
||||||
|
QuestItemUio(
|
||||||
|
id = item.id,
|
||||||
|
title = item.title,
|
||||||
|
complete = item.entries.all { it.complete },
|
||||||
|
)
|
||||||
|
}.sortedBy { it.title }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
launch {
|
||||||
|
fetchQuests()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun fetchQuests() {
|
||||||
|
try {
|
||||||
|
_isLoading.value = true
|
||||||
|
repository.fetchQuests()
|
||||||
|
}
|
||||||
|
// user need to accept OAuth2 permission.
|
||||||
|
catch (exception: UserRecoverableAuthIOException) {
|
||||||
|
Log.e(TAG, exception.message, exception)
|
||||||
|
// _error.emit(LexiconErrorUio.Permission(intent = exception.intent))
|
||||||
|
} catch (exception: IncompatibleSheetStructure) {
|
||||||
|
Log.e(TAG, exception.message, exception)
|
||||||
|
// _error.emit(LexiconErrorUio.Structure)
|
||||||
|
}
|
||||||
|
// default exception
|
||||||
|
catch (exception: Exception) {
|
||||||
|
Log.e(TAG, exception.message, exception)
|
||||||
|
// _error.emit(LexiconErrorUio.Default)
|
||||||
|
}
|
||||||
|
// clean the laoding state
|
||||||
|
finally {
|
||||||
|
_isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TAG = "QuestListViewModel"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,8 @@ import com.pixelized.rplexicon.ui.theme.dimen.LexiconDimens
|
||||||
import com.pixelized.rplexicon.ui.theme.dimen.lexiconDimen
|
import com.pixelized.rplexicon.ui.theme.dimen.lexiconDimen
|
||||||
import com.pixelized.rplexicon.ui.theme.shape.LexiconShapes
|
import com.pixelized.rplexicon.ui.theme.shape.LexiconShapes
|
||||||
import com.pixelized.rplexicon.ui.theme.shape.lexiconShapes
|
import com.pixelized.rplexicon.ui.theme.shape.lexiconShapes
|
||||||
|
import com.pixelized.rplexicon.ui.theme.typography.LexiconTypography
|
||||||
|
import com.pixelized.rplexicon.ui.theme.typography.lexiconTypography
|
||||||
|
|
||||||
val LocalLexiconTheme = compositionLocalOf<LexiconTheme> {
|
val LocalLexiconTheme = compositionLocalOf<LexiconTheme> {
|
||||||
error("LocalLexiconTheme not ready yet.")
|
error("LocalLexiconTheme not ready yet.")
|
||||||
|
|
@ -29,6 +31,7 @@ data class LexiconTheme(
|
||||||
val colorScheme: LexiconColors,
|
val colorScheme: LexiconColors,
|
||||||
val shapes: LexiconShapes,
|
val shapes: LexiconShapes,
|
||||||
val dimens: LexiconDimens,
|
val dimens: LexiconDimens,
|
||||||
|
val typography: LexiconTypography,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -44,6 +47,7 @@ fun LexiconTheme(
|
||||||
},
|
},
|
||||||
shapes = lexiconShapes(),
|
shapes = lexiconShapes(),
|
||||||
dimens = lexiconDimen(),
|
dimens = lexiconDimen(),
|
||||||
|
typography = lexiconTypography(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,14 +64,16 @@ fun LexiconTheme(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(
|
MaterialTheme(
|
||||||
LocalLexiconTheme provides lexiconTheme,
|
colorScheme = lexiconTheme.colorScheme.base,
|
||||||
) {
|
shapes = lexiconTheme.shapes.base,
|
||||||
MaterialTheme(
|
typography = lexiconTheme.typography.base,
|
||||||
colorScheme = lexiconTheme.colorScheme.base,
|
content = {
|
||||||
shapes = lexiconTheme.shapes.base,
|
CompositionLocalProvider(
|
||||||
typography = Typography,
|
LocalLexiconTheme provides lexiconTheme,
|
||||||
content = content
|
) {
|
||||||
)
|
content()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,34 +0,0 @@
|
||||||
package com.pixelized.rplexicon.ui.theme
|
|
||||||
|
|
||||||
import androidx.compose.material3.Typography
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
|
||||||
import androidx.compose.ui.unit.sp
|
|
||||||
|
|
||||||
// Set of Material typography styles to start with
|
|
||||||
val Typography = Typography(
|
|
||||||
bodyLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 16.sp,
|
|
||||||
lineHeight = 24.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
/* Other default text styles to override
|
|
||||||
titleLarge = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Normal,
|
|
||||||
fontSize = 22.sp,
|
|
||||||
lineHeight = 28.sp,
|
|
||||||
letterSpacing = 0.sp
|
|
||||||
),
|
|
||||||
labelSmall = TextStyle(
|
|
||||||
fontFamily = FontFamily.Default,
|
|
||||||
fontWeight = FontWeight.Medium,
|
|
||||||
fontSize = 11.sp,
|
|
||||||
lineHeight = 16.sp,
|
|
||||||
letterSpacing = 0.5.sp
|
|
||||||
)
|
|
||||||
*/
|
|
||||||
)
|
|
||||||
|
|
@ -12,7 +12,7 @@ data class LexiconDimens(
|
||||||
)
|
)
|
||||||
|
|
||||||
fun lexiconDimen(
|
fun lexiconDimen(
|
||||||
item: Dp = 48.dp
|
item: Dp = 52.dp
|
||||||
) = LexiconDimens(
|
) = LexiconDimens(
|
||||||
item = item,
|
item = item,
|
||||||
)
|
)
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.pixelized.rplexicon.ui.theme.typography
|
||||||
|
|
||||||
|
import androidx.compose.material3.Typography
|
||||||
|
import androidx.compose.runtime.Stable
|
||||||
|
import androidx.compose.ui.text.SpanStyle
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.Font
|
||||||
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.text.style.BaselineShift
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.pixelized.rplexicon.R
|
||||||
|
|
||||||
|
@Stable
|
||||||
|
val regal = FontFamily(
|
||||||
|
Font(resId = R.font.regal, weight = FontWeight.Normal),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("MemberVisibilityCanBePrivate")
|
||||||
|
@Stable
|
||||||
|
class LexiconTypography(
|
||||||
|
val base: Typography = Typography(),
|
||||||
|
val dropCapMedium: TextStyle = base.displaySmall.copy(
|
||||||
|
fontFamily = regal,
|
||||||
|
baselineShift = BaselineShift(-0.3f),
|
||||||
|
letterSpacing = (-6).sp
|
||||||
|
),
|
||||||
|
val dropCapLarge: TextStyle = base.displayMedium.copy(
|
||||||
|
fontFamily = regal,
|
||||||
|
baselineShift = BaselineShift.Subscript,
|
||||||
|
letterSpacing = (-8).sp
|
||||||
|
),
|
||||||
|
) {
|
||||||
|
val dropCapMediumSpan: SpanStyle = dropCapMedium.toSpanStyle()
|
||||||
|
val dropCapLargeSpan: SpanStyle = dropCapLarge.toSpanStyle()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lexiconTypography() = LexiconTypography()
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.pixelized.rplexicon.utilitary.exceptions
|
||||||
|
|
||||||
|
class IncompatibleSheetStructure(message: String?) : Exception(message)
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
package com.pixelized.rplexicon.utilitary.exceptions
|
||||||
|
|
||||||
|
class ServiceNotReady : Exception()
|
||||||
|
|
@ -3,12 +3,17 @@ package com.pixelized.rplexicon.utilitary.extentions
|
||||||
import androidx.compose.animation.core.FiniteAnimationSpec
|
import androidx.compose.animation.core.FiniteAnimationSpec
|
||||||
import androidx.compose.animation.core.Transition
|
import androidx.compose.animation.core.Transition
|
||||||
import androidx.compose.animation.core.spring
|
import androidx.compose.animation.core.spring
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.composed
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.Shape
|
import androidx.compose.ui.graphics.Shape
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import com.google.accompanist.placeholder.PlaceholderHighlight
|
import com.google.accompanist.placeholder.PlaceholderHighlight
|
||||||
import com.google.accompanist.placeholder.placeholder
|
import com.google.accompanist.placeholder.placeholder
|
||||||
|
|
||||||
|
|
@ -20,11 +25,21 @@ fun Modifier.placeholder(
|
||||||
placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
|
placeholderFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
|
||||||
contentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
|
contentFadeTransitionSpec: @Composable Transition.Segment<Boolean>.() -> FiniteAnimationSpec<Float> = { spring() },
|
||||||
visible: () -> Boolean,
|
visible: () -> Boolean,
|
||||||
): Modifier = placeholder(
|
): Modifier = composed {
|
||||||
visible = visible(),
|
placeholder(
|
||||||
color = color,
|
visible = visible(),
|
||||||
shape = shape,
|
color = color,
|
||||||
highlight = highlight,
|
shape = shape,
|
||||||
placeholderFadeTransitionSpec = placeholderFadeTransitionSpec,
|
highlight = highlight,
|
||||||
contentFadeTransitionSpec = contentFadeTransitionSpec,
|
placeholderFadeTransitionSpec = placeholderFadeTransitionSpec,
|
||||||
)
|
contentFadeTransitionSpec = contentFadeTransitionSpec,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun Modifier.cell() = composed {
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = MaterialTheme.lexicon.dimens.item)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 4.dp)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
package com.pixelized.rplexicon.utilitary.extentions
|
||||||
|
|
||||||
|
fun MutableCollection<Any>?.sheet(): List<*>? {
|
||||||
|
return this?.firstOrNull {
|
||||||
|
val sheet = it as? ArrayList<*>
|
||||||
|
sheet != null
|
||||||
|
} as List<*>?
|
||||||
|
}
|
||||||
|
|
@ -20,18 +20,18 @@ fun Regex?.foldAll(
|
||||||
}
|
}
|
||||||
}?.takeIf { it.isNotEmpty() }?.let { "$it..." }
|
}?.takeIf { it.isNotEmpty() }?.let { "$it..." }
|
||||||
|
|
||||||
fun Regex.annotatedSpan(
|
fun Regex?.annotatedSpan(
|
||||||
input: String,
|
input: String,
|
||||||
startIndex: Int = 0,
|
startIndex: Int = 0,
|
||||||
spanStyle: SpanStyle
|
spanStyle: SpanStyle
|
||||||
): List<AnnotatedString.Range<SpanStyle>> {
|
): List<AnnotatedString.Range<SpanStyle>> {
|
||||||
return findAll(input = input, startIndex = startIndex).map {
|
return this?.findAll(input = input, startIndex = startIndex)?.map {
|
||||||
AnnotatedString.Range(
|
AnnotatedString.Range(
|
||||||
item = spanStyle,
|
item = spanStyle,
|
||||||
start = it.range.first,
|
start = it.range.first,
|
||||||
end = it.range.last + 1
|
end = it.range.last + 1
|
||||||
)
|
)
|
||||||
}.toList()
|
}?.toList() ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Regex.annotatedString(
|
fun Regex.annotatedString(
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.pixelized.rplexicon.utilitary.extentions
|
||||||
|
|
||||||
|
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||||
|
|
||||||
|
@Throws(IncompatibleSheetStructure::class)
|
||||||
|
fun Any?.checkSheetStructure(model: List<String>): HashMap<String, Int> {
|
||||||
|
// check if the row is a list
|
||||||
|
if (this !is ArrayList<*>) {
|
||||||
|
throw IncompatibleSheetStructure("First row is not a List: $this")
|
||||||
|
}
|
||||||
|
// parse the first line to find element that we recognize.
|
||||||
|
val sheetStructure = hashMapOf<String, Int>()
|
||||||
|
forEachIndexed { index, cell ->
|
||||||
|
if (cell is String && model.contains(cell)) {
|
||||||
|
sheetStructure[cell] = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// check if we found everything we need.
|
||||||
|
when {
|
||||||
|
sheetStructure.size < model.size -> throw IncompatibleSheetStructure(
|
||||||
|
message = "Sheet header row does not have enough column: $size.\nstructure: $this\nheader: $sheetStructure"
|
||||||
|
)
|
||||||
|
|
||||||
|
sheetStructure.size > model.size -> throw IncompatibleSheetStructure(
|
||||||
|
message = "Sheet header row does have too mush columns: $size.\nstructure: $this\nheader: $sheetStructure"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sheetStructure
|
||||||
|
}
|
||||||
10
app/src/main/res/drawable/art_clip_1.xml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="48dp"
|
||||||
|
android:height="16dp"
|
||||||
|
android:viewportWidth="1280"
|
||||||
|
android:viewportHeight="425">
|
||||||
|
<path
|
||||||
|
android:fillColor="#000000"
|
||||||
|
android:pathData="M961.5,1.9c-9.1,3.3 -16.9,7.2 -25,12.6 -23.2,15.4 -34.6,32.5 -44.4,67 -1.7,6.1 -3.8,12.7 -4.6,14.8 -0.8,2 -1.5,6.6 -1.5,10.1 0,12.6 8.9,24.2 24.7,32.3 9.9,5.1 18.6,7.3 29.4,7.3 11.1,-0 16.6,-1.9 21.7,-7.4 3.4,-3.7 4.1,-5.4 5.2,-11.3 0.9,-5.5 0.9,-8 -0.1,-11.2 -2.7,-9.2 -8.7,-10.1 -28.1,-4.3 -17.4,5.3 -22.7,6 -27.7,3.8 -5,-2.3 -7.1,-6.2 -7.8,-14.4 -1.1,-13.4 3.8,-32.3 12.5,-48.2 5.5,-10 20.2,-25.5 29.4,-30.9 17.4,-10.2 35.8,-13.2 58.2,-9.6 47.7,7.7 88.8,46.1 102.8,96 4.1,14.3 4.7,17.4 5.9,30 2.4,25 -1.3,47.7 -10.9,67 -4.8,9.7 -11,18.7 -29.4,42.9 -40.1,52.4 -60.3,73.6 -96,100.4 -29.1,21.8 -49,32.2 -61.8,32.2 -11.3,-0 -24,-3.4 -73.2,-19.6 -60.6,-19.9 -100.3,-34.7 -154.8,-57.7 -29.3,-12.3 -54.7,-24.4 -108,-51.3 -23.9,-12.1 -52,-25.7 -62.5,-30.3 -58.4,-25.8 -108.4,-38.8 -165.8,-43.2 -20.2,-1.5 -70.9,-0.7 -94.7,1.6 -17.2,1.6 -21.2,2.4 -47.5,8.4 -26.1,5.9 -48.3,7.8 -54.5,4.6 -3.6,-1.8 -3.9,-4.2 -0.7,-5.5 51.5,-21.1 65.9,-26.1 94.1,-32.6 50.5,-11.6 88.6,-16.3 137.3,-17.1 78.9,-1.3 144.1,8 230.3,32.9 33.6,9.7 61.3,20.6 115.7,45.6 23.5,10.8 72.9,31.3 103.1,42.8 12,4.5 39.1,12.5 50.7,14.9 13.3,2.7 30.1,3.8 42.5,2.5 6.3,-0.6 17.6,-1.5 25,-2 22.4,-1.7 35.9,-6.1 49.3,-16.3 11.3,-8.6 18.1,-19.1 17.5,-27 -0.8,-11.1 -14.9,-7.2 -36.8,10.1 -11.8,9.3 -20,13.8 -34.5,18.5l-13,4.2 -20,-0.1c-18.1,-0 -21.1,-0.3 -31.5,-2.7 -27.6,-6.5 -54.9,-16.3 -107.1,-38.5 -16.7,-7.2 -38.5,-16.4 -48.4,-20.5 -9.9,-4.2 -22.9,-9.9 -28.9,-12.7 -40.8,-19.1 -70.8,-29.6 -119.1,-41.5 -23,-5.7 -43.3,-9.6 -70.2,-13.5 -39,-5.7 -56,-7.1 -97.4,-7.7 -76.2,-1.2 -122.4,4.5 -194.4,23.7 -30.9,8.3 -33.1,9.1 -63.5,23.5 -27.6,13.1 -32.2,14.5 -50.2,15.4 -13.6,0.7 -15.1,1 -21,3.9 -11.4,5.6 -16.6,12.1 -25.2,31.4 -4.1,9.2 -10.4,15.9 -17.2,18.2 -5.2,1.7 -13.7,8.4 -27.6,21.3 -12.5,11.6 -12.2,11.1 -9.5,16.4 3,5.7 6.3,6.3 12.1,2.1 12.3,-8.8 40.8,-24.2 44.8,-24.2 3.2,-0 5.9,2 7.4,5.5 1.5,3.6 7.6,8.4 14.7,11.5 7.2,3.2 21.3,5.2 36.2,5.4 13.1,0.1 14.3,-0.1 18.9,-2.4 6.1,-3.1 8.4,-6.1 9.2,-12.1 0.5,-4 0.3,-5 -1.5,-6.8 -2.8,-2.8 -4.6,-2.4 -19.7,3.5 -20,7.9 -28,8.8 -34.1,4.1 -1.4,-1.1 -5.2,-3.3 -8.4,-4.8 -14.2,-6.7 -10.4,-15.5 9.1,-21 4,-1.2 15.7,-6 25.9,-10.9 35.7,-16.8 48.2,-21 56.8,-18.9 11.2,2.7 27,11.6 50.3,28.4 4.7,3.4 14.4,9.5 21.5,13.7 23.9,13.8 101.5,52.4 143.5,71.3 22.8,10.3 48.1,21.8 56.1,25.6 28,13.2 71.3,28.2 129.4,44.8 77.6,22.1 133.6,30.3 195.5,28.7 23.7,-0.6 50.9,-3.1 69.4,-6.2 16.7,-2.8 28.1,-6.1 46.1,-13.2 25.4,-9.9 28,-10.6 39.5,-9.9 5.2,0.3 15.6,2 23,3.6 16.8,3.8 23.5,4.7 42.9,6.1 19.9,1.4 62.4,1.4 77.1,0.1 48.2,-4.5 93.9,-17.2 125.4,-34.8 24.4,-13.6 39.2,-25.4 69.6,-55.3 10.6,-10.4 16.4,-27.4 15.8,-45.8 -0.2,-9.1 -0.7,-11.5 -2.8,-15.6 -3.2,-6.3 -8.2,-9.1 -16.1,-9.1 -13.1,-0 -15.3,5.6 -10.3,26.8 3.6,15.2 3.9,28.4 0.6,34.5 -3.1,5.9 -20.7,23.3 -32.2,31.8 -12.4,9.3 -37,24 -54.5,32.5 -21.1,10.4 -39.2,15.8 -67,20.1 -13.8,2.1 -18.3,2.3 -54.5,2.2 -32.9,-0 -43.4,-0.4 -63,-2.2 -32.4,-3.1 -41.8,-4.3 -40,-5.4 53.4,-31.2 78.9,-50.2 105.1,-77.9 23.9,-25.2 38.3,-43.5 52.9,-67 20.7,-33.3 29,-61.3 27.7,-93.4 -2,-48.8 -25.2,-92.7 -65.7,-124.4 -6.3,-5 -22.7,-13.1 -31.5,-15.6 -21.3,-6.1 -55.1,-8.5 -66,-4.6zM351.5,190.8c32,2 64.4,9.2 115,25.4 37.4,12 60,22.2 110,49.8 43.3,23.9 171.4,80.5 193.8,85.5 4,0.9 20.7,6.2 37.2,11.7 29.9,10 52.5,17 74.8,23 6.4,1.7 11.7,3.5 11.7,3.9 0,0.5 -1.7,1.1 -3.7,1.4 -2.1,0.4 -10.2,2.6 -18,5 -34.9,10.8 -64.3,14.6 -113.4,14.5 -30.8,-0 -43.5,-0.6 -76.9,-4.1 -26.7,-2.7 -42,-5.3 -59,-9.9 -80,-21.6 -116,-32.8 -136,-42.3 -6.3,-3 -15.5,-6.7 -20.5,-8.2 -24.7,-7.5 -94.4,-40.7 -186,-88.4 -24.4,-12.8 -34.9,-18.8 -40.2,-23.2 -4,-3.2 -7.9,-5.9 -8.6,-5.9 -2.5,-0 -40.7,-21.7 -40.7,-23.1 0,-0.4 3,-1.2 6.8,-1.9 3.7,-0.6 14.4,-3.1 23.7,-5.5 20.4,-5.3 33.8,-7.4 54.5,-8.5 18.8,-0.9 53.7,-0.6 75.5,0.8zM143.8,202.7c3.5,0.9 5.9,1.9 5.5,2.3 -0.4,0.4 -6,2.4 -12.3,4.5 -6.2,2 -14.5,5.2 -18.2,7.1 -3.7,1.9 -10,4.7 -14,6.3 -4,1.6 -13.6,5.9 -21.3,9.5 -20,9.4 -21.8,8.9 -5.8,-1.6 11.1,-7.3 14.8,-9.5 32.3,-19.8 6.9,-4.1 13.9,-8.2 15.5,-9.2 3.4,-2 7.6,-1.8 18.3,0.9zM96.5,203c-0.4,0.6 -2.7,2.1 -5.3,3.4 -2.6,1.3 -5.6,3.4 -6.7,4.6 -2.4,2.6 -7.2,5.3 -8.7,4.8 -1.2,-0.4 9.9,-9.1 15.4,-12.1 3.6,-1.9 6.3,-2.3 5.3,-0.7z"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
||||||
10
app/src/main/res/drawable/art_divider_1.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10s10,-4.48 10,-10S17.52,2 12,2zM7.35,18.5C8.66,17.56 10.26,17 12,17s3.34,0.56 4.65,1.5C15.34,19.44 13.74,20 12,20S8.66,19.44 7.35,18.5zM18.14,17.12L18.14,17.12C16.45,15.8 14.32,15 12,15s-4.45,0.8 -6.14,2.12l0,0C4.7,15.73 4,13.95 4,12c0,-4.42 3.58,-8 8,-8s8,3.58 8,8C20,13.95 19.3,15.73 18.14,17.12z"/>
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M12,6c-1.93,0 -3.5,1.57 -3.5,3.5S10.07,13 12,13s3.5,-1.57 3.5,-3.5S13.93,6 12,6zM12,11c-0.83,0 -1.5,-0.67 -1.5,-1.5S11.17,8 12,8s1.5,0.67 1.5,1.5S12.83,11 12,11z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/ic_outline_map_24.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
<vector android:height="24dp" android:tint="#000000"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M20.5,3l-0.16,0.03L15,5.1 9,3 3.36,4.9c-0.21,0.07 -0.36,0.25 -0.36,0.48L3,20.5c0,0.28 0.22,0.5 0.5,0.5l0.16,-0.03L9,18.9l6,2.1 5.64,-1.9c0.21,-0.07 0.36,-0.25 0.36,-0.48L21,3.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM10,5.47l4,1.4v11.66l-4,-1.4L10,5.47zM5,6.46l3,-1.01v11.7l-3,1.16L5,6.46zM19,17.54l-3,1.01L16,6.86l3,-1.16v11.84z"/>
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/ic_twenty_faces_one_48.xml
Normal file
BIN
app/src/main/res/font/regal.ttf
Normal file
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 2.8 KiB After Width: | Height: | Size: 3.2 KiB |
|
Before Width: | Height: | Size: 904 B After Width: | Height: | Size: 1 KiB |
|
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2 KiB |
|
Before Width: | Height: | Size: 2.1 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4.5 KiB |
|
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.8 KiB |
|
Before Width: | Height: | Size: 6.3 KiB After Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 5.6 KiB |
|
Before Width: | Height: | Size: 8.7 KiB After Width: | Height: | Size: 9.8 KiB |
|
|
@ -1,5 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Rp-Lexique</string>
|
<string name="app_name">Rp-Compagnon</string>
|
||||||
|
|
||||||
<string name="error_generic">Ah !? y\'a un truc qui foire quelque part.</string>
|
<string name="error_generic">Ah !? y\'a un truc qui foire quelque part.</string>
|
||||||
<string name="error_structure">La structure du fichier semble avoir changé et n\'est plus compatible avec cette application.</string>
|
<string name="error_structure">La structure du fichier semble avoir changé et n\'est plus compatible avec cette application.</string>
|
||||||
|
|
@ -28,6 +28,9 @@
|
||||||
|
|
||||||
<string name="action_google_sign_in">Se connecter avec</string>
|
<string name="action_google_sign_in">Se connecter avec</string>
|
||||||
|
|
||||||
|
<string name="home_lexicon">Lexique</string>
|
||||||
|
<string name="home_quest_log">Journal de quêtes</string>
|
||||||
|
|
||||||
<string name="lexicon_search">Rechercher</string>
|
<string name="lexicon_search">Rechercher</string>
|
||||||
|
|
||||||
<string name="detail_title">Détails du personnage</string>
|
<string name="detail_title">Détails du personnage</string>
|
||||||
|
|
@ -42,4 +45,6 @@
|
||||||
<string name="search_item_description">Description :</string>
|
<string name="search_item_description">Description :</string>
|
||||||
<string name="search_item_history">Histoire :</string>
|
<string name="search_item_history">Histoire :</string>
|
||||||
<string name="search_item_tags">Mots clés :</string>
|
<string name="search_item_tags">Mots clés :</string>
|
||||||
|
|
||||||
|
<string name="quest_detail_title">Détails de quête</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Rp-Lexicon</string>
|
<string name="app_name">Rp-Companion</string>
|
||||||
|
|
||||||
<string name="error_generic">Oups, it should not be rocket science.</string>
|
<string name="error_generic">Oups, it should not be rocket science.</string>
|
||||||
<string name="error_structure">The file structure appears to have changed and is no longer compatible with this application</string>
|
<string name="error_structure">The file structure appears to have changed and is no longer compatible with this application</string>
|
||||||
|
|
@ -28,6 +28,9 @@
|
||||||
|
|
||||||
<string name="action_google_sign_in">Sign in with</string>
|
<string name="action_google_sign_in">Sign in with</string>
|
||||||
|
|
||||||
|
<string name="home_lexicon">Lexicon</string>
|
||||||
|
<string name="home_quest_log">Quest logs</string>
|
||||||
|
|
||||||
<string name="lexicon_search">Search</string>
|
<string name="lexicon_search">Search</string>
|
||||||
|
|
||||||
<string name="detail_title">Character\'s details</string>
|
<string name="detail_title">Character\'s details</string>
|
||||||
|
|
@ -42,4 +45,6 @@
|
||||||
<string name="search_item_description">Description:</string>
|
<string name="search_item_description">Description:</string>
|
||||||
<string name="search_item_history">History:</string>
|
<string name="search_item_history">History:</string>
|
||||||
<string name="search_item_tags">Tags:</string>
|
<string name="search_item_tags">Tags:</string>
|
||||||
|
|
||||||
|
<string name="quest_detail_title">Quest details</string>
|
||||||
</resources>
|
</resources>
|
||||||