From 244b4a785c6a23ffec086e2d7260668e8e262394 Mon Sep 17 00:00:00 2001 From: pathos Date: Wed, 1 May 2024 02:09:45 +0900 Subject: [PATCH] =?UTF-8?q?[RBP-19]=20=EB=A1=9C=EC=BB=AC=20=EC=BA=90?= =?UTF-8?q?=EC=8B=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/AndroidManifest.xml | 3 +- .../android/portfolio/core/di/LocalModule.kt | 32 ++++ .../di/{DataModule.kt => RepositoryModule.kt} | 10 +- .../data/datasource/local/LocalDataSource.kt | 148 ++++++++++++++++++ .../data/datasource/local/db/CacheDatabase.kt | 24 +++ .../datasource/local/db/dao/CharacterDao.kt | 57 +++++++ .../data/datasource/local/db/dao/FameDao.kt | 19 +++ .../datasource/local/db/table/AvatarTbl.kt | 24 +++ .../datasource/local/db/table/CharacterTbl.kt | 24 +++ .../datasource/local/db/table/EquipmentTbl.kt | 32 ++++ .../data/datasource/local/db/table/FameTbl.kt | 21 +++ .../datasource/remote/NetworkDataSource.kt | 2 +- .../repository/NetworkCharacterRepository.kt | 6 +- .../data/repository/NetworkFameRepository.kt | 8 +- .../OfflineFirstCharacterRepository.kt | 117 ++++++++++++++ .../repository/OfflineFirstFameRepository.kt | 41 +++++ .../domain/repository/CharacterRepository.kt | 2 +- .../domain/repository/FameRepository.kt | 1 - .../domain/usecase/GetCharacterInfoUseCase.kt | 2 +- .../viewmodel/CharacterInfoViewModel.kt | 2 - .../presentation/viewmodel/MainViewModel.kt | 5 +- .../remote/NetworkDataSourceTest.kt | 2 +- .../NetworkCharacterRepositoryTest.kt | 8 +- .../repository/NetworkFameRepositoryTest.kt | 36 +---- 24 files changed, 562 insertions(+), 64 deletions(-) create mode 100644 app/src/main/java/today/pathos/android/portfolio/core/di/LocalModule.kt rename app/src/main/java/today/pathos/android/portfolio/core/di/{DataModule.kt => RepositoryModule.kt} (62%) create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/LocalDataSource.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/CacheDatabase.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/CharacterDao.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/FameDao.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/AvatarTbl.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/CharacterTbl.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/EquipmentTbl.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/FameTbl.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstCharacterRepository.kt create mode 100644 app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstFameRepository.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 623b3b8..beb8a42 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,8 +13,9 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.Portfolio" - tools:targetApi="31"> + tools:targetApi="33"> = + fameDao.getFameList() + + suspend fun createFameList(characterList: List) { + fameDao.createFame( + fameList = characterList.map { + FameTbl( + serverId = checkNotNull(it.serverId), + characterId = it.characterId, + characterName = it.characterName, + characterImage = Character.getCharacterImageUrl(it.serverId, it.characterId, 3), + level = it.level, + jobId = it.jobId, + jobGrowId = it.jobGrowId, + jobName = it.jobName, + jobGrowName = it.jobGrowName, + fame = it.fame ?: 0, + ) + } + ) + } + + suspend fun isCharacterEmpty( + serverId: String, + characterId: String, + ): Boolean = characterDao.hasCharacter(serverId, characterId).not() + + suspend fun getCharacter( + serverId: String, + characterId: String, + ): CharacterTbl = characterDao.getCharacter(serverId, characterId) + + suspend fun createCharacter( + serverId: String, + character: ResCharacter, + ) { + characterDao.createCharacter( + CharacterTbl( + serverId = serverId, + characterId = character.characterId, + characterName = character.characterName, + characterImage = Character.getCharacterImageUrl(serverId, character.characterId, 3), + level = character.level, + jobId = character.jobId, + jobGrowId = character.jobGrowId, + jobName = character.jobName, + jobGrowName = character.jobGrowName, + fame = character.fame ?: 0, + ) + ) + } + + suspend fun isCharacterEquipmentEmpty( + serverId: String, + characterId: String, + ): Boolean = characterDao.hasCharacterEquipment(serverId, characterId).not() + + suspend fun getCharacterEquipment( + serverId: String, + characterId: String, + ): List = characterDao.getCharacterEquipment(serverId, characterId) + + suspend fun createCharacterEquipment( + serverId: String, + characterId: String, + equipmentList: List, + ) { + characterDao.upsertEquipment( + equipmentList.map { + EquipmentTbl( + serverId = serverId, + characterId = characterId, + slotId = it.slotId, + slotName = it.slotName, + itemId = it.itemId, + itemName = it.itemName, + itemTypeId = it.itemTypeId, + itemType = it.itemType, + itemTypeDetailId = it.itemTypeDetailId, + itemTypeDetail = it.itemTypeDetail, + itemAvailableLevel = it.itemAvailableLevel, + itemRarity = it.itemRarity, + setItemId = it.setItemId, + setItemName = it.setItemName, + reinforce = it.reinforce, + itemGradeName = it.itemGradeName, + amplificationName = it.amplificationName, + expiredDate = it.expiredDate, + refine = it.refine, + ) + } + ) + } + + suspend fun isCharacterAvatarEmpty( + serverId: String, + characterId: String, + ): Boolean = characterDao.hasCharacterAvatar(serverId, characterId).not() + + suspend fun getCharacterAvatar( + serverId: String, + characterId: String, + ): List = characterDao.getCharacterAvatar(serverId, characterId) + + suspend fun createCharacterAvatar( + serverId: String, + characterId: String, + equipmentList: List, + ) { + characterDao.upsertAvatar( + equipmentList.map { + AvatarTbl( + serverId = serverId, + characterId = characterId, + slotId = it.slotId, + slotName = it.slotName, + itemId = it.itemId, + itemName = it.itemName, + cloneItemId = it.clone.itemId, + cloneItemName = it.clone.itemName, + itemRarity = it.itemRarity, + optionAbility = it.optionAbility + ) + } + ) + } +} diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/CacheDatabase.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/CacheDatabase.kt new file mode 100644 index 0000000..78f5fff --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/CacheDatabase.kt @@ -0,0 +1,24 @@ +package today.pathos.android.portfolio.data.datasource.local.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import today.pathos.android.portfolio.data.datasource.local.db.dao.CharacterDao +import today.pathos.android.portfolio.data.datasource.local.db.dao.FameDao +import today.pathos.android.portfolio.data.datasource.local.db.table.AvatarTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.CharacterTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.EquipmentTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.FameTbl + +@Database( + entities = [ + FameTbl::class, + CharacterTbl::class, + EquipmentTbl::class, + AvatarTbl::class + ], + version = 1 +) +abstract class CacheDatabase : RoomDatabase() { + abstract fun fameDao(): FameDao + abstract fun characterDao(): CharacterDao +} diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/CharacterDao.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/CharacterDao.kt new file mode 100644 index 0000000..c1e3b1e --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/CharacterDao.kt @@ -0,0 +1,57 @@ +package today.pathos.android.portfolio.data.datasource.local.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import androidx.room.Upsert +import today.pathos.android.portfolio.data.datasource.local.db.table.AvatarTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.CharacterTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.EquipmentTbl + +@Dao +abstract class CharacterDao { + @Insert + abstract suspend fun createCharacter(character: CharacterTbl) + + @Query("SELECT 1 FROM CHARACTER_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun hasCharacter( + serverId: String, + characterId: String, + ): Boolean + + @Query("SELECT * FROM CHARACTER_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun getCharacter( + serverId: String, + characterId: String, + ): CharacterTbl + + @Upsert + abstract suspend fun upsertEquipment(equipmentList: List) + + @Query("SELECT count(*) FROM EQUIPMENT_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun hasCharacterEquipment( + serverId: String, + characterId: String, + ): Boolean + + @Query("SELECT * FROM EQUIPMENT_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun getCharacterEquipment( + serverId: String, + characterId: String, + ): List + + @Upsert + abstract suspend fun upsertAvatar(equipmentList: List) + + @Query("SELECT count(*) FROM AVATAR_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun hasCharacterAvatar( + serverId: String, + characterId: String, + ): Boolean + + @Query("SELECT * FROM AVATAR_TBL WHERE server_id = :serverId and character_id = :characterId") + abstract suspend fun getCharacterAvatar( + serverId: String, + characterId: String, + ): List +} diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/FameDao.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/FameDao.kt new file mode 100644 index 0000000..c8d52c4 --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/dao/FameDao.kt @@ -0,0 +1,19 @@ +package today.pathos.android.portfolio.data.datasource.local.db.dao + +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.Query +import today.pathos.android.portfolio.data.datasource.local.db.table.FameTbl + +@Dao +abstract class FameDao { + @Insert + abstract suspend fun createFame(fameList: List) + + @Query("SELECT count(*) FROM FAME_TBL") + abstract suspend fun fameListCount(): Int + + @Query("SELECT * FROM FAME_TBL") + abstract suspend fun getFameList(): List + +} diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/AvatarTbl.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/AvatarTbl.kt new file mode 100644 index 0000000..5fbdc4e --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/AvatarTbl.kt @@ -0,0 +1,24 @@ +package today.pathos.android.portfolio.data.datasource.local.db.table + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import today.pathos.android.portfolio.entity.AvatarSlotId +import today.pathos.android.portfolio.entity.EquipmentSlotId + +@Entity( + tableName = "AVATAR_TBL", + primaryKeys = ["server_id", "character_id", "item_id"] +) +data class AvatarTbl( + @ColumnInfo(name = "server_id") val serverId: String, + @ColumnInfo(name = "character_id") val characterId: String, + @ColumnInfo(name = "item_id") val itemId: String, + @ColumnInfo(name = "item_name") val itemName: String, + @ColumnInfo(name = "slot_id") val slotId: AvatarSlotId, + @ColumnInfo(name = "slot_name") val slotName: String, + @ColumnInfo(name = "clone_item_id") val cloneItemId: String?, + @ColumnInfo(name = "clone_item_name") val cloneItemName: String?, + @ColumnInfo(name = "item_rarity") val itemRarity: String, + @ColumnInfo(name = "option_ability") val optionAbility: String?, +) diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/CharacterTbl.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/CharacterTbl.kt new file mode 100644 index 0000000..871a27b --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/CharacterTbl.kt @@ -0,0 +1,24 @@ +package today.pathos.android.portfolio.data.datasource.local.db.table + +import androidx.room.ColumnInfo +import androidx.room.Entity + +@Entity( + tableName = "CHARACTER_TBL", + primaryKeys = ["server_id", "character_id"] +) +data class CharacterTbl( + @ColumnInfo(name = "server_id") val serverId: String, + @ColumnInfo(name = "character_id") val characterId: String, + @ColumnInfo(name = "character_name") val characterName: String, + @ColumnInfo(name = "character_image") val characterImage: String, + @ColumnInfo(name = "level") val level: Int, + @ColumnInfo(name = "job_id") val jobId: String, + @ColumnInfo(name = "job_grow_id") val jobGrowId: String, + @ColumnInfo(name = "job_name") val jobName: String, + @ColumnInfo(name = "job_grow_name") val jobGrowName: String, + @ColumnInfo(name = "fame") val fame: Int = 0, + @ColumnInfo(name = "adventure_name") val adventureName: String? = null, + @ColumnInfo(name = "guild_id") val guildId: String? = null, + @ColumnInfo(name = "guild_name") val guildName: String? = null, +) diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/EquipmentTbl.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/EquipmentTbl.kt new file mode 100644 index 0000000..17e0b16 --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/EquipmentTbl.kt @@ -0,0 +1,32 @@ +package today.pathos.android.portfolio.data.datasource.local.db.table + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey +import today.pathos.android.portfolio.entity.EquipmentSlotId + +@Entity( + tableName = "EQUIPMENT_TBL", + primaryKeys = ["server_id", "character_id", "item_id"] +) +data class EquipmentTbl( + @ColumnInfo(name = "server_id") val serverId: String, + @ColumnInfo(name = "character_id") val characterId: String, + @ColumnInfo(name = "item_id") val itemId: String, + @ColumnInfo(name = "item_name") val itemName: String, + @ColumnInfo(name = "slot_id") val slotId: EquipmentSlotId, + @ColumnInfo(name = "slot_name") val slotName: String, + @ColumnInfo(name = "item_type_id") val itemTypeId: String, + @ColumnInfo(name = "item_type") val itemType: String, + @ColumnInfo(name = "item_type_detail_id") val itemTypeDetailId: String, + @ColumnInfo(name = "item_type_detail") val itemTypeDetail: String, + @ColumnInfo(name = "item_available_level") val itemAvailableLevel: Int, + @ColumnInfo(name = "item_rarity") val itemRarity: String, + @ColumnInfo(name = "set_item_id") val setItemId: String? = null, + @ColumnInfo(name = "set_item_name") val setItemName: String? = null, + @ColumnInfo(name = "reinforce") val reinforce: Int, + @ColumnInfo(name = "item_grade_name") val itemGradeName: String? = null, + @ColumnInfo(name = "amplification_name") val amplificationName: String? = null, + @ColumnInfo(name = "expired_date") val expiredDate: Long? = null, + @ColumnInfo(name = "refine") val refine: Int, +) diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/FameTbl.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/FameTbl.kt new file mode 100644 index 0000000..c1446cf --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/local/db/table/FameTbl.kt @@ -0,0 +1,21 @@ +package today.pathos.android.portfolio.data.datasource.local.db.table + +import androidx.room.ColumnInfo +import androidx.room.Entity + +@Entity( + tableName = "FAME_TBL", + primaryKeys = ["server_id", "character_id"] +) +data class FameTbl( + @ColumnInfo(name = "server_id") val serverId: String, + @ColumnInfo(name = "character_id") val characterId: String, + @ColumnInfo(name = "character_name") val characterName: String, + @ColumnInfo(name = "character_image") val characterImage: String, + @ColumnInfo(name = "level") val level: Int, + @ColumnInfo(name = "job_id") val jobId: String, + @ColumnInfo(name = "job_grow_id") val jobGrowId: String, + @ColumnInfo(name = "job_name") val jobName: String, + @ColumnInfo(name = "job_grow_name") val jobGrowName: String, + @ColumnInfo(name = "fame") val fame: Int = 0, +) diff --git a/app/src/main/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSource.kt b/app/src/main/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSource.kt index 216e29f..59bcc9b 100644 --- a/app/src/main/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSource.kt +++ b/app/src/main/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSource.kt @@ -25,7 +25,7 @@ class NetworkDataSource @Inject constructor( limit: Int, ): ResRows = apiService.searchCharacter(serverId, characterName, limit) - suspend fun getCharacterInfo( + suspend fun getCharacter( serverId: String, characterId: String, ): ResCharacter = apiService.getCharacterInfo(serverId, characterId) diff --git a/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepository.kt b/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepository.kt index 92511ad..151b0d6 100644 --- a/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepository.kt +++ b/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepository.kt @@ -19,11 +19,11 @@ class NetworkCharacterRepository @Inject constructor( private val dataSource: NetworkDataSource, @IoDispatcher private val dispatcher: CoroutineDispatcher, ) : CharacterRepository { - override suspend fun getCharacterInfo( + override suspend fun getCharacter( serverId: String, characterId: String, ): Character = withContext(dispatcher) { - dataSource.getCharacterInfo(serverId, characterId).toEntity(serverId) + dataSource.getCharacter(serverId, characterId).toEntity(serverId) } override suspend fun getCharacterEquipment( @@ -49,7 +49,7 @@ private fun ResCharacter.toEntity(serverId: String) = Character( serverId = serverId, characterId = characterId, characterName = characterName, - characterImage = Character.getCharacterImageUrl(serverId, characterId, 1), + characterImage = Character.getCharacterImageUrl(serverId, characterId, 3), level = level, jobId = jobId, jobGrowId = jobGrowId, diff --git a/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkFameRepository.kt b/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkFameRepository.kt index 4cbfdb8..97c0e00 100644 --- a/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkFameRepository.kt +++ b/app/src/main/java/today/pathos/android/portfolio/data/repository/NetworkFameRepository.kt @@ -13,12 +13,8 @@ class NetworkFameRepository @Inject constructor( private val dataSource: NetworkDataSource, @IoDispatcher private val dispatcher: CoroutineDispatcher, ) : FameRepository { - override suspend fun getTop5Fame(): List = withContext(dispatcher) { - dataSource.getCharacterFame(limit = 5).rows.toEntity() - } - override suspend fun getFameCharacterList(): List = withContext(dispatcher) { - dataSource.getCharacterFame().rows.toEntity().drop(5) + dataSource.getCharacterFame().rows.toEntity() } } @@ -26,7 +22,7 @@ private fun ResCharacter.toEntity() = Character( serverId = checkNotNull(serverId), characterId = characterId, characterName = characterName, - characterImage = Character.getCharacterImageUrl(serverId, characterId, 1), + characterImage = Character.getCharacterImageUrl(serverId, characterId, 3), level = level, jobId = jobId, jobGrowId = jobGrowId, diff --git a/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstCharacterRepository.kt b/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstCharacterRepository.kt new file mode 100644 index 0000000..946e13d --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstCharacterRepository.kt @@ -0,0 +1,117 @@ +package today.pathos.android.portfolio.data.repository + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import today.pathos.android.portfolio.core.di.IoDispatcher +import today.pathos.android.portfolio.data.datasource.local.LocalDataSource +import today.pathos.android.portfolio.data.datasource.local.db.table.AvatarTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.CharacterTbl +import today.pathos.android.portfolio.data.datasource.local.db.table.EquipmentTbl +import today.pathos.android.portfolio.data.datasource.remote.NetworkDataSource +import today.pathos.android.portfolio.domain.repository.CharacterRepository +import today.pathos.android.portfolio.entity.Avatar +import today.pathos.android.portfolio.entity.Character +import today.pathos.android.portfolio.entity.Equipment +import today.pathos.android.portfolio.entity.Item +import javax.inject.Inject + +class OfflineFirstCharacterRepository @Inject constructor( + private val localDataSource: LocalDataSource, + private val networkDataSource: NetworkDataSource, + @IoDispatcher private val dispatcher: CoroutineDispatcher, +) : CharacterRepository { + override suspend fun getCharacter( + serverId: String, + characterId: String, + ): Character = withContext(dispatcher) { + if (localDataSource.isCharacterEmpty(serverId, characterId)) { + val result = networkDataSource.getCharacter(serverId, characterId) + localDataSource.createCharacter(serverId, result) + + } + + localDataSource.getCharacter(serverId, characterId).toEntity() + } + + override suspend fun getCharacterEquipment( + serverId: String, + characterId: String, + ): List = withContext(dispatcher) { + if (localDataSource.isCharacterEquipmentEmpty(serverId, characterId)) { + val result = networkDataSource.getCharacterEquipment(serverId, characterId).equipment + localDataSource.createCharacterEquipment(serverId, characterId, result) + } + + localDataSource.getCharacterEquipment(serverId, characterId).toEntity() + } + + override suspend fun getCharacterAvatar( + serverId: String, + characterId: String, + ): List = withContext(dispatcher) { + if (localDataSource.isCharacterAvatarEmpty(serverId, characterId)) { + val result = networkDataSource.getCharacterAvatar(serverId, characterId).avatar + localDataSource.createCharacterAvatar(serverId, characterId, result) + } + + localDataSource.getCharacterAvatar(serverId, characterId).toEntity() + } + + override suspend fun getItemInfo(itemId: String): Item = withContext(dispatcher) { + TODO() + } +} + +private fun CharacterTbl.toEntity() = Character( + serverId = serverId, + characterId = characterId, + characterName = characterName, + characterImage = characterImage, + level = level, + jobId = jobId, + jobGrowId = jobGrowId, + jobName = jobName, + jobGrowName = jobGrowName, + fame = fame, + adventureName = adventureName, + guildId = guildId, + guildName = guildName, +) + +private fun EquipmentTbl.toEntity() = Equipment( + slotId = slotId, + slotName = slotName, + itemId = itemId, + itemName = itemName, + itemTypeId = itemTypeId, + itemType = itemType, + itemTypeDetailId = itemTypeDetailId, + itemTypeDetail = itemTypeDetail, + itemAvailableLevel = itemAvailableLevel, + itemRarity = itemRarity, + setItemId = setItemId, + setItemName = setItemName, + reinforce = reinforce, + itemGradeName = itemGradeName, + amplificationName = amplificationName, + expiredDate = expiredDate, + refine = refine, +) + + +@JvmName("callFromEquipment") +private fun List.toEntity() = map { it.toEntity() } + +private fun AvatarTbl.toEntity() = Avatar( + slotId = slotId, + slotName = slotName, + itemId = itemId, + itemName = itemName, + cloneItemId = cloneItemId, + cloneItemName = cloneItemName, + itemRarity = itemRarity, + optionAbility = optionAbility +) + +@JvmName("callFromAvatar") +private fun List.toEntity() = map { it.toEntity() } diff --git a/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstFameRepository.kt b/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstFameRepository.kt new file mode 100644 index 0000000..c45330c --- /dev/null +++ b/app/src/main/java/today/pathos/android/portfolio/data/repository/OfflineFirstFameRepository.kt @@ -0,0 +1,41 @@ +package today.pathos.android.portfolio.data.repository + +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.withContext +import today.pathos.android.portfolio.core.di.IoDispatcher +import today.pathos.android.portfolio.data.datasource.local.LocalDataSource +import today.pathos.android.portfolio.data.datasource.local.db.table.FameTbl +import today.pathos.android.portfolio.data.datasource.remote.NetworkDataSource +import today.pathos.android.portfolio.domain.repository.FameRepository +import today.pathos.android.portfolio.entity.Character +import javax.inject.Inject + +class OfflineFirstFameRepository @Inject constructor( + private val localDataSource: LocalDataSource, + private val networkDataSource: NetworkDataSource, + @IoDispatcher private val dispatcher: CoroutineDispatcher, +) : FameRepository { + override suspend fun getFameCharacterList(): List = withContext(dispatcher) { + if (localDataSource.isFameListEmpty()) { + val result = networkDataSource.getCharacterFame().rows + localDataSource.createFameList(result) + } + + localDataSource.getFameList().toEntity() + } +} + +private fun FameTbl.toEntity() = Character( + serverId = serverId, + characterId = characterId, + characterName = characterName, + characterImage = characterImage, + level = level, + jobId = jobId, + jobGrowId = jobGrowId, + jobName = jobName, + jobGrowName = jobGrowName, + fame = fame, +) + +private fun List.toEntity() = map { it.toEntity() } diff --git a/app/src/main/java/today/pathos/android/portfolio/domain/repository/CharacterRepository.kt b/app/src/main/java/today/pathos/android/portfolio/domain/repository/CharacterRepository.kt index 4ce26ef..7d3d86a 100644 --- a/app/src/main/java/today/pathos/android/portfolio/domain/repository/CharacterRepository.kt +++ b/app/src/main/java/today/pathos/android/portfolio/domain/repository/CharacterRepository.kt @@ -5,7 +5,7 @@ import today.pathos.android.portfolio.entity.Character import today.pathos.android.portfolio.entity.Equipment interface CharacterRepository { - suspend fun getCharacterInfo( + suspend fun getCharacter( serverId: String, characterId: String, ): Character diff --git a/app/src/main/java/today/pathos/android/portfolio/domain/repository/FameRepository.kt b/app/src/main/java/today/pathos/android/portfolio/domain/repository/FameRepository.kt index a0051bd..5bb523d 100644 --- a/app/src/main/java/today/pathos/android/portfolio/domain/repository/FameRepository.kt +++ b/app/src/main/java/today/pathos/android/portfolio/domain/repository/FameRepository.kt @@ -3,6 +3,5 @@ package today.pathos.android.portfolio.domain.repository import today.pathos.android.portfolio.entity.Character interface FameRepository { - suspend fun getTop5Fame(): List suspend fun getFameCharacterList(): List } diff --git a/app/src/main/java/today/pathos/android/portfolio/domain/usecase/GetCharacterInfoUseCase.kt b/app/src/main/java/today/pathos/android/portfolio/domain/usecase/GetCharacterInfoUseCase.kt index 2f0804c..7aedfdf 100644 --- a/app/src/main/java/today/pathos/android/portfolio/domain/usecase/GetCharacterInfoUseCase.kt +++ b/app/src/main/java/today/pathos/android/portfolio/domain/usecase/GetCharacterInfoUseCase.kt @@ -11,7 +11,7 @@ class GetCharacterInfoUseCase @Inject constructor( serverId: String, characterId: String, ): Character { - val characterInfo = characterRepository.getCharacterInfo(serverId, characterId) + val characterInfo = characterRepository.getCharacter(serverId, characterId) val equipment = characterRepository.getCharacterEquipment(serverId, characterId) val avatar = characterRepository.getCharacterAvatar(serverId, characterId) diff --git a/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/CharacterInfoViewModel.kt b/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/CharacterInfoViewModel.kt index 25ead35..d59269f 100644 --- a/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/CharacterInfoViewModel.kt +++ b/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/CharacterInfoViewModel.kt @@ -1,6 +1,5 @@ package today.pathos.android.portfolio.presentation.viewmodel -import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -34,7 +33,6 @@ class CharacterInfoViewModel @Inject constructor( ) private fun initInfo() { - Log.i("TAG", "[$serverId]:$characterId ") viewModelScope.launch { val characterInfo = getCharacterInfoUseCase(serverId, characterId) val armorList = characterInfo.equipment diff --git a/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/MainViewModel.kt b/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/MainViewModel.kt index 080f1f3..a87a85e 100644 --- a/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/MainViewModel.kt +++ b/app/src/main/java/today/pathos/android/portfolio/presentation/viewmodel/MainViewModel.kt @@ -29,13 +29,12 @@ class MainViewModel @Inject constructor( private fun initMain() { viewModelScope.launch { - val fameTop5List = repository.getTop5Fame() val fameList = repository.getFameCharacterList() _state.update { it.copy( - fameTop5List = fameTop5List, - fameList = fameList + fameTop5List = fameList.take(5), + fameList = fameList.drop(5) ) } } diff --git a/app/src/test/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSourceTest.kt b/app/src/test/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSourceTest.kt index 38f502a..b618c43 100644 --- a/app/src/test/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSourceTest.kt +++ b/app/src/test/java/today/pathos/android/portfolio/data/datasource/remote/NetworkDataSourceTest.kt @@ -129,7 +129,7 @@ class NetworkDataSourceTest { val expectedCharacterName = "사용자이름" val expectedLevel = 100 - val result = dataSource.getCharacterInfo( + val result = dataSource.getCharacter( serverId = serverId, characterId = characterId, ) diff --git a/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepositoryTest.kt b/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepositoryTest.kt index 06f9623..a80dcc2 100644 --- a/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepositoryTest.kt +++ b/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkCharacterRepositoryTest.kt @@ -25,7 +25,7 @@ class NetworkCharacterRepositoryTest { @Before fun setUp() { repository = NetworkCharacterRepository( - dataSource = dataSource, + networkDataSource = dataSource, dispatcher = testDispatcher ) } @@ -36,7 +36,7 @@ class NetworkCharacterRepositoryTest { val characterId = "CHARACTER_ID" coEvery { - dataSource.getCharacterInfo(serverId, characterId) + dataSource.getCharacter(serverId, characterId) } returns ResCharacter( serverId = serverId, characterId = characterId, @@ -50,12 +50,12 @@ class NetworkCharacterRepositoryTest { val expectedServerId = "SERVER_ID" val expectedCharacterId = "CHARACTER_ID" - val result = repository.getCharacterInfo(serverId, characterId) + val result = repository.getCharacter(serverId, characterId) assertEquals(expectedServerId, result.serverId) assertEquals(expectedCharacterId, result.characterId) - coVerify { dataSource.getCharacterInfo(serverId, characterId) } + coVerify { dataSource.getCharacter(serverId, characterId) } } @Test diff --git a/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkFameRepositoryTest.kt b/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkFameRepositoryTest.kt index 3482afb..521bf90 100644 --- a/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkFameRepositoryTest.kt +++ b/app/src/test/java/today/pathos/android/portfolio/data/repository/NetworkFameRepositoryTest.kt @@ -21,45 +21,11 @@ class NetworkFameRepositoryTest { @Before fun setUp() { repository = NetworkFameRepository( - dataSource = dataSource, + networkDataSource = dataSource, dispatcher = testDispatcher ) } - @Test - fun getFameTop5() = runTest(testDispatcher) { - val dummyResCharacter = ResCharacter( - serverId = "serverId", - characterId = "characterId", - characterName = "characterName", - level = 100, - jobId = "jobId", - jobGrowId = "jobGrowId", - jobName = "jobName", - jobGrowName = "jobGrowName", - ) - - coEvery { - dataSource.getCharacterFame(limit = 5) - } returns ResRows( - rows = listOf( - dummyResCharacter, - dummyResCharacter, - dummyResCharacter, - dummyResCharacter, - dummyResCharacter, - ) - ) - - val expectedCount = 5 - - val result = repository.getTop5Fame() - - assertEquals(expectedCount, result.size) - - coVerify { dataSource.getCharacterFame(limit = 5) } - } - @Test fun getFameList() = runTest(testDispatcher) { val dummyResCharacter = ResCharacter(