Improve: Let coil handle error

Update: Move List Adapter to seperate package
This commit is contained in:
LooKeR
2021-11-10 16:29:03 +05:30
parent 65541e7f4f
commit c338cb9bae
3 changed files with 22 additions and 31 deletions

View File

@ -0,0 +1,212 @@
package com.looker.droidify.ui.appsList
import android.content.Context
import android.graphics.drawable.Drawable
import android.graphics.drawable.GradientDrawable
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.view.animation.AnimationUtils
import androidx.recyclerview.widget.RecyclerView
import coil.load
import coil.transform.RoundedCornersTransformation
import com.google.android.material.circularreveal.CircularRevealFrameLayout
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.progressindicator.CircularProgressIndicator
import com.google.android.material.textview.MaterialTextView
import com.looker.droidify.R
import com.looker.droidify.content.Preferences
import com.looker.droidify.database.Database
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.entity.Repository
import com.looker.droidify.network.CoilDownloader
import com.looker.droidify.utility.Utils
import com.looker.droidify.utility.extension.resources.*
import com.looker.droidify.utility.extension.text.nullIfEmpty
import com.looker.droidify.widget.CursorRecyclerAdapter
class AppListAdapter(private val onClick: (ProductItem) -> Unit) :
CursorRecyclerAdapter<AppListAdapter.ViewType, RecyclerView.ViewHolder>() {
private var lastPosition = 0
enum class ViewType { PRODUCT, LOADING, EMPTY }
private class ProductViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val name = itemView.findViewById<MaterialTextView>(R.id.name)!!
val status = itemView.findViewById<MaterialTextView>(R.id.status)!!
val summary = itemView.findViewById<MaterialTextView>(R.id.summary)!!
val icon = itemView.findViewById<ShapeableImageView>(R.id.icon)!!
val progressIcon: Drawable
val defaultIcon: Drawable
init {
val (progressIcon, defaultIcon) = Utils.getDefaultApplicationIcons(icon.context)
this.progressIcon = progressIcon
this.defaultIcon = defaultIcon
}
}
private class LoadingViewHolder(context: Context) :
RecyclerView.ViewHolder(CircularRevealFrameLayout(context)) {
init {
itemView as CircularRevealFrameLayout
val progressBar = CircularProgressIndicator(itemView.context)
itemView.addView(progressBar)
itemView.layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.MATCH_PARENT
)
}
}
private class EmptyViewHolder(context: Context) :
RecyclerView.ViewHolder(MaterialTextView(context)) {
val text: MaterialTextView
get() = itemView as MaterialTextView
init {
itemView as MaterialTextView
itemView.gravity = Gravity.CENTER
itemView.resources.sizeScaled(20).let { itemView.setPadding(it, it, it, it) }
itemView.typeface = TypefaceExtra.light
itemView.setTextColor(context.getColorFromAttr(android.R.attr.colorPrimary))
itemView.setTextSizeScaled(20)
itemView.layoutParams = RecyclerView.LayoutParams(
RecyclerView.LayoutParams.MATCH_PARENT,
RecyclerView.LayoutParams.MATCH_PARENT
)
}
}
var repositories: Map<Long, Repository> = emptyMap()
set(value) {
field = value
notifyDataSetChanged()
}
var emptyText: String = ""
set(value) {
if (field != value) {
field = value
if (isEmpty) {
notifyDataSetChanged()
}
}
}
override val viewTypeClass: Class<ViewType>
get() = ViewType::class.java
private val isEmpty: Boolean
get() = super.getItemCount() == 0
override fun getItemCount(): Int = if (isEmpty) 1 else super.getItemCount()
override fun getItemId(position: Int): Long = if (isEmpty) -1 else super.getItemId(position)
override fun getItemEnumViewType(position: Int): ViewType {
return when {
!isEmpty -> ViewType.PRODUCT
cursor == null -> ViewType.LOADING
else -> ViewType.EMPTY
}
}
private fun getProductItem(position: Int): ProductItem {
return Database.ProductAdapter.transformItem(moveTo(position))
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: ViewType
): RecyclerView.ViewHolder {
return when (viewType) {
ViewType.PRODUCT -> ProductViewHolder(parent.inflate(R.layout.product_item)).apply {
itemView.setOnClickListener { onClick(getProductItem(adapterPosition)) }
}
ViewType.LOADING -> LoadingViewHolder(parent.context)
ViewType.EMPTY -> EmptyViewHolder(parent.context)
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (Preferences[Preferences.Key.ListAnimation]) {
holder.itemView.clearAnimation()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemEnumViewType(position)) {
ViewType.PRODUCT -> {
holder as ProductViewHolder
val productItem = getProductItem(position)
holder.name.text = productItem.name
holder.summary.text =
if (productItem.name == productItem.summary) "" else productItem.summary
holder.summary.visibility =
if (holder.summary.text.isNotEmpty()) View.VISIBLE else View.GONE
val repository: Repository? = repositories[productItem.repositoryId]
holder.icon.load(
repository?.let {
CoilDownloader.createIconUri(
holder.icon, productItem.packageName,
productItem.icon, productItem.metadataIcon, it
)
}
) {
transformations(RoundedCornersTransformation(4.toPx))
placeholder(holder.progressIcon)
error(holder.defaultIcon)
}
holder.status.apply {
if (productItem.canUpdate) {
text = productItem.version
if (background == null) {
resources.sizeScaled(4).let { setPadding(it, 0, it, 0) }
setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorBackground))
background = GradientDrawable(
GradientDrawable.Orientation.TOP_BOTTOM,
null
).apply {
color =
holder.status.context.getColorFromAttr(android.R.attr.colorAccent)
cornerRadius = holder.status.resources.sizeScaled(2).toFloat()
}
}
} else {
text = productItem.installedVersion.nullIfEmpty() ?: productItem.version
if (background != null) {
setPadding(0, 0, 0, 0)
setTextColor(holder.status.context.getColorFromAttr(android.R.attr.colorPrimary))
background = null
}
}
}
val enabled = productItem.compatible || productItem.installedVersion.isNotEmpty()
sequenceOf(holder.name, holder.status, holder.summary).forEach {
it.isEnabled = enabled
}
}
ViewType.LOADING -> {
// Do nothing
}
ViewType.EMPTY -> {
holder as EmptyViewHolder
holder.text.text = emptyText
}
}::class
if (Preferences[Preferences.Key.ListAnimation]) {
setAnimation(holder.itemView, holder.adapterPosition)
}
}
private fun setAnimation(itemView: View, position: Int) {
val animation = AnimationUtils.loadAnimation(
itemView.context,
if (position > lastPosition) R.anim.slide_up else R.anim.slide_down
)
itemView.startAnimation(animation)
lastPosition = position
}
}

View File

@ -14,7 +14,6 @@ import com.looker.droidify.database.CursorOwner
import com.looker.droidify.database.Database
import com.looker.droidify.entity.ProductItem
import com.looker.droidify.screen.BaseFragment
import com.looker.droidify.screen.ProductsAdapter
import com.looker.droidify.utility.RxUtils
import com.looker.droidify.widget.RecyclerFastScroller
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
@ -61,8 +60,8 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback {
isMotionEventSplittingEnabled = false
isVerticalScrollBarEnabled = false
setHasFixedSize(true)
recycledViewPool.setMaxRecycledViews(ProductsAdapter.ViewType.PRODUCT.ordinal, 30)
val adapter = ProductsAdapter { screenActivity.navigateProduct(it.packageName) }
recycledViewPool.setMaxRecycledViews(AppListAdapter.ViewType.PRODUCT.ordinal, 30)
val adapter = AppListAdapter { screenActivity.navigateProduct(it.packageName) }
this.adapter = adapter
RecyclerFastScroller(this)
recyclerView = this
@ -79,7 +78,7 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback {
.flatMapSingle { RxUtils.querySingle { Database.RepositoryAdapter.getAll(it) } }
.map { list -> list.asSequence().map { Pair(it.id, it) }.toMap() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe { (recyclerView?.adapter as? ProductsAdapter)?.repositories = it }
.subscribe { (recyclerView?.adapter as? AppListAdapter)?.repositories = it }
}
override fun onDestroyView() {
@ -93,7 +92,7 @@ class AppListFragment() : BaseFragment(), CursorOwner.Callback {
}
override fun onCursorData(request: CursorOwner.Request, cursor: Cursor?) {
(recyclerView?.adapter as? ProductsAdapter)?.apply {
(recyclerView?.adapter as? AppListAdapter)?.apply {
this.cursor = cursor
viewLifecycleOwner.lifecycleScope.launchWhenCreated {
emptyText = when {