Configurable manga lists badges
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -11,7 +11,7 @@ fun ListPreference.setDefaultValueCompat(defaultValue: String) {
|
||||
}
|
||||
|
||||
fun MultiSelectListPreference.setDefaultValueCompat(defaultValue: Set<String>) {
|
||||
setDefaultValue(defaultValue)
|
||||
setDefaultValue(defaultValue) // FIXME not working
|
||||
}
|
||||
|
||||
fun <E : Enum<E>> SharedPreferences.getEnumValue(key: String, enumClass: Class<E>): E? {
|
||||
|
||||
@@ -146,7 +146,6 @@ class DetailsViewModel @Inject constructor(
|
||||
mangaListMapper.toListModelList(
|
||||
manga = relatedMangaUseCase(it).orEmpty(),
|
||||
mode = ListMode.GRID,
|
||||
flags = 0,
|
||||
)
|
||||
} else {
|
||||
emptyList()
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -204,9 +204,6 @@ class ExploreViewModel @Inject constructor(
|
||||
coverUrl = manga.coverUrl,
|
||||
manga = manga,
|
||||
counter = 0,
|
||||
progress = null,
|
||||
isFavorite = false,
|
||||
isSaved = false,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<Manga>,
|
||||
mode: ListMode,
|
||||
@Flags flags: Int
|
||||
): List<MangaListModel> = manga.map {
|
||||
toListModel(it, mode, flags)
|
||||
@Flags flags: Int = DEFAULTS,
|
||||
): List<MangaListModel> {
|
||||
val options = getOptions(flags)
|
||||
return manga.map { toListModelImpl(it, mode, options) }
|
||||
}
|
||||
|
||||
suspend fun toListModelList(
|
||||
destination: MutableCollection<in MangaListModel>,
|
||||
manga: Collection<Manga>,
|
||||
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<MangaTag>) = 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ChipsView.ChipModel>,
|
||||
) : 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<in ListModel>,
|
||||
manga: Collection<Manga>,
|
||||
mode: ListMode
|
||||
) = mangaListMapper.toListModelList(destination, manga, mode, 0)
|
||||
) = mangaListMapper.toListModelList(destination, manga, mode)
|
||||
|
||||
fun openRandom() {
|
||||
if (randomJob?.isActive == true) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<MultiSelectListPreference>(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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<Unit>()
|
||||
val onActionDone = MutableEventFlow<ReversibleAction>()
|
||||
|
||||
@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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.BadgeView
|
||||
android:id="@+id/badge"
|
||||
|
||||
@@ -32,9 +32,22 @@
|
||||
app:layout_constraintBottom_toBottomOf="@id/imageView_cover"
|
||||
app:layout_constraintEnd_toEndOf="@id/imageView_cover" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.IconsView
|
||||
android:id="@+id/iconsView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="@dimen/card_indicator_offset"
|
||||
android:background="@drawable/bg_list_icons"
|
||||
android:orientation="horizontal"
|
||||
android:padding="4dp"
|
||||
app:iconSize="14dp"
|
||||
app:iconSpacing="4dp"
|
||||
app:layout_constraintStart_toStartOf="@id/imageView_cover"
|
||||
app:layout_constraintTop_toTopOf="@id/imageView_cover" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:orientation="vertical"
|
||||
|
||||
@@ -132,4 +132,8 @@
|
||||
<item>@string/screen_orientation</item>
|
||||
<item>@string/save_page</item>
|
||||
</string-array>
|
||||
<string-array name="list_badges" translatable="false">
|
||||
<item>@string/favourites</item>
|
||||
<item>@string/saved_manga</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -77,4 +77,9 @@
|
||||
<item>1</item>
|
||||
<item>2</item>
|
||||
</string-array>
|
||||
<string-array name="values_list_badges" translatable="false">
|
||||
<!-- MangaListMapper flags -->
|
||||
<item>4</item>
|
||||
<item>1</item>
|
||||
</string-array>
|
||||
</resources>
|
||||
|
||||
@@ -800,4 +800,5 @@
|
||||
<string name="pages_slider">Page switch slider</string>
|
||||
<string name="screen_rotation_locked">Screen rotation has been locked</string>
|
||||
<string name="screen_rotation_unlocked">Screen rotation has been unlocked</string>
|
||||
<string name="badges_in_lists">Badges in lists</string>
|
||||
</resources>
|
||||
|
||||
@@ -51,6 +51,13 @@
|
||||
android:title="@string/show_reading_indicators"
|
||||
app:useSimpleSummaryProvider="true" />
|
||||
|
||||
<MultiSelectListPreference
|
||||
android:defaultValue="@array/values_list_badges"
|
||||
android:entries="@array/list_badges"
|
||||
android:entryValues="@array/values_list_badges"
|
||||
android:key="manga_list_badges"
|
||||
android:title="@string/badges_in_lists" />
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/details">
|
||||
|
||||
Reference in New Issue
Block a user