Add room database for data caching & is new feature.
This commit is contained in:
parent
a2f26cc2a5
commit
79a36fa9d9
23 changed files with 974 additions and 89 deletions
|
|
@ -1,9 +1,14 @@
|
|||
import java.text.SimpleDateFormat
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
||||
plugins {
|
||||
id("com.android.application")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("com.google.dagger.hilt.android")
|
||||
id("com.google.gms.google-services")
|
||||
id("com.google.firebase.crashlytics")
|
||||
id("androidx.room")
|
||||
id("com.google.devtools.ksp")
|
||||
}
|
||||
|
||||
|
|
@ -19,7 +24,8 @@ android {
|
|||
keyPassword = "123456"
|
||||
}
|
||||
create("pixelized") {
|
||||
storeFile = (project.properties["PIXELIZED_RELEASE_STORE_FILE"] as? String)?.let { file(it) }
|
||||
storeFile =
|
||||
(project.properties["PIXELIZED_RELEASE_STORE_FILE"] as? String)?.let { file(it) }
|
||||
storePassword = project.properties["PIXELIZED_RELEASE_STORE_PASSWORD"] as? String
|
||||
keyAlias = project.properties["PIXELIZED_RELEASE_KEY_ALIAS"] as? String
|
||||
keyPassword = project.properties["PIXELIZED_RELEASE_KEY_PASSWORD"] as? String
|
||||
|
|
@ -64,6 +70,10 @@ android {
|
|||
}
|
||||
}
|
||||
|
||||
buildTypes.onEach {
|
||||
it.buildConfigField("String", "DEFAULT_READ_TIME_STAMP", "\"$defaultReadTimestamp\"")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
|
|
@ -141,6 +151,11 @@ dependencies {
|
|||
ksp("com.google.dagger:hilt-android-compiler:2.50")
|
||||
ksp("com.google.dagger:hilt-compiler:2.50")
|
||||
|
||||
// Room
|
||||
implementation("androidx.room:room-runtime:2.6.1")
|
||||
implementation("androidx.room:room-ktx:2.6.1")
|
||||
ksp("androidx.room:room-compiler:2.6.1")
|
||||
|
||||
// Image
|
||||
implementation("io.coil-kt:coil-compose:2.5.0")
|
||||
}
|
||||
|
|
@ -155,6 +170,16 @@ kotlin {
|
|||
jvmToolchain(17)
|
||||
}
|
||||
|
||||
room {
|
||||
schemaDirectory("$projectDir/schemas")
|
||||
}
|
||||
|
||||
val defaultReadTimestamp: String
|
||||
get() {
|
||||
val formatter = SimpleDateFormat("dd/MM/yyyy HH:mm:ss", Locale.FRANCE)
|
||||
return formatter.format(Date()).toString()
|
||||
}
|
||||
|
||||
val gitBuildNumber: Int
|
||||
get() {
|
||||
val stdout = org.apache.commons.io.output.ByteArrayOutputStream()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,210 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 1,
|
||||
"identityHash": "f2d0339fd127a0e9f6e2e816647d9ea9",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "lexicon",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `category` TEXT, `diminutive` TEXT, `gender` TEXT, `race` TEXT, `status` TEXT, `location` TEXT, `portrait` TEXT, `description` TEXT, `history` TEXT, `tags` TEXT, `lastUpdated` INTEGER, `lastRead` INTEGER, 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": "diminutive",
|
||||
"columnName": "diminutive",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gender",
|
||||
"columnName": "gender",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "race",
|
||||
"columnName": "race",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "location",
|
||||
"columnName": "location",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "portrait",
|
||||
"columnName": "portrait",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "history",
|
||||
"columnName": "history",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdated",
|
||||
"columnName": "lastUpdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastRead",
|
||||
"columnName": "lastRead",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"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, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "category",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "subTitle",
|
||||
"columnName": "subTitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "completed",
|
||||
"columnName": "completed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "questGiver",
|
||||
"columnName": "questGiver",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "area",
|
||||
"columnName": "area",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "groupReward",
|
||||
"columnName": "groupReward",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "individualReward",
|
||||
"columnName": "individualReward",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "illustrations",
|
||||
"columnName": "illustrations",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "background",
|
||||
"columnName": "background",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdated",
|
||||
"columnName": "lastUpdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastRead",
|
||||
"columnName": "lastRead",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"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, 'f2d0339fd127a0e9f6e2e816647d9ea9')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,210 @@
|
|||
{
|
||||
"formatVersion": 1,
|
||||
"database": {
|
||||
"version": 2,
|
||||
"identityHash": "f2d0339fd127a0e9f6e2e816647d9ea9",
|
||||
"entities": [
|
||||
{
|
||||
"tableName": "lexicon",
|
||||
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `category` TEXT, `diminutive` TEXT, `gender` TEXT, `race` TEXT, `status` TEXT, `location` TEXT, `portrait` TEXT, `description` TEXT, `history` TEXT, `tags` TEXT, `lastUpdated` INTEGER, `lastRead` INTEGER, 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": "diminutive",
|
||||
"columnName": "diminutive",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "gender",
|
||||
"columnName": "gender",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "race",
|
||||
"columnName": "race",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "status",
|
||||
"columnName": "status",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "location",
|
||||
"columnName": "location",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "portrait",
|
||||
"columnName": "portrait",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "history",
|
||||
"columnName": "history",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "tags",
|
||||
"columnName": "tags",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdated",
|
||||
"columnName": "lastUpdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastRead",
|
||||
"columnName": "lastRead",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"indices": [],
|
||||
"foreignKeys": []
|
||||
},
|
||||
{
|
||||
"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, PRIMARY KEY(`id`))",
|
||||
"fields": [
|
||||
{
|
||||
"fieldPath": "id",
|
||||
"columnName": "id",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "category",
|
||||
"columnName": "category",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "title",
|
||||
"columnName": "title",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "subTitle",
|
||||
"columnName": "subTitle",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "completed",
|
||||
"columnName": "completed",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "questGiver",
|
||||
"columnName": "questGiver",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "area",
|
||||
"columnName": "area",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "groupReward",
|
||||
"columnName": "groupReward",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "individualReward",
|
||||
"columnName": "individualReward",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "description",
|
||||
"columnName": "description",
|
||||
"affinity": "TEXT",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"fieldPath": "illustrations",
|
||||
"columnName": "illustrations",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "background",
|
||||
"columnName": "background",
|
||||
"affinity": "TEXT",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastUpdated",
|
||||
"columnName": "lastUpdated",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
},
|
||||
{
|
||||
"fieldPath": "lastRead",
|
||||
"columnName": "lastRead",
|
||||
"affinity": "INTEGER",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"primaryKey": {
|
||||
"autoGenerate": false,
|
||||
"columnNames": [
|
||||
"id"
|
||||
]
|
||||
},
|
||||
"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, 'f2d0339fd127a0e9f6e2e816647d9ea9')"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.pixelized.rplexicon.data.database
|
||||
|
||||
import android.content.Context
|
||||
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.quest.QuestDao
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestDbo
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Database(
|
||||
entities = [LexiconDbo::class, QuestDbo::class],
|
||||
version = 2,
|
||||
exportSchema = true,
|
||||
)
|
||||
abstract class CompanionDatabase : RoomDatabase() {
|
||||
abstract fun lexiconDao(): LexiconDao
|
||||
abstract fun questsDao(): QuestDao
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class DatabaseModule {
|
||||
|
||||
@Provides
|
||||
fun provideCompanionDatabase(
|
||||
@ApplicationContext context: Context,
|
||||
): CompanionDatabase {
|
||||
return synchronized(this) {
|
||||
Room.databaseBuilder(context, CompanionDatabase::class.java, "companion_database")
|
||||
.fallbackToDestructiveMigration()
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.pixelized.rplexicon.data.database.lexicon
|
||||
|
||||
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 LexiconDao {
|
||||
|
||||
@Query("SELECT * from lexicon")
|
||||
fun getAllFlow(): Flow<List<LexiconDbo>>
|
||||
|
||||
@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(entity = LexiconDbo::class, onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insert(item: LexiconDataDbo)
|
||||
|
||||
@Update(entity = LexiconDbo::class)
|
||||
fun update(item: LexiconDataDbo): Int
|
||||
|
||||
@Update(entity = LexiconDbo::class)
|
||||
suspend fun update(item: LexiconReadTimestampDbo): Int
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.pixelized.rplexicon.data.database.lexicon
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "lexicon")
|
||||
data class LexiconDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val diminutive: String?,
|
||||
val gender: String?,
|
||||
val race: String?,
|
||||
val status: String?,
|
||||
val location: String?,
|
||||
val portrait: String?,
|
||||
val description: String?,
|
||||
val history: String?,
|
||||
val tags: String?,
|
||||
val lastUpdated: Long?,
|
||||
val lastRead: Long?,
|
||||
)
|
||||
|
||||
@Entity(tableName = "lexicon")
|
||||
data class LexiconDataDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val name: String,
|
||||
val category: String?,
|
||||
val diminutive: String?,
|
||||
val gender: String?,
|
||||
val race: String?,
|
||||
val status: String?,
|
||||
val location: String?,
|
||||
val portrait: String?,
|
||||
val description: String?,
|
||||
val history: String?,
|
||||
val tags: String?,
|
||||
val lastUpdated: Long?,
|
||||
)
|
||||
|
||||
@Entity(tableName = "lexicon")
|
||||
data class LexiconReadTimestampDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val lastRead: Long?,
|
||||
)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.pixelized.rplexicon.data.database.quest
|
||||
|
||||
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 QuestDao {
|
||||
@Query("SELECT * from quest")
|
||||
fun getAllFlow(): Flow<List<QuestDbo>>
|
||||
|
||||
@Query("SELECT * from quest WHERE title IN (SELECT title from quest WHERE id = :id)")
|
||||
fun getByIdFlow(id: String): Flow<List<QuestDbo>>
|
||||
|
||||
@Insert(entity = QuestDbo::class, onConflict = OnConflictStrategy.IGNORE)
|
||||
fun insert(item: QuestDataDbo)
|
||||
|
||||
@Update(entity = QuestDbo::class)
|
||||
fun update(item: QuestDataDbo): Int
|
||||
|
||||
@Update(entity = QuestDbo::class)
|
||||
suspend fun update(item: QuestsReadTimestampDbo): Int
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package com.pixelized.rplexicon.data.database.quest
|
||||
|
||||
import androidx.room.Entity
|
||||
import androidx.room.PrimaryKey
|
||||
|
||||
@Entity(tableName = "quest")
|
||||
data class QuestDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val category: String?,
|
||||
val title: String,
|
||||
val subTitle: String?,
|
||||
val completed: Boolean,
|
||||
val questGiver: String?,
|
||||
val area: String?,
|
||||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
val illustrations: String?,
|
||||
val background: String?,
|
||||
val lastUpdated: Long?,
|
||||
val lastRead: Long?,
|
||||
)
|
||||
|
||||
@Entity(tableName = "quest")
|
||||
data class QuestDataDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val category: String?,
|
||||
val title: String,
|
||||
val subTitle: String?,
|
||||
val completed: Boolean,
|
||||
val questGiver: String?,
|
||||
val area: String?,
|
||||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
val illustrations: String?,
|
||||
val background: String?,
|
||||
val lastUpdated: Long?,
|
||||
)
|
||||
|
||||
@Entity(tableName = "quest")
|
||||
data class QuestsReadTimestampDbo(
|
||||
@PrimaryKey
|
||||
val id: String,
|
||||
val lastRead: Long?,
|
||||
)
|
||||
|
||||
|
|
@ -17,4 +17,8 @@ data class Lexicon(
|
|||
val description: String?,
|
||||
val history: String?,
|
||||
val tags: String?,
|
||||
)
|
||||
val lastUpdated: Long?,
|
||||
val lastRead: Long,
|
||||
) {
|
||||
val isNew: Boolean get() = lastRead - (lastUpdated ?: 0) < 0
|
||||
}
|
||||
|
|
@ -8,15 +8,17 @@ data class Quest(
|
|||
val id: String,
|
||||
val category: String?,
|
||||
val title: String,
|
||||
val lastRead: Long,
|
||||
val entries: List<QuestEntry>,
|
||||
) {
|
||||
val complete = entries.all { it.complete }
|
||||
val isNew = lastRead - entries.maxOf { it.lastUpdated ?: 0 } < 0
|
||||
}
|
||||
|
||||
@Stable
|
||||
data class QuestEntry(
|
||||
val sheetIndex: Int,
|
||||
val group: String?,
|
||||
val id: String,
|
||||
val category: String?,
|
||||
val title: String,
|
||||
val subtitle: String?,
|
||||
val complete: Boolean,
|
||||
|
|
@ -25,6 +27,7 @@ data class QuestEntry(
|
|||
val groupReward: String?,
|
||||
val individualReward: String?,
|
||||
val description: String,
|
||||
val images: List<Uri>,
|
||||
val illustrations: List<Uri>,
|
||||
val background: Uri?,
|
||||
val lastUpdated: Long?,
|
||||
)
|
||||
|
|
@ -1,28 +1,31 @@
|
|||
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.lexicon.LexiconDataDbo
|
||||
import com.pixelized.rplexicon.data.database.lexicon.LexiconDbo
|
||||
import com.pixelized.rplexicon.data.model.Lexicon
|
||||
import com.pixelized.rplexicon.utilitary.exceptions.IncompatibleSheetStructure
|
||||
import javax.inject.Inject
|
||||
|
||||
class LexiconParser @Inject constructor(
|
||||
private val illustrationParser: IllustrationParser,
|
||||
private val timeParser: TimeUpdateParser
|
||||
) {
|
||||
@Throws(IncompatibleSheetStructure::class)
|
||||
fun parse(sheet: ValueRange): List<Lexicon> = parserScope {
|
||||
val ids = hashMapOf<String, Int>()
|
||||
val lexicons = mutableListOf<Lexicon>()
|
||||
fun parse(sheet: ValueRange): List<LexiconDataDbo> = parserScope(timeParser) {
|
||||
val lexicons = mutableListOf<LexiconDataDbo>()
|
||||
|
||||
sheet.forEachRowIndexed { index, row ->
|
||||
when (index) {
|
||||
0 -> updateStructure(row = row, columns = COLUMNS)
|
||||
|
||||
else -> {
|
||||
val id = row.parse(column = ID)
|
||||
val name = row.parse(column = NAME)
|
||||
if (name != null) {
|
||||
ids[name] = ids.getOrDefault(name, 0) + 1
|
||||
val lexicon = Lexicon(
|
||||
id = "$name-${ids[name]}",
|
||||
if (id != null && name != null) {
|
||||
val lexicon = LexiconDataDbo(
|
||||
id = id,
|
||||
name = name,
|
||||
category = row.parse(column = CATEGORY),
|
||||
diminutive = row.parse(column = SHORT),
|
||||
|
|
@ -30,10 +33,11 @@ class LexiconParser @Inject constructor(
|
|||
race = row.parse(column = RACE),
|
||||
status = row.parse(column = STATUS),
|
||||
location = row.parse(column = LOCATION),
|
||||
portrait = illustrationParser.parse(row.parse(column = ILLUSTRATIONS)),
|
||||
portrait = row.parse(column = ILLUSTRATIONS),
|
||||
description = row.parse(column = DESCRIPTION),
|
||||
history = row.parse(column = HISTORY),
|
||||
tags = row.parse(column = TAGS),
|
||||
lastUpdated = row.parseTime(column = UPDATE),
|
||||
)
|
||||
lexicons.add(lexicon)
|
||||
}
|
||||
|
|
@ -44,7 +48,31 @@ class LexiconParser @Inject constructor(
|
|||
return@parserScope lexicons
|
||||
}
|
||||
|
||||
fun convert(data: List<LexiconDbo>): List<Lexicon> {
|
||||
return data.map { convert(data = it) }
|
||||
}
|
||||
|
||||
fun convert(data: LexiconDbo): Lexicon {
|
||||
return Lexicon(
|
||||
id = data.id,
|
||||
name = data.name,
|
||||
category = data.category,
|
||||
diminutive = data.diminutive,
|
||||
gender = data.gender,
|
||||
race = data.race,
|
||||
status = data.status,
|
||||
location = data.location,
|
||||
portrait = illustrationParser.parse(value = data.portrait),
|
||||
description = data.description,
|
||||
history = data.history,
|
||||
tags = data.tags,
|
||||
lastUpdated = data.lastUpdated,
|
||||
lastRead = data.lastRead ?: timeParser.parser(BuildConfig.DEFAULT_READ_TIME_STAMP) ?: 0,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ID = column("Id")
|
||||
private val NAME = column("Nom")
|
||||
private val CATEGORY = column("Catégorie")
|
||||
private val SHORT = column("Diminutif")
|
||||
|
|
@ -56,9 +84,11 @@ class LexiconParser @Inject constructor(
|
|||
private val DESCRIPTION = column("Description")
|
||||
private val HISTORY = column("Histoire")
|
||||
private val TAGS = column("Mots clés")
|
||||
private val UPDATE = column("Mise à jour")
|
||||
|
||||
private val COLUMNS
|
||||
get() = listOf(
|
||||
ID,
|
||||
NAME,
|
||||
CATEGORY,
|
||||
SHORT,
|
||||
|
|
@ -70,6 +100,7 @@ class LexiconParser @Inject constructor(
|
|||
DESCRIPTION,
|
||||
HISTORY,
|
||||
TAGS,
|
||||
UPDATE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,57 +2,88 @@ 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
|
||||
import com.pixelized.rplexicon.data.model.QuestEntry
|
||||
import com.pixelized.rplexicon.utilitary.extentions.toUriOrNull
|
||||
import javax.inject.Inject
|
||||
|
||||
class QuestParser @Inject constructor(
|
||||
private val imageParser: IllustrationParser
|
||||
private val illustrationParser: IllustrationParser,
|
||||
private val timeParser: TimeUpdateParser
|
||||
) {
|
||||
fun parse(sheet: ValueRange): List<Quest> = parserScope {
|
||||
val entries = hashMapOf<String, MutableList<QuestEntry>>()
|
||||
fun parse(sheet: ValueRange): List<QuestDataDbo> = parserScope(timeParser) {
|
||||
val quests = mutableListOf<QuestDataDbo>()
|
||||
|
||||
sheet.forEachRowIndexed { index, item ->
|
||||
when (index) {
|
||||
0 -> updateStructure(row = item, columns = COLUMNS)
|
||||
|
||||
else -> {
|
||||
val id = item.parse(column = ID)
|
||||
val quest = item.parse(column = TITLE)
|
||||
val description = item.parse(column = DESCRIPTION)
|
||||
if (quest != null && description != null) {
|
||||
val entry = QuestEntry(
|
||||
sheetIndex = index,
|
||||
if (id != null && quest != null && description != null) {
|
||||
val entry = QuestDataDbo(
|
||||
id = id,
|
||||
category = item.parse(column = CATEGORY),
|
||||
title = quest,
|
||||
group = item.parse(column = CATEGORY),
|
||||
subtitle = item.parse(column = SUB_TITLE),
|
||||
complete = item.parseBool(column = COMPLETED) ?: false,
|
||||
subTitle = item.parse(column = SUB_TITLE),
|
||||
completed = item.parseBool(column = COMPLETED) ?: false,
|
||||
questGiver = item.parse(column = QUEST_GIVER),
|
||||
area = item.parse(column = AREA),
|
||||
groupReward = item.parse(column = GROUP_REWARD),
|
||||
individualReward = item.parse(column = INDIVIDUAL_REWARD),
|
||||
description = description,
|
||||
images = imageParser.parse(item.parse(column = ILLUSTRATIONS)),
|
||||
background = item.parseUri(column = BACKGROUND),
|
||||
illustrations = item.parse(column = ILLUSTRATIONS),
|
||||
background = item.parse(column = BACKGROUND),
|
||||
lastUpdated = item.parseTime(column = UPDATE),
|
||||
)
|
||||
entries.getOrPut(quest) { mutableListOf() }.add(entry)
|
||||
quests.add(entry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val quests = entries.keys.map { quest ->
|
||||
val relatedEntries = entries[quest] ?: emptyList()
|
||||
Quest(
|
||||
id = "$quest-1", // TODO refactor that when quest have ids in the google sheet.
|
||||
title = quest,
|
||||
category = relatedEntries.firstNotNullOfOrNull { it.group },
|
||||
entries = relatedEntries,
|
||||
)
|
||||
}
|
||||
|
||||
return@parserScope quests
|
||||
}
|
||||
|
||||
fun convert(data: List<QuestDbo>): List<Quest> {
|
||||
return data.groupBy { it.title }.mapNotNull { entry ->
|
||||
entry.value.firstOrNull()?.let { main ->
|
||||
Quest(
|
||||
id = main.id,
|
||||
category = main.category,
|
||||
title = main.title,
|
||||
lastRead = main.lastRead ?: timeParser.parser(BuildConfig.DEFAULT_READ_TIME_STAMP) ?: 0,
|
||||
entries = entry.value.map { convert(data = it) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun convert(data: QuestDbo): QuestEntry {
|
||||
return QuestEntry(
|
||||
id = data.id,
|
||||
category = data.category,
|
||||
title = data.title,
|
||||
subtitle = data.subTitle,
|
||||
complete = data.completed,
|
||||
questGiver = data.questGiver,
|
||||
area = data.area,
|
||||
groupReward = data.groupReward,
|
||||
individualReward = data.individualReward,
|
||||
description = data.description,
|
||||
illustrations = illustrationParser.parse(value = data.illustrations),
|
||||
background = data.background?.toUriOrNull(),
|
||||
lastUpdated = data.lastUpdated,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val ID = column("Id")
|
||||
private val TITLE = column("Titre")
|
||||
private val CATEGORY = column("Catégorie")
|
||||
private val SUB_TITLE = column("Sous Titre")
|
||||
|
|
@ -62,11 +93,13 @@ class QuestParser @Inject constructor(
|
|||
private val GROUP_REWARD = column("Récompense de groupe")
|
||||
private val INDIVIDUAL_REWARD = column("Récompense individuelle")
|
||||
private val DESCRIPTION = column("Description")
|
||||
private val ILLUSTRATIONS = column("Image", "Illustrations") // TODO remove Image after 0.9.0 release
|
||||
private val BACKGROUND = column("fond", "Fond") // TODO remove "fond" after 0.7.0 release
|
||||
private val ILLUSTRATIONS = column("Illustrations")
|
||||
private val BACKGROUND = column("Fond")
|
||||
private val UPDATE = column("Mise à jour")
|
||||
|
||||
private val COLUMNS
|
||||
get() = listOf(
|
||||
ID,
|
||||
TITLE,
|
||||
CATEGORY,
|
||||
SUB_TITLE,
|
||||
|
|
@ -78,6 +111,7 @@ class QuestParser @Inject constructor(
|
|||
DESCRIPTION,
|
||||
ILLUSTRATIONS,
|
||||
BACKGROUND,
|
||||
UPDATE,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -7,12 +7,15 @@ import com.pixelized.rplexicon.utilitary.extentions.local.checkSheetStructure
|
|||
import com.pixelized.rplexicon.utilitary.extentions.sheet
|
||||
|
||||
inline fun <reified T> parserScope(
|
||||
timeParser: TimeUpdateParser? = null,
|
||||
noinline block: SheetParserScope<T>.() -> T
|
||||
): T {
|
||||
return SheetParserScope<T>().parse(block)
|
||||
return SheetParserScope<T>(timeParser).parse(block)
|
||||
}
|
||||
|
||||
class SheetParserScope<T> {
|
||||
class SheetParserScope<T>(
|
||||
private val timeParser: TimeUpdateParser?,
|
||||
) {
|
||||
private var structure: Map<Column, Int> = hashMapOf()
|
||||
|
||||
fun updateStructure(row: Any, columns: List<Column>) {
|
||||
|
|
@ -81,6 +84,9 @@ class SheetParserScope<T> {
|
|||
fun List<*>.parseUri(column: Column): Uri? =
|
||||
parse(column)?.takeIf { it.isNotBlank() }?.toUri()
|
||||
|
||||
fun List<*>.parseTime(column: Column): Long? =
|
||||
parse(column = column)?.let { timeParser?.parser(value = it) }
|
||||
|
||||
fun List<*>.parseList(column: Column, separator: String = ","): List<String> =
|
||||
parse(column)
|
||||
?.takeIf { it.isNotBlank() }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package com.pixelized.rplexicon.data.parser
|
||||
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import javax.inject.Inject
|
||||
|
||||
class TimeUpdateParser @Inject constructor() {
|
||||
private val formatter = SimpleDateFormat("dd/MM/yyyy HH:mm:ss", Locale.FRANCE)
|
||||
|
||||
fun parser(value: String?): Long? {
|
||||
return value?.let { formatter.parse(it) }?.time
|
||||
}
|
||||
}
|
||||
|
|
@ -1,43 +1,101 @@
|
|||
package com.pixelized.rplexicon.data.repository.lexicon
|
||||
|
||||
import com.pixelized.rplexicon.data.database.CompanionDatabase
|
||||
import com.pixelized.rplexicon.data.database.lexicon.LexiconReadTimestampDbo
|
||||
import com.pixelized.rplexicon.data.model.Lexicon
|
||||
import com.pixelized.rplexicon.data.parser.LexiconParser
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class LexiconRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val database: CompanionDatabase,
|
||||
private val lexiconParser: LexiconParser,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||
|
||||
private val _data = MutableStateFlow<List<Lexicon>>(emptyList())
|
||||
val data: StateFlow<List<Lexicon>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun find(id: String?): Lexicon? {
|
||||
return id?.let { _data.value.firstOrNull { item -> item.id == it } }
|
||||
init {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
database.lexiconDao().getAllFlow().collect { data ->
|
||||
_data.value = lexiconParser.convert(data = data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun findId(name: String?): String? {
|
||||
return name?.let { _data.value.firstOrNull { item -> item.name == it }?.id }
|
||||
/**
|
||||
* Get a [Flow] of a nullable [Lexicon] instance in the list by filtering by id.
|
||||
* @param id the id of the [Lexicon] instance.
|
||||
* @return a [Flow] of a nullable [Lexicon] instance.
|
||||
*/
|
||||
fun getByIdFlow(id: String?): Flow<Lexicon> = when (id) {
|
||||
null -> emptyFlow()
|
||||
else -> database.lexiconDao().getByIdFlow(id = id).map {
|
||||
lexiconParser.convert(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @throws Exception if other kind of exception happen, network for example.
|
||||
*/
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchLexicon() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(LexiconBinder.ID, LexiconBinder.LEXICON)
|
||||
val data = lexiconParser.parse(sheet = request.execute())
|
||||
_data.tryEmit(data)
|
||||
|
||||
database.lexiconDao().also { dao ->
|
||||
data.forEach {
|
||||
val row = dao.update(item = it)
|
||||
if (row == 0) dao.insert(item = it)
|
||||
}
|
||||
}
|
||||
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the [Lexicon#lastTime] field of a [Lexicon] instance.
|
||||
* @param id the id of the [Lexicon] instance.
|
||||
* @param timestamp the timestamp that will update the lastRead filed.
|
||||
*/
|
||||
suspend fun updateReadTime(id: String, timestamp: Long = System.currentTimeMillis()) {
|
||||
database.lexiconDao().update(
|
||||
item = LexiconReadTimestampDbo(
|
||||
id = id,
|
||||
lastRead = timestamp,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +1,91 @@
|
|||
package com.pixelized.rplexicon.data.repository.lexicon
|
||||
|
||||
import com.pixelized.rplexicon.data.database.CompanionDatabase
|
||||
import com.pixelized.rplexicon.data.database.quest.QuestsReadTimestampDbo
|
||||
import com.pixelized.rplexicon.data.model.Quest
|
||||
import com.pixelized.rplexicon.data.parser.QuestParser
|
||||
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.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.emptyFlow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
class QuestRepository @Inject constructor(
|
||||
private val googleRepository: GoogleSheetServiceRepository,
|
||||
private val database: CompanionDatabase,
|
||||
private val questParser: QuestParser,
|
||||
) {
|
||||
private val scope = CoroutineScope(Dispatchers.Default + Job())
|
||||
|
||||
private val _data = MutableStateFlow<List<Quest>>(emptyList())
|
||||
val data: StateFlow<List<Quest>> get() = _data
|
||||
|
||||
var lastSuccessFullUpdate: Update = Update.INITIAL
|
||||
private set
|
||||
|
||||
fun find(id: String?): Quest? {
|
||||
return id?.let { data.value.firstOrNull { it.id == id } }
|
||||
init {
|
||||
scope.launch(Dispatchers.IO) {
|
||||
database.questsDao().getAllFlow().collect { data ->
|
||||
_data.value = questParser.convert(data = data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the first or null [Quest] instance in the database by filtering by id.
|
||||
* @param id the id of the [Quest] instance.
|
||||
* @return a nullable [Quest] instance.
|
||||
*/
|
||||
fun getByIdFlow(id: String?): Flow<Quest> = when (id) {
|
||||
null -> emptyFlow()
|
||||
else -> database.questsDao().getByIdFlow(id = id).mapNotNull {
|
||||
questParser.convert(it).firstOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the [Quest] from the backend.
|
||||
* @throws IncompatibleSheetStructure if the data structure change and mandatory data are missing.
|
||||
* @throws Exception if other kind of exception happen, network for example.
|
||||
*/
|
||||
@Throws(IncompatibleSheetStructure::class, Exception::class)
|
||||
suspend fun fetchQuests() {
|
||||
googleRepository.fetch { sheet ->
|
||||
val request = sheet.get(LexiconBinder.ID, LexiconBinder.QUEST_JOURNAL)
|
||||
val quests = questParser.parse(sheet = request.execute())
|
||||
_data.emit(quests)
|
||||
|
||||
val dao = database.questsDao()
|
||||
quests.forEach {
|
||||
val row = dao.update(item = it)
|
||||
if (row == 0) dao.insert(item = it)
|
||||
}
|
||||
|
||||
lastSuccessFullUpdate = Update.currentTime()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the [QuestBbo#lastTime] field of a [Quest] instance.
|
||||
* @param id the id of the [Quest] instance.
|
||||
* @param timestamp the timestamp that will update the lastRead filed.
|
||||
*/
|
||||
suspend fun updateReadTime(id: String, timestamp: Long = System.currentTimeMillis()) {
|
||||
database.questsDao().update(
|
||||
item = QuestsReadTimestampDbo(
|
||||
id = id,
|
||||
lastRead = timestamp,
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -4,10 +4,14 @@ 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.character.CharacterSheetRepository
|
||||
import com.pixelized.rplexicon.data.repository.lexicon.LexiconRepository
|
||||
import com.pixelized.rplexicon.ui.navigation.screens.lexiconDetailArgument
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -16,7 +20,9 @@ class LexiconDetailViewModel @Inject constructor(
|
|||
lexiconRepository: LexiconRepository,
|
||||
characterSheetRepository: CharacterSheetRepository,
|
||||
) : ViewModel() {
|
||||
val haveCharacterSheet: State<Boolean>
|
||||
|
||||
private val _haveCharacterSheet = mutableStateOf(false)
|
||||
val haveCharacterSheet: State<Boolean> get() = _haveCharacterSheet
|
||||
|
||||
private val _character = mutableStateOf<LexiconDetailUio?>(null)
|
||||
val character: State<LexiconDetailUio?> get() = _character
|
||||
|
|
@ -25,9 +31,16 @@ class LexiconDetailViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
val argument = savedStateHandle.lexiconDetailArgument
|
||||
val source = lexiconRepository.find(id = argument.id)
|
||||
if (source != null) {
|
||||
_character.value = LexiconDetailUio(
|
||||
|
||||
viewModelScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
// update the last read time for that lexicon
|
||||
lexiconRepository.updateReadTime(id = argument.id)
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
lexiconRepository.getByIdFlow(id = argument.id).collect { source ->
|
||||
// build the UI object.
|
||||
val character = LexiconDetailUio(
|
||||
name = source.name,
|
||||
diminutive = source.diminutive?.let { "./ $it" },
|
||||
gender = source.gender,
|
||||
|
|
@ -39,10 +52,17 @@ class LexiconDetailViewModel @Inject constructor(
|
|||
history = source.history,
|
||||
tags = source.tags,
|
||||
)
|
||||
}
|
||||
|
||||
haveCharacterSheet = mutableStateOf(
|
||||
characterSheetRepository.find(name = source?.name) != null
|
||||
)
|
||||
// Check if we have a character sheet for that character.
|
||||
val haveCharacterSheet = characterSheetRepository.find(
|
||||
name = source.name,
|
||||
) != null
|
||||
// Update the UI state
|
||||
withContext(Dispatchers.Main) {
|
||||
_character.value = character
|
||||
_haveCharacterSheet.value = haveCharacterSheet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ data class LexiconItemUio(
|
|||
val race: String?,
|
||||
val isPlayingCharacter: Boolean = false,
|
||||
val placeholder: Boolean = false,
|
||||
val isNew: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
fun placeholder() = LexiconItemUio(
|
||||
|
|
@ -93,6 +94,7 @@ fun LexiconItem(
|
|||
.padding(end = 4.dp)
|
||||
.alignByBaseline(),
|
||||
style = typography.base.titleMedium,
|
||||
color = if (item.isNew) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
|
||||
text = if (item.isPlayingCharacter) LOS_FULL else LOS_HOLLOW,
|
||||
)
|
||||
|
||||
|
|
@ -206,6 +208,7 @@ private class LexiconItemPreviewProvider : PreviewParameterProvider<LexiconItemU
|
|||
diminutive = "Bru",
|
||||
gender = "Femme",
|
||||
race = "Demi-orc",
|
||||
isNew = true,
|
||||
),
|
||||
LexiconItemUio.placeholder(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -62,7 +62,8 @@ class LexiconViewModel @Inject constructor(
|
|||
diminutive = item.diminutive?.let { "./ $it" },
|
||||
gender = item.gender,
|
||||
race = item.race,
|
||||
isPlayingCharacter = characterSheetRepository.haveSheet(item.name)
|
||||
isPlayingCharacter = characterSheetRepository.haveSheet(item.name),
|
||||
isNew = item.isNew,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,15 +1,20 @@
|
|||
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
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
|
|
@ -26,10 +31,16 @@ class QuestDetailViewModel @Inject constructor(
|
|||
|
||||
init {
|
||||
val argument = savedStateHandle.questDetailArgument
|
||||
val source = questRepository.find(id = argument.id)
|
||||
|
||||
if (source != null) {
|
||||
_quest.value = QuestDetailUio(
|
||||
viewModelScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
// update the last read time for that lexicon
|
||||
questRepository.updateReadTime(id = argument.id)
|
||||
}
|
||||
launch(Dispatchers.IO) {
|
||||
// fetch and display the detail data.
|
||||
questRepository.getByIdFlow(id = argument.id).collect { source ->
|
||||
val quest = QuestDetailUio(
|
||||
id = source.id,
|
||||
completed = source.entries.all { it.complete },
|
||||
background = source.entries.mapNotNull { it.background }.randomOrNull(),
|
||||
|
|
@ -38,17 +49,23 @@ class QuestDetailViewModel @Inject constructor(
|
|||
val location = locationRepository.find(id = entry.area)
|
||||
QuestDetailUio.QuestStep(
|
||||
subtitle = entry.subtitle,
|
||||
giverId = lexiconRepository.findId(entry.questGiver),
|
||||
giverId = lexiconRepository.getByNameFlow(name = entry.questGiver),
|
||||
giver = entry.questGiver,
|
||||
placeId = location?.id,
|
||||
place = location?.name ?: entry.area,
|
||||
globalReward = entry.groupReward,
|
||||
individualReward = entry.individualReward,
|
||||
description = entry.description,
|
||||
images = entry.images,
|
||||
images = entry.illustrations,
|
||||
)
|
||||
},
|
||||
)
|
||||
// Update the UI state
|
||||
withContext(Dispatchers.Main) {
|
||||
_quest.value = quest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -40,6 +40,7 @@ data class QuestItemUio(
|
|||
val title: String,
|
||||
val complete: Boolean,
|
||||
val placeholder: Boolean = false,
|
||||
val isNew: Boolean = false,
|
||||
) {
|
||||
companion object {
|
||||
fun preview(
|
||||
|
|
@ -77,6 +78,7 @@ fun QuestItem(
|
|||
Text(
|
||||
modifier = alignModifier,
|
||||
style = typography.base.titleMedium,
|
||||
color = if (item.isNew) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurface,
|
||||
text = if (item.complete) LOS_FULL else LOS_HOLLOW,
|
||||
)
|
||||
Text(
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ class QuestListViewModel @Inject constructor(
|
|||
id = item.id,
|
||||
title = item.title,
|
||||
complete = item.complete,
|
||||
isNew = item.isNew,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@ plugins {
|
|||
id("com.google.gms.google-services") version "4.3.14" apply false
|
||||
id("com.google.dagger.hilt.android") version "2.50" apply false
|
||||
id("com.google.firebase.crashlytics") version "2.9.7" apply false
|
||||
id("org.jetbrains.kotlin.kapt") version "1.9.10" apply false
|
||||
id("androidx.room") version "2.6.0" apply false
|
||||
id("com.google.devtools.ksp") version "1.9.21-1.0.16" apply false
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue