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 @@ + + + + + +