diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 840fb88d3..90cd28ace 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -208,6 +208,9 @@
android:launchMode="singleTop" />
+
+ @Query("SELECT * FROM preferences WHERE title_override IS NOT NULL OR cover_override IS NOT NULL OR content_rating_override IS NOT NULL")
+ abstract suspend fun getOverrides(): List
+
@Query("UPDATE preferences SET cf_brightness = 0, cf_contrast = 0, cf_invert = 0, cf_grayscale = 0")
abstract suspend fun resetColorFilters()
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
index 167f64e3c..ae78aaf95 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/db/entity/MangaPrefsEntity.kt
@@ -4,9 +4,10 @@ import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
+import org.koitharu.kotatsu.core.db.TABLE_PREFERENCES
@Entity(
- tableName = "preferences",
+ tableName = TABLE_PREFERENCES,
foreignKeys = [
ForeignKey(
entity = MangaEntity::class,
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
index 10304ef2b..1943702bb 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/model/Manga.kt
@@ -11,6 +11,7 @@ import androidx.core.os.LocaleListCompat
import androidx.core.text.buildSpannedString
import androidx.core.text.strikeThrough
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
import org.koitharu.kotatsu.core.util.ext.iterator
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.parsers.model.ContentRating
@@ -20,6 +21,7 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaListFilter
import org.koitharu.kotatsu.parsers.model.MangaState
import org.koitharu.kotatsu.parsers.util.findById
+import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
import org.koitharu.kotatsu.parsers.util.mapToSet
import com.google.android.material.R as materialR
@@ -192,3 +194,14 @@ fun MangaChapter.getLocalizedTitle(resources: Resources, index: Int = -1): Strin
else -> resources.getString(R.string.unnamed_chapter)
}
}
+
+fun Manga.withOverride(override: MangaOverride?) = if (override != null) {
+ copy(
+ title = override.title.ifNullOrEmpty { title },
+ coverUrl = override.coverUrl.ifNullOrEmpty { coverUrl },
+ largeCoverUrl = override.coverUrl.ifNullOrEmpty { largeCoverUrl },
+ contentRating = override.contentRating ?: contentRating,
+ )
+} else {
+ this
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt
index e12dcf7a9..4374f5c39 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/nav/AppRouter.kt
@@ -95,6 +95,7 @@ import org.koitharu.kotatsu.settings.SettingsActivity
import org.koitharu.kotatsu.settings.about.AppUpdateActivity
import org.koitharu.kotatsu.settings.backup.BackupDialogFragment
import org.koitharu.kotatsu.settings.backup.RestoreDialogFragment
+import org.koitharu.kotatsu.settings.override.OverrideConfigActivity
import org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity
import org.koitharu.kotatsu.settings.sources.auth.SourceAuthActivity
import org.koitharu.kotatsu.settings.sources.catalog.SourcesCatalogActivity
@@ -249,6 +250,12 @@ class AppRouter private constructor(
startActivity(mangaUpdatesIntent(contextOrNull() ?: return))
}
+ fun openMangaOverrideConfig(manga: Manga) {
+ val intent = Intent(contextOrNull() ?: return, OverrideConfigActivity::class.java)
+ .putExtra(KEY_MANGA, ParcelableManga(manga, withDescription = false))
+ startActivity(intent)
+ }
+
fun openSettings() = startActivity(SettingsActivity::class.java)
fun openReaderSettings() {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
index 74a49bf57..3aad4e2fb 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/parser/MangaDataRepository.kt
@@ -1,5 +1,7 @@
package org.koitharu.kotatsu.core.parser
+import androidx.collection.LongObjectMap
+import androidx.collection.MutableLongObjectMap
import androidx.core.net.toUri
import androidx.room.withTransaction
import dagger.Reusable
@@ -7,6 +9,8 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import org.koitharu.kotatsu.core.db.MangaDatabase
+import org.koitharu.kotatsu.core.db.TABLE_PREFERENCES
+import org.koitharu.kotatsu.core.db.entity.ContentRating
import org.koitharu.kotatsu.core.db.entity.MangaPrefsEntity
import org.koitharu.kotatsu.core.db.entity.toEntities
import org.koitharu.kotatsu.core.db.entity.toEntity
@@ -17,10 +21,12 @@ import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.nav.MangaIntent
import org.koitharu.kotatsu.core.os.AppShortcutManager
import org.koitharu.kotatsu.core.prefs.ReaderMode
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.util.nullIfEmpty
import org.koitharu.kotatsu.reader.domain.ReaderColorFilter
import javax.inject.Inject
import javax.inject.Provider
@@ -67,6 +73,33 @@ class MangaDataRepository @Inject constructor(
return db.getPreferencesDao().find(mangaId)?.getColorFilterOrNull()
}
+ suspend fun getOverride(mangaId: Long): MangaOverride? {
+ return db.getPreferencesDao().find(mangaId)?.getOverrideOrNull()
+ }
+
+ suspend fun getOverrides(): LongObjectMap {
+ val entities = db.getPreferencesDao().getOverrides()
+ val map = MutableLongObjectMap(entities.size)
+ for (entity in entities) {
+ map[entity.mangaId] = entity.getOverrideOrNull() ?: continue
+ }
+ return map
+ }
+
+ suspend fun setOverride(mangaId: Long, override: MangaOverride?) {
+ db.withTransaction {
+ val dao = db.getPreferencesDao()
+ val entity = dao.find(mangaId) ?: newEntity(mangaId)
+ dao.upsert(
+ entity.copy(
+ titleOverride = override?.title?.nullIfEmpty(),
+ coverUrlOverride = override?.coverUrl?.nullIfEmpty(),
+ contentRatingOverride = override?.contentRating?.name,
+ ),
+ )
+ }
+ }
+
fun observeColorFilter(mangaId: Long): Flow {
return db.getPreferencesDao().observe(mangaId)
.map { it?.getColorFilterOrNull() }
@@ -146,6 +179,11 @@ class MangaDataRepository @Inject constructor(
}
}
+ fun observeOverridesTrigger(emitInitialState: Boolean) = db.invalidationTracker.createFlow(
+ tables = arrayOf(TABLE_PREFERENCES),
+ emitInitialState = emitInitialState,
+ )
+
private fun MangaPrefsEntity.getColorFilterOrNull(): ReaderColorFilter? {
return if (cfBrightness != 0f || cfContrast != 0f || cfInvert || cfGrayscale) {
ReaderColorFilter(cfBrightness, cfContrast, cfInvert, cfGrayscale)
@@ -154,6 +192,18 @@ class MangaDataRepository @Inject constructor(
}
}
+ private fun MangaPrefsEntity.getOverrideOrNull(): MangaOverride? {
+ return if (titleOverride.isNullOrEmpty() && coverUrlOverride.isNullOrEmpty() && contentRatingOverride.isNullOrEmpty()) {
+ null
+ } else {
+ MangaOverride(
+ coverUrl = coverUrlOverride?.nullIfEmpty(),
+ title = titleOverride?.nullIfEmpty(),
+ contentRating = ContentRating(contentRatingOverride),
+ )
+ }
+ }
+
private fun newEntity(mangaId: Long) = MangaPrefsEntity(
mangaId = mangaId,
mode = -1,
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/MangaOverride.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/MangaOverride.kt
new file mode 100644
index 000000000..a63d5b8f0
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/model/MangaOverride.kt
@@ -0,0 +1,9 @@
+package org.koitharu.kotatsu.core.ui.model
+
+import org.koitharu.kotatsu.parsers.model.ContentRating
+
+data class MangaOverride(
+ val coverUrl: String?,
+ val title: String?,
+ val contentRating: ContentRating?,
+)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt
index e2f796f7e..7d044958b 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/data/MangaDetails.kt
@@ -2,6 +2,8 @@ package org.koitharu.kotatsu.details.data
import org.koitharu.kotatsu.core.model.getLocale
import org.koitharu.kotatsu.core.model.isLocal
+import org.koitharu.kotatsu.core.model.withOverride
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
@@ -13,6 +15,7 @@ import java.util.Locale
data class MangaDetails(
private val manga: Manga,
private val localManga: LocalManga?,
+ private val override: MangaOverride?,
val description: CharSequence?,
val isLoaded: Boolean,
) {
@@ -34,12 +37,13 @@ data class MangaDetails(
get() = localManga ?: if (manga.isLocal) LocalManga(manga) else null
val coverUrl: String?
- get() = manga.largeCoverUrl
+ get() = override?.coverUrl
+ .ifNullOrEmpty { manga.largeCoverUrl }
.ifNullOrEmpty { manga.coverUrl }
.ifNullOrEmpty { localManga?.manga?.coverUrl }
?.nullIfEmpty()
- fun toManga() = manga
+ fun toManga() = manga.withOverride(override)
fun getLocale(): Locale? {
findAppropriateLocale(chapters.keys.singleOrNull())?.let {
@@ -48,13 +52,11 @@ data class MangaDetails(
return manga.source.getLocale()
}
- fun filterChapters(branch: String?) = MangaDetails(
+ fun filterChapters(branch: String?) = copy(
manga = manga.filterChapters(branch),
localManga = localManga?.run {
copy(manga = manga.filterChapters(branch))
},
- description = description,
- isLoaded = isLoaded,
)
private fun mergeChapters(): List {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt
index c35fae7d1..b8b2a33f9 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/domain/DetailsLoadUseCase.kt
@@ -52,7 +52,16 @@ class DetailsLoadUseCase @Inject constructor(
m
}
}
- send(MangaDetails(manga, null, null, false))
+ val override = mangaDataRepository.getOverride(manga.id)
+ send(
+ MangaDetails(
+ manga = manga,
+ localManga = null,
+ override = override,
+ description = null,
+ isLoaded = false,
+ ),
+ )
val local = if (!manga.isLocal) {
async {
localMangaRepository.findSavedManga(manga)
@@ -66,28 +75,31 @@ class DetailsLoadUseCase @Inject constructor(
launch { updateTracker(details) }
send(
MangaDetails(
- details,
- local?.peek(),
- details.description?.parseAsHtml(withImages = false)?.trim(),
- false,
+ manga = details,
+ localManga = local?.peek(),
+ override = override,
+ description = details.description?.parseAsHtml(withImages = false)?.trim(),
+ isLoaded = false,
),
)
send(
MangaDetails(
- details,
- local?.await(),
- details.description?.parseAsHtml(withImages = true)?.trim(),
- true,
+ manga = details,
+ localManga = local?.await(),
+ override = override,
+ description = details.description?.parseAsHtml(withImages = true)?.trim(),
+ isLoaded = true,
),
)
} catch (e: IOException) {
local?.await()?.manga?.also { localManga ->
send(
MangaDetails(
- localManga,
- null,
- localManga.description?.parseAsHtml(withImages = false)?.trim(),
- true,
+ manga = localManga,
+ localManga = null,
+ override = override,
+ description = localManga.description?.parseAsHtml(withImages = false)?.trim(),
+ isLoaded = true,
),
)
} ?: close(e)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
index 48ef798b1..c09d356a3 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt
@@ -87,7 +87,7 @@ class DetailsViewModel @Inject constructor(
val mangaId = intent.mangaId
init {
- mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, false) }
+ mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, null, false) }
}
val history = historyRepository.observeOne(mangaId)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListViewModel.kt
index 3a9e30e9b..7fe1101ca 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/related/RelatedListViewModel.kt
@@ -14,12 +14,12 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
import org.koitharu.kotatsu.core.nav.AppRouter
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
import org.koitharu.kotatsu.core.util.ext.require
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.ui.MangaListViewModel
import org.koitharu.kotatsu.list.ui.model.EmptyState
@@ -34,8 +34,8 @@ class RelatedListViewModel @Inject constructor(
mangaRepositoryFactory: MangaRepository.Factory,
settings: AppSettings,
private val mangaListMapper: MangaListMapper,
- downloadScheduler: DownloadWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler) {
+ mangaDataRepository: MangaDataRepository,
+) : MangaListViewModel(settings, mangaDataRepository) {
private val seed = savedStateHandle.require(AppRouter.KEY_MANGA).manga
private val repository = mangaRepositoryFactory.create(seed.source)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt
index 1b6b101f2..83fbe7363 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/edit/FavouritesCategoryEditActivity.kt
@@ -93,7 +93,7 @@ class FavouritesCategoryEditActivity :
}
override fun afterTextChanged(s: Editable?) {
- viewBinding.buttonDone.isEnabled = !s.isNullOrBlank()
+ viewBinding.buttonDone.isEnabled = !s.isNullOrBlank() && !viewModel.isLoading.value
}
override fun onItemClick(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
@@ -119,6 +119,7 @@ class FavouritesCategoryEditActivity :
}
private fun onLoadingStateChanged(isLoading: Boolean) {
+ viewBinding.buttonDone.isEnabled = !isLoading && !viewBinding.editName.text.isNullOrBlank()
viewBinding.editSort.isEnabled = !isLoading
viewBinding.editName.isEnabled = !isLoading
viewBinding.switchTracker.isEnabled = !isLoading
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
index efb732b87..b748f6481 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt
@@ -17,13 +17,13 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.flattenLatest
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.favourites.ui.list.FavouritesListFragment.Companion.NO_ID
@@ -51,8 +51,8 @@ class FavouritesListViewModel @Inject constructor(
private val markAsReadUseCase: MarkAsReadUseCase,
quickFilterFactory: FavoritesListQuickFilter.Factory,
settings: AppSettings,
- downloadScheduler: DownloadWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener {
+ mangaDataRepository: MangaDataRepository,
+) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener {
val categoryId: Long = savedStateHandle[AppRouter.KEY_ID] ?: NO_ID
private val quickFilter = quickFilterFactory.create(categoryId)
@@ -92,7 +92,8 @@ class FavouritesListViewModel @Inject constructor(
override fun onRetry() = Unit
- override fun setFilterOption(option: ListFilterOption, isApplied: Boolean) = quickFilter.setFilterOption(option, isApplied)
+ override fun setFilterOption(option: ListFilterOption, isApplied: Boolean) =
+ quickFilter.setFilterOption(option, isApplied)
override fun toggleFilterOption(option: ListFilterOption) = quickFilter.toggleFilterOption(option)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt
index 221c58e37..7ad13061b 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt
@@ -14,6 +14,7 @@ import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaHistory
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
@@ -22,7 +23,6 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.flattenLatest
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.domain.HistoryListQuickFilter
import org.koitharu.kotatsu.history.domain.MarkAsReadUseCase
@@ -53,8 +53,8 @@ class HistoryListViewModel @Inject constructor(
private val mangaListMapper: MangaListMapper,
private val markAsReadUseCase: MarkAsReadUseCase,
private val quickFilter: HistoryListQuickFilter,
- downloadScheduler: DownloadWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
+ mangaDataRepository: MangaDataRepository,
+) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
private val sortOrder: StateFlow = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.IO,
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListMapper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListMapper.kt
index 0a2b538c8..99a809d9d 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListMapper.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/domain/MangaListMapper.kt
@@ -6,10 +6,13 @@ import androidx.annotation.ColorRes
import androidx.annotation.IntDef
import androidx.collection.MutableScatterSet
import androidx.collection.ScatterSet
+import dagger.Reusable
import dagger.hilt.android.qualifiers.ApplicationContext
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
import org.koitharu.kotatsu.core.ui.widgets.ChipsView
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
import org.koitharu.kotatsu.history.data.HistoryRepository
@@ -20,11 +23,11 @@ import org.koitharu.kotatsu.list.ui.model.MangaListModel
import org.koitharu.kotatsu.local.data.index.LocalMangaIndex
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag
+import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
import javax.inject.Inject
-import javax.inject.Singleton
-@Singleton
+@Reusable
class MangaListMapper @Inject constructor(
@ApplicationContext context: Context,
private val settings: AppSettings,
@@ -32,6 +35,7 @@ class MangaListMapper @Inject constructor(
private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository,
private val localMangaIndex: LocalMangaIndex,
+ private val dataRepository: MangaDataRepository,
) {
private val dict by lazy { readTagsDict(context) }
@@ -40,9 +44,13 @@ class MangaListMapper @Inject constructor(
manga: Collection,
mode: ListMode,
@Flags flags: Int = DEFAULTS,
- ): List {
- val options = getOptions(flags)
- return manga.map { toListModelImpl(it, mode, options) }
+ ): List = ArrayList(manga.size).apply {
+ toListModelList(
+ destination = this,
+ manga = manga,
+ mode = mode,
+ flags = flags,
+ )
}
suspend fun toListModelList(
@@ -52,8 +60,9 @@ class MangaListMapper @Inject constructor(
@Flags flags: Int = DEFAULTS,
) {
val options = getOptions(flags)
+ val overrides = dataRepository.getOverrides()
manga.mapTo(destination) {
- toListModelImpl(it, mode, options)
+ toListModelImpl(it, mode, options, overrides[it.id])
}
}
@@ -61,7 +70,12 @@ class MangaListMapper @Inject constructor(
manga: Manga,
mode: ListMode,
@Flags flags: Int = DEFAULTS,
- ): MangaListModel = toListModelImpl(manga, mode, getOptions(flags))
+ ): MangaListModel = toListModelImpl(
+ manga = manga,
+ mode = mode,
+ options = getOptions(flags),
+ override = dataRepository.getOverride(manga.id),
+ )
fun mapTags(tags: Collection) = tags.map {
ChipsView.ChipModel(
@@ -71,20 +85,28 @@ class MangaListMapper @Inject constructor(
)
}
- private suspend fun toCompactListModel(manga: Manga, @Options options: Int) = MangaCompactListModel(
+ private suspend fun toCompactListModel(
+ manga: Manga,
+ @Options options: Int,
+ override: MangaOverride?,
+ ) = MangaCompactListModel(
id = manga.id,
- title = manga.title,
+ title = override?.title.ifNullOrEmpty { manga.title },
subtitle = manga.tags.joinToString(", ") { it.title },
- coverUrl = manga.coverUrl,
+ coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },
manga = manga,
counter = getCounter(manga.id, options),
)
- private suspend fun toDetailedListModel(manga: Manga, @Options options: Int) = MangaDetailedListModel(
+ private suspend fun toDetailedListModel(
+ manga: Manga,
+ @Options options: Int,
+ override: MangaOverride?,
+ ) = MangaDetailedListModel(
id = manga.id,
- title = manga.title,
- subtitle = manga.altTitle,
- coverUrl = manga.coverUrl,
+ title = override?.title.ifNullOrEmpty { manga.title },
+ subtitle = manga.altTitles.firstOrNull(),
+ coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },
manga = manga,
counter = getCounter(manga.id, options),
progress = getProgress(manga.id, options),
@@ -93,10 +115,14 @@ class MangaListMapper @Inject constructor(
tags = mapTags(manga.tags),
)
- private suspend fun toGridModel(manga: Manga, @Options options: Int) = MangaGridModel(
+ private suspend fun toGridModel(
+ manga: Manga,
+ @Options options: Int,
+ override: MangaOverride?
+ ) = MangaGridModel(
id = manga.id,
- title = manga.title,
- coverUrl = manga.coverUrl,
+ title = override?.title.ifNullOrEmpty { manga.title },
+ coverUrl = override?.coverUrl.ifNullOrEmpty { manga.coverUrl },
manga = manga,
counter = getCounter(manga.id, options),
progress = getProgress(manga.id, options),
@@ -107,11 +133,12 @@ class MangaListMapper @Inject constructor(
private suspend fun toListModelImpl(
manga: Manga,
mode: ListMode,
- @Options options: Int
+ @Options options: Int,
+ override: MangaOverride?,
): MangaListModel = when (mode) {
- ListMode.LIST -> toCompactListModel(manga, options)
- ListMode.DETAILED_LIST -> toDetailedListModel(manga, options)
- ListMode.GRID -> toGridModel(manga, options)
+ ListMode.LIST -> toCompactListModel(manga, options, override)
+ ListMode.DETAILED_LIST -> toDetailedListModel(manga, options, override)
+ ListMode.GRID -> toGridModel(manga, options, override)
}
private suspend fun getCounter(mangaId: Long, @Options options: Int): Int {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
index ab71a388a..3b1b336b5 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListFragment.kt
@@ -275,8 +275,10 @@ abstract class MangaListFragment :
@CallSuper
override fun onPrepareActionMode(controller: ListSelectionController, mode: ActionMode?, menu: Menu): Boolean {
val hasNoLocal = selectedItems.none { it.isLocal }
+ val isSingleSelection = controller.count == 1
menu.findItem(R.id.action_save)?.isVisible = hasNoLocal
menu.findItem(R.id.action_fix)?.isVisible = hasNoLocal
+ menu.findItem(R.id.action_edit_override)?.isVisible = isSingleSelection
return super.onPrepareActionMode(controller, mode, menu)
}
@@ -316,6 +318,12 @@ abstract class MangaListFragment :
true
}
+ R.id.action_edit_override -> {
+ router.openMangaOverrideConfig(selectedItems.singleOrNull() ?: return false)
+ mode?.finish()
+ true
+ }
+
R.id.action_fix -> {
val itemsSnapshot = selectedItemsIds
buildAlertDialog(context ?: return false, isCentered = true) {
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt
index 12d582315..13921f1c2 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/MangaListViewModel.kt
@@ -10,6 +10,7 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
@@ -17,14 +18,13 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.BaseViewModel
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.parsers.model.Manga
abstract class MangaListViewModel(
private val settings: AppSettings,
- private val downloadScheduler: DownloadWorker.Scheduler,
+ private val mangaDataRepository: MangaDataRepository,
) : BaseViewModel() {
abstract val content: StateFlow>
@@ -62,13 +62,14 @@ abstract class MangaListViewModel(
protected fun observeListModeWithTriggers(): Flow = combine(
listMode,
+ mangaDataRepository.observeOverridesTrigger(emitInitialState = true),
settings.observe().filter { key ->
key == AppSettings.KEY_PROGRESS_INDICATORS
|| key == AppSettings.KEY_TRACKER_ENABLED
|| key == AppSettings.KEY_QUICK_FILTER
|| key == AppSettings.KEY_MANGA_LIST_BADGES
}.onStart { emit("") },
- ) { mode, _ ->
+ ) { mode, _, _ ->
mode
}
}
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 421a723e3..cbffb2a7b 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
@@ -6,6 +6,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.SharedFlow
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
@@ -13,7 +14,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
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
@@ -36,22 +36,22 @@ class LocalListViewModel @Inject constructor(
mangaRepositoryFactory: MangaRepository.Factory,
filterCoordinator: FilterCoordinator,
private val settings: AppSettings,
- downloadScheduler: DownloadWorker.Scheduler,
mangaListMapper: MangaListMapper,
private val deleteLocalMangaUseCase: DeleteLocalMangaUseCase,
exploreRepository: ExploreRepository,
@LocalStorageChanges private val localStorageChanges: SharedFlow,
private val localStorageManager: LocalStorageManager,
sourcesRepository: MangaSourcesRepository,
+ mangaDataRepository: MangaDataRepository,
) : RemoteListViewModel(
- savedStateHandle,
- mangaRepositoryFactory,
- filterCoordinator,
- settings,
- mangaListMapper,
- downloadScheduler,
- exploreRepository,
- sourcesRepository,
+ savedStateHandle = savedStateHandle,
+ mangaRepositoryFactory = mangaRepositoryFactory,
+ filterCoordinator = filterCoordinator,
+ settings = settings,
+ mangaListMapper = mangaListMapper,
+ exploreRepository = exploreRepository,
+ sourcesRepository = sourcesRepository,
+ mangaDataRepository = mangaDataRepository,
), SharedPreferences.OnSharedPreferenceChangeListener {
val onMangaRemoved = MutableEventFlow()
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
index 08da88145..fe5d96bb7 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
@@ -106,7 +106,7 @@ class ReaderViewModel @Inject constructor(
init {
selectedBranch.value = savedStateHandle.get(ReaderIntent.EXTRA_BRANCH)
readingState.value = savedStateHandle[ReaderIntent.EXTRA_STATE]
- mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, false) }
+ mangaDetails.value = intent.manga?.let { MangaDetails(it, null, null, null, false) }
}
val readerMode = MutableStateFlow(null)
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 958c5fe4d..f09d5cec9 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
@@ -20,6 +20,7 @@ import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.distinctById
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
@@ -27,7 +28,6 @@ import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.getCauseUrl
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
-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
@@ -53,10 +53,10 @@ open class RemoteListViewModel @Inject constructor(
final override val filterCoordinator: FilterCoordinator,
settings: AppSettings,
protected val mangaListMapper: MangaListMapper,
- downloadScheduler: DownloadWorker.Scheduler,
private val exploreRepository: ExploreRepository,
sourcesRepository: MangaSourcesRepository,
-) : MangaListViewModel(settings, downloadScheduler), FilterCoordinator.Owner {
+ mangaDataRepository: MangaDataRepository
+) : MangaListViewModel(settings, mangaDataRepository), FilterCoordinator.Owner {
val source = MangaSource(savedStateHandle[RemoteListFragment.ARG_SOURCE])
val isRandomLoading = MutableStateFlow(false)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt
new file mode 100644
index 000000000..edaf08d73
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt
@@ -0,0 +1,139 @@
+package org.koitharu.kotatsu.settings.override
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import androidx.activity.result.PickVisualMediaRequest
+import androidx.activity.result.contract.ActivityResultContracts.PickVisualMedia
+import androidx.activity.viewModels
+import androidx.core.view.WindowInsetsCompat
+import androidx.core.view.isVisible
+import coil3.ImageLoader
+import coil3.request.ImageRequest
+import coil3.request.lifecycle
+import coil3.request.target
+import coil3.size.Scale
+import com.google.android.material.snackbar.Snackbar
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.flow.filterNotNull
+import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.ui.BaseActivity
+import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
+import org.koitharu.kotatsu.core.util.ext.consumeAll
+import org.koitharu.kotatsu.core.util.ext.crossfade
+import org.koitharu.kotatsu.core.util.ext.enqueueWith
+import org.koitharu.kotatsu.core.util.ext.getDisplayMessage
+import org.koitharu.kotatsu.core.util.ext.getThemeColor
+import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
+import org.koitharu.kotatsu.core.util.ext.observe
+import org.koitharu.kotatsu.core.util.ext.observeEvent
+import org.koitharu.kotatsu.core.util.ext.tryLaunch
+import org.koitharu.kotatsu.databinding.ActivityOverrideEditBinding
+import org.koitharu.kotatsu.parsers.model.Manga
+import org.koitharu.kotatsu.parsers.util.ifNullOrEmpty
+import javax.inject.Inject
+import androidx.appcompat.R as appcompatR
+import com.google.android.material.R as materialR
+
+@AndroidEntryPoint
+class OverrideConfigActivity : BaseActivity(), View.OnClickListener {
+
+ private val viewModel: OverrideConfigViewModel by viewModels()
+
+ private val pickCoverFileLauncher = registerForActivityResult(
+ PickVisualMedia(),
+ ) { uri ->
+ if (uri != null) {
+ contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ viewModel.updateCover(uri.toString())
+ }
+ }
+
+ @Inject
+ lateinit var coil: ImageLoader
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(ActivityOverrideEditBinding.inflate(layoutInflater))
+ setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true)
+ viewBinding.buttonDone.setOnClickListener(this)
+ viewBinding.buttonPickFile.setOnClickListener(this)
+ viewBinding.buttonPickPage.setOnClickListener(this)
+ viewBinding.buttonResetCover.setOnClickListener(this)
+ viewBinding.layoutName.setEndIconOnClickListener(this)
+ viewModel.data.filterNotNull().observe(this, ::onDataChanged)
+ viewModel.onSaved.observeEvent(this) { finishAfterTransition() }
+ viewModel.isLoading.observe(this, ::onLoadingStateChanged)
+ viewModel.onError.observeEvent(this, ::onError)
+ }
+
+ override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
+ val typeMask = WindowInsetsCompat.Type.systemBars()
+ val barsInsets = insets.getInsets(typeMask)
+ viewBinding.root.setPadding(
+ barsInsets.left,
+ barsInsets.top,
+ barsInsets.right,
+ barsInsets.bottom,
+ )
+ return insets.consumeAll(typeMask)
+ }
+
+ override fun onClick(v: View) {
+ when (v.id) {
+ R.id.button_done -> viewModel.save(
+ title = viewBinding.editName.text?.toString()?.trim(),
+ )
+
+ materialR.id.text_input_end_icon -> viewBinding.editName.text?.clear()
+
+ R.id.button_reset_cover -> viewModel.updateCover(null)
+ R.id.button_pick_file -> {
+ val request = PickVisualMediaRequest.Builder()
+ .setMediaType(PickVisualMedia.ImageOnly)
+ .setAccentColor(getThemeColor(appcompatR.attr.colorAccent).toLong())
+ .build()
+ if (!pickCoverFileLauncher.tryLaunch(request)) {
+ Snackbar.make(
+ viewBinding.imageViewCover,
+ R.string.operation_not_supported,
+ Snackbar.LENGTH_SHORT,
+ ).show()
+ }
+ }
+ }
+ }
+
+ private fun onDataChanged(data: Pair) {
+ val (manga, override) = data
+ ImageRequest.Builder(this)
+ .target(viewBinding.imageViewCover)
+ .size(CoverSizeResolver(viewBinding.imageViewCover))
+ .scale(Scale.FILL)
+ .data(override.coverUrl.ifNullOrEmpty { manga.coverUrl })
+ .mangaSourceExtra(manga.source)
+ .crossfade(this)
+ .lifecycle(this)
+ .enqueueWith(coil)
+ viewBinding.layoutName.placeholderText = manga.title
+ if (viewBinding.editName.tag == null) {
+ viewBinding.editName.setText(override.title)
+ viewBinding.editName.tag = override.title
+ }
+ viewBinding.buttonResetCover.isEnabled = !override.coverUrl.isNullOrEmpty()
+ }
+
+ private fun onError(e: Throwable) {
+ viewBinding.textViewError.text = e.getDisplayMessage(resources)
+ viewBinding.textViewError.isVisible = true
+ }
+
+ private fun onLoadingStateChanged(isLoading: Boolean) {
+ viewBinding.buttonDone.isEnabled = !isLoading
+ viewBinding.editName.isEnabled = !isLoading
+ if (isLoading) {
+ viewBinding.textViewError.isVisible = false
+ }
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt
new file mode 100644
index 000000000..30862cde2
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigViewModel.kt
@@ -0,0 +1,53 @@
+package org.koitharu.kotatsu.settings.override
+
+import androidx.lifecycle.SavedStateHandle
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.MutableStateFlow
+import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
+import org.koitharu.kotatsu.core.nav.AppRouter
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
+import org.koitharu.kotatsu.core.ui.BaseViewModel
+import org.koitharu.kotatsu.core.ui.model.MangaOverride
+import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
+import org.koitharu.kotatsu.core.util.ext.call
+import org.koitharu.kotatsu.core.util.ext.require
+import org.koitharu.kotatsu.parsers.model.Manga
+import javax.inject.Inject
+
+@HiltViewModel
+class OverrideConfigViewModel @Inject constructor(
+ savedStateHandle: SavedStateHandle,
+ private val dataRepository: MangaDataRepository,
+) : BaseViewModel() {
+
+ private val manga = savedStateHandle.require(AppRouter.KEY_MANGA).manga
+
+ val data = MutableStateFlow?>(null)
+ val onSaved = MutableEventFlow()
+
+ init {
+ launchLoadingJob(Dispatchers.Default) {
+ data.value = manga to (dataRepository.getOverride(manga.id) ?: emptyOverride())
+ }
+ }
+
+ fun save(title: String?) {
+ launchLoadingJob(Dispatchers.Default) {
+ val override = checkNotNull(data.value).second.copy(
+ title = title,
+ )
+ dataRepository.setOverride(manga.id, override)
+ onSaved.call(Unit)
+ }
+ }
+
+ fun updateCover(coverUri: String?) {
+ val snapshot = data.value ?: return
+ data.value = snapshot.first to snapshot.second.copy(
+ coverUrl = coverUri,
+ )
+ }
+
+ private fun emptyOverride() = MangaOverride(null, null, null)
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsViewModel.kt
index e1080f5c1..bfccc2d20 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/suggestions/ui/SuggestionsViewModel.kt
@@ -11,10 +11,10 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.util.ext.onFirst
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.domain.QuickFilterListener
import org.koitharu.kotatsu.list.ui.MangaListViewModel
@@ -30,10 +30,10 @@ class SuggestionsViewModel @Inject constructor(
repository: SuggestionRepository,
settings: AppSettings,
private val mangaListMapper: MangaListMapper,
- downloadScheduler: DownloadWorker.Scheduler,
private val quickFilter: SuggestionsListQuickFilter,
private val suggestionsScheduler: SuggestionsWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
+ mangaDataRepository: MangaDataRepository,
+) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_SUGGESTIONS) { suggestionsListMode }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.suggestionsListMode)
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt
index b332edf0a..c0db14a1c 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/updates/UpdatesViewModel.kt
@@ -11,13 +11,13 @@ import kotlinx.coroutines.flow.onStart
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R
+import org.koitharu.kotatsu.core.parser.MangaDataRepository
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.model.DateTimeAgo
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
import org.koitharu.kotatsu.core.util.ext.onFirst
-import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.MangaListMapper
import org.koitharu.kotatsu.list.domain.QuickFilterListener
@@ -38,8 +38,8 @@ class UpdatesViewModel @Inject constructor(
settings: AppSettings,
private val mangaListMapper: MangaListMapper,
private val quickFilter: UpdatesListQuickFilter,
- downloadScheduler: DownloadWorker.Scheduler,
-) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
+ mangaDataRepository: MangaDataRepository,
+) : MangaListViewModel(settings, mangaDataRepository), QuickFilterListener by quickFilter {
override val content = combine(
quickFilter.appliedOptions.flatMapLatest { filterOptions ->
diff --git a/app/src/main/res/drawable/ic_revert.xml b/app/src/main/res/drawable/ic_revert.xml
new file mode 100644
index 000000000..dfe64e9ff
--- /dev/null
+++ b/app/src/main/res/drawable/ic_revert.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
diff --git a/app/src/main/res/layout/activity_override_edit.xml b/app/src/main/res/layout/activity_override_edit.xml
new file mode 100644
index 000000000..4c14239d6
--- /dev/null
+++ b/app/src/main/res/layout/activity_override_edit.xml
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/mode_favourites.xml b/app/src/main/res/menu/mode_favourites.xml
index 347d3cd26..b07663220 100644
--- a/app/src/main/res/menu/mode_favourites.xml
+++ b/app/src/main/res/menu/mode_favourites.xml
@@ -33,6 +33,12 @@
android:title="@string/categories"
app:showAsAction="ifRoom|withText" />
+
+
+
+
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 19a8eab5e..009ab4e5d 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -826,4 +826,9 @@
Highlight dangerous genres
Highlight genres that may be inappropriate for most users
The selected path cannot be used because it does not denote a file or directory
+ These changes will affect how manga is displayed in the app
+ Use default cover
+ Pick manga page
+ Pick custom file
+ Change cover