Refactor quest id reference & locatino isNew feature.
This commit is contained in:
parent
9f44ce4543
commit
3904ab22ff
21 changed files with 469 additions and 150 deletions
|
|
@ -2,7 +2,7 @@
|
|||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "c69ae3b018a2383edc96a92797dfdfd3",
|
||||
"identityHash": "5657267ebe96b4b324d58b58aefce6f8",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "lexicon",
|
||||
|
|
@ -104,7 +104,7 @@
|
|||
},
|
||||
{
|
||||
"tableName": "quest",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category` TEXT, `title` TEXT NOT NULL, `subTitle` TEXT, `completed` INTEGER NOT NULL, `questGiver` TEXT, `area` TEXT, `groupReward` TEXT, `individualReward` TEXT, `description` TEXT NOT NULL, `illustrations` TEXT, `background` TEXT, `lastUpdated` INTEGER, `lastRead` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `category` TEXT, `title` TEXT NOT NULL, `subTitle` TEXT, `completed` INTEGER NOT NULL, `questGiverId` TEXT, `questGiverName` TEXT, `locationId` TEXT, `locationName` TEXT, `groupReward` TEXT, `individualReward` TEXT, `description` TEXT NOT NULL, `illustrations` TEXT, `background` TEXT, `lastUpdated` INTEGER, `lastRead` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
|
|
@ -137,14 +137,26 @@
|
|||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "questGiver",
|
||||
"columnName": "questGiver",
|
||||
"fieldPath": "questGiverId",
|
||||
"columnName": "questGiverId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "area",
|
||||
"columnName": "area",
|
||||
"fieldPath": "questGiverName",
|
||||
"columnName": "questGiverName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "locationId",
|
||||
"columnName": "locationId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "locationName",
|
||||
"columnName": "locationName",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
|
|
@ -199,12 +211,119 @@
|
|||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "location",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `category` TEXT, `description` TEXT, `map` TEXT, `illustrations` TEXT, `lastUpdated` INTEGER, `lastRead` INTEGER NOT NULL, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "name",
|
||||
"columnName": "name",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "category",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "map",
|
||||
"columnName": "map",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "illustrations",
|
||||
"columnName": "illustrations",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdated",
|
||||
"columnName": "lastUpdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastRead",
|
||||
"columnName": "lastRead",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"tableName": "world",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`parentId` TEXT NOT NULL, `childId` TEXT NOT NULL, `child` TEXT NOT NULL, `x` REAL, `y` REAL, PRIMARY KEY(`parentId`, `childId`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "parentId",
|
||||
"columnName": "parentId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "childId",
|
||||
"columnName": "childId",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "child",
|
||||
"columnName": "child",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "x",
|
||||
"columnName": "x",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "y",
|
||||
"columnName": "y",
|
||||
"affinity": "REAL",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"parentId",
|
||||
"childId"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
}
|
||||
],
|
||||
"views": [],
|
||||
"setupQueries": [
|
||||
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c69ae3b018a2383edc96a92797dfdfd3')"
|
||||
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5657267ebe96b4b324d58b58aefce6f8')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -155,8 +155,8 @@ class SearchUseCase @Inject constructor(
|
|||
val entry = item.entries.any {
|
||||
val title = it.subtitle?.contains(criteria, true) == true
|
||||
val subTitle = it.subtitle?.contains(criteria, true) == true
|
||||
val questGiver = it.questGiver?.contains(criteria, true) == true
|
||||
val location = it.area?.contains(criteria, true) == true
|
||||
val questGiver = it.questGiverName?.contains(criteria, true) == true
|
||||
val location = it.locationName?.contains(criteria, true) == true
|
||||
val individualReward = it.individualReward?.contains(criteria, true) == true
|
||||
val groupReward = it.groupReward?.contains(criteria, true) == true
|
||||
val description = it.description.contains(criteria, true)
|
||||
|
|
@ -169,8 +169,8 @@ class SearchUseCase @Inject constructor(
|
|||
criterion.map { criteria ->
|
||||
val title = it.subtitle?.contains(criteria, true) == true
|
||||
val subTitle = it.subtitle?.contains(criteria, true) == true
|
||||
val questGiver = it.questGiver?.contains(criteria, true) == true
|
||||
val location = it.area?.contains(criteria, true) == true
|
||||
val questGiver = it.questGiverName?.contains(criteria, true) == true
|
||||
val location = it.locationName?.contains(criteria, true) == true
|
||||
val individualReward = it.individualReward?.contains(criteria, true) == true
|
||||
val groupReward = it.groupReward?.contains(criteria, true) == true
|
||||
val description = it.description.contains(criteria, true)
|
||||
|
|
@ -188,7 +188,7 @@ class SearchUseCase @Inject constructor(
|
|||
highlightRegex styleWith typography.search.titleHighlight,
|
||||
dropCapRegex styleWith typography.titleMediumDropCap,
|
||||
),
|
||||
owner = entry?.questGiver?.let {
|
||||
owner = entry?.questGiverName?.let {
|
||||
AnnotatedString(
|
||||
text = "$ownerPrefix ",
|
||||
spanStyle = typography.search.extractBold,
|
||||
|
|
@ -197,7 +197,7 @@ class SearchUseCase @Inject constructor(
|
|||
highlightRegex styleWith typography.search.extractHighlight,
|
||||
)
|
||||
},
|
||||
location = entry?.area?.let {
|
||||
location = entry?.locationName?.let {
|
||||
AnnotatedString(
|
||||
text = "$locationPrefix ",
|
||||
spanStyle = typography.search.extractBold,
|
||||
|
|
@ -256,7 +256,7 @@ class SearchUseCase @Inject constructor(
|
|||
val category = item.category?.contains(criteria, true) == true
|
||||
val name = item.name.contains(criteria, true)
|
||||
val description = item.description?.contains(criteria, true) == true
|
||||
val child = item.child.any { it.second.name.contains(criteria, true) }
|
||||
val child = item.child.any { it.name.contains(criteria, true) }
|
||||
category || name || description || child
|
||||
}.all { it }
|
||||
}.map { item ->
|
||||
|
|
@ -281,7 +281,7 @@ class SearchUseCase @Inject constructor(
|
|||
)
|
||||
},
|
||||
destination = item.child.mapNotNull { child ->
|
||||
highlightRegex.find(child.second.name)?.let { child.second.name }
|
||||
highlightRegex.find(child.name)?.let { child.name }
|
||||
}.takeIf { it.any() }?.let {
|
||||
AnnotatedString(
|
||||
text = "$destinationPrefix ",
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
package com.pixelized.rplexicon.data.database
|
||||
|
||||
import android.content.Context
|
||||
import androidx.room.AutoMigration
|
||||
import androidx.room.Database
|
||||
import androidx.room.Room
|
||||
import androidx.room.RoomDatabase
|
||||
import com.pixelized.rplexicon.data.database.lexicon.LexiconDao
|
||||
import com.pixelized.rplexicon.data.database.lexicon.LexiconDbo
|
||||
import com.pixelized.rplexicon.data.database.location.LocationDao
|
||||
import com.pixelized.rplexicon.data.database.location.LocationDbo
|
||||
import com.pixelized.rplexicon.data.database.location.WorldDao
|
||||
import com.pixelized.rplexicon.data.database.location.WorldDbo
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestDao
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestDbo
|
||||
import dagger.Module
|
||||
|
|
@ -16,19 +19,20 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Database(
|
||||
entities = [LexiconDbo::class, QuestDbo::class],
|
||||
entities = [LexiconDbo::class, QuestDbo::class, LocationDbo::class, WorldDbo::class],
|
||||
version = 1,
|
||||
exportSchema = true,
|
||||
)
|
||||
abstract class CompanionDatabase : RoomDatabase() {
|
||||
abstract fun lexiconDao(): LexiconDao
|
||||
abstract fun questsDao(): QuestDao
|
||||
abstract fun locationDao(): LocationDao
|
||||
abstract fun worldDao(): WorldDao
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DatabaseModule {
|
||||
|
||||
@Provides
|
||||
fun provideCompanionDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
|
|
|
|||
|
|
@ -16,9 +16,6 @@ interface LexiconDao {
|
|||
@Query("SELECT * from lexicon WHERE id = :id")
|
||||
fun getByIdFlow(id: String): Flow<LexiconDbo>
|
||||
|
||||
@Query("SELECT id from lexicon WHERE name = :name LIMIT 1")
|
||||
suspend fun getIdByName(name: String): String?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insert(item: LexiconDbo)
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.rplexicon.data.database.location
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import androidx.room.Update
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface LocationDao {
|
||||
@Query("SELECT * from location")
|
||||
fun getAllFlow(): Flow<List<LocationDbo>>
|
||||
|
||||
@Query("SELECT * from location WHERE id = :id")
|
||||
fun getByIdFlow(id: String): Flow<LocationDbo>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insert(item: LocationDbo)
|
||||
|
||||
@Update(entity = LocationDbo::class)
|
||||
fun update(item: LocationDataDbo): Int
|
||||
|
||||
@Update(entity = LocationDbo::class)
|
||||
suspend fun update(item: LocationReadTimestampDbo): Int
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
package com.pixelized.rplexicon.data.database.location
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "location")
|
||||
data class LocationDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val description: String?,
|
||||
val map: String?,
|
||||
val illustrations: String?,
|
||||
val lastUpdated: Long?,
|
||||
val lastRead: Long,
|
||||
)
|
||||
|
||||
@Entity(tableName = "location")
|
||||
data class LocationDataDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val description: String?,
|
||||
val map: String?,
|
||||
val illustrations: String?,
|
||||
val lastUpdated: Long?,
|
||||
) {
|
||||
infix fun with(lastRead: Long) = LocationDbo(
|
||||
id = id,
|
||||
name = name,
|
||||
category = category,
|
||||
description = description,
|
||||
map = map,
|
||||
illustrations = illustrations,
|
||||
lastUpdated = lastUpdated,
|
||||
lastRead = lastRead,
|
||||
)
|
||||
}
|
||||
|
||||
@Entity(tableName = "location")
|
||||
data class LocationReadTimestampDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val lastRead: Long,
|
||||
)
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
package com.pixelized.rplexicon.data.database.location
|
||||
|
||||
import androidx.room.Dao
|
||||
import androidx.room.Insert
|
||||
import androidx.room.OnConflictStrategy
|
||||
import androidx.room.Query
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface WorldDao {
|
||||
|
||||
@Query("SELECT * from world")
|
||||
fun getAllFlow(): Flow<List<WorldDbo>>
|
||||
|
||||
@Query("SELECT * from world WHERE parentId = :parentId")
|
||||
fun getByParentIdFlow(parentId: String): Flow<List<WorldDbo>>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
fun insert(items: List<WorldDbo>)
|
||||
|
||||
@Query("DELETE FROM world")
|
||||
fun deleteAll()
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.pixelized.rplexicon.data.database.location
|
||||
|
||||
import androidx.room.Entity
|
||||
|
||||
@Entity(tableName = "world", primaryKeys = ["parentId", "childId"])
|
||||
data class WorldDbo(
|
||||
val parentId: String,
|
||||
val childId: String,
|
||||
val child: String,
|
||||
val x: Float?,
|
||||
val y: Float?,
|
||||
)
|
||||
|
|
@ -11,8 +11,10 @@ data class QuestDbo(
|
|||
val title: String,
|
||||
val subTitle: String?,
|
||||
val completed: Boolean,
|
||||
val questGiver: String?,
|
||||
val area: String?,
|
||||
val questGiverId: String?,
|
||||
val questGiverName: String?,
|
||||
val locationId: String?,
|
||||
val locationName: String?,
|
||||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
|
|
@ -30,8 +32,10 @@ data class QuestDataDbo(
|
|||
val title: String,
|
||||
val subTitle: String?,
|
||||
val completed: Boolean,
|
||||
val questGiver: String?,
|
||||
val area: String?,
|
||||
val questGiverId: String?,
|
||||
val questGiverName: String?,
|
||||
val locationId: String?,
|
||||
val locationName: String?,
|
||||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
|
|
@ -45,8 +49,10 @@ data class QuestDataDbo(
|
|||
title = title,
|
||||
subTitle = subTitle,
|
||||
completed = completed,
|
||||
questGiver = questGiver,
|
||||
area = area,
|
||||
questGiverId = questGiverId,
|
||||
questGiverName = questGiverName,
|
||||
locationId = locationId,
|
||||
locationName = locationName,
|
||||
groupReward = groupReward,
|
||||
individualReward = individualReward,
|
||||
description = description,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,18 @@ data class Location(
|
|||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val uri: Uri?,
|
||||
val description: String?,
|
||||
val map: Uri?,
|
||||
val illustrations: List<Uri>,
|
||||
val child: List<Pair<Offset, Location>> = emptyList(),
|
||||
)
|
||||
val lastUpdated: Long?,
|
||||
val lastRead: Long,
|
||||
val child: List<Child> = emptyList(),
|
||||
) {
|
||||
val isNew: Boolean get() = lastRead - (lastUpdated ?: 0) < 0
|
||||
|
||||
data class Child(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val position: Offset,
|
||||
)
|
||||
}
|
||||
|
|
@ -22,8 +22,10 @@ data class QuestEntry(
|
|||
val title: String,
|
||||
val subtitle: String?,
|
||||
val complete: Boolean,
|
||||
val questGiver: String?,
|
||||
val area: String?,
|
||||
val questGiverId: String?,
|
||||
val questGiverName: String?,
|
||||
val locationId: String?,
|
||||
val locationName: String?,
|
||||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package com.pixelized.rplexicon.data.parser
|
|||
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.BuildConfig
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestDataDbo
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestDbo
|
||||
import com.pixelized.rplexicon.data.model.Quest
|
||||
|
|
@ -32,8 +31,10 @@ class QuestParser @Inject constructor(
|
|||
title = quest,
|
||||
subTitle = item.parse(column = SUB_TITLE),
|
||||
completed = item.parseBool(column = COMPLETED) ?: false,
|
||||
questGiver = item.parse(column = QUEST_GIVER),
|
||||
area = item.parse(column = AREA),
|
||||
questGiverId = item.parse(column = QUEST_GIVER_ID),
|
||||
questGiverName = item.parse(column = QUEST_GIVER_NAME),
|
||||
locationId = item.parse(column = LOCATION_ID),
|
||||
locationName = item.parse(column = LOCATION_NAME),
|
||||
groupReward = item.parse(column = GROUP_REWARD),
|
||||
individualReward = item.parse(column = INDIVIDUAL_REWARD),
|
||||
description = description,
|
||||
|
|
@ -71,8 +72,10 @@ class QuestParser @Inject constructor(
|
|||
title = data.title,
|
||||
subtitle = data.subTitle,
|
||||
complete = data.completed,
|
||||
questGiver = data.questGiver,
|
||||
area = data.area,
|
||||
questGiverId = data.questGiverId,
|
||||
questGiverName = data.questGiverName,
|
||||
locationId = data.locationId,
|
||||
locationName = data.locationName,
|
||||
groupReward = data.groupReward,
|
||||
individualReward = data.individualReward,
|
||||
description = data.description,
|
||||
|
|
@ -88,8 +91,10 @@ class QuestParser @Inject constructor(
|
|||
private val CATEGORY = column("Catégorie")
|
||||
private val SUB_TITLE = column("Sous Titre")
|
||||
private val COMPLETED = column("Compléter")
|
||||
private val QUEST_GIVER = column("Commanditaire")
|
||||
private val AREA = column("Lieu")
|
||||
private val QUEST_GIVER_ID = column("Id Commanditaire")
|
||||
private val QUEST_GIVER_NAME = column("Commanditaire")
|
||||
private val LOCATION_ID = column("Id Lieu")
|
||||
private val LOCATION_NAME = column("Lieu")
|
||||
private val GROUP_REWARD = column("Récompense de groupe")
|
||||
private val INDIVIDUAL_REWARD = column("Récompense individuelle")
|
||||
private val DESCRIPTION = column("Description")
|
||||
|
|
@ -104,8 +109,10 @@ class QuestParser @Inject constructor(
|
|||
CATEGORY,
|
||||
SUB_TITLE,
|
||||
COMPLETED,
|
||||
QUEST_GIVER,
|
||||
AREA,
|
||||
QUEST_GIVER_ID,
|
||||
QUEST_GIVER_NAME,
|
||||
LOCATION_ID,
|
||||
LOCATION_NAME,
|
||||
GROUP_REWARD,
|
||||
INDIVIDUAL_REWARD,
|
||||
DESCRIPTION,
|
||||
|
|
|
|||
|
|
@ -1,40 +1,42 @@
|
|||
package com.pixelized.rplexicon.data.parser.map
|
||||
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import com.pixelized.rplexicon.data.database.location.LocationDbo
|
||||
import com.pixelized.rplexicon.data.database.location.WorldDbo
|
||||
import com.pixelized.rplexicon.data.model.Location
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import com.pixelized.rplexicon.data.parser.IllustrationParser
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class LocationParser @Inject constructor(
|
||||
private val mapParser: MapParser,
|
||||
private val worldParser: WorldParser,
|
||||
private val illustrationParser: IllustrationParser,
|
||||
) {
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(mapSheet: ValueRange, worldSheet: ValueRange): List<Location> {
|
||||
val localMaps = mapParser.parse(sheet = mapSheet)
|
||||
val localWorld = worldParser.parse(sheet = worldSheet)
|
||||
|
||||
val mapHash = localMaps
|
||||
.map { localMap ->
|
||||
Location(
|
||||
id = localMap.name,
|
||||
name = localMap.name,
|
||||
category = localMap.category,
|
||||
uri = localMap.uri,
|
||||
description = localMap.description,
|
||||
illustrations = localMap.illustrations,
|
||||
child = emptyList(),
|
||||
fun convert(location: LocationDbo, world: Map<String, List<WorldDbo>>): Location {
|
||||
val map = Location(
|
||||
id = location.id,
|
||||
name = location.name,
|
||||
category = location.category,
|
||||
description = location.description,
|
||||
map = location.map?.toUriOrNull(),
|
||||
illustrations = illustrationParser.parse(value = location.illustrations),
|
||||
lastUpdated = location.lastUpdated,
|
||||
lastRead = location.lastRead,
|
||||
child = world[location.id]?.map {
|
||||
Location.Child(
|
||||
id = it.childId,
|
||||
name = it.child,
|
||||
position = when {
|
||||
it.x != null && it.y != null -> Offset(it.x, it.y)
|
||||
else -> Offset.Unspecified
|
||||
}
|
||||
)
|
||||
}.associateBy { it.name }
|
||||
|
||||
val maps = mapHash.map { entry ->
|
||||
entry.value.copy(
|
||||
child = localWorld
|
||||
.filter { it.parent == entry.key }
|
||||
.mapNotNull { world -> mapHash[world.child]?.let { map -> world.position to map } }
|
||||
} ?: emptyList()
|
||||
)
|
||||
return map
|
||||
}
|
||||
|
||||
return maps
|
||||
fun convert(locations: List<LocationDbo>, world: List<WorldDbo>): List<Location> {
|
||||
val worldHash = world.groupBy { it.parentId }
|
||||
return locations.map { convert(location = it, world = worldHash) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +1,32 @@
|
|||
package com.pixelized.rplexicon.data.parser.map
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.data.parser.IllustrationParser
|
||||
import com.pixelized.rplexicon.data.database.location.LocationDataDbo
|
||||
import com.pixelized.rplexicon.data.parser.TimeUpdateParser
|
||||
import com.pixelized.rplexicon.data.parser.column
|
||||
import com.pixelized.rplexicon.data.parser.parserScope
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import javax.inject.Inject
|
||||
|
||||
class MapParser @Inject constructor(
|
||||
private val illustrationParser: IllustrationParser,
|
||||
private val timeParser: TimeUpdateParser,
|
||||
) {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(sheet: ValueRange): List<MapDto> = parserScope {
|
||||
val maps = mutableListOf<MapDto>()
|
||||
fun parse(sheet: ValueRange): List<LocationDataDbo> = parserScope(timeParser) {
|
||||
val maps = mutableListOf<LocationDataDbo>()
|
||||
|
||||
sheet.forEachDataLine(columns = COLUMNS) {
|
||||
val id = it.parse(column = ID)
|
||||
val name = it.parse(column = NAME)
|
||||
if (name != null) {
|
||||
val map = MapDto(
|
||||
if (id != null && name != null) {
|
||||
val map = LocationDataDbo(
|
||||
id = id,
|
||||
name = name,
|
||||
category = it.parse(column = CATEGORY),
|
||||
uri = it.parseUri(column = URI),
|
||||
description = it.parse(column = DESCRIPTION),
|
||||
illustrations = illustrationParser.parse(it.parse(column = ILLUSTRATIONS)),
|
||||
map = it.parse(column = MAP),
|
||||
illustrations = it.parse(column = ILLUSTRATIONS),
|
||||
lastUpdated = it.parseTime(column = UPDATE),
|
||||
)
|
||||
maps.add(map)
|
||||
}
|
||||
|
|
@ -33,20 +35,16 @@ class MapParser @Inject constructor(
|
|||
return@parserScope maps
|
||||
}
|
||||
|
||||
data class MapDto(
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val uri: Uri?,
|
||||
val description: String?,
|
||||
val illustrations: List<Uri>,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val ID = column("Id")
|
||||
private val NAME = column("Nom")
|
||||
private val CATEGORY = column("Catégorie")
|
||||
private val URI = column("Carte")
|
||||
private val MAP = column("Carte")
|
||||
private val DESCRIPTION = column("Description")
|
||||
private val ILLUSTRATIONS = column("Illustrations")
|
||||
private val COLUMNS get() = listOf(NAME, CATEGORY, URI, DESCRIPTION, ILLUSTRATIONS)
|
||||
private val UPDATE = column("Mise à jour")
|
||||
|
||||
private val COLUMNS
|
||||
get() = listOf(ID, NAME, CATEGORY, MAP, DESCRIPTION, ILLUSTRATIONS, UPDATE)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package com.pixelized.rplexicon.data.parser.map
|
||||
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import com.google.api.services.sheets.v4.model.ValueRange
|
||||
import com.pixelized.rplexicon.data.database.location.WorldDbo
|
||||
import com.pixelized.rplexicon.data.parser.column
|
||||
import com.pixelized.rplexicon.data.parser.parserScope
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
|
|
@ -10,22 +10,28 @@ import javax.inject.Inject
|
|||
class WorldParser @Inject constructor() {
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(sheet: ValueRange): List<WorldDto> = parserScope {
|
||||
val worlds = mutableListOf<WorldDto>()
|
||||
fun parse(sheet: ValueRange): List<WorldDbo> = parserScope {
|
||||
val worlds = mutableListOf<WorldDbo>()
|
||||
|
||||
sheet.forEachDataLine(columns = COLUMNS) { line ->
|
||||
val parentId = line.parse(column = PARENT_ID)
|
||||
val parent = line.parse(column = PARENT)
|
||||
val childId = line.parse(column = CHILD_ID)
|
||||
val child = line.parse(column = CHILD)
|
||||
val x = line.parseFloat(column = X)
|
||||
val y = line.parseFloat(column = Y)
|
||||
if (child != null) {
|
||||
val world = WorldDto(
|
||||
parent = parent,
|
||||
|
||||
// We check but don't use the parent value, because of the pre validation of the datasheet
|
||||
if (
|
||||
parentId != null && parent != null &&
|
||||
childId != null && child != null
|
||||
) {
|
||||
val world = WorldDbo(
|
||||
parentId = parentId,
|
||||
childId = childId,
|
||||
child = child,
|
||||
position = when {
|
||||
x != null && y != null -> Offset(x = x, y = y)
|
||||
else -> Offset.Unspecified
|
||||
},
|
||||
x = x,
|
||||
y = y,
|
||||
)
|
||||
worlds.add(world)
|
||||
}
|
||||
|
|
@ -34,17 +40,13 @@ class WorldParser @Inject constructor() {
|
|||
return@parserScope worlds
|
||||
}
|
||||
|
||||
data class WorldDto(
|
||||
val parent: String?,
|
||||
val child: String,
|
||||
val position: Offset,
|
||||
)
|
||||
|
||||
companion object {
|
||||
private val PARENT_ID = column("Id Parent")
|
||||
private val PARENT = column("Parent")
|
||||
private val CHILD_ID = column("Id Enfant")
|
||||
private val CHILD = column("Enfant")
|
||||
private val X = column("X")
|
||||
private val Y = column("Y")
|
||||
private val COLUMNS get() = listOf(PARENT, CHILD, X, Y)
|
||||
private val COLUMNS get() = listOf(PARENT_ID, PARENT, CHILD_ID, CHILD, X, Y)
|
||||
}
|
||||
}
|
||||
|
|
@ -54,15 +54,6 @@ class LexiconRepository @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first or null [Lexicon] instance in the list by filtering by name.
|
||||
* @param name the name of the [Lexicon] instance.
|
||||
* @return a nullable [Lexicon] instance.
|
||||
*/
|
||||
suspend fun getByNameFlow(name: String?): String? {
|
||||
return name?.let { database.lexiconDao().getIdByName(name = name) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the [Lexicon] from the backend.
|
||||
* @throws IncompatibleSheetStructure if the data structure change and mandatory data are missing.
|
||||
|
|
|
|||
|
|
@ -1,31 +1,67 @@
|
|||
package com.pixelized.rplexicon.data.repository.lexicon
|
||||
|
||||
import com.pixelized.rplexicon.data.database.CompanionDatabase
|
||||
import com.pixelized.rplexicon.data.database.location.LocationReadTimestampDbo
|
||||
import com.pixelized.rplexicon.data.model.Location
|
||||
import com.pixelized.rplexicon.data.parser.map.LocationParser
|
||||
import com.pixelized.rplexicon.data.parser.map.MapParser
|
||||
import com.pixelized.rplexicon.data.parser.map.WorldParser
|
||||
import com.pixelized.rplexicon.data.repository.GoogleSheetServiceRepository
|
||||
import com.pixelized.rplexicon.data.repository.LexiconBinder
|
||||
import com.pixelized.rplexicon.utilitary.Update
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LocationRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val parser: LocationParser,
|
||||
private val database: CompanionDatabase,
|
||||
private val locationParser: LocationParser,
|
||||
private val mapParser: MapParser,
|
||||
private val worldParser: WorldParser,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||
|
||||
private val _data = MutableStateFlow<List<Location>>(emptyList())
|
||||
val data: StateFlow<List<Location>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun find(id: String?): Location? {
|
||||
return id?.let { _data.value.firstOrNull { it.id == id } }
|
||||
init {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
database.locationDao().getAllFlow()
|
||||
.combine(database.worldDao().getAllFlow()) { location, world -> location to world }
|
||||
.collect { data ->
|
||||
val (locations, world) = data
|
||||
_data.value = locationParser.convert(locations = locations, world = world)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getByIdFlow(id: String?): Flow<Location> = when (id) {
|
||||
null -> emptyFlow()
|
||||
else -> database.locationDao().getByIdFlow(id = id)
|
||||
.combine(database.worldDao().getByParentIdFlow(parentId = id)) { map, link ->
|
||||
map to link.groupBy { it.parentId }
|
||||
}
|
||||
.map { data ->
|
||||
val (locations, world) = data
|
||||
locationParser.convert(location = locations, world = world)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
|
|
@ -35,12 +71,36 @@ class LocationRepository @Inject constructor(
|
|||
async { sheets.get(LexiconBinder.ID, LexiconBinder.MAP).execute() },
|
||||
async { sheets.get(LexiconBinder.ID, LexiconBinder.WORLD).execute() },
|
||||
)
|
||||
val data = parser.parse(mapSheet = map, worldSheet = world)
|
||||
_data.emit(data)
|
||||
database.runInTransaction {
|
||||
val mapDao = database.locationDao()
|
||||
mapParser.parse(map).forEach { item ->
|
||||
val row = mapDao.update(item)
|
||||
if (row == 0) mapDao.insert(item with System.currentTimeMillis())
|
||||
}
|
||||
|
||||
val worldDao = database.worldDao()
|
||||
worldDao.deleteAll()
|
||||
worldDao.insert(items = worldParser.parse(world))
|
||||
}
|
||||
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the [Location#lastTime] field of a [Location] instance.
|
||||
* @param id the id of the [Location] instance.
|
||||
* @param timestamp the timestamp that will update the lastRead filed.
|
||||
*/
|
||||
suspend fun updateReadTime(id: String, timestamp: Long = System.currentTimeMillis()) {
|
||||
database.locationDao().update(
|
||||
item = LocationReadTimestampDbo(
|
||||
id = id,
|
||||
lastRead = timestamp,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val WORLD_SHEET_URL =
|
||||
"https://docs.google.com/spreadsheets/d/${LexiconBinder.ID}/edit#gid=1943877267"
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.LocationRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.lexiconDetailArgument
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.locationDetailArgument
|
||||
|
|
@ -16,6 +17,9 @@ import com.pixelized.rplexicon.utilitary.cells
|
|||
import com.pixelized.rplexicon.utilitary.line
|
||||
import com.pixelized.rplexicon.utilitary.table
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.max
|
||||
|
||||
|
|
@ -28,7 +32,9 @@ class LocationDetailViewModel @Inject constructor(
|
|||
private val clipboard = application.getSystemService(CLIPBOARD_SERVICE) as ClipboardManager
|
||||
|
||||
val sheetUri = LocationRepository.WORLD_SHEET_URL
|
||||
val location: State<LocationDetailUio?>
|
||||
|
||||
private val _location = mutableStateOf<LocationDetailUio?>(null)
|
||||
val location: State<LocationDetailUio?> get() = _location
|
||||
|
||||
private val _selectedMarquee = mutableStateOf<Int?>(null)
|
||||
val selectedMarquee: State<Int?> get() = _selectedMarquee
|
||||
|
|
@ -44,25 +50,35 @@ class LocationDetailViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
val argument = savedStateHandle.locationDetailArgument
|
||||
val source = repository.find(id = argument.id)
|
||||
|
||||
location = mutableStateOf(
|
||||
source?.let {
|
||||
LocationDetailUio(
|
||||
name = it.name,
|
||||
map = it.uri,
|
||||
description = it.description,
|
||||
illustrations = it.illustrations,
|
||||
marquees = it.child.map { child ->
|
||||
viewModelScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
// update the last read time for that lexicon
|
||||
repository.updateReadTime(id = argument.id)
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
// fetch and display the detail data.
|
||||
repository.getByIdFlow(id = argument.id).collect { source ->
|
||||
val location = LocationDetailUio(
|
||||
name = source.name,
|
||||
map = source.map,
|
||||
description = source.description,
|
||||
illustrations = source.illustrations,
|
||||
marquees = source.child.map { child ->
|
||||
MarqueeUio(
|
||||
id = child.second.id,
|
||||
name = child.second.name,
|
||||
position = child.first,
|
||||
id = child.id,
|
||||
name = child.name,
|
||||
position = child.position,
|
||||
)
|
||||
}
|
||||
)
|
||||
// Update the UI state
|
||||
withContext(Dispatchers.Main) {
|
||||
_location.value = location
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
fun onSelectMarquee(marqueeUio: MarqueeUio) {
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ data class LocationItemUio(
|
|||
val id: String,
|
||||
val title: String,
|
||||
val placeholder: Boolean = false,
|
||||
val isNew: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
fun preview(
|
||||
|
|
@ -73,6 +74,7 @@ fun LocationItem(
|
|||
true -> Modifier.placeholder { true }
|
||||
else -> Modifier.alignByBaseline()
|
||||
},
|
||||
color = if (item.isNew) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
|
||||
style = typography.base.titleMedium,
|
||||
text = LOS_FULL,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -55,8 +55,9 @@ class LocationViewModel @Inject constructor(
|
|||
},
|
||||
valueTransform = { entry ->
|
||||
LocationItemUio(
|
||||
id = entry.name,
|
||||
id = entry.id,
|
||||
title = entry.name,
|
||||
isNew = entry.isNew,
|
||||
)
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,10 @@
|
|||
package com.pixelized.rplexicon.ui.screens.quest.detail
|
||||
|
||||
import android.util.Log
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.LexiconRepository
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.LocationRepository
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.QuestRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.lexiconDetailArgument
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.questDetailArgument
|
||||
|
|
@ -21,8 +18,6 @@ import javax.inject.Inject
|
|||
class QuestDetailViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
questRepository: QuestRepository,
|
||||
lexiconRepository: LexiconRepository,
|
||||
locationRepository: LocationRepository,
|
||||
) : ViewModel() {
|
||||
private val _quest = mutableStateOf<QuestDetailUio?>(null)
|
||||
val quest: State<QuestDetailUio?> get() = _quest
|
||||
|
|
@ -46,13 +41,12 @@ class QuestDetailViewModel @Inject constructor(
|
|||
background = source.entries.mapNotNull { it.background }.randomOrNull(),
|
||||
title = source.title,
|
||||
steps = source.entries.map { entry ->
|
||||
val location = locationRepository.find(id = entry.area)
|
||||
QuestDetailUio.QuestStep(
|
||||
subtitle = entry.subtitle,
|
||||
giverId = lexiconRepository.getByNameFlow(name = entry.questGiver),
|
||||
giver = entry.questGiver,
|
||||
placeId = location?.id,
|
||||
place = location?.name ?: entry.area,
|
||||
giverId = entry.questGiverId,
|
||||
giver = entry.questGiverName,
|
||||
placeId = entry.locationId,
|
||||
place = entry.locationName,
|
||||
globalReward = entry.groupReward,
|
||||
individualReward = entry.individualReward,
|
||||
description = entry.description,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue