From 5ab7e586f35532326d38dec25776fb85e7340d97 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Sun, 7 Jul 2024 10:18:18 +0300 Subject: [PATCH] Option to sort manga sources by last used #947 --- .../koitharu/kotatsu/core/backup/JsonDeserializer.kt | 3 +++ .../koitharu/kotatsu/core/backup/JsonSerializer.kt | 3 +++ .../org/koitharu/kotatsu/core/db/MangaDatabase.kt | 4 +++- .../koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt | 11 ++++++++++- .../kotatsu/core/db/entity/MangaSourceEntity.kt | 2 ++ .../kotatsu/core/db/migrations/Migration21To22.kt | 12 ++++++++++++ .../kotatsu/explore/data/MangaSourcesRepository.kt | 10 ++++++++++ .../kotatsu/explore/data/SourcesSortOrder.kt | 1 + .../koitharu/kotatsu/local/ui/LocalListViewModel.kt | 3 +++ .../kotatsu/remotelist/ui/RemoteListViewModel.kt | 7 ++++++- 10 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration21To22.kt diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt index 02229b7de..cc042d7fe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonDeserializer.kt @@ -12,6 +12,7 @@ import org.koitharu.kotatsu.parsers.model.SortOrder import org.koitharu.kotatsu.parsers.util.json.getBooleanOrDefault import org.koitharu.kotatsu.parsers.util.json.getFloatOrDefault import org.koitharu.kotatsu.parsers.util.json.getIntOrDefault +import org.koitharu.kotatsu.parsers.util.json.getLongOrDefault import org.koitharu.kotatsu.parsers.util.json.getStringOrNull class JsonDeserializer(private val json: JSONObject) { @@ -85,6 +86,8 @@ class JsonDeserializer(private val json: JSONObject) { isEnabled = json.getBoolean("enabled"), sortKey = json.getInt("sort_key"), addedIn = json.getIntOrDefault("added_in", 0), + lastUsedAt = json.getLongOrDefault("used_at", 0L), + isPinned = json.getBooleanOrDefault("pinned", false), ) fun toMap(): Map { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt index cfe7451d0..28bf270da 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/backup/JsonSerializer.kt @@ -89,6 +89,9 @@ class JsonSerializer private constructor(private val json: JSONObject) { put("source", e.source) put("enabled", e.isEnabled) put("sort_key", e.sortKey) + put("added_in", e.addedIn) + put("used_at", e.lastUsedAt) + put("pinned", e.isPinned) }, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt index bda4584aa..1bec0c9b8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/MangaDatabase.kt @@ -34,6 +34,7 @@ import org.koitharu.kotatsu.core.db.migrations.Migration18To19 import org.koitharu.kotatsu.core.db.migrations.Migration19To20 import org.koitharu.kotatsu.core.db.migrations.Migration1To2 import org.koitharu.kotatsu.core.db.migrations.Migration20To21 +import org.koitharu.kotatsu.core.db.migrations.Migration21To22 import org.koitharu.kotatsu.core.db.migrations.Migration2To3 import org.koitharu.kotatsu.core.db.migrations.Migration3To4 import org.koitharu.kotatsu.core.db.migrations.Migration4To5 @@ -59,7 +60,7 @@ import org.koitharu.kotatsu.tracker.data.TrackEntity import org.koitharu.kotatsu.tracker.data.TrackLogEntity import org.koitharu.kotatsu.tracker.data.TracksDao -const val DATABASE_VERSION = 21 +const val DATABASE_VERSION = 22 @Database( entities = [ @@ -120,6 +121,7 @@ fun getDatabaseMigrations(context: Context): Array = arrayOf( Migration18To19(), Migration19To20(), Migration20To21(), + Migration21To22(), ) fun MangaDatabase(context: Context): MangaDatabase = Room diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt index de66fd655..b39df0ab6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/dao/MangaSourcesDao.kt @@ -42,6 +42,12 @@ abstract class MangaSourcesDao { @Query("UPDATE sources SET sort_key = :sortKey WHERE source = :source") abstract suspend fun setSortKey(source: String, sortKey: Int) + @Query("UPDATE sources SET used_at = :value WHERE source = :source") + abstract suspend fun setLastUsed(source: String, value: Long) + + @Query("UPDATE sources SET pinned = :isPinned WHERE source = :source") + abstract suspend fun setPinned(source: String, isPinned: Boolean) + @Insert(onConflict = OnConflictStrategy.IGNORE) @Transaction abstract suspend fun insertIfAbsent(entries: Collection) @@ -53,7 +59,7 @@ abstract class MangaSourcesDao { val orderBy = getOrderBy(order) @Language("RoomSql") - val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY $orderBy") + val query = SimpleSQLiteQuery("SELECT * FROM sources WHERE enabled = 1 ORDER BY pinned DESC, $orderBy") return observeImpl(query) } @@ -73,6 +79,8 @@ abstract class MangaSourcesDao { isEnabled = isEnabled, sortKey = getMaxSortKey() + 1, addedIn = BuildConfig.VERSION_CODE, + lastUsedAt = 0, + isPinned = false, ) upsert(entity) } @@ -91,5 +99,6 @@ abstract class MangaSourcesDao { SourcesSortOrder.ALPHABETIC -> "source ASC" SourcesSortOrder.POPULARITY -> "(SELECT COUNT(*) FROM manga WHERE source = sources.source) DESC" SourcesSortOrder.MANUAL -> "sort_key ASC" + SourcesSortOrder.LAST_USED -> "used_at DESC" } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaSourceEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaSourceEntity.kt index 8c8784a46..849cfcd61 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaSourceEntity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaSourceEntity.kt @@ -15,4 +15,6 @@ data class MangaSourceEntity( @ColumnInfo(name = "enabled") val isEnabled: Boolean, @ColumnInfo(name = "sort_key", index = true) val sortKey: Int, @ColumnInfo(name = "added_in") val addedIn: Int, + @ColumnInfo(name = "used_at") val lastUsedAt: Long, + @ColumnInfo(name = "pinned") val isPinned: Boolean, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration21To22.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration21To22.kt new file mode 100644 index 000000000..c45503038 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/migrations/Migration21To22.kt @@ -0,0 +1,12 @@ +package org.koitharu.kotatsu.core.db.migrations + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +class Migration21To22 : Migration(21, 22) { + + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE sources ADD COLUMN `used_at` INTEGER NOT NULL DEFAULT 0") + db.execSQL("ALTER TABLE sources ADD COLUMN `pinned` INTEGER NOT NULL DEFAULT 0") + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt index 485dbc1cd..48e1ffff8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/MangaSourcesRepository.kt @@ -214,6 +214,8 @@ class MangaSourcesRepository @Inject constructor( isEnabled = false, sortKey = ++maxSortKey, addedIn = BuildConfig.VERSION_CODE, + lastUsedAt = 0, + isPinned = false, ) } dao.insertIfAbsent(entities) @@ -224,6 +226,14 @@ class MangaSourcesRepository @Inject constructor( return settings.sourcesVersion == 0 && dao.findAllEnabledNames().isEmpty() } + suspend fun setIsPinned(source: MangaSource, isPinned: Boolean) { + dao.setPinned(source.name, isPinned) + } + + suspend fun trackUsage(source: MangaSource) { + dao.setLastUsed(source.name, System.currentTimeMillis()) + } + private suspend fun setSourcesEnabledImpl(sources: Collection, isEnabled: Boolean) { if (sources.size == 1) { // fast path dao.setEnabled(sources.first().name, isEnabled) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/SourcesSortOrder.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/SourcesSortOrder.kt index 9c42be758..041fbe678 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/SourcesSortOrder.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/data/SourcesSortOrder.kt @@ -9,4 +9,5 @@ enum class SourcesSortOrder( ALPHABETIC(R.string.by_name), POPULARITY(R.string.popular), MANUAL(R.string.manual), + LAST_USED(R.string.last_used), } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt index c38aa1de3..4eed4c626 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/ui/LocalListViewModel.kt @@ -13,6 +13,7 @@ import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.toFileOrNull import org.koitharu.kotatsu.core.util.ext.toUriOrNull import org.koitharu.kotatsu.download.ui.worker.DownloadWorker +import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.list.domain.ListExtraProvider @@ -39,6 +40,7 @@ class LocalListViewModel @Inject constructor( exploreRepository: ExploreRepository, @LocalStorageChanges private val localStorageChanges: SharedFlow, private val localStorageManager: LocalStorageManager, + sourcesRepository: MangaSourcesRepository, ) : RemoteListViewModel( savedStateHandle, mangaRepositoryFactory, @@ -47,6 +49,7 @@ class LocalListViewModel @Inject constructor( listExtraProvider, downloadScheduler, exploreRepository, + sourcesRepository, ), SharedPreferences.OnSharedPreferenceChangeListener { val onMangaRemoved = MutableEventFlow() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt index e1923e1ca..05653b7fc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/remotelist/ui/RemoteListViewModel.kt @@ -28,6 +28,7 @@ import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug import org.koitharu.kotatsu.core.util.ext.require import org.koitharu.kotatsu.core.util.ext.sizeOrZero import org.koitharu.kotatsu.download.ui.worker.DownloadWorker +import org.koitharu.kotatsu.explore.data.MangaSourcesRepository import org.koitharu.kotatsu.explore.domain.ExploreRepository import org.koitharu.kotatsu.filter.ui.FilterCoordinator import org.koitharu.kotatsu.filter.ui.MangaFilter @@ -45,7 +46,6 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaListFilter import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaTag -import org.koitharu.kotatsu.parsers.util.concatUrl import javax.inject.Inject private const val FILTER_MIN_INTERVAL = 250L @@ -59,6 +59,7 @@ open class RemoteListViewModel @Inject constructor( listExtraProvider: ListExtraProvider, downloadScheduler: DownloadWorker.Scheduler, private val exploreRepository: ExploreRepository, + sourcesRepository: MangaSourcesRepository, ) : MangaListViewModel(settings, downloadScheduler), MangaFilter by filter { val source = savedStateHandle.require(RemoteListFragment.ARG_SOURCE) @@ -117,6 +118,10 @@ open class RemoteListViewModel @Inject constructor( }.catch { error -> listError.value = error }.launchIn(viewModelScope) + + launchJob(Dispatchers.Default) { + sourcesRepository.trackUsage(source) + } } override fun onRefresh() {