From f5523032de37c21d1d4925032fdf985328e4e298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=A1=D0=BF=D0=B8=D1=82=D1=87=D0=B5=D0=BD=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=90=D0=BD=D0=B0=D1=82=D0=BE=D0=BB=D0=B8=D0=B9=20=D0=9C=D0=B8?= =?UTF-8?q?=D1=85=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=D0=B8=D1=87?= Date: Tue, 29 Oct 2019 10:35:46 +0700 Subject: [PATCH 1/2] replace databinding with viewbinding --- app/build.gradle.kts | 2 +- .../databinding/adapters/TextViewAdapters.kt | 10 -- .../databinding/adapters/ViewAdapters.kt | 22 --- .../component/extensions/ContextExtensions.kt | 3 + .../ImageViewExtensions.kt} | 10 +- .../extensions/LiveDataExtensions.kt | 11 +- .../RecyclerViewExtensions.kt} | 18 +- .../extensions/TextViewExtensions.kt | 47 +++++ .../component/extensions/ViewExtensions.kt | 25 +++ .../ItemPokemonDetailViewHolderFactory.kt | 21 +++ .../ItemPokemonDetailViewRenderer.kt | 24 +++ .../presentation/PokemonDetailsFragment.kt | 44 +++-- .../presentation/di/PokemonDetailsModule.kt | 25 ++- .../model/PokemonDetailUiModel.kt | 18 +- .../list/presentation/ErrorViewRenderer.kt | 24 +++ .../ItemErrorViewHolderFactory.kt | 9 +- .../ItemPokemonViewHolderFactory.kt | 16 +- .../ItemProgressViewHolderFactory.kt | 17 ++ .../list/presentation/PokemonListFragment.kt | 83 ++++++--- .../PokemonListFragmentExtensions.kt | 28 +++ .../list/presentation/PokemonListViewModel.kt | 6 +- .../list/presentation/PokemonViewRenderer.kt | 61 +++++++ .../binderadapter/BinderAdapter.kt | 27 ++- .../binderadapter/BindingClass.kt | 15 +- .../binderadapter/BindingViewHolder.kt | 16 +- .../presentation/binderadapter/LayoutId.kt | 9 - .../binderadapter/ViewHolderFactory.kt | 5 +- .../binderadapter/ViewRenderer.kt | 8 + .../presentation/binderadapter/ViewType.kt | 5 + .../binderadapter/ViewTypeOwner.kt | 6 + .../list/presentation/model/ErrorUiModel.kt | 16 +- .../list/presentation/model/PokemonUiModel.kt | 51 +----- .../presentation/model/ProgressUiModel.kt | 11 +- app/src/main/res/layout/item_error.xml | 46 ++--- app/src/main/res/layout/item_pokemon.xml | 165 +++++++----------- .../main/res/layout/item_pokemon_detail.xml | 26 +-- app/src/main/res/layout/item_progress.xml | 15 +- .../res/layout/pokemon_details_fragment.xml | 74 +++----- .../main/res/layout/pokemon_list_fragment.xml | 93 +++++----- buildSrc/build.gradle.kts | 3 +- .../main/kotlin/ProjectCommonExtensions.kt | 2 + buildSrc/src/main/kotlin/Versions.kt | 8 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 43 files changed, 627 insertions(+), 500 deletions(-) delete mode 100644 app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/TextViewAdapters.kt delete mode 100644 app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ViewAdapters.kt rename app/src/main/java/com/spitchenko/pokeapp/component/{databinding/adapters/ImageViewAdapters.kt => extensions/ImageViewExtensions.kt} (66%) rename app/src/main/java/com/spitchenko/pokeapp/component/{databinding/adapters/RecyclerViewAdapters.kt => extensions/RecyclerViewExtensions.kt} (75%) create mode 100644 app/src/main/java/com/spitchenko/pokeapp/component/extensions/TextViewExtensions.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/component/extensions/ViewExtensions.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewHolderFactory.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewRenderer.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ErrorViewRenderer.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemProgressViewHolderFactory.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragmentExtensions.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonViewRenderer.kt delete mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/LayoutId.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewRenderer.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewType.kt create mode 100644 app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewTypeOwner.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 0be6625..350a762 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,7 +52,7 @@ android { } } - dataBinding { + viewBinding { isEnabled = true } } diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/TextViewAdapters.kt b/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/TextViewAdapters.kt deleted file mode 100644 index f171342..0000000 --- a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/TextViewAdapters.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.spitchenko.pokeapp.component.databinding.adapters - -import android.widget.TextView -import androidx.databinding.BindingAdapter -import com.spitchenko.pokeapp.component.messaging.Message - -@BindingAdapter("android:text") -fun TextView.setMessage(message: Message?) { - text = message?.getString(context) -} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ViewAdapters.kt b/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ViewAdapters.kt deleted file mode 100644 index fb78ea6..0000000 --- a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ViewAdapters.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.spitchenko.pokeapp.component.databinding.adapters - -import android.view.View -import androidx.databinding.BindingAdapter - -@BindingAdapter("visibleOrGone") -fun View.setVisibleOrGone(visibleOrGone: Boolean) { - visibility = if (visibleOrGone) { - View.VISIBLE - } else { - View.GONE - } -} - -@BindingAdapter("visibleOrInvisible") -fun View.setVisibleOrInvisible(visibleOrInvisible: Boolean) { - visibility = if (visibleOrInvisible) { - View.VISIBLE - } else { - View.INVISIBLE - } -} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ContextExtensions.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ContextExtensions.kt index db383a7..04b1362 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ContextExtensions.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ContextExtensions.kt @@ -1,9 +1,12 @@ package com.spitchenko.pokeapp.component.extensions import android.content.Context +import android.view.LayoutInflater import android.widget.Toast import com.spitchenko.pokeapp.component.messaging.Message +val Context.layoutInflater: LayoutInflater get() = LayoutInflater.from(this) + fun Context.showMessage(message: Message, duration: Duration = Duration.LONG) { val toastDuration = when (duration) { Duration.LONG -> Toast.LENGTH_LONG diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ImageViewAdapters.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ImageViewExtensions.kt similarity index 66% rename from app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ImageViewAdapters.kt rename to app/src/main/java/com/spitchenko/pokeapp/component/extensions/ImageViewExtensions.kt index f693f67..6bd8382 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/ImageViewAdapters.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ImageViewExtensions.kt @@ -1,17 +1,9 @@ -package com.spitchenko.pokeapp.component.databinding.adapters +package com.spitchenko.pokeapp.component.extensions import android.widget.ImageView -import androidx.databinding.BindingAdapter import com.bumptech.glide.request.RequestOptions import com.spitchenko.pokeapp.component.glide.GlideApp -@BindingAdapter( - value = [ - "imageUrl", - "imagePlaceholder" - ], - requireAll = false -) fun ImageView.setImageGlide( imageUrl: String?, imagePlaceholder: Int? diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/LiveDataExtensions.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/LiveDataExtensions.kt index 5519806..8a082e0 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/LiveDataExtensions.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/LiveDataExtensions.kt @@ -1,5 +1,14 @@ package com.spitchenko.pokeapp.component.extensions +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LiveData +import androidx.lifecycle.Observer -fun LiveData>.size(): Int = value?.size ?: 0 \ No newline at end of file +fun LiveData>.size(): Int = value?.size ?: 0 + +inline fun LiveData.observe( + viewLifecycleOwner: LifecycleOwner, + crossinline action: (T) -> Unit +) = observe(viewLifecycleOwner, Observer { + action(it) +}) \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/RecyclerViewAdapters.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/RecyclerViewExtensions.kt similarity index 75% rename from app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/RecyclerViewAdapters.kt rename to app/src/main/java/com/spitchenko/pokeapp/component/extensions/RecyclerViewExtensions.kt index c5ae36e..71adcec 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/component/databinding/adapters/RecyclerViewAdapters.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/RecyclerViewExtensions.kt @@ -1,6 +1,5 @@ -package com.spitchenko.pokeapp.component.databinding.adapters +package com.spitchenko.pokeapp.component.extensions -import androidx.databinding.BindingAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -8,8 +7,7 @@ import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdap import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingDiffUtilCallback -@BindingAdapter("onScrolledToFooter") -fun RecyclerView.setOnScrolledToFooter(action: () -> Unit) { +inline fun RecyclerView.setOnScrolledToFooter(crossinline action: () -> Unit) { addOnScrollListener( object : RecyclerView.OnScrollListener() { @@ -22,22 +20,26 @@ fun RecyclerView.setOnScrolledToFooter(action: () -> Unit) { val totalItemCount = itemCount if ((visibleItemCount + firstVisibleItemPosition) >= totalItemCount) { - action() + post { + action() + } } } } }) } -@BindingAdapter(value = ["bindingList", "detectMoves"], requireAll = false) -fun RecyclerView.setBindingList(bindingList: List?, detectMoves: Boolean?) { +fun RecyclerView.setBindingList( + bindingList: List?, + detectMoves: Boolean = false +) { val bindingAdapter = adapter as? BinderAdapter ?: return val newOrEmptyList = bindingList.orEmpty() val bindingsDiffResult = DiffUtil.calculateDiff( BindingDiffUtilCallback(bindingAdapter.itemList, newOrEmptyList), - detectMoves ?: false + detectMoves ) bindingAdapter.setItems(bindingsDiffResult, newOrEmptyList) diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/TextViewExtensions.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/TextViewExtensions.kt new file mode 100644 index 0000000..f91f522 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/TextViewExtensions.kt @@ -0,0 +1,47 @@ +package com.spitchenko.pokeapp.component.extensions + +import android.graphics.drawable.Drawable +import android.widget.TextView +import com.spitchenko.pokeapp.component.messaging.Message + +fun TextView.setMessage(message: Message?) { + text = message?.getString(context) +} + +fun TextView.setTextIfDifferent(message: Message?) { + val newText = message?.getString(context) + + if (this.text != newText) { + this.text = newText + } +} + +fun TextView.setTextIfDifferent(text: CharSequence) { + if (this.text != text) { + this.text = text + } +} + +fun TextView.setDrawables( + drawableStart: Drawable? = null, + drawableEnd: Drawable? = null, + drawableTop: Drawable? = null, + drawableBottom: Drawable? = null +) { + if (isDrawableDifferent(drawableStart, CompoundDrawables.START) + || isDrawableDifferent(drawableEnd, CompoundDrawables.END) + || isDrawableDifferent(drawableTop, CompoundDrawables.TOP) + || isDrawableDifferent(drawableBottom, CompoundDrawables.BOTTOM)) { + setCompoundDrawablesRelativeWithIntrinsicBounds(drawableStart, drawableTop, drawableEnd, drawableBottom) + } +} + +private fun TextView.isDrawableDifferent(drawable: Drawable?, compoundDrawable: CompoundDrawables) : Boolean = + compoundDrawablesRelative[compoundDrawable.ordinal] != drawable + +enum class CompoundDrawables { + START, + TOP, + END, + BOTTOM +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ViewExtensions.kt b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ViewExtensions.kt new file mode 100644 index 0000000..d3cf046 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/component/extensions/ViewExtensions.kt @@ -0,0 +1,25 @@ +package com.spitchenko.pokeapp.component.extensions + +import android.view.View + +fun View.setVisibleOrGone(visibleOrGone: Boolean) { + if (visibleOrGone) { + if (visibility != View.VISIBLE) { + visibility = View.VISIBLE + } + } else { + if (visibility != View.GONE) { + visibility = View.GONE + } + } +} + +fun View.setClickableAndFocusable(clickable: Boolean) { + + if (isClickable != clickable) { + isClickable = clickable + } + if (isFocusable != clickable) { + isFocusable = clickable + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewHolderFactory.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewHolderFactory.kt new file mode 100644 index 0000000..08ae0c5 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewHolderFactory.kt @@ -0,0 +1,21 @@ +package com.spitchenko.pokeapp.feature.details.presentation + +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.component.extensions.layoutInflater +import com.spitchenko.pokeapp.databinding.ItemPokemonDetailBinding +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewHolderFactory + +object ItemPokemonDetailViewHolderFactory : ViewHolderFactory { + + override fun create( + parent: ViewGroup, + adapter: BinderAdapter + ): BindingViewHolder { + val binding = ItemPokemonDetailBinding.inflate(parent.context.layoutInflater, parent, false) + + return BindingViewHolder(binding) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewRenderer.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewRenderer.kt new file mode 100644 index 0000000..c5a4c29 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/ItemPokemonDetailViewRenderer.kt @@ -0,0 +1,24 @@ +package com.spitchenko.pokeapp.feature.details.presentation + +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.component.extensions.setTextIfDifferent +import com.spitchenko.pokeapp.databinding.ItemPokemonDetailBinding +import com.spitchenko.pokeapp.feature.details.presentation.model.PokemonDetailUiModel +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewRenderer + +object ItemPokemonDetailViewRenderer : ViewRenderer { + + override fun render( + item: BindingClass, + viewHolder: BindingViewHolder, + position: Int + ) { + if (item !is PokemonDetailUiModel || viewHolder.binding !is ItemPokemonDetailBinding) { + return + } + + viewHolder.binding.pokemonDetails.setTextIfDifferent(item.text) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/PokemonDetailsFragment.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/PokemonDetailsFragment.kt index be9a717..0b611d3 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/PokemonDetailsFragment.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/PokemonDetailsFragment.kt @@ -1,25 +1,30 @@ package com.spitchenko.pokeapp.feature.details.presentation import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import androidx.transition.TransitionInflater +import com.spitchenko.pokeapp.R import com.spitchenko.pokeapp.component.extensions.getViewModel import com.spitchenko.pokeapp.component.extensions.initToolbar +import com.spitchenko.pokeapp.component.extensions.setBindingList +import com.spitchenko.pokeapp.component.extensions.setImageGlide import com.spitchenko.pokeapp.component.lifecycle.ViewModelFactory import com.spitchenko.pokeapp.databinding.PokemonDetailsFragmentBinding import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel import javax.inject.Inject class PokemonDetailsFragment @Inject constructor( - private val pokemonDetailsUiConverter: PokemonDetailsUiConverter -) : Fragment() { + private val pokemonDetailsUiConverter: PokemonDetailsUiConverter, + private val adapter: BinderAdapter +) : Fragment(R.layout.pokemon_details_fragment), CoroutineScope by MainScope() { - lateinit var viewModel: PokemonDetailsViewModel + private lateinit var viewModel: PokemonDetailsViewModel lateinit var args: PokemonDetailsFragmentArgs @@ -39,16 +44,10 @@ class PokemonDetailsFragment @Inject constructor( }) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - val binding = PokemonDetailsFragmentBinding.inflate(inflater, container, false) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val binding = PokemonDetailsFragmentBinding.bind(view) - binding.viewModel = viewModel - - binding.pokemonDetailsList.adapter = BinderAdapter() + binding.pokemonDetailsList.adapter = adapter binding.pokemonDetailsList.addItemDecoration( DividerItemDecoration( @@ -61,6 +60,21 @@ class PokemonDetailsFragment @Inject constructor( binding.toolbar.initToolbar(findNavController()) - return binding.root + binding.pokemonDetailsList.setBindingList(viewModel.uiModel.details) + + binding.headerImage.setImageGlide( + args.pokemonDetails.details.image?.url, + R.drawable.ic_pokeball_pokemon + ) + + binding.headerImage.transitionName = args.transitionName + + binding.toolbar.title = args.pokemonDetails.details.name + } + + override fun onDestroyView() { + super.onDestroyView() + + cancel() } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/di/PokemonDetailsModule.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/di/PokemonDetailsModule.kt index 17cb6bd..cf5a0aa 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/di/PokemonDetailsModule.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/di/PokemonDetailsModule.kt @@ -1,13 +1,34 @@ package com.spitchenko.pokeapp.feature.details.presentation.di +import com.spitchenko.pokeapp.feature.details.presentation.ItemPokemonDetailViewHolderFactory +import com.spitchenko.pokeapp.feature.details.presentation.ItemPokemonDetailViewRenderer import com.spitchenko.pokeapp.feature.details.presentation.PokemonDetailsUiConverter import com.spitchenko.pokeapp.feature.details.presentation.PokemonDetailsUiConverterImpl +import com.spitchenko.pokeapp.feature.details.presentation.model.PokemonDetailUiModel +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter import dagger.Binds import dagger.Module +import dagger.Provides @Module -interface PokemonDetailsModule { +abstract class PokemonDetailsModule { + + @Module + companion object { + + @JvmStatic + @Provides + fun provideAdapter(): BinderAdapter = + BinderAdapter( + factory = mapOf( + PokemonDetailUiModel.viewType to ItemPokemonDetailViewHolderFactory + ), + renders = mapOf( + PokemonDetailUiModel.viewType to ItemPokemonDetailViewRenderer + ) + ) + } @Binds - fun bindPokemonDetailsUiConverter(impl: PokemonDetailsUiConverterImpl): PokemonDetailsUiConverter + abstract fun bindPokemonDetailsUiConverter(impl: PokemonDetailsUiConverterImpl): PokemonDetailsUiConverter } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/model/PokemonDetailUiModel.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/model/PokemonDetailUiModel.kt index 159c538..a37fcd2 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/model/PokemonDetailUiModel.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/details/presentation/model/PokemonDetailUiModel.kt @@ -1,20 +1,16 @@ package com.spitchenko.pokeapp.feature.details.presentation.model -import androidx.databinding.ViewDataBinding -import com.spitchenko.pokeapp.BR import com.spitchenko.pokeapp.R import com.spitchenko.pokeapp.component.messaging.Message import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewType +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewTypeOwner -data class PokemonDetailUiModel(val text: Message) : BindingClass { +data class PokemonDetailUiModel( + val text: Message +) : BindingClass, ViewTypeOwner by PokemonDetailUiModel { - override val layoutId: Int = R.layout.item_pokemon_detail - override val itemId: Long = text.hashCode().toLong() - - override fun areContentsTheSame(other: BindingClass): Boolean = - (other as? PokemonDetailUiModel)?.text == text - - override fun bind(viewDataBinding: ViewDataBinding, position: Int) { - viewDataBinding.setVariable(BR.text, text) + companion object : ViewTypeOwner { + override val viewType: ViewType = ViewType(R.layout.item_pokemon_detail) } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ErrorViewRenderer.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ErrorViewRenderer.kt new file mode 100644 index 0000000..cdf9341 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ErrorViewRenderer.kt @@ -0,0 +1,24 @@ +package com.spitchenko.pokeapp.feature.list.presentation + +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.component.extensions.setTextIfDifferent +import com.spitchenko.pokeapp.databinding.ItemErrorBinding +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewRenderer +import com.spitchenko.pokeapp.feature.list.presentation.model.ErrorUiModel + +object ErrorViewRenderer : ViewRenderer { + + override fun render( + item: BindingClass, + viewHolder: BindingViewHolder, + position: Int + ) { + if (item !is ErrorUiModel || viewHolder.binding !is ItemErrorBinding) { + return + } + + viewHolder.binding.errorMessage.setTextIfDifferent(item.message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemErrorViewHolderFactory.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemErrorViewHolderFactory.kt index 70a78c6..7a38ba8 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemErrorViewHolderFactory.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemErrorViewHolderFactory.kt @@ -1,21 +1,20 @@ package com.spitchenko.pokeapp.feature.list.presentation import android.view.ViewGroup -import androidx.databinding.ViewDataBinding +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.component.extensions.layoutInflater import com.spitchenko.pokeapp.databinding.ItemErrorBinding import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder -import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.LayoutId import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewHolderFactory class ItemErrorViewHolderFactory(private val viewModel: PokemonListViewModel) : ViewHolderFactory { override fun create( parent: ViewGroup, - layoutId: LayoutId, adapter: BinderAdapter - ): BindingViewHolder = - BindingViewHolder(parent, layoutId).apply { + ): BindingViewHolder = + BindingViewHolder(ItemErrorBinding.inflate(parent.context.layoutInflater, parent, false)).apply { binding.itemErrorRetryButton.setOnClickListener { viewModel.retry() } diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemPokemonViewHolderFactory.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemPokemonViewHolderFactory.kt index ba1652b..c453490 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemPokemonViewHolderFactory.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemPokemonViewHolderFactory.kt @@ -1,33 +1,32 @@ package com.spitchenko.pokeapp.feature.list.presentation -import android.content.Context import android.view.ViewGroup -import androidx.databinding.ViewDataBinding import androidx.navigation.NavController import androidx.navigation.fragment.FragmentNavigatorExtras +import androidx.viewbinding.ViewBinding import com.spitchenko.pokeapp.component.extensions.getAsInstanceAt +import com.spitchenko.pokeapp.component.extensions.layoutInflater import com.spitchenko.pokeapp.component.extensions.showMessage import com.spitchenko.pokeapp.databinding.ItemPokemonBinding import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder -import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.LayoutId import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewHolderFactory import com.spitchenko.pokeapp.feature.list.presentation.model.PokemonState import com.spitchenko.pokeapp.feature.list.presentation.model.PokemonUiModel import com.spitchenko.pokeapp.feature.list.presentation.model.toParcel class ItemPokemonViewHolderFactory( - private val context: Context, private val navController: NavController, private val viewModel: PokemonListViewModel ) : ViewHolderFactory { override fun create( parent: ViewGroup, - layoutId: LayoutId, adapter: BinderAdapter - ): BindingViewHolder = - BindingViewHolder(parent, layoutId).apply { + ): BindingViewHolder { + val binding = ItemPokemonBinding.inflate(parent.context.layoutInflater, parent, false) + + return BindingViewHolder(binding).apply { binding.itemPokemonRetryButton.setOnClickListener { val index = adapterPosition @@ -39,7 +38,7 @@ class ItemPokemonViewHolderFactory( val message = (item.pokemonState as PokemonState.Error).message - context.showMessage(message) + parent.context.showMessage(message) } itemView.setOnClickListener { val item: PokemonUiModel = @@ -60,4 +59,5 @@ class ItemPokemonViewHolderFactory( ) } } + } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemProgressViewHolderFactory.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemProgressViewHolderFactory.kt new file mode 100644 index 0000000..621b18b --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/ItemProgressViewHolderFactory.kt @@ -0,0 +1,17 @@ +package com.spitchenko.pokeapp.feature.list.presentation + +import android.view.ViewGroup +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.component.extensions.layoutInflater +import com.spitchenko.pokeapp.databinding.ItemProgressBinding +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewHolderFactory + +object ItemProgressViewHolderFactory : ViewHolderFactory { + + override fun create(parent: ViewGroup, adapter: BinderAdapter): BindingViewHolder { + val binding = ItemProgressBinding.inflate(parent.context.layoutInflater, parent, false) + return BindingViewHolder(binding) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragment.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragment.kt index abfbf24..6f3f080 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragment.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragment.kt @@ -1,24 +1,22 @@ package com.spitchenko.pokeapp.feature.list.presentation import android.os.Bundle -import android.view.LayoutInflater import android.view.View -import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.DividerItemDecoration import com.spitchenko.pokeapp.R -import com.spitchenko.pokeapp.component.extensions.getViewModel -import com.spitchenko.pokeapp.component.extensions.showMessage +import com.spitchenko.pokeapp.component.extensions.* import com.spitchenko.pokeapp.databinding.PokemonListFragmentBinding -import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter -import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.LayoutId +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.launch import javax.inject.Inject class PokemonListFragment @Inject constructor( private val viewModelFactory: ViewModelProvider.Factory -) : Fragment() { +) : Fragment(R.layout.pokemon_list_fragment), CoroutineScope by MainScope() { private lateinit var viewModel: PokemonListViewModel @@ -28,32 +26,17 @@ class PokemonListFragment @Inject constructor( viewModel = getViewModel(viewModelFactory) } - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { val context = requireContext() viewModel.messageEvent.observe(viewLifecycleOwner) { context.showMessage(it) } - val binding = PokemonListFragmentBinding.inflate(inflater, container, false).apply { - lifecycleOwner = viewLifecycleOwner - } - - val pokemonsAdapter = BinderAdapter( - LayoutId(R.layout.item_error) to ItemErrorViewHolderFactory(viewModel), - LayoutId(R.layout.item_pokemon) to ItemPokemonViewHolderFactory( - context, - findNavController(), - viewModel - ) - ) + val binding = PokemonListFragmentBinding.bind(view) binding.pokemonsList.apply { - adapter = pokemonsAdapter + adapter = createAdapter(viewModel) addItemDecoration( DividerItemDecoration( @@ -61,10 +44,54 @@ class PokemonListFragment @Inject constructor( DividerItemDecoration.VERTICAL ) ) + + setOnScrolledToFooter(viewModel::showNextPage) + } + + viewModel.messageEvent.observe(viewLifecycleOwner) { + context.showMessage(it) + } + + viewModel.uiModel.data.observe(viewLifecycleOwner) { + launch { + binding.pokemonsList.setBindingList(it) + } + } + + viewModel.uiModel.refreshProgressVisible.observe(viewLifecycleOwner) { + binding.pokemonListFragmentSwipeRefresh.isRefreshing = it } - binding.viewModel = viewModel + binding.pokemonListFragmentSwipeRefresh.setOnRefreshListener(viewModel::refresh) + + viewModel.uiModel.emptyText.observe(viewLifecycleOwner, binding.emptyMessage::setMessage) + + viewModel.uiModel.emptyProgressVisible.observe( + viewLifecycleOwner, + binding.emptyProgress::setVisibleOrGone + ) + + viewModel.uiModel.dataVisible.observe( + viewLifecycleOwner, + binding.pokemonsList::setVisibleOrGone + ) + + viewModel.uiModel.emptyError.observe(viewLifecycleOwner, binding.errorMessage::setMessage) + + viewModel.uiModel.emptyErrorVisible.observe(viewLifecycleOwner) { + binding.errorMessage.setVisibleOrGone(it) + binding.retryButton.setVisibleOrGone(it) + } + + viewModel.uiModel.emptyVisible.observe( + viewLifecycleOwner, + binding.emptyMessage::setVisibleOrGone + ) + } + + override fun onDestroyView() { + super.onDestroyView() - return binding.root + cancel() } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragmentExtensions.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragmentExtensions.kt new file mode 100644 index 0000000..334522f --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListFragmentExtensions.kt @@ -0,0 +1,28 @@ +package com.spitchenko.pokeapp.feature.list.presentation + +import androidx.navigation.fragment.findNavController +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BinderAdapter +import com.spitchenko.pokeapp.feature.list.presentation.model.ErrorUiModel +import com.spitchenko.pokeapp.feature.list.presentation.model.PokemonUiModel +import com.spitchenko.pokeapp.feature.list.presentation.model.ProgressUiModel + +fun PokemonListFragment.createAdapter(viewModel: PokemonListViewModel): BinderAdapter { + val viewHolderFactory = mapOf( + ErrorUiModel.viewType to ItemErrorViewHolderFactory(viewModel), + PokemonUiModel.viewType to ItemPokemonViewHolderFactory( + findNavController(), + viewModel + ), + ProgressUiModel.viewType to ItemProgressViewHolderFactory + ) + + val viewRenders = mapOf( + PokemonUiModel.viewType to PokemonViewRenderer, + ErrorUiModel.viewType to ErrorViewRenderer + ) + + return BinderAdapter( + viewHolderFactory, + viewRenders + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListViewModel.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListViewModel.kt index ce59300..6f26e54 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListViewModel.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonListViewModel.kt @@ -37,8 +37,6 @@ class PokemonListViewModel( private var pokemons: List = emptyList() - private val progressUiModel = ProgressUiModel() - private var loadPageJob: Job? = null private var currentState: PagingState = Empty() @@ -182,7 +180,7 @@ class PokemonListViewModel( currentState = PageProgress() - _uiModel.data.add(progressUiModel) + _uiModel.data.add(ProgressUiModel) startLoading() } @@ -210,7 +208,7 @@ class PokemonListViewModel( with(_uiModel) { data.transaction { - set(lastIndex, progressUiModel) + set(lastIndex, ProgressUiModel) } } diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonViewRenderer.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonViewRenderer.kt new file mode 100644 index 0000000..bcd665e --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/PokemonViewRenderer.kt @@ -0,0 +1,61 @@ +package com.spitchenko.pokeapp.feature.list.presentation + +import androidx.viewbinding.ViewBinding +import com.spitchenko.pokeapp.R +import com.spitchenko.pokeapp.component.extensions.* +import com.spitchenko.pokeapp.databinding.ItemPokemonBinding +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingViewHolder +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewRenderer +import com.spitchenko.pokeapp.feature.list.presentation.model.PokemonState +import com.spitchenko.pokeapp.feature.list.presentation.model.PokemonUiModel + +private const val TRANSITION_NAME = "PokemonViewRendererTransition" + +object PokemonViewRenderer : ViewRenderer { + + override fun render( + item: BindingClass, + viewHolder: BindingViewHolder, + position: Int + ) { + if (item !is PokemonUiModel || viewHolder.binding !is ItemPokemonBinding) { + return + } + + val binding: ItemPokemonBinding = viewHolder.binding + + binding.itemPokemonName.setTextIfDifferent(item.pokemonState.name) + binding.itemPokemonImage.transitionName = TRANSITION_NAME + position + when (val pokemonState = item.pokemonState) { + is PokemonState.Data -> { + binding.root.setClickableAndFocusable(true) + binding.itemPokemonErrorGroup.setVisibleOrGone(false) + binding.itemPokemonName.setDrawables( + drawableEnd = viewHolder.itemView.context.getDrawable(R.drawable.ic_chevron_right) + ) + binding.itemPokemonProgress.setVisibleOrGone(false) + binding.itemPokemonImage.setImageGlide( + pokemonState.details.image?.url, + R.drawable.ic_pokeball_pokemon + ) + } + + is PokemonState.Progress -> { + binding.root.setClickableAndFocusable(false) + binding.itemPokemonName.setDrawables() + binding.itemPokemonErrorGroup.setVisibleOrGone(false) + binding.itemPokemonProgress.setVisibleOrGone(true) + binding.itemPokemonImage.setImageGlide(null, R.drawable.ic_pokeball_pokemon) + } + + is PokemonState.Error -> { + binding.root.setClickableAndFocusable(false) + binding.itemPokemonName.setDrawables() + binding.itemPokemonErrorGroup.setVisibleOrGone(true) + binding.itemPokemonProgress.setVisibleOrGone(false) + binding.itemPokemonImage.setImageGlide(null, R.drawable.ic_pokeball_pokemon) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BinderAdapter.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BinderAdapter.kt index 92219fe..fc06807 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BinderAdapter.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BinderAdapter.kt @@ -1,15 +1,14 @@ package com.spitchenko.pokeapp.feature.list.presentation.binderadapter import android.view.ViewGroup -import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding class BinderAdapter( - vararg factories: Pair -) : RecyclerView.Adapter>() { - - private val factory = mapOf(*factories) + private val factory: Map, + private val renders: Map = emptyMap() +) : RecyclerView.Adapter>() { var itemList: List = emptyList() private set @@ -17,28 +16,22 @@ class BinderAdapter( override fun getItemCount(): Int = itemList.size override fun getItemViewType(position: Int): Int = - itemList.getOrNull(position)?.layoutId ?: super.getItemViewType(position) + itemList.getOrNull(position)?.viewType?.type ?: super.getItemViewType(position) override fun onCreateViewHolder( parent: ViewGroup, viewType: Int - ): BindingViewHolder { - val layoutId = LayoutId(viewType) - - return factory[layoutId]?.create(parent, layoutId, this) ?: BindingViewHolder( - parent, - layoutId - ) - } + ): BindingViewHolder = + factory.getValue(ViewType(viewType)).create(parent, this) fun setItems(diffResult: DiffUtil.DiffResult, items: List) { itemList = items diffResult.dispatchUpdatesTo(this) } - override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { + override fun onBindViewHolder(holder: BindingViewHolder, position: Int) { val model = itemList.getOrNull(position) ?: return - model.bind(holder.binding, position) - holder.binding.executePendingBindings() + val viewType = ViewType(holder.itemViewType) + renders[viewType]?.render(model, holder, position) } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingClass.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingClass.kt index 7985c32..bea06be 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingClass.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingClass.kt @@ -1,16 +1,11 @@ package com.spitchenko.pokeapp.feature.list.presentation.binderadapter -import androidx.databinding.ViewDataBinding +interface BindingClass: ViewTypeOwner { -interface BindingClass { + val itemId: Long + get() = hashCode().toLong() - val layoutId: Int + fun areContentsTheSame(other: BindingClass): Boolean = equals(other) - val itemId: Long - - fun areContentsTheSame(other: BindingClass): Boolean - - fun areItemsTheSame(other: BindingClass): Boolean = other.itemId == itemId - - fun bind(viewDataBinding: ViewDataBinding, position: Int) + fun areItemsTheSame(other: BindingClass): Boolean = other.itemId == itemId } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingViewHolder.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingViewHolder.kt index 6da35e0..3874d8a 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingViewHolder.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/BindingViewHolder.kt @@ -1,18 +1,8 @@ package com.spitchenko.pokeapp.feature.list.presentation.binderadapter -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.databinding.DataBindingUtil -import androidx.databinding.ViewDataBinding import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding -class BindingViewHolder( - parent: ViewGroup, - layout: LayoutId, - val binding: T = DataBindingUtil.inflate( - LayoutInflater.from(parent.context), - layout.layoutRes, - parent, - false - ) +class BindingViewHolder( + val binding: T ) : RecyclerView.ViewHolder(binding.root) \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/LayoutId.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/LayoutId.kt deleted file mode 100644 index 48c5ab3..0000000 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/LayoutId.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.spitchenko.pokeapp.feature.list.presentation.binderadapter - -import androidx.annotation.LayoutRes - -@Suppress("EXPERIMENTAL_FEATURE_WARNING") -inline class LayoutId( - @LayoutRes - val layoutRes: Int -) \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewHolderFactory.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewHolderFactory.kt index 75fd0ad..0e2bd6e 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewHolderFactory.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewHolderFactory.kt @@ -1,14 +1,13 @@ package com.spitchenko.pokeapp.feature.list.presentation.binderadapter import android.view.ViewGroup -import androidx.databinding.ViewDataBinding +import androidx.viewbinding.ViewBinding interface ViewHolderFactory { fun create( parent: ViewGroup, - layoutId: LayoutId, adapter: BinderAdapter - ): BindingViewHolder + ): BindingViewHolder } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewRenderer.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewRenderer.kt new file mode 100644 index 0000000..748fdd6 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewRenderer.kt @@ -0,0 +1,8 @@ +package com.spitchenko.pokeapp.feature.list.presentation.binderadapter + +import androidx.viewbinding.ViewBinding + +interface ViewRenderer { + + fun render(item: BindingClass, viewHolder: BindingViewHolder, position: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewType.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewType.kt new file mode 100644 index 0000000..ba478a9 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewType.kt @@ -0,0 +1,5 @@ +package com.spitchenko.pokeapp.feature.list.presentation.binderadapter + +inline class ViewType( + val type: Int +) \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewTypeOwner.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewTypeOwner.kt new file mode 100644 index 0000000..df7c296 --- /dev/null +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/binderadapter/ViewTypeOwner.kt @@ -0,0 +1,6 @@ +package com.spitchenko.pokeapp.feature.list.presentation.binderadapter + +interface ViewTypeOwner { + + val viewType: ViewType +} \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ErrorUiModel.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ErrorUiModel.kt index 5fda8f0..b4fdeeb 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ErrorUiModel.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ErrorUiModel.kt @@ -1,22 +1,20 @@ package com.spitchenko.pokeapp.feature.list.presentation.model -import androidx.databinding.ViewDataBinding -import com.spitchenko.pokeapp.BR import com.spitchenko.pokeapp.R import com.spitchenko.pokeapp.component.messaging.Message import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewType +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewTypeOwner private const val DEFAULT_ID = 9054434L -data class ErrorUiModel(private val message: Message): BindingClass { +data class ErrorUiModel( + val message: Message +): BindingClass, ViewTypeOwner by ErrorUiModel { override val itemId: Long = DEFAULT_ID - override val layoutId: Int = R.layout.item_error - - override fun areContentsTheSame(other: BindingClass): Boolean = other is ErrorUiModel - - override fun bind(viewDataBinding: ViewDataBinding, position: Int) { - viewDataBinding.setVariable(BR.errorText, message) + companion object: ViewTypeOwner { + override val viewType: ViewType = ViewType(R.layout.item_error) } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/PokemonUiModel.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/PokemonUiModel.kt index aff07f9..7c2c17a 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/PokemonUiModel.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/PokemonUiModel.kt @@ -1,52 +1,15 @@ package com.spitchenko.pokeapp.feature.list.presentation.model -import androidx.databinding.ViewDataBinding -import com.spitchenko.pokeapp.BR import com.spitchenko.pokeapp.R import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewType +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewTypeOwner -private const val TRANSITION_NAME = "PokemonImageTransition" +data class PokemonUiModel( + val pokemonState: PokemonState +): BindingClass, ViewTypeOwner by PokemonUiModel { -data class PokemonUiModel(val pokemonState: PokemonState): BindingClass { - - override val layoutId: Int = R.layout.item_pokemon - override val itemId: Long by lazy(LazyThreadSafetyMode.NONE) { - pokemonState.name.hashCode().toLong() - } - - override fun areContentsTheSame(other: BindingClass): Boolean { - if (other !is PokemonUiModel) { - return false - } - - return other.pokemonState == pokemonState - } - - override fun bind(viewDataBinding: ViewDataBinding, position: Int) { - viewDataBinding.setVariable(BR.name, pokemonState.name) - viewDataBinding.setVariable(BR.transitionName, TRANSITION_NAME + position) - when (pokemonState) { - is PokemonState.Data -> { - viewDataBinding.setVariable(BR.success, true) - viewDataBinding.setVariable(BR.progress, false) - viewDataBinding.setVariable(BR.error, false) - viewDataBinding.setVariable(BR.imageUrl, pokemonState.details.image?.url) - } - - is PokemonState.Progress -> { - viewDataBinding.setVariable(BR.success, false) - viewDataBinding.setVariable(BR.progress, true) - viewDataBinding.setVariable(BR.error, false) - viewDataBinding.setVariable(BR.imageUrl, null) - } - - is PokemonState.Error -> { - viewDataBinding.setVariable(BR.success, false) - viewDataBinding.setVariable(BR.progress, false) - viewDataBinding.setVariable(BR.error, true) - viewDataBinding.setVariable(BR.errorMessage, pokemonState.message) - viewDataBinding.setVariable(BR.imageUrl, null) - } - } + companion object : ViewTypeOwner { + override val viewType: ViewType = ViewType(R.layout.item_pokemon) } } \ No newline at end of file diff --git a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ProgressUiModel.kt b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ProgressUiModel.kt index 79f6352..102d2e8 100644 --- a/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ProgressUiModel.kt +++ b/app/src/main/java/com/spitchenko/pokeapp/feature/list/presentation/model/ProgressUiModel.kt @@ -1,18 +1,13 @@ package com.spitchenko.pokeapp.feature.list.presentation.model -import androidx.databinding.ViewDataBinding import com.spitchenko.pokeapp.R import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.BindingClass +import com.spitchenko.pokeapp.feature.list.presentation.binderadapter.ViewType private const val DEFAULT_ID = 3490343L -class ProgressUiModel( +object ProgressUiModel: BindingClass { override val itemId: Long = DEFAULT_ID -): BindingClass { - override val layoutId: Int = R.layout.item_progress - - override fun areContentsTheSame(other: BindingClass): Boolean = other is ProgressUiModel - - override fun bind(viewDataBinding: ViewDataBinding, position: Int) = Unit + override val viewType: ViewType = ViewType(R.layout.item_progress) } \ No newline at end of file diff --git a/app/src/main/res/layout/item_error.xml b/app/src/main/res/layout/item_error.xml index d1a56b2..0963216 100644 --- a/app/src/main/res/layout/item_error.xml +++ b/app/src/main/res/layout/item_error.xml @@ -1,35 +1,23 @@ - + - - - - - - - - + android:gravity="center_horizontal" + tools:text="@tools:sample/lorem" /> - - -