Saved and favorites indicators in manga lists (Draft implementation)(#1286)

master
Koitharu 1 year ago
parent cb5df0d73f
commit d558c2fcc0
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -0,0 +1,90 @@
package org.koitharu.kotatsu.core.ui.widgets
import android.content.Context
import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.widget.ImageView
import android.widget.LinearLayout
import androidx.annotation.DrawableRes
import androidx.core.content.withStyledAttributes
import androidx.core.view.isVisible
import org.koitharu.kotatsu.R
class IconsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : LinearLayout(context, attrs) {
private var iconSize = LinearLayout.LayoutParams.WRAP_CONTENT
private var iconSpacing = 0
val iconsCount: Int
get() {
var count = 0
repeat(childCount) { i ->
if (getChildAt(i).isVisible) {
count++
}
}
return count
}
init {
context.withStyledAttributes(attrs, R.styleable.IconsView) {
iconSize = getDimensionPixelSize(R.styleable.IconsView_iconSize, iconSize)
iconSpacing = getDimensionPixelOffset(R.styleable.IconsView_iconSpacing, iconSpacing)
}
}
fun setIcons(icons: Iterable<Drawable>) {
var index = 0
for (icon in icons) {
val imageView = (getChildAt(index) as ImageView?) ?: addImageView()
imageView.setImageDrawable(icon)
imageView.isVisible = true
index++
}
for (i in index until childCount) {
val imageView = getChildAt(i) as? ImageView ?: continue
imageView.setImageDrawable(null)
imageView.isVisible = false
}
}
fun clearIcons() {
repeat(childCount) { i ->
getChildAt(i).isVisible = false
}
}
fun addIcon(drawable: Drawable) {
val imageView = getNextImageView()
imageView.setImageDrawable(drawable)
imageView.isVisible = true
}
fun addIcon(@DrawableRes resId: Int) {
val imageView = getNextImageView()
imageView.setImageResource(resId)
imageView.isVisible = true
}
private fun getNextImageView(): ImageView {
repeat(childCount) { i ->
val child = getChildAt(i)
if (child is ImageView && !child.isVisible) {
return child
}
}
return addImageView()
}
private fun addImageView() = ImageView(context).also {
it.scaleType = ImageView.ScaleType.FIT_CENTER
val lp = LayoutParams(iconSize, iconSize)
if (childCount != 0) {
lp.marginStart = iconSpacing
}
addView(it, lp)
}
}

@ -146,6 +146,7 @@ class DetailsViewModel @Inject constructor(
mangaListMapper.toListModelList( mangaListMapper.toListModelList(
manga = relatedMangaUseCase(it).orEmpty(), manga = relatedMangaUseCase(it).orEmpty(),
mode = ListMode.GRID, mode = ListMode.GRID,
flags = 0,
) )
} else { } else {
emptyList() emptyList()

@ -52,7 +52,7 @@ class RelatedListViewModel @Inject constructor(
list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true)) list.isNullOrEmpty() && error != null -> listOf(error.toErrorState(canRetry = true))
list == null -> listOf(LoadingState) list == null -> listOf(LoadingState)
list.isEmpty() -> listOf(createEmptyState()) list.isEmpty() -> listOf(createEmptyState())
else -> mangaListMapper.toListModelList(list, mode) else -> mangaListMapper.toListModelList(list, mode, 0)
} }
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))

@ -206,6 +206,7 @@ class ExploreViewModel @Inject constructor(
counter = 0, counter = 0,
progress = null, progress = null,
isFavorite = false, isFavorite = false,
isSaved = false,
) )
} }

@ -144,7 +144,7 @@ class FavouritesListViewModel @Inject constructor(
} }
val result = ArrayList<ListModel>(size + 1) val result = ArrayList<ListModel>(size + 1)
quickFilter.filterItem(filters)?.let(result::add) quickFilter.filterItem(filters)?.let(result::add)
mangaListMapper.toListModelList(result, this, mode) mangaListMapper.toListModelList(result, this, mode, MangaListMapper.NO_FAVORITE)
return result return result
} }

@ -190,7 +190,7 @@ class HistoryListViewModel @Inject constructor(
prevHeader = header prevHeader = header
} }
} }
result += mangaListMapper.toListModel(manga, mode) result += mangaListMapper.toListModel(manga, mode, 0)
} }
if (filters.isNotEmpty() && isEmpty) { if (filters.isNotEmpty() && isEmpty) {
result += getEmptyState(hasFilters = true) result += getEmptyState(hasFilters = true)

@ -2,6 +2,7 @@ package org.koitharu.kotatsu.list.domain
import android.content.Context import android.content.Context
import androidx.annotation.ColorRes import androidx.annotation.ColorRes
import androidx.annotation.IntDef
import androidx.collection.MutableScatterSet import androidx.collection.MutableScatterSet
import androidx.collection.ScatterSet import androidx.collection.ScatterSet
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
@ -15,6 +16,7 @@ import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel
import org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel import org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel
import org.koitharu.kotatsu.list.ui.model.MangaGridModel import org.koitharu.kotatsu.list.ui.model.MangaGridModel
import org.koitharu.kotatsu.list.ui.model.MangaListModel 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.Manga
import org.koitharu.kotatsu.parsers.model.MangaTag import org.koitharu.kotatsu.parsers.model.MangaTag
import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.tracker.domain.TrackingRepository
@ -28,59 +30,74 @@ class MangaListMapper @Inject constructor(
private val trackingRepository: TrackingRepository, private val trackingRepository: TrackingRepository,
private val historyRepository: HistoryRepository, private val historyRepository: HistoryRepository,
private val favouritesRepository: FavouritesRepository, private val favouritesRepository: FavouritesRepository,
private val localMangaIndex: LocalMangaIndex,
) { ) {
private val dict by lazy { readTagsDict(context) } private val dict by lazy { readTagsDict(context) }
suspend fun toListModelList(manga: Collection<Manga>, mode: ListMode): List<MangaListModel> = manga.map { suspend fun toListModelList(
toListModel(it, mode) manga: Collection<Manga>,
mode: ListMode,
@Flags flags: Int
): List<MangaListModel> = manga.map {
toListModel(it, mode, flags)
} }
suspend fun toListModelList( suspend fun toListModelList(
destination: MutableCollection<in MangaListModel>, destination: MutableCollection<in MangaListModel>,
manga: Collection<Manga>, manga: Collection<Manga>,
mode: ListMode mode: ListMode,
) = manga.mapTo(destination) { @Flags flags: Int,
toListModel(it, mode) ) {
manga.mapTo(destination) {
toListModel(it, mode, flags)
}
} }
suspend fun toListModel(manga: Manga, mode: ListMode): MangaListModel = when (mode) { suspend fun toListModel(
ListMode.LIST -> toCompactListModel(manga) manga: Manga,
ListMode.DETAILED_LIST -> toDetailedListModel(manga) mode: ListMode,
ListMode.GRID -> toGridModel(manga) @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) = MangaCompactListModel( suspend fun toCompactListModel(manga: Manga, @Flags flags: Int) = MangaCompactListModel(
id = manga.id, id = manga.id,
title = manga.title, title = manga.title,
subtitle = manga.tags.joinToString(", ") { it.title }, subtitle = manga.tags.joinToString(", ") { it.title },
coverUrl = manga.coverUrl, coverUrl = manga.coverUrl,
manga = manga, manga = manga,
counter = getCounter(manga.id), counter = getCounter(manga.id, flags),
progress = getProgress(manga.id), progress = getProgress(manga.id, flags),
isFavorite = isFavorite(manga.id), isFavorite = isFavorite(manga.id, flags),
isSaved = isSaved(manga.id, flags),
) )
suspend fun toDetailedListModel(manga: Manga) = MangaDetailedListModel( suspend fun toDetailedListModel(manga: Manga, @Flags flags: Int) = MangaDetailedListModel(
id = manga.id, id = manga.id,
title = manga.title, title = manga.title,
subtitle = manga.altTitle, subtitle = manga.altTitle,
coverUrl = manga.coverUrl, coverUrl = manga.coverUrl,
manga = manga, manga = manga,
counter = getCounter(manga.id), counter = getCounter(manga.id, flags),
progress = getProgress(manga.id), progress = getProgress(manga.id, flags),
isFavorite = isFavorite(manga.id), isFavorite = isFavorite(manga.id, flags),
isSaved = isSaved(manga.id, flags),
tags = mapTags(manga.tags), tags = mapTags(manga.tags),
) )
suspend fun toGridModel(manga: Manga) = MangaGridModel( suspend fun toGridModel(manga: Manga, @Flags flags: Int) = MangaGridModel(
id = manga.id, id = manga.id,
title = manga.title, title = manga.title,
coverUrl = manga.coverUrl, coverUrl = manga.coverUrl,
manga = manga, manga = manga,
counter = getCounter(manga.id), counter = getCounter(manga.id, flags),
progress = getProgress(manga.id), progress = getProgress(manga.id, flags),
isFavorite = isFavorite(manga.id), isFavorite = isFavorite(manga.id, flags),
isSaved = isSaved(manga.id, flags),
) )
fun mapTags(tags: Collection<MangaTag>) = tags.map { fun mapTags(tags: Collection<MangaTag>) = tags.map {
@ -91,7 +108,7 @@ class MangaListMapper @Inject constructor(
) )
} }
private suspend fun getCounter(mangaId: Long): Int { private suspend fun getCounter(mangaId: Long, @Flags flags: Int): Int {
return if (settings.isTrackerEnabled) { return if (settings.isTrackerEnabled) {
trackingRepository.getNewChaptersCount(mangaId) trackingRepository.getNewChaptersCount(mangaId)
} else { } else {
@ -99,12 +116,20 @@ class MangaListMapper @Inject constructor(
} }
} }
private suspend fun getProgress(mangaId: Long): ReadingProgress? { private suspend fun getProgress(mangaId: Long, @Flags flags: Int): ReadingProgress? {
return historyRepository.getProgress(mangaId, settings.progressIndicatorMode) return if (flags.hasNoFlag(NO_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 fun isFavorite(mangaId: Long): Boolean { private suspend fun isSaved(mangaId: Long, @Flags flags: Int): Boolean {
return false // TODO favouritesRepository.isFavorite(mangaId) return flags.hasNoFlag(NO_SAVED) && mangaId in localMangaIndex
} }
@ColorRes @ColorRes
@ -128,4 +153,18 @@ class MangaListMapper @Inject constructor(
set.trim() set.trim()
set set
} }
private fun Int.hasNoFlag(flag: Int) = this and flag == 0
@IntDef(0, NO_SAVED, NO_PROGRESS, NO_FAVORITE)
@Retention(AnnotationRetention.SOURCE)
annotation class Flags
companion object {
const val NO_SAVED = 1
const val NO_PROGRESS = 2
const val NO_FAVORITE = 4
}
} }

@ -6,6 +6,7 @@ import coil3.ImageLoader
import coil3.request.allowRgb565 import coil3.request.allowRgb565
import coil3.request.transformations import coil3.request.transformations
import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding 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.CoverSizeResolver
import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.image.TrimTransformation
import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter
@ -36,7 +37,12 @@ fun mangaGridItemAD(
bind { payloads -> bind { payloads ->
binding.textViewTitle.text = item.title binding.textViewTitle.text = item.title
binding.progressView.setProgress(item.progress, PAYLOAD_PROGRESS_CHANGED in payloads) binding.progressView.setProgress(item.progress, PAYLOAD_PROGRESS_CHANGED in payloads)
binding.imageViewFavorite.isVisible = item.isFavorite 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 { binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run {
size(CoverSizeResolver(binding.imageViewCover)) size(CoverSizeResolver(binding.imageViewCover))
defaultPlaceholders(context) defaultPlaceholders(context)

@ -12,4 +12,5 @@ data class MangaCompactListModel(
override val counter: Int, override val counter: Int,
override val progress: ReadingProgress?, override val progress: ReadingProgress?,
override val isFavorite: Boolean, override val isFavorite: Boolean,
override val isSaved: Boolean,
) : MangaListModel() ) : MangaListModel()

@ -13,5 +13,6 @@ data class MangaDetailedListModel(
override val counter: Int, override val counter: Int,
override val progress: ReadingProgress?, override val progress: ReadingProgress?,
override val isFavorite: Boolean, override val isFavorite: Boolean,
override val isSaved: Boolean,
val tags: List<ChipsView.ChipModel>, val tags: List<ChipsView.ChipModel>,
) : MangaListModel() ) : MangaListModel()

@ -11,4 +11,5 @@ data class MangaGridModel(
override val counter: Int, override val counter: Int,
override val progress: ReadingProgress?, override val progress: ReadingProgress?,
override val isFavorite: Boolean, override val isFavorite: Boolean,
override val isSaved: Boolean,
) : MangaListModel() ) : MangaListModel()

@ -14,6 +14,7 @@ sealed class MangaListModel : ListModel {
abstract val coverUrl: String? abstract val coverUrl: String?
abstract val counter: Int abstract val counter: Int
abstract val isFavorite: Boolean abstract val isFavorite: Boolean
abstract val isSaved: Boolean
abstract val progress: ReadingProgress? abstract val progress: ReadingProgress?
val source: MangaSource val source: MangaSource
@ -27,7 +28,9 @@ sealed class MangaListModel : ListModel {
previousState !is MangaListModel || previousState.manga != manga -> null previousState !is MangaListModel || previousState.manga != manga -> null
previousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED previousState.progress != progress -> PAYLOAD_PROGRESS_CHANGED
previousState.isFavorite != isFavorite || previousState.counter != counter -> PAYLOAD_ANYTHING_CHANGED previousState.isFavorite != isFavorite ||
previousState.isSaved != isSaved ||
previousState.counter != counter -> PAYLOAD_ANYTHING_CHANGED
else -> null else -> null
} }

@ -73,6 +73,10 @@ class LocalMangaIndex @Inject constructor(
}.getOrNull() }.getOrNull()
} }
suspend operator fun contains(mangaId: Long): Boolean {
return db.getLocalMangaIndexDao().findPath(mangaId) != null
}
suspend fun put(manga: LocalManga) = mutex.withLock { suspend fun put(manga: LocalManga) = mutex.withLock {
db.withTransaction { db.withTransaction {
upsert(manga) upsert(manga)

@ -8,6 +8,7 @@ import kotlinx.coroutines.flow.SharedFlow
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.toFileOrNull import org.koitharu.kotatsu.core.util.ext.toFileOrNull
@ -25,6 +26,7 @@ import org.koitharu.kotatsu.local.data.LocalStorageChanges
import org.koitharu.kotatsu.local.data.LocalStorageManager import org.koitharu.kotatsu.local.data.LocalStorageManager
import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase import org.koitharu.kotatsu.local.domain.DeleteLocalMangaUseCase
import org.koitharu.kotatsu.local.domain.model.LocalManga import org.koitharu.kotatsu.local.domain.model.LocalManga
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel import org.koitharu.kotatsu.remotelist.ui.RemoteListViewModel
import javax.inject.Inject import javax.inject.Inject
@ -107,6 +109,12 @@ class LocalListViewModel @Inject constructor(
} }
} }
override suspend fun mapMangaList(
destination: MutableCollection<in ListModel>,
manga: Collection<Manga>,
mode: ListMode
) = mangaListMapper.toListModelList(destination, manga, mode, MangaListMapper.NO_SAVED)
override fun createEmptyState(canResetFilter: Boolean): EmptyState = if (canResetFilter) { override fun createEmptyState(canResetFilter: Boolean): EmptyState = if (canResetFilter) {
super.createEmptyState(true) super.createEmptyState(true)
} else { } else {

@ -22,6 +22,7 @@ import org.koitharu.kotatsu.core.model.MangaSource
import org.koitharu.kotatsu.core.model.distinctById import org.koitharu.kotatsu.core.model.distinctById
import org.koitharu.kotatsu.core.parser.MangaRepository import org.koitharu.kotatsu.core.parser.MangaRepository
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.getCauseUrl import org.koitharu.kotatsu.core.util.ext.getCauseUrl
@ -36,6 +37,7 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingFooter import org.koitharu.kotatsu.list.ui.model.LoadingFooter
import org.koitharu.kotatsu.list.ui.model.LoadingState 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.toErrorFooter
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
@ -50,7 +52,7 @@ open class RemoteListViewModel @Inject constructor(
mangaRepositoryFactory: MangaRepository.Factory, mangaRepositoryFactory: MangaRepository.Factory,
final override val filterCoordinator: FilterCoordinator, final override val filterCoordinator: FilterCoordinator,
settings: AppSettings, settings: AppSettings,
mangaListMapper: MangaListMapper, protected val mangaListMapper: MangaListMapper,
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
private val exploreRepository: ExploreRepository, private val exploreRepository: ExploreRepository,
sourcesRepository: MangaSourcesRepository, sourcesRepository: MangaSourcesRepository,
@ -85,7 +87,7 @@ open class RemoteListViewModel @Inject constructor(
list == null -> add(LoadingState) list == null -> add(LoadingState)
list.isEmpty() -> add(createEmptyState(canResetFilter = filterCoordinator.isFilterApplied)) list.isEmpty() -> add(createEmptyState(canResetFilter = filterCoordinator.isFilterApplied))
else -> { else -> {
mangaListMapper.toListModelList(this, list, mode) mapMangaList(this, list, mode)
when { when {
error != null -> add(error.toErrorFooter()) error != null -> add(error.toErrorFooter())
hasNext -> add(LoadingFooter()) hasNext -> add(LoadingFooter())
@ -171,6 +173,12 @@ open class RemoteListViewModel @Inject constructor(
protected open suspend fun onBuildList(list: MutableList<ListModel>) = Unit protected open suspend fun onBuildList(list: MutableList<ListModel>) = Unit
protected open suspend fun mapMangaList(
destination: MutableCollection<in ListModel>,
manga: Collection<Manga>,
mode: ListMode
) = mangaListMapper.toListModelList(destination, manga, mode, 0)
fun openRandom() { fun openRandom() {
if (randomJob?.isActive == true) { if (randomJob?.isActive == true) {
return return

@ -126,6 +126,7 @@ class SearchViewModel @Inject constructor(
mangaListMapper.toListModelList( mangaListMapper.toListModelList(
manga = repository.getList(offset = 0, null, MangaListFilter(query = q)), manga = repository.getList(offset = 0, null, MangaListFilter(query = q)),
mode = ListMode.GRID, mode = ListMode.GRID,
flags = 0,
) )
} }
}.fold( }.fold(
@ -161,7 +162,7 @@ class SearchViewModel @Inject constructor(
titleResId = R.string.history, titleResId = R.string.history,
source = UnknownMangaSource, source = UnknownMangaSource,
hasMore = false, hasMore = false,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID, flags = 0),
error = null, error = null,
) )
} else { } else {
@ -190,7 +191,7 @@ class SearchViewModel @Inject constructor(
titleResId = R.string.favourites, titleResId = R.string.favourites,
source = UnknownMangaSource, source = UnknownMangaSource,
hasMore = false, hasMore = false,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID, flags = 0),
error = null, error = null,
) )
} else { } else {
@ -219,7 +220,7 @@ class SearchViewModel @Inject constructor(
titleResId = 0, titleResId = 0,
source = LocalMangaSource, source = LocalMangaSource,
hasMore = result.size > MIN_HAS_MORE_ITEMS, hasMore = result.size > MIN_HAS_MORE_ITEMS,
list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID), list = mangaListMapper.toListModelList(manga = result, mode = ListMode.GRID,flags = 0),
error = null, error = null,
) )
} else { } else {

@ -5,6 +5,7 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
//FIXME: https://stackoverflow.com/questions/77555641/saf-no-activity-found-to-handle-intent-android-intent-action-open-document-tr
class PickDirectoryContract : ActivityResultContracts.OpenDocumentTree() { class PickDirectoryContract : ActivityResultContracts.OpenDocumentTree() {
override fun createIntent(context: Context, input: Uri?): Intent { override fun createIntent(context: Context, input: Uri?): Intent {

@ -67,7 +67,7 @@ class SuggestionsViewModel @Inject constructor(
else -> buildList(list.size + 1) { else -> buildList(list.size + 1) {
quickFilter.filterItem(filters)?.let(::add) quickFilter.filterItem(filters)?.let(::add)
mangaListMapper.toListModelList(this, list, mode) mangaListMapper.toListModelList(this, list, mode, 0)
} }
} }
}.onStart { }.onStart {

@ -151,7 +151,7 @@ class FeedViewModel @Inject constructor(
null null
} else { } else {
UpdatedMangaHeader( UpdatedMangaHeader(
mangaList.map { mangaListMapper.toGridModel(it.manga) }, mangaList.map { mangaListMapper.toGridModel(it.manga, 0) },
) )
} }
} }

@ -107,7 +107,7 @@ class UpdatesViewModel @Inject constructor(
prevHeader = header prevHeader = header
} }
} }
result += mangaListMapper.toListModel(item.manga, mode) result += mangaListMapper.toListModel(item.manga, mode, 0)
} }
return result return result
} }

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners
android:bottomRightRadius="4dp"
android:topRightRadius="4dp" />
<solid android:color="?colorBackgroundFloating" />
</shape>

@ -34,16 +34,17 @@
android:layout_gravity="bottom|end" android:layout_gravity="bottom|end"
android:layout_margin="@dimen/card_indicator_offset" /> android:layout_margin="@dimen/card_indicator_offset" />
<ImageView <org.koitharu.kotatsu.core.ui.widgets.IconsView
android:id="@+id/imageView_favorite" android:id="@+id/iconsView"
android:layout_width="@dimen/card_indicator_size" android:layout_width="wrap_content"
android:layout_height="@dimen/card_indicator_size" android:layout_height="wrap_content"
android:layout_gravity="bottom|start" android:layout_gravity="bottom|start"
android:layout_margin="@dimen/card_indicator_offset" android:layout_marginBottom="@dimen/card_indicator_offset"
android:contentDescription="@string/favourites" android:background="@drawable/bg_list_icons"
android:scaleType="centerInside" android:orientation="horizontal"
app:srcCompat="@drawable/ic_heart" android:padding="4dp"
app:tint="?colorSurfaceBright" /> app:iconSize="12dp"
app:iconSpacing="2dp" />
<org.koitharu.kotatsu.core.ui.widgets.BadgeView <org.koitharu.kotatsu.core.ui.widgets.BadgeView
android:id="@+id/badge" android:id="@+id/badge"

@ -178,6 +178,11 @@
<attr name="android:progress" /> <attr name="android:progress" />
</declare-styleable> </declare-styleable>
<declare-styleable name="IconsView">
<attr name="iconSize" />
<attr name="iconSpacing" format="dimension" />
</declare-styleable>
<declare-styleable name="FilterFieldLayout"> <declare-styleable name="FilterFieldLayout">
<attr name="title" /> <attr name="title" />
<attr name="showMoreButton" format="boolean" /> <attr name="showMoreButton" format="boolean" />

@ -31,7 +31,7 @@ material = "1.13.0-alpha10"
moshi = "1.15.2" moshi = "1.15.2"
okhttp = "4.12.0" okhttp = "4.12.0"
okio = "3.10.2" okio = "3.10.2"
parsers = "794a737b6d" parsers = "198e859850"
preference = "1.2.1" preference = "1.2.1"
recyclerview = "1.4.0" recyclerview = "1.4.0"
room = "2.6.1" room = "2.6.1"

Loading…
Cancel
Save