diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt index d5a413fb2..4dec165f5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt @@ -16,6 +16,7 @@ import androidx.documentfile.provider.DocumentFile import androidx.preference.PreferenceManager import dagger.hilt.android.qualifiers.ApplicationContext import org.json.JSONArray +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.ZoomMode import org.koitharu.kotatsu.core.network.DoHProvider import org.koitharu.kotatsu.core.util.ext.connectivityManager @@ -44,6 +45,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { private val prefs = PreferenceManager.getDefaultSharedPreferences(context) private val connectivityManager = context.connectivityManager + private val mangaListBadgesDefault = ArraySet(context.resources.getStringArray(R.array.values_list_badges)) var listMode: ListMode get() = prefs.getEnumValue(KEY_LIST_MODE, ListMode.GRID) @@ -546,6 +548,15 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { prefs.edit { putString(KEY_PAGES_SAVE_DIR, uri?.toString()) } } + fun getMangaListBadges(): Int { + val raw = prefs.getStringSet(KEY_MANGA_LIST_BADGES, mangaListBadgesDefault).orEmpty() + var result = 0 + for (item in raw) { + result = result or item.toInt() + } + return result + } + fun subscribe(listener: SharedPreferences.OnSharedPreferenceChangeListener) { prefs.registerOnSharedPreferenceChangeListener(listener) } @@ -735,12 +746,12 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) { const val KEY_QUICK_FILTER = "quick_filter" const val KEY_BACKUP_TG_ENABLED = "backup_periodic_tg_enabled" const val KEY_BACKUP_TG_CHAT = "backup_periodic_tg_chat_id" + const val KEY_MANGA_LIST_BADGES = "manga_list_badges" // keys for non-persistent preferences const val KEY_APP_VERSION = "app_version" const val KEY_IGNORE_DOZE = "ignore_dose" const val KEY_TRACKER_DEBUG = "tracker_debug" - const val KEY_APP_UPDATE = "app_update" const val KEY_LINK_WEBLATE = "about_app_translation" const val KEY_LINK_TELEGRAM = "about_telegram" const val KEY_LINK_GITHUB = "about_github" diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Preferences.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Preferences.kt index e2e915619..611d4db01 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Preferences.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Preferences.kt @@ -11,7 +11,7 @@ fun ListPreference.setDefaultValueCompat(defaultValue: String) { } fun MultiSelectListPreference.setDefaultValueCompat(defaultValue: Set) { - setDefaultValue(defaultValue) + setDefaultValue(defaultValue) // FIXME not working } fun > SharedPreferences.getEnumValue(key: String, enumClass: Class): 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 25ba313ce..01acb1b19 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 @@ -146,7 +146,6 @@ class DetailsViewModel @Inject constructor( mangaListMapper.toListModelList( manga = relatedMangaUseCase(it).orEmpty(), mode = ListMode.GRID, - flags = 0, ) } else { emptyList() 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 1c77dc858..3a9e30e9b 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 @@ -52,7 +52,7 @@ class RelatedListViewModel @Inject constructor( list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true)) list == null -> listOf(LoadingState) list.isEmpty() -> listOf(createEmptyState()) - else -> mangaListMapper.toListModelList(list, mode, 0) + else -> mangaListMapper.toListModelList(list, mode) } }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt index 4fc9ae64a..b297c1bd7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreViewModel.kt @@ -204,9 +204,6 @@ class ExploreViewModel @Inject constructor( coverUrl = manga.coverUrl, manga = manga, counter = 0, - progress = null, - isFavorite = false, - isSaved = false, ) } 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 b03725673..81de88924 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 @@ -190,7 +190,7 @@ class HistoryListViewModel @Inject constructor( prevHeader = header } } - result += mangaListMapper.toListModel(manga, mode, 0) + result += mangaListMapper.toListModel(manga, mode) } if (filters.isNotEmpty() && isEmpty) { result += getEmptyState(hasFilters = true) 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 8d918206e..48c0a40c2 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 @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.list.domain +import android.annotation.SuppressLint import android.content.Context import androidx.annotation.ColorRes import androidx.annotation.IntDef @@ -38,67 +39,29 @@ class MangaListMapper @Inject constructor( suspend fun toListModelList( manga: Collection, mode: ListMode, - @Flags flags: Int - ): List = manga.map { - toListModel(it, mode, flags) + @Flags flags: Int = DEFAULTS, + ): List { + val options = getOptions(flags) + return manga.map { toListModelImpl(it, mode, options) } } suspend fun toListModelList( destination: MutableCollection, manga: Collection, mode: ListMode, - @Flags flags: Int, + @Flags flags: Int = DEFAULTS, ) { + val options = getOptions(flags) manga.mapTo(destination) { - toListModel(it, mode, flags) + toListModelImpl(it, mode, options) } } suspend fun toListModel( manga: Manga, mode: ListMode, - @Flags flags: Int - ): MangaListModel = when (mode) { - ListMode.LIST -> toCompactListModel(manga, flags) - ListMode.DETAILED_LIST -> toDetailedListModel(manga, flags) - ListMode.GRID -> toGridModel(manga, flags) - } - - suspend fun toCompactListModel(manga: Manga, @Flags flags: Int) = MangaCompactListModel( - id = manga.id, - title = manga.title, - subtitle = manga.tags.joinToString(", ") { it.title }, - coverUrl = manga.coverUrl, - manga = manga, - counter = getCounter(manga.id, flags), - progress = getProgress(manga.id, flags), - isFavorite = isFavorite(manga.id, flags), - isSaved = isSaved(manga.id, flags), - ) - - suspend fun toDetailedListModel(manga: Manga, @Flags flags: Int) = MangaDetailedListModel( - id = manga.id, - title = manga.title, - subtitle = manga.altTitle, - coverUrl = manga.coverUrl, - manga = manga, - counter = getCounter(manga.id, flags), - progress = getProgress(manga.id, flags), - isFavorite = isFavorite(manga.id, flags), - isSaved = isSaved(manga.id, flags), - tags = mapTags(manga.tags), - ) - - suspend fun toGridModel(manga: Manga, @Flags flags: Int) = MangaGridModel( - id = manga.id, - title = manga.title, - coverUrl = manga.coverUrl, - manga = manga, - counter = getCounter(manga.id, flags), - progress = getProgress(manga.id, flags), - isFavorite = isFavorite(manga.id, flags), - isSaved = isSaved(manga.id, flags), - ) + @Flags flags: Int = DEFAULTS, + ): MangaListModel = toListModelImpl(manga, mode, getOptions(flags)) fun mapTags(tags: Collection) = tags.map { ChipsView.ChipModel( @@ -108,7 +71,50 @@ class MangaListMapper @Inject constructor( ) } - private suspend fun getCounter(mangaId: Long, @Flags flags: Int): Int { + private suspend fun toCompactListModel(manga: Manga, @Options options: Int) = MangaCompactListModel( + id = manga.id, + title = manga.title, + subtitle = manga.tags.joinToString(", ") { it.title }, + coverUrl = manga.coverUrl, + manga = manga, + counter = getCounter(manga.id, options), + ) + + private suspend fun toDetailedListModel(manga: Manga, @Options options: Int) = MangaDetailedListModel( + id = manga.id, + title = manga.title, + subtitle = manga.altTitle, + coverUrl = manga.coverUrl, + manga = manga, + counter = getCounter(manga.id, options), + progress = getProgress(manga.id, options), + isFavorite = isFavorite(manga.id, options), + isSaved = isSaved(manga.id, options), + tags = mapTags(manga.tags), + ) + + private suspend fun toGridModel(manga: Manga, @Options options: Int) = MangaGridModel( + id = manga.id, + title = manga.title, + coverUrl = manga.coverUrl, + manga = manga, + counter = getCounter(manga.id, options), + progress = getProgress(manga.id, options), + isFavorite = isFavorite(manga.id, options), + isSaved = isSaved(manga.id, options), + ) + + private suspend fun toListModelImpl( + manga: Manga, + mode: ListMode, + @Options options: Int + ): MangaListModel = when (mode) { + ListMode.LIST -> toCompactListModel(manga, options) + ListMode.DETAILED_LIST -> toDetailedListModel(manga, options) + ListMode.GRID -> toGridModel(manga, options) + } + + private suspend fun getCounter(mangaId: Long, @Options options: Int): Int { return if (settings.isTrackerEnabled) { trackingRepository.getNewChaptersCount(mangaId) } else { @@ -116,20 +122,20 @@ class MangaListMapper @Inject constructor( } } - private suspend fun getProgress(mangaId: Long, @Flags flags: Int): ReadingProgress? { - return if (flags.hasNoFlag(NO_PROGRESS)) { + private suspend fun getProgress(mangaId: Long, @Options options: Int): ReadingProgress? { + return if (options.isBadgeEnabled(PROGRESS)) { historyRepository.getProgress(mangaId, settings.progressIndicatorMode) } else { null } } - private suspend fun isFavorite(mangaId: Long, @Flags flags: Int): Boolean { - return flags.hasNoFlag(NO_FAVORITE) && favouritesRepository.isFavorite(mangaId) + private suspend fun isFavorite(mangaId: Long, @Options options: Int): Boolean { + return options.isBadgeEnabled(FAVORITE) && favouritesRepository.isFavorite(mangaId) } - private suspend fun isSaved(mangaId: Long, @Flags flags: Int): Boolean { - return flags.hasNoFlag(NO_SAVED) && mangaId in localMangaIndex + private suspend fun isSaved(mangaId: Long, @Options options: Int): Boolean { + return options.isBadgeEnabled(SAVED) && mangaId in localMangaIndex } @ColorRes @@ -154,17 +160,34 @@ class MangaListMapper @Inject constructor( set } - private fun Int.hasNoFlag(flag: Int) = this and flag == 0 + private fun Int.isBadgeEnabled(@Options badge: Int) = this and badge == badge + @Options + @SuppressLint("WrongConstant") + private fun getOptions(@Flags flags: Int): Int { + var options = settings.getMangaListBadges() or PROGRESS + options = options and flags.inv() + return options + } - @IntDef(0, NO_SAVED, NO_PROGRESS, NO_FAVORITE) + @IntDef(DEFAULTS, NO_SAVED, NO_PROGRESS, NO_FAVORITE, flag = true) @Retention(AnnotationRetention.SOURCE) annotation class Flags + @IntDef(NONE, SAVED, FAVORITE, PROGRESS) + @Retention(AnnotationRetention.SOURCE) + private annotation class Options + companion object { - const val NO_SAVED = 1 - const val NO_PROGRESS = 2 - const val NO_FAVORITE = 4 + private const val NONE = 0 + private const val SAVED = 1 + private const val PROGRESS = 2 + private const val FAVORITE = 4 + + const val DEFAULTS = NONE + const val NO_SAVED = SAVED + const val NO_PROGRESS = PROGRESS + const val NO_FAVORITE = FAVORITE } } 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 372edaba8..12d582315 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 @@ -66,6 +66,7 @@ abstract class MangaListViewModel( 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 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt index 8d6aa7688..52e4e1303 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListDetailedItemAD.kt @@ -6,6 +6,7 @@ import coil3.ImageLoader import coil3.request.allowRgb565 import coil3.request.transformations import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding +import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter @@ -36,6 +37,12 @@ fun mangaListDetailedItemAD( value = item.progress, animate = ListModelDiffCallback.PAYLOAD_PROGRESS_CHANGED in payloads, ) + with(binding.iconsView) { + clearIcons() + if (item.isSaved) addIcon(R.drawable.ic_storage) + if (item.isFavorite) addIcon(R.drawable.ic_heart_outline) + isVisible = iconsCount > 0 + } binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { size(CoverSizeResolver(binding.imageViewCover)) defaultPlaceholders(context) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaCompactListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaCompactListModel.kt index c4145c330..0a9489ed0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaCompactListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaCompactListModel.kt @@ -1,6 +1,5 @@ package org.koitharu.kotatsu.list.ui.model -import org.koitharu.kotatsu.list.domain.ReadingProgress import org.koitharu.kotatsu.parsers.model.Manga data class MangaCompactListModel( @@ -10,7 +9,4 @@ data class MangaCompactListModel( override val coverUrl: String?, override val manga: Manga, override val counter: Int, - override val progress: ReadingProgress?, - override val isFavorite: Boolean, - override val isSaved: Boolean, ) : MangaListModel() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaDetailedListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaDetailedListModel.kt index 53bfaf7fc..d1a8ad739 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaDetailedListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaDetailedListModel.kt @@ -2,6 +2,8 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.core.ui.widgets.ChipsView import org.koitharu.kotatsu.list.domain.ReadingProgress +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED import org.koitharu.kotatsu.parsers.model.Manga data class MangaDetailedListModel( @@ -11,8 +13,19 @@ data class MangaDetailedListModel( override val coverUrl: String?, override val manga: Manga, override val counter: Int, - override val progress: ReadingProgress?, - override val isFavorite: Boolean, - override val isSaved: Boolean, + val progress: ReadingProgress?, + val isFavorite: Boolean, + val isSaved: Boolean, val tags: List, -) : MangaListModel() +) : MangaListModel() { + + override fun getChangePayload(previousState: ListModel): Any? = when { + previousState !is MangaDetailedListModel || previousState.manga != manga -> null + + previousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED + previousState.isFavorite != isFavorite || + previousState.isSaved != isSaved -> PAYLOAD_ANYTHING_CHANGED + + else -> super.getChangePayload(previousState) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt index 26f6e99a8..120149e91 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaGridModel.kt @@ -1,6 +1,8 @@ package org.koitharu.kotatsu.list.ui.model import org.koitharu.kotatsu.list.domain.ReadingProgress +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED +import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED import org.koitharu.kotatsu.parsers.model.Manga data class MangaGridModel( @@ -9,7 +11,18 @@ data class MangaGridModel( override val coverUrl: String?, override val manga: Manga, override val counter: Int, - override val progress: ReadingProgress?, - override val isFavorite: Boolean, - override val isSaved: Boolean, -) : MangaListModel() + val progress: ReadingProgress?, + val isFavorite: Boolean, + val isSaved: Boolean, +) : MangaListModel() { + + override fun getChangePayload(previousState: ListModel): Any? = when { + previousState !is MangaGridModel || previousState.manga != manga -> null + + previousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED + previousState.isFavorite != isFavorite || + previousState.isSaved != isSaved -> PAYLOAD_ANYTHING_CHANGED + + else -> super.getChangePayload(previousState) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt index 06c804898..1be7efdd6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/model/MangaListModel.kt @@ -1,8 +1,6 @@ package org.koitharu.kotatsu.list.ui.model -import org.koitharu.kotatsu.list.domain.ReadingProgress import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_ANYTHING_CHANGED -import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource @@ -13,9 +11,6 @@ sealed class MangaListModel : ListModel { abstract val title: String abstract val coverUrl: String? abstract val counter: Int - abstract val isFavorite: Boolean - abstract val isSaved: Boolean - abstract val progress: ReadingProgress? val source: MangaSource get() = manga.source @@ -26,12 +21,7 @@ sealed class MangaListModel : ListModel { override fun getChangePayload(previousState: ListModel): Any? = when { previousState !is MangaListModel || previousState.manga != manga -> null - - previousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED - previousState.isFavorite != isFavorite || - previousState.isSaved != isSaved || - previousState.counter != counter -> PAYLOAD_ANYTHING_CHANGED - + previousState.counter != counter -> PAYLOAD_ANYTHING_CHANGED else -> 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 48b6c1062..5c4d8d8dd 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 @@ -37,7 +37,6 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingState -import org.koitharu.kotatsu.list.ui.model.MangaListModel import org.koitharu.kotatsu.list.ui.model.toErrorFooter import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.parsers.model.Manga @@ -177,7 +176,7 @@ open class RemoteListViewModel @Inject constructor( destination: MutableCollection, manga: Collection, mode: ListMode - ) = mangaListMapper.toListModelList(destination, manga, mode, 0) + ) = mangaListMapper.toListModelList(destination, manga, mode) fun openRandom() { if (randomJob?.isActive == true) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchViewModel.kt index 2a207c2ce..ac5cbef66 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchViewModel.kt @@ -126,7 +126,6 @@ class SearchViewModel @Inject constructor( mangaListMapper.toListModelList( manga = repository.getList(offset = 0, null, MangaListFilter(query = q)), mode = ListMode.GRID, - flags = 0, ) } }.fold( @@ -162,7 +161,7 @@ class SearchViewModel @Inject constructor( titleResId = R.string.history, source = UnknownMangaSource, hasMore = false, - list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID, flags = 0), + list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), error = null, ) } else { @@ -191,7 +190,7 @@ class SearchViewModel @Inject constructor( titleResId = R.string.favourites, source = UnknownMangaSource, hasMore = false, - list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID, flags = 0), + list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), error = null, ) } else { @@ -220,7 +219,7 @@ class SearchViewModel @Inject constructor( titleResId = 0, source = LocalMangaSource, hasMore = result.size > MIN_HAS_MORE_ITEMS, - list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID,flags = 0), + list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), error = null, ) } else { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt index f00a95fa4..9a0b7b6fe 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/AppearanceSettingsFragment.kt @@ -9,6 +9,7 @@ import android.provider.Settings import android.view.View import androidx.appcompat.app.AppCompatDelegate import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference import androidx.preference.Preference import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R @@ -26,6 +27,7 @@ import org.koitharu.kotatsu.core.util.ext.toList import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.settings.utils.ActivityListPreference +import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.settings.utils.PercentSummaryProvider import org.koitharu.kotatsu.settings.utils.SliderPreference import javax.inject.Inject @@ -63,6 +65,9 @@ class AppearanceSettingsFragment : } setDefaultValueCompat("") } + findPreference(AppSettings.KEY_MANGA_LIST_BADGES)?.run { + summaryProvider = MultiSummaryProvider(R.string.none) + } bindNavSummary() } @@ -84,7 +89,7 @@ class AppearanceSettingsFragment : AppSettings.KEY_COLOR_THEME, AppSettings.KEY_THEME_AMOLED, - -> { + -> { postRestart() } 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 fe933b5e1..e1080f5c1 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 @@ -67,7 +67,7 @@ class SuggestionsViewModel @Inject constructor( else -> buildList(list.size + 1) { quickFilter.filterItem(filters)?.let(::add) - mangaListMapper.toListModelList(this, list, mode, 0) + mangaListMapper.toListModelList(this, list, mode) } } }.onStart { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackEntity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackEntity.kt index 127a60b4c..925508f1d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackEntity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/data/TrackEntity.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.tracker.data +import androidx.annotation.IntDef import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.ForeignKey @@ -24,10 +25,15 @@ class TrackEntity( @ColumnInfo(name = "chapters_new") val newChapters: Int, @ColumnInfo(name = "last_check_time") val lastCheckTime: Long, @ColumnInfo(name = "last_chapter_date") val lastChapterDate: Long, + @TrackerResult @ColumnInfo(name = "last_result") val lastResult: Int, @ColumnInfo(name = "last_error") val lastError: String?, ) { + @IntDef(RESULT_NONE, RESULT_HAS_UPDATE, RESULT_NO_UPDATE, RESULT_FAILED, RESULT_EXTERNAL_MODIFICATION) + @Retention(AnnotationRetention.SOURCE) + annotation class TrackerResult + companion object { const val RESULT_NONE = 0 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt index 838b3a9c8..662cd08ed 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedFragment.kt @@ -11,7 +11,6 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import coil3.ImageLoader -import com.google.android.material.snackbar.Snackbar import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.drop import org.koitharu.kotatsu.R @@ -21,6 +20,7 @@ import org.koitharu.kotatsu.core.ui.BaseFragment import org.koitharu.kotatsu.core.ui.list.PaginationScrollListener import org.koitharu.kotatsu.core.ui.list.RecyclerScrollKeeper import org.koitharu.kotatsu.core.ui.util.MenuInvalidator +import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.ui.widgets.TipView import org.koitharu.kotatsu.core.util.ext.addMenuProvider import org.koitharu.kotatsu.core.util.ext.observe @@ -31,7 +31,6 @@ import org.koitharu.kotatsu.list.ui.adapter.MangaListListener import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.list.ui.size.StaticItemSizeResolver -import org.koitharu.kotatsu.main.ui.owners.BottomNavOwner import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.tracker.ui.feed.adapter.FeedAdapter @@ -76,7 +75,7 @@ class FeedFragment : viewModel.isHeaderEnabled.drop(1).observe(viewLifecycleOwner, MenuInvalidator(requireActivity())) viewModel.content.observe(viewLifecycleOwner, feedAdapter) viewModel.onError.observeEvent(viewLifecycleOwner, SnackbarErrorObserver(binding.recyclerView, this)) - viewModel.onFeedCleared.observeEvent(viewLifecycleOwner) { onFeedCleared() } + viewModel.onActionDone.observeEvent(viewLifecycleOwner, ReversibleActionObserver(binding.recyclerView)) viewModel.isRunning.observe(viewLifecycleOwner, this::onIsTrackerRunningChanged) } @@ -107,16 +106,6 @@ class FeedFragment : router.openMangaUpdates() } - private fun onFeedCleared() { - val snackbar = Snackbar.make( - requireViewBinding().recyclerView, - R.string.updates_feed_cleared, - Snackbar.LENGTH_LONG, - ) - snackbar.anchorView = (activity as? BottomNavOwner)?.bottomNav - snackbar.show() - } - private fun onIsTrackerRunningChanged(isRunning: Boolean) { requireViewBinding().swipeRefreshLayout.isRefreshing = isRunning } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt index 27869f4ad..f2c23cbdc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/FeedViewModel.kt @@ -16,10 +16,12 @@ import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R 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.prefs.observeAsStateFlow import org.koitharu.kotatsu.core.ui.BaseViewModel import org.koitharu.kotatsu.core.ui.model.DateTimeAgo +import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo import org.koitharu.kotatsu.core.util.ext.call @@ -64,7 +66,7 @@ class FeedViewModel @Inject constructor( valueProducer = { isFeedHeaderVisible }, ) - val onFeedCleared = MutableEventFlow() + val onActionDone = MutableEventFlow() @Suppress("USELESS_CAST") val content = combine( @@ -106,7 +108,7 @@ class FeedViewModel @Inject constructor( if (clearCounters) { repository.clearCounters() } - onFeedCleared.call(Unit) + onActionDone.call(ReversibleAction(R.string.updates_feed_cleared, null)) } } @@ -151,7 +153,7 @@ class FeedViewModel @Inject constructor( null } else { UpdatedMangaHeader( - mangaList.map { mangaListMapper.toGridModel(it.manga, 0) }, + mangaList.map { mangaListMapper.toListModel(it.manga, ListMode.GRID) }, ) } } 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 9cd35d993..b332edf0a 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 @@ -107,7 +107,7 @@ class UpdatesViewModel @Inject constructor( prevHeader = header } } - result += mangaListMapper.toListModel(item.manga, mode, 0) + result += mangaListMapper.toListModel(item.manga, mode) } return result } diff --git a/app/src/main/res/layout/item_manga_grid.xml b/app/src/main/res/layout/item_manga_grid.xml index de4a9bc3f..e5f7a3baa 100644 --- a/app/src/main/res/layout/item_manga_grid.xml +++ b/app/src/main/res/layout/item_manga_grid.xml @@ -38,13 +38,13 @@ android:id="@+id/iconsView" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_gravity="bottom|start" - android:layout_marginBottom="@dimen/card_indicator_offset" + android:layout_gravity="top|start" + android:layout_marginTop="@dimen/card_indicator_offset" android:background="@drawable/bg_list_icons" android:orientation="horizontal" android:padding="4dp" - app:iconSize="12dp" - app:iconSpacing="2dp" /> + app:iconSize="14dp" + app:iconSpacing="4dp" /> + + @string/screen_orientation @string/save_page + + @string/favourites + @string/saved_manga + diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml index ab1537dba..50715b413 100644 --- a/app/src/main/res/values/constants.xml +++ b/app/src/main/res/values/constants.xml @@ -77,4 +77,9 @@ 1 2 + + + 4 + 1 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 90f653468..79a600ac3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -800,4 +800,5 @@ Page switch slider Screen rotation has been locked Screen rotation has been unlocked + Badges in lists diff --git a/app/src/main/res/xml/pref_appearance.xml b/app/src/main/res/xml/pref_appearance.xml index 27d3808e3..c49cbc17a 100644 --- a/app/src/main/res/xml/pref_appearance.xml +++ b/app/src/main/res/xml/pref_appearance.xml @@ -51,6 +51,13 @@ android:title="@string/show_reading_indicators" app:useSimpleSummaryProvider="true" /> + +