Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import kotlinx.coroutines.launch
import ru.otus.cryptosample.CoinsSampleApp
import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapter
import ru.otus.cryptosample.coins.feature.adapter.CustomItemAnimator
import ru.otus.cryptosample.coins.feature.di.DaggerCoinListComponent
import ru.otus.cryptosample.databinding.FragmentCoinListBinding
import javax.inject.Inject
Expand Down Expand Up @@ -67,6 +68,7 @@ class CoinListFragment : Fragment() {
return when (coinsAdapter.getItemViewType(position)) {
0 -> 2 // Category header spans full width
1 -> 1 // Coin item spans half width
2 -> 2 // Carousel spans full width
else -> 1
}
}
Expand All @@ -75,6 +77,8 @@ class CoinListFragment : Fragment() {
binding.recyclerView.apply {
layoutManager = gridLayoutManager
adapter = coinsAdapter
itemAnimator = CustomItemAnimator()
setRecycledViewPool(CoinsAdapter.sharedPool)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package ru.otus.cryptosample.coins.feature.adapter

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cryptosample.coins.feature.CoinState
import ru.otus.cryptosample.databinding.ItemCoinBinding

class CarouselAdapter : ListAdapter<CoinState, CoinViewHolder>(CarouselDiffUtil) {

override fun getItemCount(): Int = currentList.size

override fun getItemViewType(position: Int): Int = CoinsAdapter.VIEW_TYPE_COIN

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoinViewHolder {
val binding = ItemCoinBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val params = binding.root.layoutParams
?: RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
val screenWidth = parent.resources.displayMetrics.widthPixels
params.width = (screenWidth / 2) - 16

binding.root.layoutParams = params
return CoinViewHolder(binding)
}

override fun onBindViewHolder(holder: CoinViewHolder, position: Int) {
holder.bind(getItem(position))
}

override fun onBindViewHolder(holder: CoinViewHolder, position: Int, payloads: List<Any>) {
holder.bind(getItem(position), payloads)
}
}

object CarouselDiffUtil : DiffUtil.ItemCallback<CoinState>() {
override fun areItemsTheSame(old: CoinState, new: CoinState) = old.id == new.id
override fun areContentsTheSame(old: CoinState, new: CoinState) = old == new
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.otus.cryptosample.coins.feature.adapter

import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cryptosample.coins.feature.CoinState
import ru.otus.cryptosample.databinding.ItemCarouselBinding

class CarouselViewHolder(
binding: ItemCarouselBinding,
sharedPool: RecyclerView.RecycledViewPool
) : RecyclerView.ViewHolder(binding.root) {

private val carouselAdapter = CarouselAdapter()

init {
binding.carouselRecyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = carouselAdapter
setRecycledViewPool(sharedPool)
setHasFixedSize(true)
isNestedScrollingEnabled = false
itemAnimator = CustomItemAnimator()
}
}

fun bind(coins: List<CoinState>) {
carouselAdapter.submitList(coins)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.otus.cryptosample.coins.feature.adapter

import android.os.Bundle
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
Expand Down Expand Up @@ -33,4 +34,19 @@ class CoinViewHolder(
fireBadge.isVisible = coin.highlight
}
}

fun bind(coin: CoinState, payloads: List<Any>) {
if (payloads.isNotEmpty()) {
val bundle = payloads[0] as Bundle
if (bundle.containsKey("highlight")) {
updateHighlight(bundle.getBoolean("highlight"))
}
} else {
bind(coin)
}
}

private fun updateHighlight(highlight: Boolean) {
binding.fireBadge.isVisible = highlight
}
}
Original file line number Diff line number Diff line change
@@ -1,44 +1,58 @@
package ru.otus.cryptosample.coins.feature.adapter

import android.os.Bundle
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cryptosample.coins.feature.CoinCategoryState
import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem.CarouselItems
import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem.CategoryHeader
import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem.CoinItem
import ru.otus.cryptosample.databinding.ItemCarouselBinding
import ru.otus.cryptosample.databinding.ItemCategoryHeaderBinding
import ru.otus.cryptosample.databinding.ItemCoinBinding

class CoinsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
class CoinsAdapter : ListAdapter<CoinsAdapterItem, RecyclerView.ViewHolder>(CoinDiffUtil) {

companion object {
private const val VIEW_TYPE_CATEGORY = 0
private const val VIEW_TYPE_COIN = 1
const val VIEW_TYPE_CATEGORY = 0
const val VIEW_TYPE_COIN = 1
const val VIEW_TYPE_CAROUSEL = 2

val sharedPool = RecyclerView.RecycledViewPool()
}

private var items = listOf<CoinsAdapterItem>()


fun setData(categories: List<CoinCategoryState>) {
val adapterItems = mutableListOf<CoinsAdapterItem>()

categories.forEach { category ->
adapterItems.add(CoinsAdapterItem.CategoryHeader(category.name))
category.coins.forEach { coin ->
adapterItems.add(CoinsAdapterItem.CoinItem(coin))
adapterItems.add(CategoryHeader(category.name))
when (category.coins.size) {
in 0..10 -> {
category.coins.forEach { coin ->
adapterItems.add(CoinItem(coin))
}
}

else -> adapterItems.add(CarouselItems(category.coins))
}
}

items = adapterItems
notifyDataSetChanged()

submitList(adapterItems)
}
override fun getItemCount(): Int = items.size

override fun getItemCount(): Int = currentList.size

override fun getItemViewType(position: Int): Int {
return when (items[position]) {
is CoinsAdapterItem.CategoryHeader -> VIEW_TYPE_CATEGORY
is CoinsAdapterItem.CoinItem -> VIEW_TYPE_COIN
return when (getItem(position)) {
is CategoryHeader -> VIEW_TYPE_CATEGORY
is CoinItem -> VIEW_TYPE_COIN
is CarouselItems -> VIEW_TYPE_CAROUSEL
}
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIEW_TYPE_CATEGORY -> CategoryHeaderViewHolder(
Expand All @@ -48,25 +62,100 @@ class CoinsAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
false
)
)
VIEW_TYPE_COIN -> CoinViewHolder(
ItemCoinBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false

VIEW_TYPE_COIN -> {
val binding =
ItemCoinBinding.inflate(LayoutInflater.from(parent.context), parent, false)
val params = binding.root.layoutParams
?: RecyclerView.LayoutParams(
RecyclerView.LayoutParams.WRAP_CONTENT,
RecyclerView.LayoutParams.WRAP_CONTENT
)
val screenWidth = parent.resources.displayMetrics.widthPixels
params.width = (screenWidth / 2) - 16

binding.root.layoutParams = params
CoinViewHolder(binding)
}

VIEW_TYPE_CAROUSEL -> {
CarouselViewHolder(
ItemCarouselBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
sharedPool
)
)
}

else -> throw IllegalArgumentException("Unknown view type: $viewType")
}
}

override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (val item = items[position]) {
is CoinsAdapterItem.CategoryHeader -> {
when (val item = getItem(position)) {
is CategoryHeader -> {
(holder as CategoryHeaderViewHolder).bind(item.categoryName)
}
is CoinsAdapterItem.CoinItem -> {

is CoinItem -> {
(holder as CoinViewHolder).bind(item.coin)
}

is CarouselItems -> {
(holder as CarouselViewHolder).bind(item.coins)
}
}
}

override fun onBindViewHolder(
holder: RecyclerView.ViewHolder,
position: Int,
payloads: List<Any>
) {
when (val item = getItem(position)) {
is CategoryHeader -> {
(holder as CategoryHeaderViewHolder).bind(item.categoryName)
}

is CoinItem -> {
if (payloads.isNotEmpty()) {
(holder as CoinViewHolder).bind(item.coin, payloads)
} else {
(holder as CoinViewHolder).bind(item.coin)
}
}

is CarouselItems -> {
(holder as CarouselViewHolder).bind(item.coins)
}
}
}
}

object CoinDiffUtil : DiffUtil.ItemCallback<CoinsAdapterItem>() {

override fun areItemsTheSame(
oldItem: CoinsAdapterItem,
newItem: CoinsAdapterItem
): Boolean = when (oldItem) {
is CategoryHeader if newItem is CategoryHeader -> oldItem.categoryName == newItem.categoryName
is CoinItem if newItem is CoinItem -> oldItem.coin.id == newItem.coin.id
is CarouselItems if newItem is CarouselItems -> oldItem.coins.size == newItem.coins.size
else -> false
}

override fun areContentsTheSame(oldItem: CoinsAdapterItem, newItem: CoinsAdapterItem): Boolean =
oldItem == newItem

override fun getChangePayload(oldItem: CoinsAdapterItem, newItem: CoinsAdapterItem): Any? {
if (oldItem is CoinItem && newItem is CoinItem) {
val diffBundle = Bundle()
if (oldItem.coin.highlight != newItem.coin.highlight) {
diffBundle.putBoolean("highlight", newItem.coin.highlight)
}
return if (diffBundle.size() > 0) diffBundle else null
} else return super.getChangePayload(oldItem, newItem)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ import ru.otus.cryptosample.coins.feature.CoinState
sealed class CoinsAdapterItem {
data class CategoryHeader(val categoryName: String) : CoinsAdapterItem()
data class CoinItem(val coin: CoinState) : CoinsAdapterItem()
data class CarouselItems(val coins: List<CoinState>) : CoinsAdapterItem()
}
Loading