From 10bd46f07715d58943316860ee0e1b8dc5cdd556 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 25 Apr 2025 08:17:27 +0300 Subject: [PATCH] Refactor image loading --- .../kotatsu/alternatives/ui/AlternativeAD.kt | 14 +- .../alternatives/ui/AlternativesActivity.kt | 2 +- .../bookmarks/ui/AllBookmarksFragment.kt | 6 - .../bookmarks/ui/adapter/BookmarkLargeAD.kt | 20 +- .../bookmarks/ui/adapter/BookmarksAdapter.kt | 8 +- .../kotatsu/core/image/CoilImageView.kt | 192 ++++++++++++++ .../core/ui/image/AnimatedFaviconDrawable.kt | 6 - .../core/ui/image/CoverSizeResolver.kt | 105 -------- .../kotatsu/core/ui/image/FaviconView.kt | 64 +++++ .../kotatsu/core/ui/widgets/ChipsView.kt | 3 + .../kotatsu/core/ui/widgets/CoverImageView.kt | 43 ---- .../kotatsu/core/ui/widgets/StackLayout.kt | 85 ++++++ .../koitharu/kotatsu/core/util/ext/Coil.kt | 100 +------ .../koitharu/kotatsu/core/util/ext/Theme.kt | 1 + .../kotatsu/details/ui/DetailsActivity.kt | 37 +-- .../ui/pager/bookmarks/BookmarksFragment.kt | 6 - .../details/ui/pager/pages/PageThumbnailAD.kt | 28 +- .../ui/pager/pages/PageThumbnailAdapter.kt | 2 +- .../details/ui/scrobbling/ScrobblingInfoAD.kt | 12 +- .../ui/scrobbling/ScrobblingInfoSheet.kt | 8 +- .../ui/scrobbling/ScrollingInfoAdapter.kt | 2 +- .../download/ui/list/DownloadItemAD.kt | 22 +- .../download/ui/list/DownloadsActivity.kt | 2 +- .../download/ui/list/DownloadsAdapter.kt | 6 +- .../kotatsu/explore/ui/ExploreFragment.kt | 7 +- .../explore/ui/adapter/ExploreAdapter.kt | 12 +- .../ui/adapter/ExploreAdapterDelegates.kt | 51 +--- .../categories/FavouriteCategoriesActivity.kt | 2 +- .../categories/adapter/CategoriesAdapter.kt | 10 +- .../ui/categories/adapter/CategoryAD.kt | 80 +----- .../ui/categories/select/FavoriteDialog.kt | 61 +---- .../select/adapter/MangaCategoriesAdapter.kt | 6 +- .../container/FavouritesContainerFragment.kt | 9 +- .../kotatsu/history/ui/HistoryListAdapter.kt | 6 +- .../kotatsu/history/ui/HistoryListFragment.kt | 4 +- .../kotatsu/image/ui/CoverImageView.kt | 243 ++++++++++++++++++ .../kotatsu/image/ui/CoverStackView.kt | 103 ++++++++ .../kotatsu/list/ui/MangaListFragment.kt | 4 +- .../kotatsu/list/ui/adapter/EmptyHintAD.kt | 8 +- .../list/ui/adapter/EmptyStateListAD.kt | 11 +- .../list/ui/adapter/MangaGridItemAD.kt | 23 +- .../list/ui/adapter/MangaListAdapter.kt | 14 +- .../ui/adapter/MangaListDetailedItemAD.kt | 21 +- .../list/ui/adapter/MangaListItemAD.kt | 19 +- .../list/ui/preview/PreviewFragment.kt | 36 +-- .../list/ui/size/DynamicItemSizeResolver.kt | 2 +- .../kotatsu/list/ui/size/ItemSizeResolver.kt | 2 - .../list/ui/size/StaticItemSizeResolver.kt | 2 - .../colorfilter/ColorFilterConfigActivity.kt | 61 +++-- .../reader/ui/colorfilter/DoubleViewTarget.kt | 18 -- .../ui/config/ScrobblerConfigActivity.kt | 14 +- .../ui/config/adapter/ScrobblingMangaAD.kt | 12 +- .../config/adapter/ScrobblingMangaAdapter.kt | 4 +- .../adapter/ScrobblerSelectorAdapter.kt | 2 +- .../ui/selector/adapter/ScrobblingMangaAD.kt | 14 +- .../kotatsu/search/ui/multi/SearchActivity.kt | 2 +- .../search/ui/multi/adapter/SearchAdapter.kt | 4 +- .../ui/multi/adapter/SearchResultsAD.kt | 8 +- .../adapter/SearchSuggestionAdapter.kt | 6 +- .../adapter/SearchSuggestionSourceAD.kt | 23 +- .../adapter/SearchSuggestionSourceTipAD.kt | 23 +- .../adapter/SearchSuggestionsMangaListAD.kt | 23 +- .../override/OverrideConfigActivity.kt | 18 +- .../sources/adapter/SourceConfigAdapter.kt | 2 +- .../adapter/SourceConfigAdapterDelegates.kt | 24 +- .../sources/catalog/SourceCatalogItemAD.kt | 33 +-- .../sources/catalog/SourcesCatalogActivity.kt | 11 +- .../sources/catalog/SourcesCatalogAdapter.kt | 8 +- .../kotatsu/stats/ui/StatsActivity.kt | 6 +- .../kotatsu/tracker/ui/debug/TrackDebugAD.kt | 16 +- .../tracker/ui/debug/TrackerDebugActivity.kt | 2 +- .../kotatsu/tracker/ui/feed/FeedFragment.kt | 2 +- .../tracker/ui/feed/adapter/FeedAdapter.kt | 10 +- .../tracker/ui/feed/adapter/FeedItemAD.kt | 16 +- .../tracker/ui/feed/adapter/UpdatedMangaAD.kt | 6 +- .../main/res/layout-land/item_empty_state.xml | 2 +- .../activity_color_filter.xml | 4 +- .../layout-w600dp-land/activity_details.xml | 6 +- .../main/res/layout/activity_color_filter.xml | 4 +- app/src/main/res/layout/activity_details.xml | 6 +- .../res/layout/activity_override_edit.xml | 2 +- .../res/layout/activity_scrobbler_config.xml | 5 +- .../res/layout/fragment_manga_bookmarks.xml | 2 +- app/src/main/res/layout/fragment_preview.xml | 3 +- app/src/main/res/layout/item_bookmark.xml | 19 -- .../main/res/layout/item_bookmark_large.xml | 3 +- .../main/res/layout/item_categories_all.xml | 60 +---- app/src/main/res/layout/item_category.xml | 60 +---- app/src/main/res/layout/item_download.xml | 3 +- app/src/main/res/layout/item_empty_card.xml | 2 +- app/src/main/res/layout/item_empty_hint.xml | 2 +- app/src/main/res/layout/item_empty_state.xml | 4 +- .../res/layout/item_explore_source_grid.xml | 6 +- .../res/layout/item_explore_source_list.xml | 3 +- app/src/main/res/layout/item_feed.xml | 2 +- .../res/layout/item_manga_alternative.xml | 3 +- app/src/main/res/layout/item_manga_grid.xml | 4 +- app/src/main/res/layout/item_manga_list.xml | 6 +- .../res/layout/item_manga_list_details.xml | 6 +- app/src/main/res/layout/item_page_thumb.xml | 4 +- .../res/layout/item_recommendation_manga.xml | 2 +- .../main/res/layout/item_scrobbling_info.xml | 4 +- .../main/res/layout/item_scrobbling_manga.xml | 2 +- .../item_search_suggestion_manga_grid.xml | 3 +- .../layout/item_search_suggestion_source.xml | 3 +- .../item_search_suggestion_source_tip.xml | 3 +- .../main/res/layout/item_source_catalog.xml | 3 +- .../main/res/layout/item_source_config.xml | 3 +- app/src/main/res/layout/item_track_debug.xml | 3 +- .../res/layout/sheet_favorite_categories.xml | 86 +------ app/src/main/res/layout/sheet_scrobbling.xml | 2 +- app/src/main/res/layout/view_cover_stack.xml | 51 ++++ app/src/main/res/values/attrs.xml | 24 +- app/src/main/res/values/dimens.xml | 1 + app/src/main/res/values/styles.xml | 8 + app/src/main/res/values/themes.xml | 1 + 116 files changed, 1001 insertions(+), 1297 deletions(-) create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilImageView.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconView.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/StackLayout.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverImageView.kt create mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverStackView.kt delete mode 100644 app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/DoubleViewTarget.kt delete mode 100644 app/src/main/res/layout/item_bookmark.xml create mode 100644 app/src/main/res/layout/view_cover_stack.xml diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt index 3575ded7b..7aada8372 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativeAD.kt @@ -21,16 +21,11 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.ui.image.ChipIconTarget -import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver -import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe -import org.koitharu.kotatsu.core.util.ext.mangaExtra import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemMangaAlternativeBinding import org.koitharu.kotatsu.list.ui.ListModelDiffCallback import org.koitharu.kotatsu.list.ui.model.ListModel @@ -104,13 +99,6 @@ fun alternativeAD( .allowRgb565(true) .enqueueWith(coil) } - binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run { - size(CoverSizeResolver(binding.imageViewCover)) - defaultPlaceholders(context) - transformations(TrimTransformation()) - allowRgb565(true) - mangaExtra(item.manga) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt index d3b022ee4..4adf05582 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/alternatives/ui/AlternativesActivity.kt @@ -51,7 +51,7 @@ class AlternativesActivity : BaseActivity(), } val listAdapter = BaseListAdapter() .addDelegate(ListItemType.MANGA_LIST_DETAILED, alternativeAD(coil, this, this)) - .addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, this, null)) + .addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null)) .addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) .addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) .addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(this)) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt index e76c760a4..8c04ff471 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/AllBookmarksFragment.kt @@ -12,7 +12,6 @@ import androidx.appcompat.view.ActionMode import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.viewModels import androidx.recyclerview.widget.GridLayoutManager -import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.domain.Bookmark @@ -50,9 +49,6 @@ class AllBookmarksFragment : ListSelectionController.Callback, FastScroller.FastScrollListener, ListHeaderClickListener { - @Inject - lateinit var coil: ImageLoader - @Inject lateinit var settings: AppSettings @@ -79,8 +75,6 @@ class AllBookmarksFragment : callback = this, ) bookmarksAdapter = BookmarksAdapter( - lifecycleOwner = viewLifecycleOwner, - coil = coil, clickListener = this, headerClickListener = this, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkLargeAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkLargeAD.kt index 77fa1e9d8..4691784b8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkLargeAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarkLargeAD.kt @@ -1,24 +1,13 @@ package org.koitharu.kotatsu.bookmarks.ui.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.bookmarks.domain.Bookmark -import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.bookmarkExtra -import org.koitharu.kotatsu.core.util.ext.decodeRegion -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemBookmarkLargeBinding import org.koitharu.kotatsu.list.ui.model.ListModel fun bookmarkLargeAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemBookmarkLargeBinding.inflate(inflater, parent, false) }, @@ -26,14 +15,7 @@ fun bookmarkLargeAD( AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView) bind { - binding.imageViewThumb.newImageRequest(lifecycleOwner, item.imageLoadData)?.run { - size(CoverSizeResolver(binding.imageViewThumb)) - defaultPlaceholders(context) - allowRgb565(true) - bookmarkExtra(item) - decodeRegion(item.scroll) - enqueueWith(coil) - } + binding.imageViewThumb.setImageAsync(item) binding.progressView.setProgress(item.percent, false) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt index 8854b8a78..1d54a27ff 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/bookmarks/ui/adapter/BookmarksAdapter.kt @@ -1,8 +1,6 @@ package org.koitharu.kotatsu.bookmarks.ui.adapter import android.content.Context -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener @@ -17,19 +15,17 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel class BookmarksAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, headerClickListener: ListHeaderClickListener?, ) : BaseListAdapter(), FastScroller.SectionIndexer { init { - addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(coil, lifecycleOwner, clickListener)) + addDelegate(ListItemType.PAGE_THUMB, bookmarkLargeAD(clickListener)) addDelegate(ListItemType.HEADER, listHeaderAD(headerClickListener)) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(null)) addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null)) } override fun getSectionText(context: Context, position: Int): CharSequence? { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilImageView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilImageView.kt new file mode 100644 index 000000000..d58c43ed4 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/image/CoilImageView.kt @@ -0,0 +1,192 @@ +package org.koitharu.kotatsu.core.image + +import android.content.Context +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import androidx.annotation.AttrRes +import androidx.annotation.DrawableRes +import androidx.core.content.withStyledAttributes +import androidx.lifecycle.findViewTreeLifecycleOwner +import coil3.ImageLoader +import coil3.asImage +import coil3.request.Disposable +import coil3.request.ErrorResult +import coil3.request.ImageRequest +import coil3.request.NullRequestData +import coil3.request.SuccessResult +import coil3.request.allowRgb565 +import coil3.request.crossfade +import coil3.request.lifecycle +import coil3.request.target +import coil3.size.Scale +import coil3.size.Size +import coil3.size.SizeResolver +import coil3.size.ViewSizeResolver +import coil3.util.CoilUtils +import com.google.android.material.imageview.ShapeableImageView +import dagger.hilt.android.AndroidEntryPoint +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.util.ext.decodeRegion +import org.koitharu.kotatsu.core.util.ext.getAnimationDuration +import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled +import java.util.LinkedList +import javax.inject.Inject + +@AndroidEntryPoint +open class CoilImageView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0, +) : ShapeableImageView(context, attrs, defStyleAttr), ImageRequest.Listener { + + @Inject + lateinit var coil: ImageLoader + + var allowRgb565: Boolean = false + var useExistingDrawable: Boolean = false + var decodeRegion: Boolean = false + var exactImageSize: Size? = null + var crossfadeDurationFactor: Float = 1f + + var placeholderDrawable: Drawable? = null + var errorDrawable: Drawable? = null + var fallbackDrawable: Drawable? = null + + private var currentRequest: Disposable? = null + private var currentImageData: Any = NullRequestData + + private var listeners: MutableList? = null + + init { + context.withStyledAttributes(attrs, R.styleable.CoilImageView, defStyleAttr) { + allowRgb565 = getBoolean(R.styleable.CoilImageView_allowRgb565, allowRgb565) + useExistingDrawable = getBoolean(R.styleable.CoilImageView_useExistingDrawable, useExistingDrawable) + decodeRegion = getBoolean(R.styleable.CoilImageView_decodeRegion, decodeRegion) + placeholderDrawable = getDrawable(R.styleable.CoilImageView_placeholderDrawable) + errorDrawable = getDrawable(R.styleable.CoilImageView_errorDrawable) + fallbackDrawable = getDrawable(R.styleable.CoilImageView_fallbackDrawable) + crossfadeDurationFactor = if (getBoolean(R.styleable.CoilImageView_crossfadeEnabled, true)) { + crossfadeDurationFactor + } else { + 0f + } + } + } + + override fun onCancel(request: ImageRequest) { + super.onCancel(request) + listeners?.forEach { it.onCancel(request) } + } + + override fun onError(request: ImageRequest, result: ErrorResult) { + super.onError(request, result) + listeners?.forEach { it.onError(request, result) } + } + + override fun onStart(request: ImageRequest) { + super.onStart(request) + listeners?.forEach { it.onStart(request) } + } + + override fun onSuccess(request: ImageRequest, result: SuccessResult) { + super.onSuccess(request, result) + listeners?.forEach { it.onSuccess(request, result) } + } + + fun addImageRequestListener(listener: ImageRequest.Listener) { + val list = listeners ?: LinkedList().also { listeners = it } + list.add(listener) + } + + fun removeImageRequestListener(listener: ImageRequest.Listener) { + listeners?.remove(listener) + } + + fun setImageAsync(@DrawableRes resourceId: Int) = enqueueRequest( + newRequestBuilder() + .data(resourceId) + .build(), + ) + + fun setImageAsync(url: String?) = enqueueRequest( + newRequestBuilder() + .data(url) + .build(), + ) + + @Deprecated("Use more specific overrides instead") + fun setImageAsync(request: ImageRequest) = enqueueRequest( + request.newBuilder() + .lifecycle(request.lifecycle ?: findViewTreeLifecycleOwner()?.lifecycle) + .target(this) + .size( + if (request.sizeResolver == SizeResolver.ORIGINAL) { + ViewSizeResolver(this) + } else { + request.sizeResolver + }, + ).build(), + ) + + fun disposeImage() { + CoilUtils.dispose(this) + currentRequest = null + currentImageData = NullRequestData + setImageDrawable(null) + } + + protected fun enqueueRequest(request: ImageRequest): Disposable { + val previous = currentRequest + if (currentImageData == request.data && previous?.job?.isCancelled == false) { + return previous + } + currentImageData = request.data + return coil.enqueue(request).also { currentRequest = it } + } + + protected open fun newRequestBuilder() = ImageRequest.Builder(context).apply { + lifecycle(findViewTreeLifecycleOwner()) + val crossfadeDuration = if (context.isAnimationsEnabled) { + (context.getAnimationDuration(R.integer.config_defaultAnimTime) * crossfadeDurationFactor).toInt() + } else { + 0 + } + crossfade(crossfadeDuration) + if (useExistingDrawable) { + val previousDrawable = this@CoilImageView.drawable?.asImage() + if (previousDrawable != null) { + fallback(previousDrawable) + placeholder(previousDrawable) + error(previousDrawable) + } else { + setupPlaceholders() + } + } else { + setupPlaceholders() + } + if (decodeRegion) { + decodeRegion(0) + } + size( + exactImageSize?.let { + SizeResolver(it) + } ?: ViewSizeResolver(this@CoilImageView), + ) + scale(scaleType.toCoilScale()) + listener(this@CoilImageView) + allowRgb565(allowRgb565) + target(this@CoilImageView) + } + + private fun ImageRequest.Builder.setupPlaceholders() { + placeholder(placeholderDrawable?.asImage()) + error(errorDrawable?.asImage()) + fallback(fallbackDrawable?.asImage()) + } + + private fun ScaleType.toCoilScale(): Scale = if (this == ScaleType.CENTER_CROP) { + Scale.FILL + } else { + Scale.FIT + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt index 2d89252c7..02b8a9476 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/AnimatedFaviconDrawable.kt @@ -44,12 +44,6 @@ class AnimatedFaviconDrawable( super.draw(canvas) } - // override fun setAlpha(alpha: Int) = Unit - // - // override fun getAlpha(): Int = 255 - // - // override fun isOpaque(): Boolean = false - override fun onTimeUpdate(animation: TimeAnimator?, totalTime: Long, deltaTime: Long) { callback?.also { updateColor() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt deleted file mode 100644 index 588543142..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/CoverSizeResolver.kt +++ /dev/null @@ -1,105 +0,0 @@ -package org.koitharu.kotatsu.core.ui.image - -import android.view.ViewGroup -import android.view.ViewTreeObserver -import android.view.ViewTreeObserver.OnPreDrawListener -import android.widget.ImageView -import coil3.size.Dimension -import coil3.size.Size -import coil3.size.ViewSizeResolver -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlin.coroutines.resume -import kotlin.math.roundToInt - -private const val ASPECT_RATIO_HEIGHT = 18f -private const val ASPECT_RATIO_WIDTH = 13f - -class CoverSizeResolver( - override val view: ImageView, -) : ViewSizeResolver { - - override suspend fun size(): Size { - // Fast path: the view is already measured. - getSize()?.let { return it } - - // Slow path: wait for the view to be measured. - return suspendCancellableCoroutine { continuation -> - val viewTreeObserver = view.viewTreeObserver - - val preDrawListener = object : OnPreDrawListener { - private var isResumed = false - - override fun onPreDraw(): Boolean { - val size = getSize() - if (size != null) { - viewTreeObserver.removePreDrawListenerSafe(this) - - if (!isResumed) { - isResumed = true - continuation.resume(size) - } - } - return true - } - } - - viewTreeObserver.addOnPreDrawListener(preDrawListener) - - continuation.invokeOnCancellation { - viewTreeObserver.removePreDrawListenerSafe(preDrawListener) - } - } - } - - private fun getSize(): Size? { - var width = getWidth() - var height = getHeight() - when { - width == null && height == null -> { - return null - } - height == null && width != null -> { - height = Dimension((width.px * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt()) - } - width == null && height != null -> { - width = Dimension((height.px * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt()) - } - } - return Size(checkNotNull(width), checkNotNull(height)) - } - - private fun getWidth() = getDimension( - paramSize = view.layoutParams?.width ?: -1, - viewSize = view.width, - paddingSize = if (subtractPadding) view.paddingLeft + view.paddingRight else 0 - ) - - private fun getHeight() = getDimension( - paramSize = view.layoutParams?.height ?: -1, - viewSize = view.height, - paddingSize = if (subtractPadding) view.paddingTop + view.paddingBottom else 0 - ) - - private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? { - if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { - return null - } - val insetParamSize = paramSize - paddingSize - if (insetParamSize > 0) { - return Dimension(insetParamSize) - } - val insetViewSize = viewSize - paddingSize - if (insetViewSize > 0) { - return Dimension(insetViewSize) - } - return null - } - - private fun ViewTreeObserver.removePreDrawListenerSafe(victim: OnPreDrawListener) { - if (isAlive) { - removeOnPreDrawListener(victim) - } else { - view.viewTreeObserver.removeOnPreDrawListener(victim) - } - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconView.kt new file mode 100644 index 000000000..300ed45a1 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/image/FaviconView.kt @@ -0,0 +1,64 @@ +package org.koitharu.kotatsu.core.ui.image + +import android.content.Context +import android.util.AttributeSet +import androidx.annotation.AttrRes +import androidx.annotation.StyleRes +import androidx.core.content.withStyledAttributes +import coil3.Image +import coil3.asImage +import coil3.request.Disposable +import coil3.request.ImageRequest +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier.Companion.ignoreCaptchaErrors +import org.koitharu.kotatsu.core.image.CoilImageView +import org.koitharu.kotatsu.core.parser.favicon.faviconUri +import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled +import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra +import org.koitharu.kotatsu.parsers.model.MangaSource + +class FaviconView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0, +) : CoilImageView(context, attrs, defStyleAttr) { + + @StyleRes + private var iconStyle: Int = R.style.FaviconDrawable + + init { + context.withStyledAttributes(attrs, R.styleable.FaviconView, defStyleAttr) { + iconStyle = getResourceId(R.styleable.FaviconView_iconStyle, iconStyle) + } + if (isInEditMode) { + setImageDrawable( + FaviconDrawable( + context = context, + styleResId = iconStyle, + name = context.getString(R.string.app_name).random().toString(), + ), + ) + } + } + + fun setImageAsync(mangaSource: MangaSource): Disposable { + val fallbackFactory: (ImageRequest) -> Image? = { + FaviconDrawable(context, iconStyle, mangaSource.name).asImage() + } + val placeholderFactory: (ImageRequest) -> Image? = if (context.isAnimationsEnabled) { + { AnimatedFaviconDrawable(context, iconStyle, mangaSource.name).asImage() } + } else { + fallbackFactory + } + return enqueueRequest( + newRequestBuilder() + .data(mangaSource.faviconUri()) + .error(fallbackFactory) + .fallback(fallbackFactory) + .placeholder(placeholderFactory) + .mangaSourceExtra(mangaSource) + .ignoreCaptchaErrors() + .build(), + ) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt index 599879360..0552599cf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/ChipsView.kt @@ -8,6 +8,7 @@ import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.content.ContextCompat import androidx.core.view.children +import androidx.lifecycle.findViewTreeLifecycleOwner import coil3.ImageLoader import coil3.request.Disposable import coil3.request.ImageRequest @@ -15,6 +16,7 @@ import coil3.request.allowRgb565 import coil3.request.crossfade import coil3.request.error import coil3.request.fallback +import coil3.request.lifecycle import coil3.request.placeholder import coil3.request.transformations import coil3.transform.RoundedCornersTransformation @@ -203,6 +205,7 @@ class ChipsView @JvmOverloads constructor( .target(ChipIconTarget(this)) .placeholder(placeholder) .fallback(placeholder) + .lifecycle(this@ChipsView.findViewTreeLifecycleOwner()) .error(placeholder) .transformations(RoundedCornersTransformation(resources.getDimension(R.dimen.chip_icon_corner))) .allowRgb565(true) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt deleted file mode 100644 index 797dade5a..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/CoverImageView.kt +++ /dev/null @@ -1,43 +0,0 @@ -package org.koitharu.kotatsu.core.ui.widgets - -import android.content.Context -import android.util.AttributeSet -import android.widget.LinearLayout.HORIZONTAL -import android.widget.LinearLayout.VERTICAL -import androidx.annotation.AttrRes -import androidx.core.content.withStyledAttributes -import com.google.android.material.imageview.ShapeableImageView -import org.koitharu.kotatsu.R -import kotlin.math.roundToInt - -private const val ASPECT_RATIO_HEIGHT = 3f -private const val ASPECT_RATIO_WIDTH = 2f - -class CoverImageView @JvmOverloads constructor( - context: Context, - attrs: AttributeSet? = null, - @AttrRes defStyleAttr: Int = 0, -) : ShapeableImageView(context, attrs, defStyleAttr) { - - private var orientation: Int = HORIZONTAL - - init { - context.withStyledAttributes(attrs, R.styleable.CoverImageView, defStyleAttr) { - orientation = getInt(R.styleable.CoverImageView_android_orientation, orientation) - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec) - val desiredWidth: Int - val desiredHeight: Int - if (orientation == VERTICAL) { - desiredHeight = measuredHeight - desiredWidth = (desiredHeight * ASPECT_RATIO_WIDTH / ASPECT_RATIO_HEIGHT).roundToInt() - } else { - desiredWidth = measuredWidth - desiredHeight = (desiredWidth * ASPECT_RATIO_HEIGHT / ASPECT_RATIO_WIDTH).roundToInt() - } - setMeasuredDimension(desiredWidth, desiredHeight) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/StackLayout.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/StackLayout.kt new file mode 100644 index 000000000..620cbe21a --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/ui/widgets/StackLayout.kt @@ -0,0 +1,85 @@ +package org.koitharu.kotatsu.core.ui.widgets + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.view.ViewGroup +import androidx.annotation.AttrRes +import androidx.core.view.children +import androidx.core.view.isEmpty +import androidx.core.view.isGone + +open class StackLayout @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0, +) : ViewGroup(context, attrs, defStyleAttr) { + + private val visibleChildren = ArrayList() + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + val w = r - l - paddingLeft - paddingRight + val h = b - t - paddingTop - paddingBottom + visibleChildren.clear() + children.filterNotTo(visibleChildren) { it.isGone } + if (w <= 0 || h <= 0 || visibleChildren.isEmpty) { + return + } + val xStep = w / (visibleChildren.size + 1) + val yStep = h / (visibleChildren.size + 1) + val maxW = w + val maxH = h + val total = visibleChildren.size + for ((index, child) in visibleChildren.withIndex()) { + var cx = paddingLeft + xStep * (total - index) + var cy = paddingTop + yStep * (index + 1) + val rx = child.measuredWidth.coerceAtMost(maxW) / 2 + val ry = child.measuredHeight.coerceAtMost(maxH) / 2 + if (cx < rx) { + cx = rx + } + if (cy < ry) { + cy = ry + } + if (cx + rx > width) { + cx = width - rx + } + if (cy + ry > height) { + cy = height - ry + } + child.layout(cx - rx, cy - ry, cx + rx, cy + ry) + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + measureChildren(widthMeasureSpec, heightMeasureSpec) + if (isEmpty()) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + return + } + + var h = 0 + var w = 0 + for (i in 0 until childCount) { + val child = getChildAt(i) + if (child.isGone) { + continue + } + val mw = child.measuredWidth + val mh = child.measuredHeight + if (h == 0 || w == 0) { + h = mh + w = mw + } else { + h += mh / 2 + w += mw / 2 + } + } + h += paddingTop + paddingBottom + w += paddingLeft + paddingRight + setMeasuredDimension( + resolveSizeAndState(w, widthMeasureSpec, 0), + resolveSizeAndState(h, heightMeasureSpec, 0), + ) + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt index 4256bae0b..c28ae0c32 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Coil.kt @@ -1,11 +1,6 @@ package org.koitharu.kotatsu.core.util.ext -import android.content.Context import android.graphics.drawable.Drawable -import android.widget.ImageView -import androidx.core.graphics.ColorUtils -import androidx.core.graphics.drawable.toDrawable -import androidx.lifecycle.LifecycleOwner import coil3.Extras import coil3.ImageLoader import coil3.asDrawable @@ -16,48 +11,11 @@ import coil3.request.ImageResult import coil3.request.Options import coil3.request.SuccessResult import coil3.request.bitmapConfig -import coil3.request.crossfade -import coil3.request.error -import coil3.request.fallback -import coil3.request.lifecycle -import coil3.request.placeholder -import coil3.request.target -import coil3.size.Scale -import coil3.size.ViewSizeResolver import coil3.toBitmap -import coil3.util.CoilUtils -import com.google.android.material.progressindicator.BaseProgressIndicator -import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.core.image.RegionBitmapDecoder -import org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable -import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaSource -import androidx.appcompat.R as appcompatR -import com.google.android.material.R as materialR - -fun ImageView.newImageRequest(lifecycleOwner: LifecycleOwner, data: Any?): ImageRequest.Builder? { - val current = CoilUtils.result(this) - if (current?.request?.lifecycle === lifecycleOwner.lifecycle) { - if (current is SuccessResult && current.request.data == data) { - return null - } - } - // disposeImageRequest() - return ImageRequest.Builder(context) - .data(data?.takeUnless { it == "" || it == 0 }) - .lifecycle(lifecycleOwner) - .crossfade(context) - .size(ViewSizeResolver(this)) - .scale(scaleType.toCoilScale()) - .target(this) -} - -fun ImageView.disposeImageRequest() { - CoilUtils.dispose(this) - setImageDrawable(null) -} fun ImageRequest.Builder.enqueueWith(loader: ImageLoader) = loader.enqueue(build()) @@ -79,10 +37,6 @@ fun ImageResult.toBitmapOrNull() = when (this) { is ErrorResult -> null } -fun ImageRequest.Builder.indicator(indicators: List>): ImageRequest.Builder { - return addListener(ImageRequestIndicatorListener(indicators)) -} - fun ImageRequest.Builder.decodeRegion( scroll: Int = RegionBitmapDecoder.SCROLL_UNDEFINED, ): ImageRequest.Builder = apply { @@ -90,19 +44,13 @@ fun ImageRequest.Builder.decodeRegion( extras[RegionBitmapDecoder.regionScrollKey] = scroll } -@Suppress("SpellCheckingInspection") -fun ImageRequest.Builder.crossfade(context: Context): ImageRequest.Builder { - val duration = context.resources.getInteger(R.integer.config_defaultAnimTime) * context.animatorDurationScale - return crossfade(duration.toInt()) -} - fun ImageRequest.Builder.mangaSourceExtra(source: MangaSource?): ImageRequest.Builder = apply { extras[mangaSourceKey] = source } -fun ImageRequest.Builder.mangaExtra(manga: Manga): ImageRequest.Builder = apply { +fun ImageRequest.Builder.mangaExtra(manga: Manga?): ImageRequest.Builder = apply { extras[mangaKey] = manga - mangaSourceExtra(manga.source) + mangaSourceExtra(manga?.source) } fun ImageRequest.Builder.bookmarkExtra(bookmark: Bookmark): ImageRequest.Builder = apply { @@ -110,56 +58,12 @@ fun ImageRequest.Builder.bookmarkExtra(bookmark: Bookmark): ImageRequest.Builder mangaSourceExtra(bookmark.manga.source) } -fun ImageRequest.Builder.defaultPlaceholders(context: Context): ImageRequest.Builder { - val errorColor = ColorUtils.blendARGB( - context.getThemeColor(materialR.attr.colorErrorContainer), - context.getThemeColor(appcompatR.attr.colorBackgroundFloating), - 0.25f, - ) - return placeholder(AnimatedPlaceholderDrawable(context)) - .fallback(context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable()) - .error(errorColor.toDrawable()) -} - -private fun ImageView.ScaleType.toCoilScale(): Scale = if (this == ImageView.ScaleType.CENTER_CROP) { - Scale.FILL -} else { - Scale.FIT -} - -fun ImageRequest.Builder.addListener(listener: ImageRequest.Listener): ImageRequest.Builder { - val existing = build().listener - return listener( - when (existing) { - null -> listener - is CompositeImageRequestListener -> existing + listener - else -> CompositeImageRequestListener(arrayOf(existing, listener)) - }, - ) -} - suspend fun ImageLoader.fetch(data: Any, options: Options): FetchResult? { val mappedData = components.map(data, options) val fetcher = components.newFetcher(mappedData, options, this)?.first return fetcher?.fetch() } -private class CompositeImageRequestListener( - private val delegates: Array, -) : ImageRequest.Listener { - - override fun onCancel(request: ImageRequest) = delegates.forEach { it.onCancel(request) } - - override fun onError(request: ImageRequest, result: ErrorResult) = delegates.forEach { it.onError(request, result) } - - override fun onStart(request: ImageRequest) = delegates.forEach { it.onStart(request) } - - override fun onSuccess(request: ImageRequest, result: SuccessResult) = - delegates.forEach { it.onSuccess(request, result) } - - operator fun plus(other: ImageRequest.Listener) = CompositeImageRequestListener(delegates + other) -} - val mangaKey = Extras.Key(null) val bookmarkKey = Extras.Key(null) val mangaSourceKey = Extras.Key(null) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt index b01b62d9d..f30b83d79 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/ext/Theme.kt @@ -76,6 +76,7 @@ fun Context.getThemeResId( it.getResourceId(0, fallback) } +@Deprecated("") fun TypedArray.getDrawableCompat(context: Context, index: Int): Drawable? { val resId = getResourceId(index, 0) return if (resId != 0) ContextCompat.getDrawable(context, resId) else null diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt index 02980f9c4..b11668888 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/DetailsActivity.kt @@ -22,19 +22,12 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.transition.TransitionManager import coil3.ImageLoader import coil3.request.ImageRequest -import coil3.request.SuccessResult import coil3.request.allowRgb565 import coil3.request.crossfade -import coil3.request.error -import coil3.request.fallback import coil3.request.lifecycle -import coil3.request.placeholder -import coil3.request.target import coil3.request.transformations import coil3.size.Precision -import coil3.size.Scale import coil3.transform.RoundedCornersTransformation -import coil3.util.CoilUtils import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint @@ -59,7 +52,6 @@ import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.dialog.buildAlertDialog -import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.image.TextDrawable import org.koitharu.kotatsu.core.ui.image.TextViewTarget @@ -72,9 +64,6 @@ import org.koitharu.kotatsu.core.util.FileSize import org.koitharu.kotatsu.core.util.LocaleUtils import org.koitharu.kotatsu.core.util.ext.consume import org.koitharu.kotatsu.core.util.ext.copyToClipboard -import org.koitharu.kotatsu.core.util.ext.crossfade -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.drawable import org.koitharu.kotatsu.core.util.ext.drawableStart import org.koitharu.kotatsu.core.util.ext.end import org.koitharu.kotatsu.core.util.ext.enqueueWith @@ -369,8 +358,7 @@ class DetailsActivity : .addDelegate( ListItemType.MANGA_GRID, mangaGridItemAD( - coil, this, - StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)), + sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)), ) { item, view -> router.openDetails(item) }, @@ -505,28 +493,7 @@ class DetailsActivity : } private fun loadCover(imageUrl: String?) { - viewBinding.imageViewCover.isEnabled = !imageUrl.isNullOrEmpty() - val lastResult = CoilUtils.result(viewBinding.imageViewCover) - if (lastResult is SuccessResult && lastResult.request.data == imageUrl) { - return - } - val request = ImageRequest.Builder(this) - .target(viewBinding.imageViewCover) - .size(CoverSizeResolver(viewBinding.imageViewCover)) - .scale(Scale.FILL) - .data(imageUrl) - .mangaSourceExtra(viewModel.getMangaOrNull()?.source) - .crossfade(this) - .lifecycle(this) - val previousDrawable = lastResult?.drawable - if (previousDrawable != null) { - request.fallback(previousDrawable) - .placeholder(previousDrawable) - .error(previousDrawable) - } else { - request.defaultPlaceholders(this) - } - request.enqueueWith(coil) + viewBinding.imageViewCover.setImageAsync(imageUrl, viewModel.getMangaOrNull()) } private fun String.withEstimatedTime(time: ReadingTime?): String { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt index 0237f1ac5..504d8e1c6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/bookmarks/BookmarksFragment.kt @@ -12,7 +12,6 @@ import androidx.core.view.WindowInsetsCompat import androidx.fragment.app.viewModels import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView -import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.bookmarks.domain.Bookmark @@ -52,9 +51,6 @@ class BookmarksFragment : BaseFragment(), private val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this) private val viewModel by viewModels() - @Inject - lateinit var coil: ImageLoader - @Inject lateinit var settings: AppSettings @@ -89,8 +85,6 @@ class BookmarksFragment : BaseFragment(), callback = this, ) bookmarksAdapter = BookmarksAdapter( - coil = coil, - lifecycleOwner = viewLifecycleOwner, clickListener = this@BookmarksFragment, headerClickListener = null, ) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt index 1e907039e..18389d3b0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAD.kt @@ -1,37 +1,23 @@ package org.koitharu.kotatsu.details.ui.pager.pages -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.transformations -import coil3.size.Scale import coil3.size.Size import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.decodeRegion -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextColorAttr import org.koitharu.kotatsu.databinding.ItemPageThumbBinding import org.koitharu.kotatsu.list.ui.model.ListModel -import org.koitharu.kotatsu.parsers.util.nullIfEmpty import com.google.android.material.R as materialR fun pageThumbnailAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemPageThumbBinding.inflate(inflater, parent, false) }, ) { val gridWidth = itemView.context.resources.getDimensionPixelSize(R.dimen.preferred_grid_width) - val thumbSize = Size( + binding.imageViewThumb.exactImageSize = Size( width = gridWidth, height = (gridWidth / 13f * 18f).toInt(), ) @@ -39,17 +25,7 @@ fun pageThumbnailAD( AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView) bind { - val data: Any = item.page.preview?.nullIfEmpty() ?: item.page.toMangaPage() - binding.imageViewThumb.newImageRequest(lifecycleOwner, data)?.run { - defaultPlaceholders(context) - size(thumbSize) - scale(Scale.FILL) - allowRgb565(true) - transformations(TrimTransformation()) - decodeRegion(0) - mangaSourceExtra(item.page.source) - enqueueWith(coil) - } + binding.imageViewThumb.setImageAsync(item.page) with(binding.textViewNumber) { setBackgroundResource(if (item.isCurrent) R.drawable.bg_badge_accent else R.drawable.bg_badge_empty) setTextColorAttr(if (item.isCurrent) materialR.attr.colorOnTertiary else android.R.attr.textColorPrimary) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAdapter.kt index 923c7776b..cca53c9bc 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/pager/pages/PageThumbnailAdapter.kt @@ -17,7 +17,7 @@ class PageThumbnailAdapter( ) : BaseListAdapter(), FastScroller.SectionIndexer { init { - addDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(coil, lifecycleOwner, clickListener)) + addDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(clickListener)) addDelegate(ListItemType.HEADER, listHeaderAD(null)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt index 1e1990a15..96939f7ee 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoAD.kt @@ -1,20 +1,13 @@ package org.koitharu.kotatsu.details.ui.scrobbling -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.nav.AppRouter -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemScrobblingInfoBinding import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo fun scrobblingInfoAD( - lifecycleOwner: LifecycleOwner, - coil: ImageLoader, router: AppRouter, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemScrobblingInfoBinding.inflate(layoutInflater, parent, false) }, @@ -24,10 +17,7 @@ fun scrobblingInfoAD( } bind { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { - defaultPlaceholders(context) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl) binding.textViewTitle.setText(item.scrobbler.titleResId) binding.imageViewIcon.setImageResource(item.scrobbler.iconResId) binding.ratingBar.rating = item.rating * binding.ratingBar.numStars diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoSheet.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoSheet.kt index fa701d28d..676d29dde 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoSheet.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrobblingInfoSheet.kt @@ -21,10 +21,7 @@ import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet import org.koitharu.kotatsu.core.util.ext.consume -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getDisplayMessage -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.sanitize @@ -137,10 +134,7 @@ class ScrobblingInfoSheet : binding.spinnerStatus.setSelection(scrobbling.status?.ordinal ?: -1) binding.imageViewLogo.contentDescription = getString(scrobbling.scrobbler.titleResId) binding.imageViewLogo.setImageResource(scrobbling.scrobbler.iconResId) - binding.imageViewCover.newImageRequest(viewLifecycleOwner, scrobbling.coverUrl)?.apply { - defaultPlaceholders(binding.imageViewCover.context) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(scrobbling.coverUrl) } override fun onMenuItemClick(item: MenuItem): Boolean { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt index f9ab7a6d0..6829d668c 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/details/ui/scrobbling/ScrollingInfoAdapter.kt @@ -13,6 +13,6 @@ class ScrollingInfoAdapter( ) : BaseListAdapter() { init { - delegatesManager.addDelegate(scrobblingInfoAD(lifecycleOwner, coil, router)) + delegatesManager.addDelegate(scrobblingInfoAD(router)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemAD.kt index 549289096..f4ffdecd7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadItemAD.kt @@ -7,23 +7,13 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.work.WorkInfo -import coil3.ImageLoader -import coil3.request.SuccessResult -import coil3.request.allowRgb565 -import coil3.request.transformations -import coil3.util.CoilUtils import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Job import kotlinx.coroutines.launch import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseListAdapter -import org.koitharu.kotatsu.core.ui.image.TrimTransformation -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemDownloadBinding import org.koitharu.kotatsu.download.ui.list.chapters.DownloadChapter @@ -35,7 +25,6 @@ import org.koitharu.kotatsu.parsers.util.format fun downloadItemAD( lifecycleOwner: LifecycleOwner, - coil: ImageLoader, listener: DownloadItemListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemDownloadBinding.inflate(inflater, parent, false) }, @@ -89,16 +78,7 @@ fun downloadItemAD( bind { payloads -> binding.textViewTitle.text = item.manga?.title ?: getString(R.string.unknown) - if ((CoilUtils.result(binding.imageViewCover) as? SuccessResult)?.memoryCacheKey != item.coverCacheKey) { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga?.coverUrl)?.apply { - defaultPlaceholders(context) - allowRgb565(true) - transformations(TrimTransformation()) - memoryCacheKey(item.coverCacheKey) - mangaSourceExtra(item.manga?.source) - enqueueWith(coil) - } - } + binding.imageViewCover.setImageAsync(item.manga?.coverUrl, item.manga) if (chaptersJob == null || payloads.isEmpty()) { chaptersJob?.cancel() chaptersJob = lifecycleOwner.lifecycleScope.launch(start = CoroutineStart.UNDISPATCHED) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt index 87dd34145..856b2566b 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsActivity.kt @@ -44,7 +44,7 @@ class DownloadsActivity : BaseActivity(), super.onCreate(savedInstanceState) setContentView(ActivityDownloadsBinding.inflate(layoutInflater)) setDisplayHomeAsUp(true, false) - val downloadsAdapter = DownloadsAdapter(this, coil, this) + val downloadsAdapter = DownloadsAdapter(this, this) val decoration = TypedListSpacingDecoration(this, false) selectionController = ListSelectionController( appCompatDelegate = delegate, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt index 46db5de9c..e4aae71fa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/download/ui/list/DownloadsAdapter.kt @@ -1,7 +1,6 @@ package org.koitharu.kotatsu.download.ui.list import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.list.ui.adapter.ListItemType import org.koitharu.kotatsu.list.ui.adapter.emptyStateListAD @@ -11,14 +10,13 @@ import org.koitharu.kotatsu.list.ui.model.ListModel class DownloadsAdapter( lifecycleOwner: LifecycleOwner, - coil: ImageLoader, listener: DownloadItemListener, ) : BaseListAdapter() { init { - addDelegate(ListItemType.DOWNLOAD, downloadItemAD(lifecycleOwner, coil, listener)) + addDelegate(ListItemType.DOWNLOAD, downloadItemAD(lifecycleOwner, listener)) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null)) addDelegate(ListItemType.HEADER, listHeaderAD(null)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt index 660eae254..eaf20bea3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/ExploreFragment.kt @@ -17,7 +17,6 @@ import androidx.fragment.app.viewModels import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import coil3.ImageLoader import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.exceptions.resolve.SnackbarErrorObserver @@ -45,7 +44,6 @@ import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.list.ui.model.ListHeader import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.MangaParserSource -import javax.inject.Inject @AndroidEntryPoint class ExploreFragment : @@ -54,9 +52,6 @@ class ExploreFragment : ExploreListEventListener, OnListItemClickListener, ListSelectionController.Callback { - @Inject - lateinit var coil: ImageLoader - private val viewModel by viewModels() private var exploreAdapter: ExploreAdapter? = null private var sourceSelectionController: ListSelectionController? = null @@ -70,7 +65,7 @@ class ExploreFragment : override fun onViewBindingCreated(binding: FragmentExploreBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) - exploreAdapter = ExploreAdapter(coil, viewLifecycleOwner, this, this) { manga, view -> + exploreAdapter = ExploreAdapter(this, this) { manga, view -> router.openDetails(manga) } sourceSelectionController = ListSelectionController( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt index f8268614d..88056148a 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapter.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.explore.ui.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.explore.ui.model.MangaSourceItem @@ -13,8 +11,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.parsers.model.Manga class ExploreAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: ExploreListEventListener, clickListener: OnListItemClickListener, mangaClickListener: OnListItemClickListener, @@ -24,12 +20,12 @@ class ExploreAdapter( addDelegate(ListItemType.EXPLORE_BUTTONS, exploreButtonsAD(listener)) addDelegate( ListItemType.EXPLORE_SUGGESTION, - exploreRecommendationItemAD(coil, mangaClickListener, lifecycleOwner), + exploreRecommendationItemAD(mangaClickListener), ) addDelegate(ListItemType.HEADER, listHeaderAD(listener)) - addDelegate(ListItemType.EXPLORE_SOURCE_LIST, exploreSourceListItemAD(coil, clickListener, lifecycleOwner)) - addDelegate(ListItemType.EXPLORE_SOURCE_GRID, exploreSourceGridItemAD(coil, clickListener, lifecycleOwner)) - addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(coil, lifecycleOwner, listener)) + addDelegate(ListItemType.EXPLORE_SOURCE_LIST, exploreSourceListItemAD(clickListener)) + addDelegate(ListItemType.EXPLORE_SOURCE_GRID, exploreSourceGridItemAD(clickListener)) + addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(listener)) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt index 3f51907ca..ae72de2a6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/explore/ui/adapter/ExploreAdapterDelegates.kt @@ -2,29 +2,14 @@ package org.koitharu.kotatsu.explore.ui.adapter import android.view.View import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder -import coil3.request.transformations import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.parser.favicon.faviconUri import org.koitharu.kotatsu.core.ui.BaseListAdapter -import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable -import org.koitharu.kotatsu.core.ui.image.FaviconDrawable -import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.drawableStart -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.recyclerView import org.koitharu.kotatsu.core.util.ext.setProgressIcon import org.koitharu.kotatsu.core.util.ext.textAndVisible @@ -63,15 +48,13 @@ fun exploreButtonsAD( } fun exploreRecommendationItemAD( - coil: ImageLoader, itemClickListener: OnListItemClickListener, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) }, ) { val adapter = BaseListAdapter() - .addDelegate(ListItemType.MANGA_LIST, recommendationMangaItemAD(coil, itemClickListener, lifecycleOwner)) + .addDelegate(ListItemType.MANGA_LIST, recommendationMangaItemAD(itemClickListener)) binding.pager.adapter = adapter binding.pager.recyclerView?.isNestedScrollingEnabled = false binding.dots.bindToViewPager(binding.pager) @@ -82,9 +65,7 @@ fun exploreRecommendationItemAD( } fun recommendationMangaItemAD( - coil: ImageLoader, itemClickListener: OnListItemClickListener, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemRecommendationMangaBinding.inflate(layoutInflater, parent, false) }, ) { @@ -95,21 +76,13 @@ fun recommendationMangaItemAD( bind { binding.textViewTitle.text = item.manga.title binding.textViewSubtitle.textAndVisible = item.subtitle - binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run { - defaultPlaceholders(context) - allowRgb565(true) - transformations(TrimTransformation()) - mangaSourceExtra(item.manga.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga.source) } } fun exploreSourceListItemAD( - coil: ImageLoader, listener: OnListItemClickListener, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemExploreSourceListBinding.inflate( @@ -128,21 +101,12 @@ fun exploreSourceListItemAD( binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null binding.textViewSubtitle.text = item.source.getSummary(context) - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) - binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - fallback(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) - error(fallbackIcon) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewIcon.setImageAsync(item.source) } } fun exploreSourceGridItemAD( - coil: ImageLoader, listener: OnListItemClickListener, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemExploreSourceGridBinding.inflate( @@ -160,13 +124,6 @@ fun exploreSourceGridItemAD( bind { binding.textViewTitle.text = item.source.getTitle(context) binding.textViewTitle.drawableStart = if (item.source.isPinned) iconPinned else null - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name) - binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - fallback(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Large, item.source.name)) - error(fallbackIcon) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewIcon.setImageAsync(item.source) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt index 88413354d..46079afdf 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/FavouriteCategoriesActivity.kt @@ -50,7 +50,7 @@ class FavouriteCategoriesActivity : super.onCreate(savedInstanceState) setContentView(ActivityCategoriesBinding.inflate(layoutInflater)) setDisplayHomeAsUp(true, false) - adapter = CategoriesAdapter(coil, this, this, this) + adapter = CategoriesAdapter(this, this) selectionController = ListSelectionController( appCompatDelegate = delegate, decoration = CategoriesSelectionDecoration(this), diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt index 67a5484f6..3fe8867fb 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoriesAdapter.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.ReorderableListAdapter import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener import org.koitharu.kotatsu.list.ui.adapter.ListItemType @@ -11,16 +9,14 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel class CategoriesAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, onItemClickListener: FavouriteCategoriesListListener, listListener: ListStateHolderListener, ) : ReorderableListAdapter() { init { - addDelegate(ListItemType.CATEGORY_LARGE, categoryAD(coil, lifecycleOwner, onItemClickListener)) - addDelegate(ListItemType.NAV_ITEM, allCategoriesAD(coil, lifecycleOwner, onItemClickListener)) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listListener)) + addDelegate(ListItemType.CATEGORY_LARGE, categoryAD(onItemClickListener)) + addDelegate(ListItemType.NAV_ITEM, allCategoriesAD(onItemClickListener)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listListener)) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt index 96a86abb0..01f701e7e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/adapter/CategoryAD.kt @@ -1,33 +1,16 @@ package org.koitharu.kotatsu.favourites.ui.categories.adapter import android.annotation.SuppressLint -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.view.MotionEvent import android.view.View import android.view.View.OnClickListener import android.view.View.OnLongClickListener import android.view.View.OnTouchListener -import androidx.core.graphics.ColorUtils import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.core.widget.ImageViewCompat -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.crossfade -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe -import org.koitharu.kotatsu.core.util.ext.getThemeColor -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemCategoriesAllBinding import org.koitharu.kotatsu.databinding.ItemCategoryBinding import org.koitharu.kotatsu.favourites.ui.categories.FavouriteCategoriesListListener @@ -35,8 +18,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel @SuppressLint("ClickableViewAccessibility") fun categoryAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: FavouriteCategoriesListListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemCategoryBinding.inflate(inflater, parent, false) }, @@ -52,22 +33,6 @@ fun categoryAD( override fun onTouch(v: View?, event: MotionEvent): Boolean = event.actionMasked == MotionEvent.ACTION_DOWN && clickListener.onDragHandleTouch(this@adapterDelegateViewBinding) } - val backgroundColor = context.getThemeColor(android.R.attr.colorBackground) - ImageViewCompat.setImageTintList( - binding.imageViewCover3, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)), - ) - ImageViewCompat.setImageTintList( - binding.imageViewCover2, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)), - ) - binding.imageViewCover2.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)) - binding.imageViewCover3.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)) - val fallback = ColorDrawable(Color.TRANSPARENT) - val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3) - val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt() itemView.setOnClickListener(eventListener) itemView.setOnLongClickListener(eventListener) binding.imageViewEdit.setOnClickListener(eventListener) @@ -88,24 +53,11 @@ fun categoryAD( } binding.imageViewTracker.isVisible = item.category.isTrackingEnabled binding.imageViewHidden.isGone = item.category.isVisibleInLibrary - repeat(coverViews.size) { i -> - val cover = item.covers.getOrNull(i) - coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run { - placeholder(R.drawable.ic_placeholder) - fallback(fallback) - mangaSourceExtra(cover?.mangaSource) - crossfade(crossFadeDuration * (i + 1)) - error(R.drawable.ic_error_placeholder) - allowRgb565(true) - enqueueWith(coil) - } - } + binding.coversView.setCoversAsync(item.covers) } } fun allCategoriesAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: FavouriteCategoriesListListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemCategoriesAllBinding.inflate(inflater, parent, false) }, @@ -117,22 +69,7 @@ fun allCategoriesAD( clickListener.onItemClick(null, v) } } - val backgroundColor = context.getThemeColor(android.R.attr.colorBackground) - ImageViewCompat.setImageTintList( - binding.imageViewCover3, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)), - ) - ImageViewCompat.setImageTintList( - binding.imageViewCover2, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)), - ) - binding.imageViewCover2.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)) - binding.imageViewCover3.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)) - val fallback = ColorDrawable(Color.TRANSPARENT) - val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3) - val crossFadeDuration = context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt() + itemView.setOnClickListener(eventListener) binding.imageViewVisible.setOnClickListener(eventListener) @@ -154,17 +91,6 @@ fun allCategoriesAD( R.drawable.ic_eye_off }, ) - repeat(coverViews.size) { i -> - val cover = item.covers.getOrNull(i) - coverViews[i].newImageRequest(lifecycleOwner, cover?.url)?.run { - placeholder(R.drawable.ic_placeholder) - fallback(fallback) - mangaSourceExtra(cover?.mangaSource) - crossfade(crossFadeDuration * (i + 1)) - error(R.drawable.ic_error_placeholder) - allowRgb565(true) - enqueueWith(coil) - } - } + binding.coversView.setCoversAsync(item.covers) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialog.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialog.kt index 73b64a2d9..076213a94 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialog.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/FavoriteDialog.kt @@ -1,24 +1,12 @@ package org.koitharu.kotatsu.favourites.ui.categories.select import android.content.DialogInterface -import android.content.res.ColorStateList -import android.graphics.Color -import android.graphics.drawable.ColorDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Toast -import androidx.core.graphics.ColorUtils -import androidx.core.view.isVisible -import androidx.core.widget.ImageViewCompat import androidx.fragment.app.viewModels -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.crossfade -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.google.android.material.checkbox.MaterialCheckBox import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint @@ -26,20 +14,13 @@ import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.ui.AlertDialogFragment import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.disposeImageRequest -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.getAnimationDuration import org.koitharu.kotatsu.core.util.ext.getDisplayMessage -import org.koitharu.kotatsu.core.util.ext.getThemeColor import org.koitharu.kotatsu.core.util.ext.joinToStringWithLimit -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.databinding.SheetFavoriteCategoriesBinding import org.koitharu.kotatsu.favourites.ui.categories.select.adapter.MangaCategoriesAdapter import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem -import javax.inject.Inject @AndroidEntryPoint class FavoriteDialog : AlertDialogFragment(), @@ -47,9 +28,6 @@ class FavoriteDialog : AlertDialogFragment(), private val viewModel by viewModels() - @Inject - lateinit var coil: ImageLoader - override fun onCreateViewBinding( inflater: LayoutInflater, container: ViewGroup?, @@ -66,7 +44,7 @@ class FavoriteDialog : AlertDialogFragment(), savedInstanceState: Bundle?, ) { super.onViewBindingCreated(binding, savedInstanceState) - val adapter = MangaCategoriesAdapter(coil, viewLifecycleOwner, this) + val adapter = MangaCategoriesAdapter(this) binding.recyclerViewCategories.adapter = adapter viewModel.content.observe(viewLifecycleOwner, adapter) viewModel.onError.observeEvent(viewLifecycleOwner, ::onError) @@ -88,42 +66,7 @@ class FavoriteDialog : AlertDialogFragment(), private fun bindHeader() { val manga = viewModel.manga val binding = viewBinding ?: return - val backgroundColor = binding.root.context.getThemeColor(android.R.attr.colorBackground) - ImageViewCompat.setImageTintList( - binding.imageViewCover3, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)), - ) - ImageViewCompat.setImageTintList( - binding.imageViewCover2, - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)), - ) - binding.imageViewCover2.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)) - binding.imageViewCover3.backgroundTintList = - ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)) - val fallback = ColorDrawable(Color.TRANSPARENT) - val coverViews = arrayOf(binding.imageViewCover1, binding.imageViewCover2, binding.imageViewCover3) - val crossFadeDuration = binding.root.context.getAnimationDuration(R.integer.config_defaultAnimTime).toInt() - binding.textViewTitle.text = manga.joinToStringWithLimit(binding.root.context, 92) { it.title } - - repeat(coverViews.size) { i -> - val m = manga.getOrNull(i) - val view = coverViews[i] - view.isVisible = m != null - if (m == null) { - view.disposeImageRequest() - } else { - view.newImageRequest(viewLifecycleOwner, m.coverUrl)?.run { - placeholder(R.drawable.ic_placeholder) - fallback(fallback) - mangaSourceExtra(m.source) - crossfade(crossFadeDuration * (i + 1)) - error(R.drawable.ic_error_placeholder) - allowRgb565(true) - enqueueWith(coil) - } - } - } + binding.coversStack.setCoversAsync(manga) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt index cb697f552..ba2e0916f 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/categories/select/adapter/MangaCategoriesAdapter.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.favourites.ui.categories.select.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.favourites.ui.categories.select.model.MangaCategoryItem @@ -11,14 +9,12 @@ import org.koitharu.kotatsu.list.ui.adapter.loadingStateAD import org.koitharu.kotatsu.list.ui.model.ListModel class MangaCategoriesAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, ) : BaseListAdapter() { init { addDelegate(ListItemType.NAV_ITEM, mangaCategoryAD(clickListener)) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt index 10905b98f..3e2eb2b70 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/container/FavouritesContainerFragment.kt @@ -12,7 +12,6 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels import androidx.recyclerview.widget.RecyclerView -import coil3.ImageLoader import com.google.android.material.tabs.TabLayoutMediator import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R @@ -22,9 +21,7 @@ import org.koitharu.kotatsu.core.ui.util.ActionModeListener import org.koitharu.kotatsu.core.ui.util.RecyclerViewOwner import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.ext.addMenuProvider -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.findCurrentPagerFragment -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.recyclerView @@ -32,7 +29,6 @@ import org.koitharu.kotatsu.core.util.ext.setTabsEnabled import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.FragmentFavouritesContainerBinding import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding -import javax.inject.Inject @AndroidEntryPoint class FavouritesContainerFragment : BaseFragment(), @@ -41,9 +37,6 @@ class FavouritesContainerFragment : BaseFragment 0 && aspectRationWidth > 0 + + init { + context.withStyledAttributes(attrs, R.styleable.CoverImageView, defStyleAttr) { + aspectRationHeight = getInt(R.styleable.CoverImageView_aspectRationHeight, aspectRationHeight) + aspectRationWidth = getInt(R.styleable.CoverImageView_aspectRationWidth, aspectRationWidth) + trimImage = getBoolean(R.styleable.CoverImageView_trimImage, trimImage) + } + if (placeholderDrawable == null) { + placeholderDrawable = AnimatedPlaceholderDrawable(context) + } + if (errorDrawable == null) { + errorDrawable = ColorUtils.blendARGB( + context.getThemeColor(materialR.attr.colorErrorContainer), + context.getThemeColor(appcompatR.attr.colorBackgroundFloating), + 0.25f, + ).toDrawable() + } + if (fallbackDrawable == null) { + fallbackDrawable = context.getThemeColor(materialR.attr.colorSurfaceContainer).toDrawable() + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (!hasAspectRatio) { + return + } + val isExactWidth = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY + val isExactHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY + when { + isExactHeight && isExactWidth -> Unit + isExactHeight -> setMeasuredDimension( + /* measuredWidth = */ measuredHeight * aspectRationWidth / aspectRationHeight, + /* measuredHeight = */ measuredHeight, + ) + + isExactWidth -> setMeasuredDimension( + /* measuredWidth = */ measuredWidth, + /* measuredHeight = */ measuredWidth * aspectRationHeight / aspectRationWidth, + ) + } + } + + fun setImageAsync(page: ReaderPage) = enqueueRequest( + newRequestBuilder() + .data(page.preview?.nullIfEmpty() ?: page.toMangaPage()) + .mangaSourceExtra(page.source) + .build(), + ) + + fun setImageAsync(page: MangaPage) = enqueueRequest( + newRequestBuilder() + .data(page.preview?.nullIfEmpty() ?: page) + .mangaSourceExtra(page.source) + .build(), + ) + + fun setImageAsync(cover: Cover?) = enqueueRequest( + newRequestBuilder() + .data(cover?.url) + .mangaSourceExtra(cover?.mangaSource) + .build(), + ) + + fun setImageAsync( + coverUrl: String?, + manga: Manga?, + ) = enqueueRequest( + newRequestBuilder() + .data(coverUrl) + .mangaExtra(manga) + .build(), + ) + + fun setImageAsync( + coverUrl: String?, + source: MangaSource, + ) = enqueueRequest( + newRequestBuilder() + .data(coverUrl) + .mangaSourceExtra(source) + .build(), + ) + + fun setImageAsync( + bookmark: Bookmark + ) = enqueueRequest( + newRequestBuilder() + .data(bookmark.imageLoadData) + .decodeRegion(bookmark.scroll) + .bookmarkExtra(bookmark) + .build(), + ) + + override fun newRequestBuilder() = super.newRequestBuilder().apply { + if (trimImage) { + transformations(listOf(TrimTransformation())) + } + if (hasAspectRatio) { + size(CoverSizeResolver(this@CoverImageView)) + } + } + + private class CoverSizeResolver( + override val view: CoverImageView, + ) : ViewSizeResolver { + + override suspend fun size(): Size { + // Fast path: the view is already measured. + getSize()?.let { return it } + + // Slow path: wait for the view to be measured. + return suspendCancellableCoroutine { continuation -> + val viewTreeObserver = view.viewTreeObserver + + val preDrawListener = object : OnPreDrawListener { + private var isResumed = false + + override fun onPreDraw(): Boolean { + val size = getSize() + if (size != null) { + viewTreeObserver.removePreDrawListenerSafe(this) + + if (!isResumed) { + isResumed = true + continuation.resume(size) + } + } + return true + } + } + + viewTreeObserver.addOnPreDrawListener(preDrawListener) + + continuation.invokeOnCancellation { + viewTreeObserver.removePreDrawListenerSafe(preDrawListener) + } + } + } + + private fun getSize(): Size? { + var width = getWidth() + var height = getHeight() + when { + width == null && height == null -> { + return null + } + + height == null -> { + height = Dimension(width!!.px * view.aspectRationHeight / view.aspectRationWidth) + } + + width == null -> { + width = Dimension(height.px * view.aspectRationWidth / view.aspectRationHeight) + } + } + return Size(checkNotNull(width), checkNotNull(height)) + } + + private fun getWidth() = getDimension( + paramSize = view.layoutParams?.width ?: -1, + viewSize = view.width, + paddingSize = if (subtractPadding) view.paddingLeft + view.paddingRight else 0, + ) + + private fun getHeight() = getDimension( + paramSize = view.layoutParams?.height ?: -1, + viewSize = view.height, + paddingSize = if (subtractPadding) view.paddingTop + view.paddingBottom else 0, + ) + + private fun getDimension(paramSize: Int, viewSize: Int, paddingSize: Int): Dimension.Pixels? { + if (paramSize == ViewGroup.LayoutParams.WRAP_CONTENT) { + return null + } + val insetParamSize = paramSize - paddingSize + if (insetParamSize > 0) { + return Dimension(insetParamSize) + } + val insetViewSize = viewSize - paddingSize + if (insetViewSize > 0) { + return Dimension(insetViewSize) + } + return null + } + + private fun ViewTreeObserver.removePreDrawListenerSafe(victim: OnPreDrawListener) { + if (isAlive) { + removeOnPreDrawListener(victim) + } else { + view.viewTreeObserver.removeOnPreDrawListener(victim) + } + } + } +} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverStackView.kt b/app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverStackView.kt new file mode 100644 index 000000000..61912bbe7 --- /dev/null +++ b/app/src/main/kotlin/org/koitharu/kotatsu/image/ui/CoverStackView.kt @@ -0,0 +1,103 @@ +package org.koitharu.kotatsu.image.ui + +import android.content.Context +import android.content.res.ColorStateList +import android.util.AttributeSet +import android.view.LayoutInflater +import androidx.annotation.AttrRes +import androidx.annotation.Px +import androidx.core.content.withStyledAttributes +import androidx.core.graphics.ColorUtils +import androidx.core.view.children +import androidx.core.view.isVisible +import androidx.core.view.updateLayoutParams +import androidx.core.widget.ImageViewCompat +import org.koitharu.kotatsu.R +import org.koitharu.kotatsu.core.model.UnknownMangaSource +import org.koitharu.kotatsu.core.ui.widgets.StackLayout +import org.koitharu.kotatsu.core.util.ext.getThemeColor +import org.koitharu.kotatsu.databinding.ViewCoverStackBinding +import org.koitharu.kotatsu.favourites.domain.model.Cover +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.model.MangaSource + +class CoverStackView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + @AttrRes defStyleAttr: Int = 0, +) : StackLayout(context, attrs, defStyleAttr) { + + private val binding = ViewCoverStackBinding.inflate(LayoutInflater.from(context), this) + private val coverViews = arrayOf( + binding.imageViewCover1, + binding.imageViewCover2, + binding.imageViewCover3, + ) + private var hideEmptyView: Boolean = true + + init { + context.withStyledAttributes(attrs, R.styleable.CoverStackView, defStyleAttr) { + val coverSize = getDimension(R.styleable.CoverStackView_coverSize, 0f) + if (coverSize > 0f) { + setCoverSize(coverSize) + } + } + val backgroundColor = context.getThemeColor(android.R.attr.colorBackground) + ImageViewCompat.setImageTintList( + binding.imageViewCover3, + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 153)), + ) + ImageViewCompat.setImageTintList( + binding.imageViewCover2, + ColorStateList.valueOf(ColorUtils.setAlphaComponent(backgroundColor, 76)), + ) + binding.imageViewCover2.backgroundTintList = ColorStateList.valueOf( + ColorUtils.setAlphaComponent(backgroundColor, 76), + ) + binding.imageViewCover3.backgroundTintList = ColorStateList.valueOf( + ColorUtils.setAlphaComponent(backgroundColor, 153), + ) + coverViews.forEachIndexed { index, view -> + view.crossfadeDurationFactor = index + 1f + } + } + + fun setCoversAsync(covers: List) { + coverViews.forEachIndexed { index, view -> + view.setImageAsync(covers.getOrNull(index)) + } + } + + @JvmName("setMangaCoversAsync") + fun setCoversAsync(manga: List) { + coverViews.forEachIndexed { index, view -> + val m = manga.getOrNull(index) + view.setCoverOrHide(m?.coverUrl, m, m?.source) + } + } + + fun setCoverSize(@Px coverSize: Float) { + val coverWidth = (coverSize * 13f).toInt() + val coverHeight = (coverSize * 18f).toInt() + children.forEach { + it.updateLayoutParams { + width = coverWidth + height = coverHeight + } + } + } + + private fun CoverImageView.setCoverOrHide(url: String?, manga: Manga?, source: MangaSource?) { + if (url.isNullOrEmpty() && hideEmptyView) { + disposeImage() + isVisible = false + } else { + isVisible = true + if (manga != null) { + setImageAsync(url, manga) + } else { + setImageAsync(url, source ?: UnknownMangaSource) + } + } + } +} 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 3b1b336b5..ffc66ebd5 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 @@ -218,10 +218,8 @@ abstract class MangaListFragment : protected open fun onCreateAdapter(): MangaListAdapter { return MangaListAdapter( - coil = coil, - lifecycleOwner = viewLifecycleOwner, listener = this, - sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = false), + sizeResolver = DynamicItemSizeResolver(resources, viewLifecycleOwner, settings, adjustWidth = false), ) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyHintAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyHintAD.kt index 96905bd07..12ebd8ab5 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyHintAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyHintAD.kt @@ -1,18 +1,12 @@ package org.koitharu.kotatsu.list.ui.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemEmptyCardBinding import org.koitharu.kotatsu.list.ui.model.EmptyHint import org.koitharu.kotatsu.list.ui.model.ListModel fun emptyHintAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: ListStateHolderListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemEmptyCardBinding.inflate(inflater, parent, false) }, @@ -21,7 +15,7 @@ fun emptyHintAD( binding.buttonRetry.setOnClickListener { listener.onEmptyActionClick() } bind { - binding.icon.newImageRequest(lifecycleOwner, item.icon)?.enqueueWith(coil) + binding.icon.setImageAsync(item.icon) binding.textPrimary.setText(item.textPrimary) binding.textSecondary.setTextAndVisible(item.textSecondary) binding.buttonRetry.setTextAndVisible(item.actionStringRes) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyStateListAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyStateListAD.kt index 2c5967262..0dfcb9129 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyStateListAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/EmptyStateListAD.kt @@ -1,20 +1,13 @@ package org.koitharu.kotatsu.list.ui.adapter import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.core.util.ext.disposeImageRequest -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemEmptyStateBinding import org.koitharu.kotatsu.list.ui.model.EmptyState import org.koitharu.kotatsu.list.ui.model.ListModel fun emptyStateListAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: ListStateHolderListener?, ) = adapterDelegateViewBinding( { inflater, parent -> ItemEmptyStateBinding.inflate(inflater, parent, false) }, @@ -27,10 +20,10 @@ fun emptyStateListAD( bind { if (item.icon == 0) { binding.icon.isVisible = false - binding.icon.disposeImageRequest() + binding.icon.disposeImage() } else { binding.icon.isVisible = true - binding.icon.newImageRequest(lifecycleOwner, item.icon)?.enqueueWith(coil) + binding.icon.setImageAsync(item.icon) } binding.textPrimary.setText(item.textPrimary) binding.textSecondary.setTextAndVisible(item.textSecondary) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt index 79a37cb1f..a4e4abfaa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaGridItemAD.kt @@ -1,20 +1,10 @@ package org.koitharu.kotatsu.list.ui.adapter import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner -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 import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemMangaGridBinding import org.koitharu.kotatsu.list.ui.ListModelDiffCallback.Companion.PAYLOAD_PROGRESS_CHANGED import org.koitharu.kotatsu.list.ui.model.ListModel @@ -23,8 +13,6 @@ import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver import org.koitharu.kotatsu.parsers.model.Manga fun mangaGridItemAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, sizeResolver: ItemSizeResolver, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( @@ -32,7 +20,7 @@ fun mangaGridItemAD( ) { AdapterDelegateClickListenerAdapter(this, clickListener, MangaGridModel::manga).attach(itemView) - sizeResolver.attachToView(lifecycleOwner, itemView, binding.textViewTitle, binding.progressView) + sizeResolver.attachToView(itemView, binding.textViewTitle, binding.progressView) bind { payloads -> binding.textViewTitle.text = item.title @@ -43,14 +31,7 @@ fun mangaGridItemAD( 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) - transformations(TrimTransformation()) - allowRgb565(true) - mangaExtra(item.manga) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl, item.manga) binding.badge.number = item.counter binding.badge.isVisible = item.counter > 0 } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt index 503299e8c..bb3f16045 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListAdapter.kt @@ -1,28 +1,24 @@ package org.koitharu.kotatsu.list.ui.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver open class MangaListAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: MangaListListener, sizeResolver: ItemSizeResolver, ) : BaseListAdapter() { init { - addDelegate(ListItemType.MANGA_LIST, mangaListItemAD(coil, lifecycleOwner, listener)) - addDelegate(ListItemType.MANGA_LIST_DETAILED, mangaListDetailedItemAD(coil, lifecycleOwner, listener)) - addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, sizeResolver, listener)) + addDelegate(ListItemType.MANGA_LIST, mangaListItemAD(listener)) + addDelegate(ListItemType.MANGA_LIST_DETAILED, mangaListDetailedItemAD(listener)) + addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(sizeResolver, listener)) addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener)) addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener)) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) - addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(coil, lifecycleOwner, listener)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener)) + addDelegate(ListItemType.HINT_EMPTY, emptyHintAD(listener)) addDelegate(ListItemType.HEADER, listHeaderAD(listener)) addDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener)) addDelegate(ListItemType.TIP, tipAD(listener)) 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 78ecefd83..4e54a295a 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 @@ -1,19 +1,9 @@ package org.koitharu.kotatsu.list.ui.adapter import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner -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 -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemMangaListDetailsBinding import org.koitharu.kotatsu.list.ui.ListModelDiffCallback @@ -21,8 +11,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.MangaDetailedListModel fun mangaListDetailedItemAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: MangaDetailsClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemMangaListDetailsBinding.inflate(inflater, parent, false) }, @@ -43,14 +31,7 @@ fun mangaListDetailedItemAD( 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) - transformations(TrimTransformation()) - allowRgb565(true) - mangaExtra(item.manga) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl, item.manga) binding.textViewTags.text = item.tags.joinToString(separator = ", ") { it.title ?: "" } binding.badge.number = item.counter binding.badge.isVisible = item.counter > 0 diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt index c8ff4a504..a58a1cf12 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/adapter/MangaListItemAD.kt @@ -1,18 +1,9 @@ package org.koitharu.kotatsu.list.ui.adapter import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.transformations import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemMangaListBinding import org.koitharu.kotatsu.list.ui.model.ListModel @@ -20,8 +11,6 @@ import org.koitharu.kotatsu.list.ui.model.MangaCompactListModel import org.koitharu.kotatsu.parsers.model.Manga fun mangaListItemAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) }, @@ -32,13 +21,7 @@ fun mangaListItemAD( bind { binding.textViewTitle.text = item.title binding.textViewSubtitle.textAndVisible = item.subtitle - binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { - defaultPlaceholders(context) - allowRgb565(true) - transformations(TrimTransformation()) - mangaExtra(item.manga) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl, item.manga) binding.badge.number = item.counter binding.badge.isVisible = item.counter > 0 } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt index 8fb488e50..8ef4a8063 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/preview/PreviewFragment.kt @@ -10,26 +10,12 @@ import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.fragment.app.viewModels import coil3.ImageLoader -import coil3.request.ImageRequest -import coil3.request.SuccessResult -import coil3.request.error -import coil3.request.fallback -import coil3.request.lifecycle -import coil3.request.placeholder -import coil3.request.target -import coil3.util.CoilUtils import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.ui.BaseFragment -import org.koitharu.kotatsu.core.ui.image.CoverSizeResolver import org.koitharu.kotatsu.core.ui.widgets.ChipsView -import org.koitharu.kotatsu.core.util.ext.crossfade -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.drawable -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.FragmentPreviewBinding @@ -143,27 +129,7 @@ class PreviewFragment : BaseFragment(), View.OnClickList private fun loadCover(manga: Manga) { val imageUrl = manga.largeCoverUrl.ifNullOrEmpty { manga.coverUrl } - val lastResult = CoilUtils.result(requireViewBinding().imageViewCover) - if (lastResult is SuccessResult && lastResult.request.data == imageUrl) { - return - } - val request = ImageRequest.Builder(context ?: return) - .target(requireViewBinding().imageViewCover) - .size(CoverSizeResolver(requireViewBinding().imageViewCover)) - .data(imageUrl) - .mangaSourceExtra(manga.source) - .crossfade(requireContext()) - .lifecycle(viewLifecycleOwner) - .placeholderMemoryCacheKey(manga.coverUrl) - val previousDrawable = lastResult?.drawable - if (previousDrawable != null) { - request.fallback(previousDrawable) - .placeholder(previousDrawable) - .error(previousDrawable) - } else { - request.defaultPlaceholders(requireContext()) - } - request.enqueueWith(coil) + requireViewBinding().imageViewCover.setImageAsync(imageUrl, manga) } private fun onTagsChipsChanged(chips: List) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/DynamicItemSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/DynamicItemSizeResolver.kt index a26cf770f..6e48711aa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/DynamicItemSizeResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/DynamicItemSizeResolver.kt @@ -15,6 +15,7 @@ import kotlin.math.roundToInt class DynamicItemSizeResolver( resources: Resources, + private val lifecycleOwner: LifecycleOwner, private val settings: AppSettings, private val adjustWidth: Boolean, ) : ItemSizeResolver { @@ -27,7 +28,6 @@ class DynamicItemSizeResolver( get() = (gridWidth * scaleFactor).roundToInt() override fun attachToView( - lifecycleOwner: LifecycleOwner, view: View, textView: TextView?, progressView: ReadingProgressView? diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/ItemSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/ItemSizeResolver.kt index de44dff58..f2f0be0e0 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/ItemSizeResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/ItemSizeResolver.kt @@ -2,7 +2,6 @@ package org.koitharu.kotatsu.list.ui.size import android.view.View import android.widget.TextView -import androidx.lifecycle.LifecycleOwner import org.koitharu.kotatsu.history.ui.util.ReadingProgressView interface ItemSizeResolver { @@ -10,7 +9,6 @@ interface ItemSizeResolver { val cellWidth: Int fun attachToView( - lifecycleOwner: LifecycleOwner, view: View, textView: TextView?, progressView: ReadingProgressView?, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/StaticItemSizeResolver.kt b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/StaticItemSizeResolver.kt index e4f1bc919..5dbdf97a8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/StaticItemSizeResolver.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/list/ui/size/StaticItemSizeResolver.kt @@ -4,7 +4,6 @@ import android.view.View import android.widget.TextView import androidx.core.view.updateLayoutParams import androidx.core.widget.TextViewCompat -import androidx.lifecycle.LifecycleOwner import org.koitharu.kotatsu.R import org.koitharu.kotatsu.history.ui.util.ReadingProgressView @@ -16,7 +15,6 @@ class StaticItemSizeResolver( private var textAppearanceResId = R.style.TextAppearance_Kotatsu_GridTitle override fun attachToView( - lifecycleOwner: LifecycleOwner, view: View, textView: TextView?, progressView: ReadingProgressView? diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt index ad45d14bb..99b93b8c3 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/ColorFilterConfigActivity.kt @@ -1,18 +1,17 @@ package org.koitharu.kotatsu.reader.ui.colorfilter import android.content.res.Resources -import android.graphics.Bitmap import android.os.Bundle import android.view.View import android.widget.CompoundButton +import android.widget.ImageView import androidx.activity.viewModels import androidx.core.view.WindowInsetsCompat import coil3.ImageLoader +import coil3.asDrawable +import coil3.request.ErrorResult import coil3.request.ImageRequest -import coil3.request.bitmapConfig -import coil3.request.error -import coil3.size.Scale -import coil3.size.ViewSizeResolver +import coil3.request.SuccessResult import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.slider.LabelFormatter import com.google.android.material.slider.Slider @@ -20,19 +19,15 @@ import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets -import org.koitharu.kotatsu.core.util.ext.decodeRegion -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.indicator -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.setChecked import org.koitharu.kotatsu.core.util.ext.setValueRounded import org.koitharu.kotatsu.core.util.ext.systemBarsInsets +import org.koitharu.kotatsu.core.util.progress.ImageRequestIndicatorListener import org.koitharu.kotatsu.databinding.ActivityColorFilterBinding import org.koitharu.kotatsu.parsers.model.MangaPage import org.koitharu.kotatsu.parsers.util.format -import org.koitharu.kotatsu.parsers.util.nullIfEmpty import org.koitharu.kotatsu.reader.domain.ReaderColorFilter import javax.inject.Inject @@ -50,7 +45,7 @@ class ColorFilterConfigActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivityColorFilterBinding.inflate(layoutInflater)) - setDisplayHomeAsUp(true, true) + setDisplayHomeAsUp(isEnabled = true, showUpAsClose = true) viewBinding.sliderBrightness.addOnChangeListener(this) viewBinding.sliderContrast.addOnChangeListener(this) val formatter = PercentLabelFormatter(resources) @@ -128,19 +123,17 @@ class ColorFilterConfigActivity : viewBinding.imageViewAfter.colorFilter = readerColorFilter?.toColorFilter() } - private fun loadPreview(page: MangaPage) { - val data: Any = page.preview?.nullIfEmpty() ?: page - ImageRequest.Builder(this@ColorFilterConfigActivity) - .data(data) - .scale(Scale.FILL) - .decodeRegion() - .mangaSourceExtra(page.source) - .bitmapConfig(if (viewModel.is32BitColorsEnabled) Bitmap.Config.ARGB_8888 else Bitmap.Config.RGB_565) - .indicator(listOf(viewBinding.progressBefore, viewBinding.progressAfter)) - .error(R.drawable.ic_error_placeholder) - .size(ViewSizeResolver(viewBinding.imageViewBefore)) - .target(DoubleViewTarget(viewBinding.imageViewBefore, viewBinding.imageViewAfter)) - .enqueueWith(coil) + private fun loadPreview(page: MangaPage) = with(viewBinding.imageViewBefore) { + addImageRequestListener( + ImageRequestIndicatorListener( + listOf( + viewBinding.progressBefore, + viewBinding.progressAfter, + ), + ), + ) + addImageRequestListener(ShadowImageListener(viewBinding.imageViewAfter)) + setImageAsync(page) } private fun onLoadingChanged(isLoading: Boolean) { @@ -160,4 +153,24 @@ class ColorFilterConfigActivity : return pattern.format(percent) } } + + private class ShadowImageListener( + private val imageView: ImageView + ) : ImageRequest.Listener { + + override fun onError(request: ImageRequest, result: ErrorResult) { + super.onError(request, result) + imageView.setImageDrawable(result.image?.asDrawable(imageView.resources)) + } + + override fun onStart(request: ImageRequest) { + super.onStart(request) + imageView.setImageDrawable(request.placeholder()?.asDrawable(imageView.resources)) + } + + override fun onSuccess(request: ImageRequest, result: SuccessResult) { + super.onSuccess(request, result) + imageView.setImageDrawable(result.image.asDrawable(imageView.resources)) + } + } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/DoubleViewTarget.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/DoubleViewTarget.kt deleted file mode 100644 index eeaf9e1ae..000000000 --- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/colorfilter/DoubleViewTarget.kt +++ /dev/null @@ -1,18 +0,0 @@ -package org.koitharu.kotatsu.reader.ui.colorfilter - -import android.graphics.drawable.Drawable -import android.widget.ImageView -import coil3.target.ImageViewTarget - -class DoubleViewTarget( - primaryView: ImageView, - private val secondaryView: ImageView, -) : ImageViewTarget(primaryView) { - - override var drawable: Drawable? - get() = super.drawable - set(value) { - super.drawable = value - secondaryView.setImageDrawable(value?.constantState?.newDrawable()) - } -} diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt index cacfa6b04..0702f38f7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/ScrobblerConfigActivity.kt @@ -7,9 +7,6 @@ import androidx.activity.viewModels import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding import coil3.ImageLoader -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint import org.koitharu.kotatsu.R @@ -18,9 +15,6 @@ import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.ui.BaseActivity import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.util.ext.consumeAllSystemBarsInsets -import org.koitharu.kotatsu.core.util.ext.disposeImageRequest -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.showOrHide @@ -113,15 +107,11 @@ class ScrobblerConfigActivity : BaseActivity(), private fun onUserChanged(user: ScrobblerUser?) { if (user == null) { - viewBinding.imageViewAvatar.disposeImageRequest() + viewBinding.imageViewAvatar.disposeImage() viewBinding.imageViewAvatar.setImageResource(appcompatR.drawable.abc_ic_menu_overflow_material) return } - viewBinding.imageViewAvatar.newImageRequest(this, user.avatar) - ?.placeholder(R.drawable.bg_badge_empty) - ?.fallback(R.drawable.ic_shortcut_default) - ?.error(R.drawable.ic_shortcut_default) - ?.enqueueWith(coil) + viewBinding.imageViewAvatar.setImageAsync(user.avatar) } private fun onLoadingStateChanged(isLoading: Boolean) { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAD.kt index c27da88a8..44b1a0e60 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAD.kt @@ -1,21 +1,14 @@ package org.koitharu.kotatsu.scrobbling.common.ui.config.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.core.ui.list.AdapterDelegateClickListenerAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemScrobblingMangaBinding import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo fun scrobblingMangaAD( clickListener: OnListItemClickListener, - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemScrobblingMangaBinding.inflate(layoutInflater, parent, false) }, ) { @@ -23,10 +16,7 @@ fun scrobblingMangaAD( AdapterDelegateClickListenerAdapter(this, clickListener).attach(itemView) bind { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { - defaultPlaceholders(context) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl, null) binding.textViewTitle.text = item.title binding.ratingBar.rating = item.rating * binding.ratingBar.numStars } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAdapter.kt index 04511f5cf..48acb4bd8 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/config/adapter/ScrobblingMangaAdapter.kt @@ -17,7 +17,7 @@ class ScrobblingMangaAdapter( init { addDelegate(ListItemType.HEADER, scrobblingHeaderAD()) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, null)) - addDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(clickListener, coil, lifecycleOwner)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(null)) + addDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(clickListener)) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerSelectorAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerSelectorAdapter.kt index 38acf30a2..a4a5b29c9 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerSelectorAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblerSelectorAdapter.kt @@ -20,7 +20,7 @@ class ScrobblerSelectorAdapter( init { addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) - addDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(lifecycleOwner, coil, clickListener)) + addDelegate(ListItemType.MANGA_SCROBBLING, scrobblingMangaAD(clickListener)) addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) addDelegate(ListItemType.HINT_EMPTY, scrobblerHintAD(stateHolderListener)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblingMangaAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblingMangaAD.kt index 2aca8cf01..c864d7ff1 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblingMangaAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/scrobbling/common/ui/selector/adapter/ScrobblingMangaAD.kt @@ -1,21 +1,13 @@ package org.koitharu.kotatsu.scrobbling.common.ui.selector.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.textAndVisible import org.koitharu.kotatsu.databinding.ItemMangaListBinding import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga fun scrobblingMangaAD( - lifecycleOwner: LifecycleOwner, - coil: ImageLoader, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemMangaListBinding.inflate(inflater, parent, false) }, @@ -27,10 +19,6 @@ fun scrobblingMangaAD( bind { binding.textViewTitle.text = item.name binding.textViewSubtitle.textAndVisible = item.altName - binding.imageViewCover.newImageRequest(lifecycleOwner, item.cover)?.run { - defaultPlaceholders(context) - allowRgb565(true) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.cover, null) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchActivity.kt index c7e2c6a71..a196f7332 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/SearchActivity.kt @@ -76,7 +76,7 @@ class SearchActivity : router.openList(item.source, item.listFilter, item.sortOrder) } } - val sizeResolver = DynamicItemSizeResolver(resources, settings, adjustWidth = true) + val sizeResolver = DynamicItemSizeResolver(resources, this, settings, adjustWidth = true) val selectionDecoration = MangaSelectionDecoration(this) selectionController = ListSelectionController( appCompatDelegate = delegate, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchAdapter.kt index a7c7323dd..da8031745 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchAdapter.kt @@ -34,8 +34,6 @@ class SearchAdapter( ListItemType.MANGA_NESTED_GROUP, searchResultsAD( sharedPool = pool, - lifecycleOwner = lifecycleOwner, - coil = coil, sizeResolver = sizeResolver, selectionDecoration = selectionDecoration, listener = listener, @@ -44,7 +42,7 @@ class SearchAdapter( ) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) addDelegate(ListItemType.FOOTER_LOADING, loadingFooterAD()) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener)) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener)) addDelegate(ListItemType.FOOTER_BUTTON, buttonFooterAD(listener)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt index 989e97f2b..a6a698552 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/multi/adapter/SearchResultsAD.kt @@ -2,9 +2,7 @@ package org.koitharu.kotatsu.search.ui.multi.adapter import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.RecyclerView.RecycledViewPool -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.ListDelegationAdapter import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R @@ -24,8 +22,6 @@ import org.koitharu.kotatsu.search.ui.multi.SearchResultsListModel fun searchResultsAD( sharedPool: RecycledViewPool, - lifecycleOwner: LifecycleOwner, - coil: ImageLoader, sizeResolver: ItemSizeResolver, selectionDecoration: MangaSelectionDecoration, listener: OnListItemClickListener, @@ -35,9 +31,7 @@ fun searchResultsAD( ) { binding.recyclerView.setRecycledViewPool(sharedPool) - val adapter = ListDelegationAdapter( - mangaGridItemAD(coil, lifecycleOwner, sizeResolver, listener), - ) + val adapter = ListDelegationAdapter(mangaGridItemAD(sizeResolver, listener)) binding.recyclerView.addItemDecoration(selectionDecoration) binding.recyclerView.adapter = adapter val spacing = context.resources.getDimensionPixelOffset(R.dimen.grid_spacing_outer) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt index a788c6b88..5ffc5e4d6 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionAdapter.kt @@ -17,10 +17,10 @@ class SearchSuggestionAdapter( init { delegatesManager .addDelegate(SEARCH_SUGGESTION_ITEM_TYPE_QUERY, searchSuggestionQueryAD(listener)) - .addDelegate(searchSuggestionSourceAD(coil, lifecycleOwner, listener)) - .addDelegate(searchSuggestionSourceTipAD(coil, lifecycleOwner, listener)) + .addDelegate(searchSuggestionSourceAD(listener)) + .addDelegate(searchSuggestionSourceTipAD(listener)) .addDelegate(searchSuggestionTagsAD(listener)) - .addDelegate(searchSuggestionMangaListAD(coil, lifecycleOwner, listener)) + .addDelegate(searchSuggestionMangaListAD(listener)) .addDelegate(searchSuggestionQueryHintAD(listener)) .addDelegate(searchSuggestionAuthorAD(listener)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt index 8bb08321b..cb76bcd14 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceAD.kt @@ -1,27 +1,13 @@ package org.koitharu.kotatsu.search.ui.suggestion.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.parser.favicon.faviconUri -import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable -import org.koitharu.kotatsu.core.ui.image.FaviconDrawable -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceBinding import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem fun searchSuggestionSourceAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: SearchSuggestionListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemSearchSuggestionSourceBinding.inflate(inflater, parent, false) }, @@ -38,13 +24,6 @@ fun searchSuggestionSourceAD( binding.textViewTitle.text = item.source.getTitle(context) binding.textViewSubtitle.text = item.source.getSummary(context) binding.switchLocal.isChecked = item.isEnabled - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) - binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - fallback(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) - error(fallbackIcon) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.source) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt index 7d9b07ab1..714c92d12 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionSourceTipAD.kt @@ -1,27 +1,13 @@ package org.koitharu.kotatsu.search.ui.suggestion.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding -import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.parser.favicon.faviconUri -import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable -import org.koitharu.kotatsu.core.ui.image.FaviconDrawable -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemSearchSuggestionSourceTipBinding import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem fun searchSuggestionSourceTipAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: SearchSuggestionListener, ) = adapterDelegateViewBinding( @@ -35,13 +21,6 @@ fun searchSuggestionSourceTipAD( bind { binding.textViewTitle.text = item.source.getTitle(context) binding.textViewSubtitle.text = item.source.getSummary(context) - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) - binding.imageViewCover.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - fallback(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) - error(fallbackIcon) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.source) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt index 3c58a75ac..76056d893 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/search/ui/suggestion/adapter/SearchSuggestionsMangaListAD.kt @@ -1,36 +1,25 @@ package org.koitharu.kotatsu.search.ui.suggestion.adapter import androidx.core.view.updatePadding -import androidx.lifecycle.LifecycleOwner import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView -import coil3.ImageLoader -import coil3.request.allowRgb565 -import coil3.request.transformations import com.hannesdorfmann.adapterdelegates4.AsyncListDifferDelegationAdapter import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.core.ui.image.TrimTransformation import org.koitharu.kotatsu.core.ui.list.decor.SpacingItemDecoration import org.koitharu.kotatsu.core.util.RecyclerViewScrollCallback -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemSearchSuggestionMangaGridBinding import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.search.ui.suggestion.SearchSuggestionListener import org.koitharu.kotatsu.search.ui.suggestion.model.SearchSuggestionItem fun searchSuggestionMangaListAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: SearchSuggestionListener, ) = adapterDelegate(R.layout.item_search_suggestion_manga_list) { val adapter = AsyncListDifferDelegationAdapter( SuggestionMangaDiffCallback(), - searchSuggestionMangaGridAD(coil, lifecycleOwner, listener), + searchSuggestionMangaGridAD(listener), ) val recyclerView = itemView as RecyclerView recyclerView.adapter = adapter @@ -48,8 +37,6 @@ fun searchSuggestionMangaListAD( } private fun searchSuggestionMangaGridAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: SearchSuggestionListener, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemSearchSuggestionMangaGridBinding.inflate(layoutInflater, parent, false) }, @@ -59,13 +46,7 @@ private fun searchSuggestionMangaGridAD( } bind { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.coverUrl)?.run { - defaultPlaceholders(context) - allowRgb565(true) - transformations(TrimTransformation()) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.coverUrl, item.source) binding.textViewTitle.text = item.title } } 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 index edaf08d73..e162dd9aa 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/override/OverrideConfigActivity.kt @@ -9,23 +9,15 @@ 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 @@ -107,15 +99,7 @@ class OverrideConfigActivity : BaseActivity(), View 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.imageViewCover.setImageAsync(override.coverUrl.ifNullOrEmpty { manga.coverUrl }, manga) viewBinding.layoutName.placeholderText = manga.title if (viewBinding.editName.tag == null) { viewBinding.editName.setText(override.title) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt index ae2ccf073..de7955262 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapter.kt @@ -13,7 +13,7 @@ class SourceConfigAdapter( init { with(delegatesManager) { - addDelegate(sourceConfigItemDelegate2(listener, coil, lifecycleOwner)) + addDelegate(sourceConfigItemDelegate2(listener)) addDelegate(sourceConfigEmptySearchDelegate()) addDelegate(sourceConfigTipDelegate(listener)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt index d114b3036..bf202efb7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/adapter/SourceConfigAdapterDelegates.kt @@ -6,33 +6,19 @@ import androidx.core.content.ContextCompat import androidx.core.content.pm.ShortcutManagerCompat import androidx.core.view.isGone import androidx.core.view.isVisible -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegate import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.parser.favicon.faviconUri -import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable -import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.list.OnTipCloseListener -import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.drawableStart -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemSourceConfigBinding import org.koitharu.kotatsu.databinding.ItemTipBinding import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem fun sourceConfigItemDelegate2( listener: SourceConfigListener, - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemSourceConfigBinding.inflate( @@ -62,15 +48,7 @@ fun sourceConfigItemDelegate2( binding.imageViewMenu.isVisible = item.isEnabled binding.textViewTitle.drawableStart = if (item.isPinned) iconPinned else null binding.textViewDescription.text = item.source.getSummary(context) - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) - binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - crossfade(context) - error(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) - fallback(fallbackIcon) - mangaSourceExtra(item.source) - enqueueWith(coil) - } + binding.imageViewIcon.setImageAsync(item.source) } } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt index 4cb75e40d..d838a975d 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourceCatalogItemAD.kt @@ -3,26 +3,14 @@ package org.koitharu.kotatsu.settings.sources.catalog import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.core.view.updatePaddingRelative -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.error -import coil3.request.fallback -import coil3.request.placeholder import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R -import org.koitharu.kotatsu.browser.cloudflare.CaptchaNotifier.Companion.ignoreCaptchaErrors import org.koitharu.kotatsu.core.model.getSummary import org.koitharu.kotatsu.core.model.getTitle -import org.koitharu.kotatsu.core.parser.favicon.faviconUri -import org.koitharu.kotatsu.core.ui.image.AnimatedFaviconDrawable import org.koitharu.kotatsu.core.ui.image.FaviconDrawable import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.crossfade import org.koitharu.kotatsu.core.util.ext.drawableStart -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getThemeDimensionPixelOffset -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.setTextAndVisible import org.koitharu.kotatsu.databinding.ItemEmptyHintBinding import org.koitharu.kotatsu.databinding.ItemSourceCatalogBinding @@ -30,8 +18,6 @@ import org.koitharu.kotatsu.list.ui.model.ListModel import androidx.appcompat.R as appcompatR fun sourceCatalogItemSourceAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: OnListItemClickListener ) = adapterDelegateViewBinding( { layoutInflater, parent -> @@ -61,30 +47,19 @@ fun sourceCatalogItemSourceAD( } else { null } - val fallbackIcon = FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) - binding.imageViewIcon.newImageRequest(lifecycleOwner, item.source.faviconUri())?.run { - crossfade(context) - error(fallbackIcon) - placeholder(AnimatedFaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name)) - fallback(fallbackIcon) - mangaSourceExtra(item.source) - ignoreCaptchaErrors() - enqueueWith(coil) - } + FaviconDrawable(context, R.style.FaviconDrawable_Small, item.source.name) + binding.imageViewIcon.setImageAsync(item.source) } } -fun sourceCatalogItemHintAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, -) = adapterDelegateViewBinding( +fun sourceCatalogItemHintAD() = adapterDelegateViewBinding( { inflater, parent -> ItemEmptyHintBinding.inflate(inflater, parent, false) }, ) { binding.buttonRetry.isVisible = false bind { - binding.icon.newImageRequest(lifecycleOwner, item.icon)?.enqueueWith(coil) + binding.icon.setImageAsync(item.icon) binding.textPrimary.setText(item.title) binding.textSecondary.setTextAndVisible(item.text) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt index b05e569c5..bf6fdc0ad 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogActivity.kt @@ -10,7 +10,6 @@ import androidx.appcompat.widget.SearchView import androidx.core.graphics.Insets import androidx.core.view.WindowInsetsCompat import androidx.core.view.updatePadding -import coil3.ImageLoader import com.google.android.material.appbar.AppBarLayout import com.google.android.material.chip.Chip import dagger.hilt.android.AndroidEntryPoint @@ -33,7 +32,6 @@ import org.koitharu.kotatsu.databinding.ActivitySourcesCatalogBinding import org.koitharu.kotatsu.list.ui.adapter.TypedListSpacingDecoration import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.parsers.model.ContentType -import javax.inject.Inject @AndroidEntryPoint class SourcesCatalogActivity : BaseActivity(), @@ -42,9 +40,6 @@ class SourcesCatalogActivity : BaseActivity(), MenuItem.OnActionExpandListener, ChipsView.OnChipClickListener { - @Inject - lateinit var coil: ImageLoader - override val appBar: AppBarLayout get() = viewBinding.appbar @@ -53,8 +48,8 @@ class SourcesCatalogActivity : BaseActivity(), override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(ActivitySourcesCatalogBinding.inflate(layoutInflater)) - setDisplayHomeAsUp(true, false) - val sourcesAdapter = SourcesCatalogAdapter(this, coil, this) + setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false) + val sourcesAdapter = SourcesCatalogAdapter(this) with(viewBinding.recyclerView) { setHasFixedSize(true) addItemDecoration(TypedListSpacingDecoration(context, false)) @@ -85,7 +80,7 @@ class SourcesCatalogActivity : BaseActivity(), right = bars.right, top = bars.top, ) - return return WindowInsetsCompat.Builder(insets) + return WindowInsetsCompat.Builder(insets) .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE) .build() } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogAdapter.kt index bb2e411e5..01d552f01 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/sources/catalog/SourcesCatalogAdapter.kt @@ -1,8 +1,6 @@ package org.koitharu.kotatsu.settings.sources.catalog import android.content.Context -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.model.getTitle import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener @@ -13,13 +11,11 @@ import org.koitharu.kotatsu.list.ui.model.ListModel class SourcesCatalogAdapter( listener: OnListItemClickListener, - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, ) : BaseListAdapter(), FastScroller.SectionIndexer { init { - addDelegate(ListItemType.CHAPTER_LIST, sourceCatalogItemSourceAD(coil, lifecycleOwner, listener)) - addDelegate(ListItemType.HINT_EMPTY, sourceCatalogItemHintAD(coil, lifecycleOwner)) + addDelegate(ListItemType.CHAPTER_LIST, sourceCatalogItemSourceAD(listener)) + addDelegate(ListItemType.HINT_EMPTY, sourceCatalogItemHintAD()) addDelegate(ListItemType.STATE_LOADING, loadingStateAD()) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsActivity.kt index 3e5bc104d..0646274a7 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/stats/ui/StatsActivity.kt @@ -30,8 +30,6 @@ import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.util.ReversibleActionObserver import org.koitharu.kotatsu.core.util.KotatsuColors import org.koitharu.kotatsu.core.util.ext.end -import org.koitharu.kotatsu.core.util.ext.enqueueWith -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.setTextAndVisible @@ -125,7 +123,7 @@ class StatsActivity : BaseActivity(), marginStart = baseMargin + bars.start(v) marginEnd = if (isTablet) baseMargin else baseMargin + bars.end(v) } - return return WindowInsetsCompat.Builder(insets) + return WindowInsetsCompat.Builder(insets) .setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE) .build() } @@ -177,7 +175,7 @@ class StatsActivity : BaseActivity(), override fun onInflate(stub: ViewStub?, inflated: View) { val stubBinding = ItemEmptyStateBinding.bind(inflated) - stubBinding.icon.newImageRequest(this, R.drawable.ic_empty_history)?.enqueueWith(coil) + stubBinding.icon.setImageAsync(R.drawable.ic_empty_history) stubBinding.textPrimary.setText(R.string.text_empty_holder_primary) stubBinding.textSecondary.setTextAndVisible(R.string.empty_stats_text) stubBinding.buttonRetry.isVisible = false diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt index 33e146b62..8dd5c58f2 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackDebugAD.kt @@ -6,25 +6,16 @@ import androidx.core.content.ContextCompat import androidx.core.text.bold import androidx.core.text.buildSpannedString import androidx.core.text.color -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.drawableStart -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getThemeColor -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemTrackDebugBinding import org.koitharu.kotatsu.tracker.data.TrackEntity import androidx.appcompat.R as appcompatR fun trackDebugAD( - lifecycleOwner: LifecycleOwner, - coil: ImageLoader, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { layoutInflater, parent -> ItemTrackDebugBinding.inflate(layoutInflater, parent, false) }, @@ -36,12 +27,7 @@ fun trackDebugAD( } bind { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.manga.coverUrl)?.run { - defaultPlaceholders(context) - allowRgb565(true) - mangaSourceExtra(item.manga.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.manga.coverUrl, item.manga) binding.textViewTitle.text = item.manga.title binding.textViewSummary.text = buildSpannedString { append( diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt index 70eabceb9..126f51f70 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/debug/TrackerDebugActivity.kt @@ -32,7 +32,7 @@ class TrackerDebugActivity : BaseActivity(), OnList setContentView(ActivityTrackerDebugBinding.inflate(layoutInflater)) setDisplayHomeAsUp(true, false) val tracksAdapter = BaseListAdapter() - .addDelegate(ListItemType.FEED, trackDebugAD(this, coil, this)) + .addDelegate(ListItemType.FEED, trackDebugAD(this)) with(viewBinding.recyclerView) { setHasFixedSize(true) adapter = tracksAdapter 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 92930937c..d5f9d33f5 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 @@ -61,7 +61,7 @@ class FeedFragment : override fun onViewBindingCreated(binding: FragmentListBinding, savedInstanceState: Bundle?) { super.onViewBindingCreated(binding, savedInstanceState) val sizeResolver = StaticItemSizeResolver(resources.getDimensionPixelSize(R.dimen.smaller_grid_width)) - val feedAdapter = FeedAdapter(coil, viewLifecycleOwner, this, sizeResolver) { item, v -> + val feedAdapter = FeedAdapter(this, sizeResolver) { item, v -> viewModel.onItemClick(item) onItemClick(item.manga, v) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt index d6c4951ff..6f9ae2624 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedAdapter.kt @@ -1,8 +1,6 @@ package org.koitharu.kotatsu.tracker.ui.feed.adapter import android.content.Context -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import org.koitharu.kotatsu.core.ui.BaseListAdapter import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller @@ -20,20 +18,16 @@ import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem class FeedAdapter( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, listener: MangaListListener, sizeResolver: ItemSizeResolver, feedClickListener: OnListItemClickListener, ) : BaseListAdapter(), FastScroller.SectionIndexer { init { - addDelegate(ListItemType.FEED, feedItemAD(coil, lifecycleOwner, feedClickListener)) + addDelegate(ListItemType.FEED, feedItemAD(feedClickListener)) addDelegate( ListItemType.MANGA_NESTED_GROUP, updatedMangaAD( - lifecycleOwner = lifecycleOwner, - coil = coil, sizeResolver = sizeResolver, listener = listener, headerClickListener = listener, @@ -44,7 +38,7 @@ class FeedAdapter( addDelegate(ListItemType.FOOTER_ERROR, errorFooterAD(listener)) addDelegate(ListItemType.STATE_ERROR, errorStateListAD(listener)) addDelegate(ListItemType.HEADER, listHeaderAD(listener)) - addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(coil, lifecycleOwner, listener)) + addDelegate(ListItemType.STATE_EMPTY, emptyStateListAD(listener)) addDelegate(ListItemType.QUICK_FILTER, quickFilterAD(listener)) } diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedItemAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedItemAD.kt index 75b71b11a..96471aced 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedItemAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/FeedItemAD.kt @@ -1,25 +1,16 @@ package org.koitharu.kotatsu.tracker.ui.feed.adapter import androidx.core.content.ContextCompat -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader -import coil3.request.allowRgb565 import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.list.OnListItemClickListener -import org.koitharu.kotatsu.core.util.ext.defaultPlaceholders import org.koitharu.kotatsu.core.util.ext.drawableStart -import org.koitharu.kotatsu.core.util.ext.enqueueWith import org.koitharu.kotatsu.core.util.ext.getQuantityStringSafe -import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra -import org.koitharu.kotatsu.core.util.ext.newImageRequest import org.koitharu.kotatsu.databinding.ItemFeedBinding import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.tracker.ui.feed.model.FeedItem fun feedItemAD( - coil: ImageLoader, - lifecycleOwner: LifecycleOwner, clickListener: OnListItemClickListener, ) = adapterDelegateViewBinding( { inflater, parent -> ItemFeedBinding.inflate(inflater, parent, false) }, @@ -31,12 +22,7 @@ fun feedItemAD( } bind { - binding.imageViewCover.newImageRequest(lifecycleOwner, item.imageUrl)?.run { - defaultPlaceholders(context) - allowRgb565(true) - mangaSourceExtra(item.manga.source) - enqueueWith(coil) - } + binding.imageViewCover.setImageAsync(item.imageUrl, item.manga.source) binding.textViewTitle.text = item.title binding.textViewSummary.text = context.resources.getQuantityStringSafe( R.plurals.new_chapters, diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/UpdatedMangaAD.kt b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/UpdatedMangaAD.kt index 4d6db468d..b8f8e2d71 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/UpdatedMangaAD.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/tracker/ui/feed/adapter/UpdatedMangaAD.kt @@ -1,7 +1,5 @@ package org.koitharu.kotatsu.tracker.ui.feed.adapter -import androidx.lifecycle.LifecycleOwner -import coil3.ImageLoader import com.hannesdorfmann.adapterdelegates4.dsl.adapterDelegateViewBinding import org.koitharu.kotatsu.R import org.koitharu.kotatsu.core.ui.BaseListAdapter @@ -17,8 +15,6 @@ import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.tracker.ui.feed.model.UpdatedMangaHeader fun updatedMangaAD( - lifecycleOwner: LifecycleOwner, - coil: ImageLoader, sizeResolver: ItemSizeResolver, listener: OnListItemClickListener, headerClickListener: ListHeaderClickListener, @@ -27,7 +23,7 @@ fun updatedMangaAD( ) { val adapter = BaseListAdapter() - .addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(coil, lifecycleOwner, sizeResolver, listener)) + .addDelegate(ListItemType.MANGA_GRID, mangaGridItemAD(sizeResolver, listener)) binding.recyclerView.adapter = adapter binding.buttonMore.setOnClickListener { v -> headerClickListener.onListHeaderClick(ListHeader(0, payload = item), v) diff --git a/app/src/main/res/layout-land/item_empty_state.xml b/app/src/main/res/layout-land/item_empty_state.xml index 660a4a677..1fe4eac58 100644 --- a/app/src/main/res/layout-land/item_empty_state.xml +++ b/app/src/main/res/layout-land/item_empty_state.xml @@ -8,7 +8,7 @@ android:orientation="horizontal" android:paddingHorizontal="32dp"> - - - diff --git a/app/src/main/res/layout/activity_color_filter.xml b/app/src/main/res/layout/activity_color_filter.xml index c38c88891..7e39dcbda 100644 --- a/app/src/main/res/layout/activity_color_filter.xml +++ b/app/src/main/res/layout/activity_color_filter.xml @@ -38,12 +38,14 @@ android:orientation="vertical" android:padding="@dimen/margin_normal"> - - diff --git a/app/src/main/res/layout/activity_override_edit.xml b/app/src/main/res/layout/activity_override_edit.xml index 4c14239d6..c42618822 100644 --- a/app/src/main/res/layout/activity_override_edit.xml +++ b/app/src/main/res/layout/activity_override_edit.xml @@ -36,7 +36,7 @@ android:orientation="vertical" android:paddingBottom="@dimen/screen_padding"> - - + tools:listitem="@layout/item_bookmark_large" /> diff --git a/app/src/main/res/layout/fragment_preview.xml b/app/src/main/res/layout/fragment_preview.xml index 2c99d65bd..e541b84f8 100644 --- a/app/src/main/res/layout/fragment_preview.xml +++ b/app/src/main/res/layout/fragment_preview.xml @@ -13,7 +13,7 @@ android:layout_height="wrap_content" android:paddingBottom="?actionBarSize"> - diff --git a/app/src/main/res/layout/item_bookmark.xml b/app/src/main/res/layout/item_bookmark.xml deleted file mode 100644 index 140713df3..000000000 --- a/app/src/main/res/layout/item_bookmark.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - diff --git a/app/src/main/res/layout/item_bookmark_large.xml b/app/src/main/res/layout/item_bookmark_large.xml index 370762f4a..6dcbed6d1 100644 --- a/app/src/main/res/layout/item_bookmark_large.xml +++ b/app/src/main/res/layout/item_bookmark_large.xml @@ -9,12 +9,11 @@ app:cardBackgroundColor="?attr/colorSurfaceContainerHighest" tools:layout_width="140dp"> - diff --git a/app/src/main/res/layout/item_categories_all.xml b/app/src/main/res/layout/item_categories_all.xml index 2356973cb..e3926d7dd 100644 --- a/app/src/main/res/layout/item_categories_all.xml +++ b/app/src/main/res/layout/item_categories_all.xml @@ -6,61 +6,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_selector" - android:minHeight="98dp" + android:paddingVertical="4dp" android:paddingStart="?android:listPreferredItemPaddingStart" tools:ignore="RtlSymmetry"> - - - - - + app:layout_constraintTop_toTopOf="parent" /> @@ -91,7 +49,7 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@id/imageView_visible" app:layout_constraintHorizontal_bias="0" - app:layout_constraintStart_toEndOf="@id/imageView_cover3" + app:layout_constraintStart_toEndOf="@id/coversView" app:layout_constraintTop_toBottomOf="@id/textView_title" app:layout_constraintVertical_chainStyle="packed" tools:text="@tools:sample/lorem[1]" /> diff --git a/app/src/main/res/layout/item_category.xml b/app/src/main/res/layout/item_category.xml index 9fcdcda0f..faee6121d 100644 --- a/app/src/main/res/layout/item_category.xml +++ b/app/src/main/res/layout/item_category.xml @@ -6,61 +6,19 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@drawable/list_selector" - android:minHeight="98dp" + android:paddingVertical="4dp" android:paddingStart="?android:listPreferredItemPaddingStart" tools:ignore="RtlSymmetry"> - - - - - + app:layout_constraintTop_toTopOf="parent" /> @@ -92,7 +50,7 @@ app:layout_constraintEnd_toStartOf="@id/imageView_tracker" app:layout_constraintHorizontal_bias="0" app:layout_constraintHorizontal_chainStyle="packed" - app:layout_constraintStart_toEndOf="@id/imageView_cover3" + app:layout_constraintStart_toEndOf="@id/coversView" app:layout_constraintTop_toBottomOf="@id/textView_title" app:layout_constraintVertical_chainStyle="packed" tools:text="@tools:sample/lorem[1]" /> diff --git a/app/src/main/res/layout/item_download.xml b/app/src/main/res/layout/item_download.xml index 9bac49bd8..4c84637a7 100644 --- a/app/src/main/res/layout/item_download.xml +++ b/app/src/main/res/layout/item_download.xml @@ -13,13 +13,12 @@ android:layout_height="match_parent" android:paddingBottom="12dp"> - - - - - + app:strokeWidth="1dp" /> - - - - diff --git a/app/src/main/res/layout/item_manga_list.xml b/app/src/main/res/layout/item_manga_list.xml index 589393959..552360be3 100644 --- a/app/src/main/res/layout/item_manga_list.xml +++ b/app/src/main/res/layout/item_manga_list.xml @@ -8,7 +8,7 @@ android:background="@drawable/list_selector" android:clipChildren="false"> - - - - - - - diff --git a/app/src/main/res/layout/item_search_suggestion_source.xml b/app/src/main/res/layout/item_search_suggestion_source.xml index 7aad1e997..e026343d7 100644 --- a/app/src/main/res/layout/item_search_suggestion_source.xml +++ b/app/src/main/res/layout/item_search_suggestion_source.xml @@ -11,12 +11,13 @@ android:orientation="horizontal" android:paddingVertical="@dimen/margin_small"> - diff --git a/app/src/main/res/layout/item_search_suggestion_source_tip.xml b/app/src/main/res/layout/item_search_suggestion_source_tip.xml index f3a9da423..01031d5c4 100644 --- a/app/src/main/res/layout/item_search_suggestion_source_tip.xml +++ b/app/src/main/res/layout/item_search_suggestion_source_tip.xml @@ -11,12 +11,13 @@ android:orientation="horizontal" android:paddingVertical="@dimen/margin_small"> - diff --git a/app/src/main/res/layout/item_source_catalog.xml b/app/src/main/res/layout/item_source_catalog.xml index 8618bd413..f1d63a6a4 100644 --- a/app/src/main/res/layout/item_source_catalog.xml +++ b/app/src/main/res/layout/item_source_catalog.xml @@ -12,13 +12,14 @@ android:paddingStart="?listPreferredItemPaddingStart" tools:ignore="RtlSymmetry"> - diff --git a/app/src/main/res/layout/item_source_config.xml b/app/src/main/res/layout/item_source_config.xml index 8799600fd..507689154 100644 --- a/app/src/main/res/layout/item_source_config.xml +++ b/app/src/main/res/layout/item_source_config.xml @@ -13,13 +13,14 @@ android:paddingStart="?listPreferredItemPaddingStart" android:paddingEnd="?listPreferredItemPaddingEnd"> - diff --git a/app/src/main/res/layout/item_track_debug.xml b/app/src/main/res/layout/item_track_debug.xml index e0fe28213..042c7c82a 100644 --- a/app/src/main/res/layout/item_track_debug.xml +++ b/app/src/main/res/layout/item_track_debug.xml @@ -9,13 +9,12 @@ android:clipChildren="false" android:minHeight="72dp"> - - - - - - - - - - + android:layout_height="@dimen/category_covers_height" + android:layout_marginStart="@dimen/screen_padding" + app:coverSize="3.4dp" + app:layout_constraintDimensionRatio="13:18" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> - - diff --git a/app/src/main/res/layout/sheet_scrobbling.xml b/app/src/main/res/layout/sheet_scrobbling.xml index 43c27b88b..432b00e75 100644 --- a/app/src/main/res/layout/sheet_scrobbling.xml +++ b/app/src/main/res/layout/sheet_scrobbling.xml @@ -21,7 +21,7 @@ app:layout_constraintTop_toTopOf="parent" app:title="@string/tracking" /> - + + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index de4b48348..3fab9381c 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -11,10 +11,30 @@ + + + + + + + + + + + - - + + + + + + + + + + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 1200328b7..68741b601 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -36,6 +36,7 @@ 24dp 120dp 56dp + 86dp 142dp 6dp diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 36bbbc778..1abac0400 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -241,6 +241,14 @@ ?colorOnBackground + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 4088d10f9..bc4efc2b9 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -86,6 +86,7 @@ @style/Widget.Kotatsu.Spinner.DropDown @style/Widget.Kotatsu.DotIndicator @style/Widget.Kotatsu.BadgeView + @style/Widget.Kotatsu.ImageView.Cover @style/TextAppearance.Kotatsu.Menu