Refactor image loading
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ class AlternativesActivity : BaseActivity<ActivityAlternativesBinding>(),
|
||||
}
|
||||
val listAdapter = BaseListAdapter<ListModel>()
|
||||
.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))
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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<Bookmark>,
|
||||
) = adapterDelegateViewBinding<Bookmark, ListModel, ItemBookmarkLargeBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Bookmark>,
|
||||
headerClickListener: ListHeaderClickListener?,
|
||||
) : BaseListAdapter<ListModel>(), 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? {
|
||||
|
||||
@@ -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<ImageRequest.Listener>? = 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<ImageRequest.Listener>().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
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<ImageView> {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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<View>()
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -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<BaseProgressIndicator<*>>): 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>,
|
||||
) : 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<Manga?>(null)
|
||||
val bookmarkKey = Extras.Key<Bookmark?>(null)
|
||||
val mangaSourceKey = Extras.Key<MangaSource?>(null)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<FragmentMangaBookmarksBinding>(),
|
||||
private val activityViewModel by ChaptersPagesViewModel.ActivityVMLazy(this)
|
||||
private val viewModel by viewModels<BookmarksViewModel>()
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
@@ -89,8 +85,6 @@ class BookmarksFragment : BaseFragment<FragmentMangaBookmarksBinding>(),
|
||||
callback = this,
|
||||
)
|
||||
bookmarksAdapter = BookmarksAdapter(
|
||||
coil = coil,
|
||||
lifecycleOwner = viewLifecycleOwner,
|
||||
clickListener = this@BookmarksFragment,
|
||||
headerClickListener = null,
|
||||
)
|
||||
|
||||
@@ -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<PageThumbnail>,
|
||||
) = adapterDelegateViewBinding<PageThumbnail, ListModel, ItemPageThumbBinding>(
|
||||
{ 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)
|
||||
|
||||
@@ -17,7 +17,7 @@ class PageThumbnailAdapter(
|
||||
) : BaseListAdapter<ListModel>(), FastScroller.SectionIndexer {
|
||||
|
||||
init {
|
||||
addDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(coil, lifecycleOwner, clickListener))
|
||||
addDelegate(ListItemType.PAGE_THUMB, pageThumbnailAD(clickListener))
|
||||
addDelegate(ListItemType.HEADER, listHeaderAD(null))
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ScrobblingInfo, ListModel, ItemScrobblingInfoBinding>(
|
||||
{ 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -13,6 +13,6 @@ class ScrollingInfoAdapter(
|
||||
) : BaseListAdapter<ListModel>() {
|
||||
|
||||
init {
|
||||
delegatesManager.addDelegate(scrobblingInfoAD(lifecycleOwner, coil, router))
|
||||
delegatesManager.addDelegate(scrobblingInfoAD(router))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<DownloadItemModel, ListModel, ItemDownloadBinding>(
|
||||
{ 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) {
|
||||
|
||||
@@ -44,7 +44,7 @@ class DownloadsActivity : BaseActivity<ActivityDownloadsBinding>(),
|
||||
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,
|
||||
|
||||
@@ -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<ListModel>() {
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MangaSourceItem>, ListSelectionController.Callback {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel by viewModels<ExploreViewModel>()
|
||||
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(
|
||||
|
||||
@@ -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<MangaSourceItem>,
|
||||
mangaClickListener: OnListItemClickListener<Manga>,
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Manga>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<RecommendationsItem, ListModel, ItemRecommendationBinding>(
|
||||
{ layoutInflater, parent -> ItemRecommendationBinding.inflate(layoutInflater, parent, false) },
|
||||
) {
|
||||
|
||||
val adapter = BaseListAdapter<MangaCompactListModel>()
|
||||
.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<Manga>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<MangaCompactListModel, MangaCompactListModel, ItemRecommendationMangaBinding>(
|
||||
{ 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<MangaSourceItem>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceListBinding>(
|
||||
{ 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<MangaSourceItem>,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<MangaSourceItem, ListModel, ItemExploreSourceGridBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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<ListModel>() {
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CategoryListModel, ListModel, ItemCategoryBinding>(
|
||||
{ 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<AllCategoriesListModel, ListModel, ItemCategoriesAllBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SheetFavoriteCategoriesBinding>(),
|
||||
@@ -47,9 +28,6 @@ class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
|
||||
|
||||
private val viewModel by viewModels<FavoriteDialogViewModel>()
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
override fun onCreateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
@@ -66,7 +44,7 @@ class FavoriteDialog : AlertDialogFragment<SheetFavoriteCategoriesBinding>(),
|
||||
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<SheetFavoriteCategoriesBinding>(),
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<MangaCategoryItem>,
|
||||
) : BaseListAdapter<ListModel>() {
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<FragmentFavouritesContainerBinding>(),
|
||||
@@ -41,9 +37,6 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
ViewStub.OnInflateListener,
|
||||
View.OnClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
private val viewModel: FavouritesContainerViewModel by viewModels()
|
||||
|
||||
override val recyclerView: RecyclerView?
|
||||
@@ -96,7 +89,7 @@ class FavouritesContainerFragment : BaseFragment<FragmentFavouritesContainerBind
|
||||
|
||||
override fun onInflate(stub: ViewStub?, inflated: View) {
|
||||
val stubBinding = ItemEmptyStateBinding.bind(inflated)
|
||||
stubBinding.icon.newImageRequest(viewLifecycleOwner, R.drawable.ic_empty_favourites)?.enqueueWith(coil)
|
||||
stubBinding.icon.setImageAsync(R.drawable.ic_empty_favourites)
|
||||
stubBinding.textPrimary.setText(R.string.text_empty_holder_primary)
|
||||
stubBinding.textSecondary.setTextAndVisible(R.string.empty_favourite_categories)
|
||||
stubBinding.buttonRetry.setTextAndVisible(R.string.manage)
|
||||
|
||||
@@ -1,19 +1,15 @@
|
||||
package org.koitharu.kotatsu.history.ui
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import coil3.ImageLoader
|
||||
import org.koitharu.kotatsu.core.ui.list.fastscroll.FastScroller
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListAdapter
|
||||
import org.koitharu.kotatsu.list.ui.adapter.MangaListListener
|
||||
import org.koitharu.kotatsu.list.ui.size.ItemSizeResolver
|
||||
|
||||
class HistoryListAdapter(
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
listener: MangaListListener,
|
||||
sizeResolver: ItemSizeResolver,
|
||||
) : MangaListAdapter(coil, lifecycleOwner, listener, sizeResolver), FastScroller.SectionIndexer {
|
||||
) : MangaListAdapter(listener, sizeResolver), FastScroller.SectionIndexer {
|
||||
|
||||
override fun getSectionText(context: Context, position: Int): CharSequence? {
|
||||
return findHeader(position)?.getText(context)
|
||||
|
||||
@@ -73,9 +73,7 @@ class HistoryListFragment : MangaListFragment() {
|
||||
}
|
||||
|
||||
override fun onCreateAdapter() = HistoryListAdapter(
|
||||
coil,
|
||||
viewLifecycleOwner,
|
||||
this,
|
||||
DynamicItemSizeResolver(resources, settings, adjustWidth = false),
|
||||
DynamicItemSizeResolver(resources, viewLifecycleOwner, settings, adjustWidth = false),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,243 @@
|
||||
package org.koitharu.kotatsu.image.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewTreeObserver
|
||||
import android.view.ViewTreeObserver.OnPreDrawListener
|
||||
import androidx.annotation.AttrRes
|
||||
import androidx.core.content.withStyledAttributes
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.core.graphics.drawable.toDrawable
|
||||
import coil3.request.transformations
|
||||
import coil3.size.Dimension
|
||||
import coil3.size.Size
|
||||
import coil3.size.ViewSizeResolver
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.core.image.CoilImageView
|
||||
import org.koitharu.kotatsu.core.ui.image.AnimatedPlaceholderDrawable
|
||||
import org.koitharu.kotatsu.core.ui.image.TrimTransformation
|
||||
import org.koitharu.kotatsu.core.util.ext.bookmarkExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.decodeRegion
|
||||
import org.koitharu.kotatsu.core.util.ext.getThemeColor
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaExtra
|
||||
import org.koitharu.kotatsu.core.util.ext.mangaSourceExtra
|
||||
import org.koitharu.kotatsu.favourites.domain.model.Cover
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.nullIfEmpty
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import kotlin.coroutines.resume
|
||||
import androidx.appcompat.R as appcompatR
|
||||
import com.google.android.material.R as materialR
|
||||
|
||||
class CoverImageView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
@AttrRes defStyleAttr: Int = R.attr.coverImageViewStyle,
|
||||
) : CoilImageView(context, attrs, defStyleAttr) {
|
||||
|
||||
private var aspectRationHeight: Int = 0
|
||||
private var aspectRationWidth: Int = 0
|
||||
var trimImage: Boolean = false
|
||||
|
||||
private val hasAspectRatio: Boolean
|
||||
get() = aspectRationHeight > 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<CoverImageView> {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Cover>) {
|
||||
coverViews.forEachIndexed { index, view ->
|
||||
view.setImageAsync(covers.getOrNull(index))
|
||||
}
|
||||
}
|
||||
|
||||
@JvmName("setMangaCoversAsync")
|
||||
fun setCoversAsync(manga: List<Manga>) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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<EmptyHint, ListModel, ItemEmptyCardBinding>(
|
||||
{ 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)
|
||||
|
||||
@@ -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<EmptyState, ListModel, ItemEmptyStateBinding>(
|
||||
{ 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)
|
||||
|
||||
@@ -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<Manga>,
|
||||
) = adapterDelegateViewBinding<MangaGridModel, ListModel, ItemMangaGridBinding>(
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<ListModel>() {
|
||||
|
||||
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))
|
||||
|
||||
@@ -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<MangaDetailedListModel, ListModel, ItemMangaListDetailsBinding>(
|
||||
{ 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
|
||||
|
||||
@@ -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<Manga>,
|
||||
) = adapterDelegateViewBinding<MangaCompactListModel, ListModel, ItemMangaListBinding>(
|
||||
{ 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
|
||||
}
|
||||
|
||||
@@ -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<FragmentPreviewBinding>(), 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<ChipsView.ChipModel>) {
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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?,
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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<ActivityScrobblerConfigBinding>(),
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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<ScrobblingInfo>,
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) = adapterDelegateViewBinding<ScrobblingInfo, ListModel, ItemScrobblingMangaBinding>(
|
||||
{ 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
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<ScrobblerManga>,
|
||||
) = adapterDelegateViewBinding<ScrobblerManga, ListModel, ItemMangaListBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<Manga>,
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<SearchSuggestionItem.Source, SearchSuggestionItem, ItemSearchSuggestionSourceBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SearchSuggestionItem.SourceTip, SearchSuggestionItem, ItemSearchSuggestionSourceTipBinding>(
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<SearchSuggestionItem.MangaList, SearchSuggestionItem>(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<Manga, Manga, ItemSearchSuggestionMangaGridBinding>(
|
||||
{ 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ActivityOverrideEditBinding>(), View
|
||||
|
||||
private fun onDataChanged(data: Pair<Manga, MangaOverride>) {
|
||||
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)
|
||||
|
||||
@@ -13,7 +13,7 @@ class SourceConfigAdapter(
|
||||
|
||||
init {
|
||||
with(delegatesManager) {
|
||||
addDelegate(sourceConfigItemDelegate2(listener, coil, lifecycleOwner))
|
||||
addDelegate(sourceConfigItemDelegate2(listener))
|
||||
addDelegate(sourceConfigEmptySearchDelegate())
|
||||
addDelegate(sourceConfigTipDelegate(listener))
|
||||
}
|
||||
|
||||
@@ -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<SourceConfigItem.SourceItem, SourceConfigItem, ItemSourceConfigBinding>(
|
||||
{ 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SourceCatalogItem.Source>
|
||||
) = adapterDelegateViewBinding<SourceCatalogItem.Source, ListModel, ItemSourceCatalogBinding>(
|
||||
{ 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<SourceCatalogItem.Hint, ListModel, ItemEmptyHintBinding>(
|
||||
fun sourceCatalogItemHintAD() = adapterDelegateViewBinding<SourceCatalogItem.Hint, ListModel, ItemEmptyHintBinding>(
|
||||
{ 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)
|
||||
}
|
||||
|
||||
@@ -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<ActivitySourcesCatalogBinding>(),
|
||||
@@ -42,9 +40,6 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
||||
MenuItem.OnActionExpandListener,
|
||||
ChipsView.OnChipClickListener {
|
||||
|
||||
@Inject
|
||||
lateinit var coil: ImageLoader
|
||||
|
||||
override val appBar: AppBarLayout
|
||||
get() = viewBinding.appbar
|
||||
|
||||
@@ -53,8 +48,8 @@ class SourcesCatalogActivity : BaseActivity<ActivitySourcesCatalogBinding>(),
|
||||
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<ActivitySourcesCatalogBinding>(),
|
||||
right = bars.right,
|
||||
top = bars.top,
|
||||
)
|
||||
return return WindowInsetsCompat.Builder(insets)
|
||||
return WindowInsetsCompat.Builder(insets)
|
||||
.setInsets(WindowInsetsCompat.Type.systemBars(), Insets.NONE)
|
||||
.build()
|
||||
}
|
||||
|
||||
@@ -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<SourceCatalogItem.Source>,
|
||||
coil: ImageLoader,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
) : BaseListAdapter<ListModel>(), 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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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<ActivityStatsBinding>(),
|
||||
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<ActivityStatsBinding>(),
|
||||
|
||||
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
|
||||
|
||||
@@ -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<TrackDebugItem>,
|
||||
) = adapterDelegateViewBinding<TrackDebugItem, TrackDebugItem, ItemTrackDebugBinding>(
|
||||
{ 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(
|
||||
|
||||
@@ -32,7 +32,7 @@ class TrackerDebugActivity : BaseActivity<ActivityTrackerDebugBinding>(), OnList
|
||||
setContentView(ActivityTrackerDebugBinding.inflate(layoutInflater))
|
||||
setDisplayHomeAsUp(true, false)
|
||||
val tracksAdapter = BaseListAdapter<TrackDebugItem>()
|
||||
.addDelegate(ListItemType.FEED, trackDebugAD(this, coil, this))
|
||||
.addDelegate(ListItemType.FEED, trackDebugAD(this))
|
||||
with(viewBinding.recyclerView) {
|
||||
setHasFixedSize(true)
|
||||
adapter = tracksAdapter
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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<FeedItem>,
|
||||
) : BaseListAdapter<ListModel>(), 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))
|
||||
}
|
||||
|
||||
|
||||
@@ -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<FeedItem>,
|
||||
) = adapterDelegateViewBinding<FeedItem, ListModel, ItemFeedBinding>(
|
||||
{ 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,
|
||||
|
||||
@@ -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<Manga>,
|
||||
headerClickListener: ListHeaderClickListener,
|
||||
@@ -27,7 +23,7 @@ fun updatedMangaAD(
|
||||
) {
|
||||
|
||||
val adapter = BaseListAdapter<ListModel>()
|
||||
.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)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="32dp">
|
||||
|
||||
<ImageView
|
||||
<org.koitharu.kotatsu.core.image.CoilImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="192dp"
|
||||
android:layout_height="192dp"
|
||||
|
||||
@@ -54,12 +54,14 @@
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_before"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:padding="2dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:allowRgb565="false"
|
||||
app:decodeRegion="true"
|
||||
app:layout_constraintDimensionRatio="W,14:9"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_arrow"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/margin_normal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
@@ -63,6 +63,9 @@
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:allowRgb565="false"
|
||||
app:aspectRationHeight="0"
|
||||
app:aspectRationWidth="0"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
@@ -70,6 +73,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_percent="0.3"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
app:useExistingDrawable="true"
|
||||
tools:background="@tools:sample/backgrounds/scenic[5]"
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
|
||||
@@ -38,12 +38,14 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/margin_normal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_before"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:padding="2dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:allowRgb565="false"
|
||||
app:decodeRegion="true"
|
||||
app:layout_constraintDimensionRatio="W,14:9"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_arrow"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="@dimen/margin_normal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
@@ -55,6 +55,9 @@
|
||||
android:clipToOutline="true"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:scaleType="centerCrop"
|
||||
app:allowRgb565="false"
|
||||
app:aspectRationHeight="0"
|
||||
app:aspectRationWidth="0"
|
||||
app:layout_constraintDimensionRatio="H,13:18"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0"
|
||||
@@ -62,6 +65,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_percent="0.3"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
app:useExistingDrawable="true"
|
||||
tools:background="@tools:sample/backgrounds/scenic[5]"
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="@dimen/screen_padding">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:layout_collapseMode="pin">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.core.image.CoilImageView
|
||||
android:id="@+id/imageView_avatar"
|
||||
android:layout_width="28dp"
|
||||
android:layout_height="28dp"
|
||||
@@ -35,6 +35,9 @@
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="1dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:errorDrawable="@drawable/ic_shortcut_default"
|
||||
app:fallbackDrawable="@drawable/ic_shortcut_default"
|
||||
app:placeholderDrawable="@drawable/bg_badge_empty"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Circle"
|
||||
app:strokeColor="?colorOutline"
|
||||
app:strokeWidth="1dp"
|
||||
|
||||
@@ -12,4 +12,4 @@
|
||||
app:bubbleSize="small"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
app:spanCount="3"
|
||||
tools:listitem="@layout/item_bookmark" />
|
||||
tools:listitem="@layout/item_bookmark_large" />
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="?actionBarSize">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
@@ -30,6 +30,7 @@
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintWidth_percent="0.3"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
app:useExistingDrawable="true"
|
||||
tools:background="@tools:sample/backgrounds/scenic[5]"
|
||||
tools:ignore="ContentDescription,UnusedAttribute" />
|
||||
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/Widget.Material3.CardView.Outlined"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/bookmark_item_height"
|
||||
app:cardCornerRadius="12dp">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CoverImageView
|
||||
android:id="@+id/imageView_thumb"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:scaleType="centerCrop"
|
||||
tools:src="@drawable/ic_placeholder" />
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@@ -9,12 +9,11 @@
|
||||
app:cardBackgroundColor="?attr/colorSurfaceContainerHighest"
|
||||
tools:layout_width="140dp">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CoverImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:scaleType="centerCrop"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/backgrounds/scenic[5]" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover3"
|
||||
<org.koitharu.kotatsu.image.ui.CoverStackView
|
||||
android:id="@+id/coversView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_height="@dimen/category_covers_height"
|
||||
app:coverSize="3.4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintDimensionRatio="13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#99FFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#99FFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#4DFFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#4DFFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
@@ -74,7 +32,7 @@
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_visible"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
||||
app:layout_constraintStart_toEndOf="@id/coversView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed" />
|
||||
|
||||
@@ -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]" />
|
||||
|
||||
@@ -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">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover3"
|
||||
<org.koitharu.kotatsu.image.ui.CoverStackView
|
||||
android:id="@+id/coversView"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
android:layout_height="@dimen/category_covers_height"
|
||||
app:coverSize="3.4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintDimensionRatio="13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#99FFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#99FFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover2"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:tintMode="src_atop"
|
||||
tools:backgroundTint="#4DFFFFFF"
|
||||
tools:src="@tools:sample/backgrounds/scenic"
|
||||
tools:tint="#4DFFFFFF" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/imageView_cover1"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:background="?attr/colorSecondaryContainer"
|
||||
android:backgroundTintMode="src_atop"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="W,13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
@@ -73,7 +31,7 @@
|
||||
android:textAppearance="?attr/textAppearanceBodyLarge"
|
||||
app:layout_constraintBottom_toTopOf="@id/textView_subtitle"
|
||||
app:layout_constraintEnd_toStartOf="@id/imageView_edit"
|
||||
app:layout_constraintStart_toEndOf="@id/imageView_cover3"
|
||||
app:layout_constraintStart_toEndOf="@id/coversView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
tools:text="@tools:sample/lorem[1]" />
|
||||
@@ -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]" />
|
||||
|
||||
@@ -13,13 +13,12 @@
|
||||
android:layout_height="match_parent"
|
||||
android:paddingBottom="12dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<ImageView
|
||||
<org.koitharu.kotatsu.core.image.CoilImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="@dimen/margin_normal">
|
||||
|
||||
<ImageView
|
||||
<org.koitharu.kotatsu.core.image.CoilImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="120dp"
|
||||
android:layout_height="120dp"
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
@@ -9,12 +10,13 @@
|
||||
android:paddingHorizontal="32dp"
|
||||
android:paddingBottom="?actionBarSize">
|
||||
|
||||
<ImageView
|
||||
<org.koitharu.kotatsu.core.image.CoilImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="192dp"
|
||||
android:layout_height="192dp"
|
||||
android:contentDescription="@null"
|
||||
android:scaleType="fitCenter"
|
||||
app:crossfadeEnabled="false"
|
||||
tools:src="@drawable/ic_empty_favourites" />
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
android:padding="@dimen/list_spacing"
|
||||
tools:layout_width="120dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.core.ui.image.FaviconView
|
||||
android:id="@+id/imageView_icon"
|
||||
android:layout_width="72dp"
|
||||
android:layout_height="72dp"
|
||||
@@ -20,10 +20,10 @@
|
||||
android:labelFor="@id/textView_title"
|
||||
android:padding="1dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:iconStyle="@style/FaviconDrawable.Large"
|
||||
app:shapeAppearance="?shapeAppearanceCornerMedium"
|
||||
app:strokeColor="?colorOutline"
|
||||
app:strokeWidth="1dp"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
app:strokeWidth="1dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.core.ui.image.FaviconView
|
||||
android:id="@+id/imageView_icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
@@ -20,6 +20,7 @@
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?colorSurfaceContainer"
|
||||
android:scaleType="centerCrop"
|
||||
app:iconStyle="@style/FaviconDrawable.Small"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:background="@drawable/list_selector"
|
||||
android:clipChildren="false">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
|
||||
@@ -11,12 +11,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="98dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="?colorSurfaceContainer"
|
||||
android:scaleType="centerCrop"
|
||||
app:layout_constraintDimensionRatio="13:18"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
||||
@@ -16,13 +16,13 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CoverImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?colorSurfaceContainer"
|
||||
android:orientation="horizontal"
|
||||
android:scaleType="centerCrop"
|
||||
app:allowRgb565="true"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@tools:sample/backgrounds/scenic[5]" />
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
android:background="@drawable/list_selector"
|
||||
android:clipChildren="false">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
@@ -16,11 +16,13 @@
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:background="?colorSurfaceContainer"
|
||||
android:scaleType="centerCrop"
|
||||
app:aspectRationHeight="0"
|
||||
app:aspectRationWidth="0"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Small"
|
||||
app:trimImage="true"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<TextView
|
||||
|
||||
@@ -11,17 +11,19 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_cover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="118dp"
|
||||
android:background="?colorSurfaceContainer"
|
||||
android:scaleType="centerCrop"
|
||||
app:aspectRationHeight="0"
|
||||
app:aspectRationWidth="0"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintDimensionRatio="2:3"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearanceOverlay="@style/ShapeAppearanceOverlay.Kotatsu.Cover.Medium"
|
||||
app:trimImage="true"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<org.koitharu.kotatsu.history.ui.util.ReadingProgressView
|
||||
|
||||
@@ -8,11 +8,13 @@
|
||||
android:layout_height="wrap_content"
|
||||
app:cardBackgroundColor="?colorBackgroundFloating">
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CoverImageView
|
||||
<org.koitharu.kotatsu.image.ui.CoverImageView
|
||||
android:id="@+id/imageView_thumb"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:scaleType="centerCrop"
|
||||
app:decodeRegion="true"
|
||||
app:trimImage="true"
|
||||
tools:src="@drawable/ic_placeholder" />
|
||||
|
||||
<TextView
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user