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 @@ -65,8 +65,9 @@ class CoinListFragment : Fragment() {
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int {
return when (coinsAdapter.getItemViewType(position)) {
0 -> 2 // Category header spans full width
1 -> 1 // Coin item spans half width
CoinsAdapter.VIEW_TYPE_CATEGORY -> 2 // Category header spans full width
CoinsAdapter.VIEW_TYPE_COIN -> 1 // Coin item spans half width
CoinsAdapter.VIEW_TYPE_CAROUSEL -> 2
else -> 1
}
}
Expand All @@ -75,6 +76,8 @@ class CoinListFragment : Fragment() {
binding.recyclerView.apply {
layoutManager = gridLayoutManager
adapter = coinsAdapter
itemAnimator = ru.otus.cryptosample.coins.feature.adapter.ItemAnimator()
setRecycledViewPool(CoinsAdapter.sharedPool)
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
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>(CarouselDiff) {

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

тут 16 это пиксели и они зависят от плотности, лучше переводить все в dp


binding.root.layoutParams = params

return CoinViewHolder(binding)
}

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

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 CarouselDiff : DiffUtil.ItemCallback<CoinState>() {

override fun areItemsTheSame(old: CoinState, new: CoinState) = old.id == new.id

override fun areContentsTheSame(old: CoinState, new: CoinState) = old == new

override fun getChangePayload(oldItem: CoinState, newItem: CoinState): Any? =
if (oldItem.highlight != newItem.highlight) {
Payload.HOT_MOVER_CHANGED
} else null

object Payload {
const val HOT_MOVER_CHANGED = "HOT_MOVER_CHANGED"
}
}
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.recyclerView.apply {
layoutManager = LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
adapter = carouselAdapter
setRecycledViewPool(sharedPool)
isNestedScrollingEnabled = false
setHasFixedSize(true)
itemAnimator = ItemAnimator()
}
}

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

import androidx.recyclerview.widget.DiffUtil
import ru.otus.cryptosample.coins.feature.CoinState

data class Carousel(val categoryId: String, val coins: List<CoinState>)

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

override fun areItemsTheSame(oldItem: CoinsAdapterItem, newItem: CoinsAdapterItem): Boolean =
when {
oldItem is CoinsAdapterItem.CategoryHeader &&
newItem is CoinsAdapterItem.CategoryHeader ->
oldItem.categoryName == newItem.categoryName

oldItem is CoinsAdapterItem.CoinItem &&
newItem is CoinsAdapterItem.CoinItem ->
oldItem.coin.id == newItem.coin.id

oldItem is CoinsAdapterItem.Carousel &&
newItem is CoinsAdapterItem.Carousel ->
oldItem.categoryId == newItem.categoryId

else -> false
}

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

override fun getChangePayload(oldItem: CoinsAdapterItem, newItem: CoinsAdapterItem): Any? {
return when (oldItem) {
is CoinsAdapterItem.CoinItem -> {
val oldCoin = oldItem.coin
val newCoin = (newItem as CoinsAdapterItem.CoinItem).coin
if (oldCoin.highlight != newCoin.highlight) {
Payload.HOT_MOVER_CHANGED
} else {
null
}
}

else -> null
}
}

object Payload {
const val HOT_MOVER_CHANGED = "HOT_MOVER_CHANGED"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ class CoinViewHolder(
fireBadge.isVisible = coin.highlight
}
}

fun bind(coin: CoinState, payloads: List<Any>) {
if (CoinDiff.Payload.HOT_MOVER_CHANGED in payloads) {
binding.fireBadge.isVisible = coin.highlight
} else {
bind(coin)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,43 +1,55 @@
package ru.otus.cryptosample.coins.feature.adapter

import android.content.Context
import android.util.DisplayMetrics
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import ru.otus.cryptosample.coins.feature.CoinCategoryState
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>(CoinDiff) {

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))
if (category.coins.size <= 10) {
category.coins.forEach { coin ->
adapterItems.add(CoinsAdapterItem.CoinItem(coin))
}
} else {
adapterItems.add(CoinsAdapterItem.Carousel(CarouselCategory.CoinsCategory, 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]) {
return when (getItem(position)) {
is CoinsAdapterItem.CategoryHeader -> VIEW_TYPE_CATEGORY
is CoinsAdapterItem.CoinItem -> VIEW_TYPE_COIN
is CoinsAdapterItem.Carousel -> VIEW_TYPE_CAROUSEL
}
}

fun View.dpToPx(dp: Int): Int = (dp * resources.displayMetrics.density).toInt()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
Expand All @@ -48,25 +60,64 @@ 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 - parent.dpToPx(8)

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]) {
when (val item = getItem(position)) {
is CoinsAdapterItem.CategoryHeader -> {
(holder as CategoryHeaderViewHolder).bind(item.categoryName)
}
is CoinsAdapterItem.CoinItem -> {
(holder as CoinViewHolder).bind(item.coin)
}
is CoinsAdapterItem.Carousel -> {
(holder as CarouselViewHolder).bind(item.coins)
}
}
}

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

}
is CoinsAdapterItem.Carousel -> {
(holder as CarouselViewHolder).bind(item.coins)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@ 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 Carousel(val categoryId: String, val coins: List<CoinState>) : CoinsAdapterItem()
}

object CarouselCategory {
const val CoinsCategory = "CoinsCategory"
}
Loading