From e00489b5365267ceb861c56767f700381e7e1087 Mon Sep 17 00:00:00 2001 From: 283375 Date: Mon, 2 Nov 2026 02:26:39 +0800 Subject: [PATCH 1/2] add missing fields --- .../17.json | 711 ++++++++++++++++++ .../core/database/ArcaeaOfflineDatabase.kt | 3 +- .../core/database/entities/Pack.kt | 5 + .../externals/ArcaeaListDefinitions.kt | 4 + .../importers/ArcaeaPacklistImporter.kt | 4 + 5 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 shared/schemas/xyz.sevive.arcaeaoffline.core.database.ArcaeaOfflineDatabase/17.json diff --git a/shared/schemas/xyz.sevive.arcaeaoffline.core.database.ArcaeaOfflineDatabase/17.json b/shared/schemas/xyz.sevive.arcaeaoffline.core.database.ArcaeaOfflineDatabase/17.json new file mode 100644 index 00000000..1ab8b681 --- /dev/null +++ b/shared/schemas/xyz.sevive.arcaeaoffline.core.database.ArcaeaOfflineDatabase/17.json @@ -0,0 +1,711 @@ +{ + "formatVersion": 1, + "database": { + "version": 17, + "identityHash": "e0e51bdd3d1f6e08654a63bdeffebb9b", + "entities": [ + { + "tableName": "properties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`key`))", + "fields": [ + { + "fieldPath": "key", + "columnName": "key", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "key" + ] + } + }, + { + "tableName": "packs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `name` TEXT NOT NULL, `description` TEXT, `parent_pack_id` TEXT, `section` TEXT, `plus_character_id` INTEGER, `is_extend_pack` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "parentPackId", + "columnName": "parent_pack_id", + "affinity": "TEXT" + }, + { + "fieldPath": "section", + "columnName": "section", + "affinity": "TEXT" + }, + { + "fieldPath": "plusCharacterId", + "columnName": "plus_character_id", + "affinity": "INTEGER" + }, + { + "fieldPath": "isExtendPack", + "columnName": "is_extend_pack", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "packs_localized", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lang` TEXT NOT NULL, `name` TEXT, `description` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `packs`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lang", + "columnName": "lang", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT" + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_packs_localized_id_lang", + "unique": true, + "columnNames": [ + "id", + "lang" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_packs_localized_id_lang` ON `${TABLE_NAME}` (`id`, `lang`)" + }, + { + "name": "index_packs_localized_lang", + "unique": false, + "columnNames": [ + "lang" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_packs_localized_lang` ON `${TABLE_NAME}` (`lang`)" + } + ], + "foreignKeys": [ + { + "table": "packs", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "songs", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`idx` INTEGER NOT NULL, `id` TEXT NOT NULL, `title` TEXT NOT NULL, `artist` TEXT NOT NULL, `set` TEXT NOT NULL, `bpm` TEXT, `bpm_base` REAL, `audio_preview` INTEGER, `audio_preview_end` INTEGER, `side` INTEGER NOT NULL, `version` TEXT, `date` INTEGER, `bg` TEXT, `bg_inverse` TEXT, `bg_day` TEXT, `bg_night` TEXT, `source` TEXT, `source_copyright` TEXT, `deleted_in_game` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "idx", + "columnName": "idx", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "set", + "columnName": "set", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "bpm", + "columnName": "bpm", + "affinity": "TEXT" + }, + { + "fieldPath": "bpmBase", + "columnName": "bpm_base", + "affinity": "REAL" + }, + { + "fieldPath": "audioPreview", + "columnName": "audio_preview", + "affinity": "INTEGER" + }, + { + "fieldPath": "audioPreviewEnd", + "columnName": "audio_preview_end", + "affinity": "INTEGER" + }, + { + "fieldPath": "side", + "columnName": "side", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER" + }, + { + "fieldPath": "bg", + "columnName": "bg", + "affinity": "TEXT" + }, + { + "fieldPath": "bgInverse", + "columnName": "bg_inverse", + "affinity": "TEXT" + }, + { + "fieldPath": "bgDay", + "columnName": "bg_day", + "affinity": "TEXT" + }, + { + "fieldPath": "bgNight", + "columnName": "bg_night", + "affinity": "TEXT" + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT" + }, + { + "fieldPath": "sourceCopyright", + "columnName": "source_copyright", + "affinity": "TEXT" + }, + { + "fieldPath": "deletedInGame", + "columnName": "deleted_in_game", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "songs_localized", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `lang` TEXT NOT NULL, `title` TEXT, `source` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `songs`(`id`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lang", + "columnName": "lang", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT" + }, + { + "fieldPath": "source", + "columnName": "source", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_songs_localized_id_lang", + "unique": true, + "columnNames": [ + "id", + "lang" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_songs_localized_id_lang` ON `${TABLE_NAME}` (`id`, `lang`)" + }, + { + "name": "index_songs_localized_lang", + "unique": false, + "columnNames": [ + "lang" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_songs_localized_lang` ON `${TABLE_NAME}` (`lang`)" + } + ], + "foreignKeys": [ + { + "table": "songs", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "difficulties", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`song_id` TEXT NOT NULL, `rating_class` INTEGER NOT NULL, `rating_class_alias` INTEGER, `rating` INTEGER NOT NULL, `rating_plus` INTEGER NOT NULL, `chart_designer` TEXT, `jacket_designer` TEXT, `audio_override` INTEGER NOT NULL, `jacket_override` INTEGER NOT NULL, `jacket_night` TEXT, `title` TEXT, `artist` TEXT, `bg` TEXT, `bg_inverse` TEXT, `bpm` TEXT, `bpm_base` REAL, `version` TEXT, `date` INTEGER, PRIMARY KEY(`song_id`, `rating_class`))", + "fields": [ + { + "fieldPath": "songId", + "columnName": "song_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratingClass", + "columnName": "rating_class", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ratingClassAlias", + "columnName": "rating_class_alias", + "affinity": "INTEGER" + }, + { + "fieldPath": "rating", + "columnName": "rating", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "ratingPlus", + "columnName": "rating_plus", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "chartDesigner", + "columnName": "chart_designer", + "affinity": "TEXT" + }, + { + "fieldPath": "jacketDesigner", + "columnName": "jacket_designer", + "affinity": "TEXT" + }, + { + "fieldPath": "audioOverride", + "columnName": "audio_override", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "jacketOverride", + "columnName": "jacket_override", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "jacketNight", + "columnName": "jacket_night", + "affinity": "TEXT" + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT" + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT" + }, + { + "fieldPath": "bg", + "columnName": "bg", + "affinity": "TEXT" + }, + { + "fieldPath": "bgInverse", + "columnName": "bg_inverse", + "affinity": "TEXT" + }, + { + "fieldPath": "bpm", + "columnName": "bpm", + "affinity": "TEXT" + }, + { + "fieldPath": "bpmBase", + "columnName": "bpm_base", + "affinity": "REAL" + }, + { + "fieldPath": "version", + "columnName": "version", + "affinity": "TEXT" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "song_id", + "rating_class" + ] + } + }, + { + "tableName": "difficulties_localized", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`song_id` TEXT NOT NULL, `rating_class` INTEGER NOT NULL, `lang` TEXT NOT NULL, `title` TEXT, `artist` TEXT, PRIMARY KEY(`song_id`, `rating_class`), FOREIGN KEY(`song_id`, `rating_class`) REFERENCES `difficulties`(`song_id`, `rating_class`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "songId", + "columnName": "song_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratingClass", + "columnName": "rating_class", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lang", + "columnName": "lang", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT" + }, + { + "fieldPath": "artist", + "columnName": "artist", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "song_id", + "rating_class" + ] + }, + "indices": [ + { + "name": "index_difficulties_localized_song_id_rating_class_lang", + "unique": true, + "columnNames": [ + "song_id", + "rating_class", + "lang" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_difficulties_localized_song_id_rating_class_lang` ON `${TABLE_NAME}` (`song_id`, `rating_class`, `lang`)" + }, + { + "name": "index_difficulties_localized_lang", + "unique": false, + "columnNames": [ + "lang" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_difficulties_localized_lang` ON `${TABLE_NAME}` (`lang`)" + } + ], + "foreignKeys": [ + { + "table": "difficulties", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "song_id", + "rating_class" + ], + "referencedColumns": [ + "song_id", + "rating_class" + ] + } + ] + }, + { + "tableName": "charts_info", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`song_id` TEXT NOT NULL, `rating_class` INTEGER NOT NULL, `constant` INTEGER NOT NULL, `notes` INTEGER, PRIMARY KEY(`song_id`, `rating_class`))", + "fields": [ + { + "fieldPath": "songId", + "columnName": "song_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratingClass", + "columnName": "rating_class", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "constant", + "columnName": "constant", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "song_id", + "rating_class" + ] + } + }, + { + "tableName": "play_results", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` BLOB NOT NULL, `song_id` TEXT NOT NULL, `rating_class` INTEGER NOT NULL, `score` INTEGER NOT NULL, `pure` INTEGER, `far` INTEGER, `lost` INTEGER, `date` INTEGER, `max_recall` INTEGER, `modifier` INTEGER, `clear_type` INTEGER, `comment` TEXT)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "BLOB", + "notNull": true + }, + { + "fieldPath": "songId", + "columnName": "song_id", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "ratingClass", + "columnName": "rating_class", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "score", + "columnName": "score", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "pure", + "columnName": "pure", + "affinity": "INTEGER" + }, + { + "fieldPath": "far", + "columnName": "far", + "affinity": "INTEGER" + }, + { + "fieldPath": "lost", + "columnName": "lost", + "affinity": "INTEGER" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER" + }, + { + "fieldPath": "maxRecall", + "columnName": "max_recall", + "affinity": "INTEGER" + }, + { + "fieldPath": "modifier", + "columnName": "modifier", + "affinity": "INTEGER" + }, + { + "fieldPath": "clearType", + "columnName": "clear_type", + "affinity": "INTEGER" + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_play_results_uuid", + "unique": true, + "columnNames": [ + "uuid" + ], + "orders": [], + "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_play_results_uuid` ON `${TABLE_NAME}` (`uuid`)" + }, + { + "name": "index_play_results_song_id_rating_class", + "unique": false, + "columnNames": [ + "song_id", + "rating_class" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_play_results_song_id_rating_class` ON `${TABLE_NAME}` (`song_id`, `rating_class`)" + } + ] + }, + { + "tableName": "r30_entries", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `uuid` BLOB NOT NULL, FOREIGN KEY(`uuid`) REFERENCES `play_results`(`uuid`) ON UPDATE CASCADE ON DELETE CASCADE DEFERRABLE INITIALLY DEFERRED)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "uuid", + "columnName": "uuid", + "affinity": "BLOB", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [ + { + "name": "index_r30_entries_uuid", + "unique": false, + "columnNames": [ + "uuid" + ], + "orders": [], + "createSql": "CREATE INDEX IF NOT EXISTS `index_r30_entries_uuid` ON `${TABLE_NAME}` (`uuid`)" + } + ], + "foreignKeys": [ + { + "table": "play_results", + "onDelete": "CASCADE", + "onUpdate": "CASCADE", + "columns": [ + "uuid" + ], + "referencedColumns": [ + "uuid" + ] + } + ] + } + ], + "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, 'e0e51bdd3d1f6e08654a63bdeffebb9b')" + ] + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/ArcaeaOfflineDatabase.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/ArcaeaOfflineDatabase.kt index 21cd3a10..4912c2a2 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/ArcaeaOfflineDatabase.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/ArcaeaOfflineDatabase.kt @@ -67,8 +67,9 @@ import xyz.sevive.arcaeaoffline.core.database.migrations.Migration_7_8 AutoMigration(from = 12, to = 13), AutoMigration(from = 13, to = 14), AutoMigration(from = 14, to = 15), + AutoMigration(from = 16, to = 17), ], - version = 16, + version = 17, exportSchema = true, ) @ColumnTypeConverters( diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt index fd4587d5..e37dd739 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt @@ -1,5 +1,6 @@ package xyz.sevive.arcaeaoffline.core.database.entities +import androidx.room3.ColumnInfo import androidx.room3.Entity import androidx.room3.PrimaryKey @@ -10,6 +11,10 @@ data class Pack( @PrimaryKey val id: String, val name: String, val description: String? = null, + @ColumnInfo(name = "parent_pack_id") val parentPackId: String? = null, + val section: String? = null, + @ColumnInfo(name = "plus_character_id") val plusCharacterId: Int? = null, + @ColumnInfo(name = "is_extend_pack", defaultValue = "0") val isExtendPack: Boolean = false, ) { fun basePackId(): String? = appendPackRegexPattern.matchEntire(this.id)?.groupValues?.getOrNull(1) diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/ArcaeaListDefinitions.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/ArcaeaListDefinitions.kt index 7b42241a..dd183476 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/ArcaeaListDefinitions.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/ArcaeaListDefinitions.kt @@ -35,8 +35,12 @@ data class ArcaeaListLocalizedArrayObject( @Serializable data class ArcaeaPacklistItem( val id: String, + val section: String? = null, @SerialName("name_localized") val nameLocalized: ArcaeaListLocalizedObject, @SerialName("description_localized") val descriptionLocalized: ArcaeaListLocalizedObject, + @SerialName("pack_parent") val packParent: String? = null, + @SerialName("plus_character") val plusCharacter: Int? = null, + @SerialName("is_extend_pack") val isExtendPack: Boolean = false, ) { fun getPackLocalized(lang: ArcaeaLanguage): PackLocalized? { val name = diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporter.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporter.kt index a01d8e2f..e6e14053 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporter.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporter.kt @@ -35,6 +35,10 @@ class ArcaeaPacklistImporter( id = pack.id, name = pack.nameLocalized.en ?: "Pack", description = pack.descriptionLocalized.en, + parentPackId = pack.packParent, + section = pack.section, + plusCharacterId = pack.plusCharacter?.takeIf { it != -1 }, + isExtendPack = pack.isExtendPack, ), ) } From b726eaf2d209710100275ccae885f0d9a11658c9 Mon Sep 17 00:00:00 2001 From: 283375 Date: Mon, 2 Nov 2026 02:43:04 +0800 Subject: [PATCH 2/2] add importer tests and optimize properties --- .../ui/components/ArcaeaPackSelector.kt | 4 +- .../core/database/entities/Pack.kt | 7 +-- .../database/repositories/PackRepository.kt | 14 ------ .../core/search/ArcaeaCatalog.kt | 6 +-- .../core/search/ArcaeaCatalogTest.kt | 2 +- .../importers/ArcaeaPacklistImporterTest.kt | 46 +++++++++++++++++++ .../jvmTest/resources/packlist_fixture.json | 26 +++++++++++ 7 files changed, 80 insertions(+), 25 deletions(-) create mode 100644 shared/src/jvmTest/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporterTest.kt create mode 100644 shared/src/jvmTest/resources/packlist_fixture.json diff --git a/app/src/main/java/xyz/sevive/arcaeaoffline/ui/components/ArcaeaPackSelector.kt b/app/src/main/java/xyz/sevive/arcaeaoffline/ui/components/ArcaeaPackSelector.kt index 39002903..4fccb74c 100644 --- a/app/src/main/java/xyz/sevive/arcaeaoffline/ui/components/ArcaeaPackSelector.kt +++ b/app/src/main/java/xyz/sevive/arcaeaoffline/ui/components/ArcaeaPackSelector.kt @@ -31,12 +31,12 @@ fun ArcaeaPackSelector( ) { val basePackMap: Map = packs - .filter { !it.isAppendPack() } + .filter { !it.isAppendPack } .associateBy(keySelector = { it.id }, valueTransform = { it }) val labels = packs.map { - val basePack = basePackMap[it.basePackId()] + val basePack = basePackMap[it.parentPackId] val packLabel = if (basePack != null) { buildAnnotatedString { diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt index e37dd739..ec973bd0 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/entities/Pack.kt @@ -4,8 +4,6 @@ import androidx.room3.ColumnInfo import androidx.room3.Entity import androidx.room3.PrimaryKey -val appendPackRegexPattern = """(.*)_append_.*$""".toRegex() - @Entity(tableName = "packs") data class Pack( @PrimaryKey val id: String, @@ -16,7 +14,6 @@ data class Pack( @ColumnInfo(name = "plus_character_id") val plusCharacterId: Int? = null, @ColumnInfo(name = "is_extend_pack", defaultValue = "0") val isExtendPack: Boolean = false, ) { - fun basePackId(): String? = appendPackRegexPattern.matchEntire(this.id)?.groupValues?.getOrNull(1) - - fun isAppendPack(): Boolean = basePackId() != null + val isAppendPack: Boolean + get() = parentPackId != null } diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/repositories/PackRepository.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/repositories/PackRepository.kt index a4714ed6..f98c1083 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/repositories/PackRepository.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/database/repositories/PackRepository.kt @@ -1,8 +1,6 @@ package xyz.sevive.arcaeaoffline.core.database.repositories import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.asStateFlow import xyz.sevive.arcaeaoffline.core.database.daos.PackDao import xyz.sevive.arcaeaoffline.core.database.entities.Pack @@ -11,8 +9,6 @@ interface PackRepository { fun findAll(): Flow> - fun findBasePack(id: String): Flow - fun searchByName(input: String): Flow> fun count(): Flow @@ -29,20 +25,10 @@ interface PackRepository { class PackRepositoryImpl( private val dao: PackDao, ) : PackRepository { - private val basePackIdRegex = """_append_.*$""".toRegex() - override fun find(id: String): Flow = dao.find(id) override fun findAll(): Flow> = dao.findAll() - override fun findBasePack(id: String): Flow = - if (basePackIdRegex.find(id) != null) { - val baseId = basePackIdRegex.replace(id, "") - dao.find(baseId) - } else { - MutableStateFlow(null).asStateFlow() - } - override fun searchByName(input: String) = dao.searchByName(input) override fun count() = dao.count() diff --git a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalog.kt b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalog.kt index 1ebf9ea1..ac33f060 100644 --- a/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalog.kt +++ b/shared/src/commonMain/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalog.kt @@ -51,12 +51,12 @@ data class ArcaeaCatalog( */ val basePacks: List = packs - .filter { it.basePackId() == null || it.basePackId() !in packsById } + .filter { it.parentPackId == null || it.parentPackId !in packsById } .sortedBy { it.name.lowercase() } val appendPacksByBasePackId: Map> = packs - .mapNotNull { pack -> pack.basePackId()?.let { it to pack } } + .mapNotNull { pack -> pack.parentPackId?.let { it to pack } } .groupBy({ it.first }, { it.second }) /** @@ -160,7 +160,7 @@ private fun buildCatalog( val songIdsByBasePackId = catalogSongs - .groupBy({ packsById[it.song.set]?.basePackId() ?: it.song.set }, { it.song.id }) + .groupBy({ packsById[it.song.set]?.parentPackId ?: it.song.set }, { it.song.id }) .mapValues { (_, songIds) -> songIds.toSet() } return ArcaeaCatalog( diff --git a/shared/src/commonTest/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalogTest.kt b/shared/src/commonTest/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalogTest.kt index acae5ecd..db8e8183 100644 --- a/shared/src/commonTest/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalogTest.kt +++ b/shared/src/commonTest/kotlin/xyz/sevive/arcaeaoffline/core/search/ArcaeaCatalogTest.kt @@ -7,7 +7,7 @@ import kotlin.test.assertEquals class ArcaeaCatalogTest { private val basePack = Pack(id = "nova", name = "Nova Pack") - private val appendPack = Pack(id = "nova_append_1", name = "Nova Append") + private val appendPack = Pack(id = "nova_append_1", name = "Nova Append", parentPackId = "nova") private val catalog = ArcaeaCatalog( diff --git a/shared/src/jvmTest/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporterTest.kt b/shared/src/jvmTest/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporterTest.kt new file mode 100644 index 00000000..eb427243 --- /dev/null +++ b/shared/src/jvmTest/kotlin/xyz/sevive/arcaeaoffline/core/database/externals/importers/ArcaeaPacklistImporterTest.kt @@ -0,0 +1,46 @@ +package xyz.sevive.arcaeaoffline.core.database.externals.importers + +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertNull + +class ArcaeaPacklistImporterTest { + private val fixture = + javaClass.classLoader!! + .getResource("packlist_fixture.json")!! + .readText() + + @Test + fun mapsPackFieldsToPack() { + val packs = ArcaeaPacklistImporter(fixture).packs().associateBy { it.id } + + val base = packs.getValue("testbase") + assertEquals("Test Base", base.name) + assertEquals("Base description", base.description) + assertEquals("collab", base.section) + assertEquals(101, base.plusCharacterId) + assertEquals(false, base.isExtendPack) + assertNull(base.parentPackId) + + val append = packs.getValue("testbase_append_1") + assertEquals("testbase", append.parentPackId) + assertNull(append.plusCharacterId) + assertNull(append.description) + assertEquals(true, append.isAppendPack) + + val extend = packs.getValue("extend_x") + assertEquals(true, extend.isExtendPack) + assertNull(extend.parentPackId) + } + + @Test + fun includesBuiltInMemoryArchive() { + val packs = ArcaeaPacklistImporter(fixture).packs() + + assertEquals(4, packs.size) + val single = packs.single { it.id == "single" } + assertEquals("Memory Archive", single.name) + assertNull(single.parentPackId) + assertNull(single.section) + } +} diff --git a/shared/src/jvmTest/resources/packlist_fixture.json b/shared/src/jvmTest/resources/packlist_fixture.json new file mode 100644 index 00000000..fffd8cdb --- /dev/null +++ b/shared/src/jvmTest/resources/packlist_fixture.json @@ -0,0 +1,26 @@ +{ + "packs": [ + { + "id": "testbase", + "section": "collab", + "name_localized": { "en": "Test Base" }, + "description_localized": { "en": "Base description" }, + "plus_character": 101 + }, + { + "id": "testbase_append_1", + "section": "collab", + "name_localized": { "en": "Test Append" }, + "description_localized": {}, + "pack_parent": "testbase", + "plus_character": -1 + }, + { + "id": "extend_x", + "section": "arcaea", + "name_localized": { "en": "World Extend" }, + "description_localized": {}, + "is_extend_pack": true + } + ] +}