From f596f0f9ee55fcd96866c968cae74240f824188f Mon Sep 17 00:00:00 2001 From: GeorgeBozon Date: Tue, 17 Feb 2026 23:09:20 +0300 Subject: [PATCH] - home work --- .../coins/feature/CoinListFragment.kt | 16 +- .../coins/feature/CoinListViewModel.kt | 9 +- .../coins/feature/CoinListViewModelFactory.kt | 3 + .../adapter/CategoryHorizontalAdapter.kt | 66 +++++++ .../CategoryHorizontalItemViewHolder.kt | 29 +++ .../coins/feature/adapter/CoinViewHolder.kt | 4 + .../coins/feature/adapter/CoinsAdapter.kt | 72 -------- .../coins/feature/adapter/CoinsAdapterItem.kt | 1 + .../coins/feature/adapter/CoinsListAdapter.kt | 174 ++++++++++++++++++ .../adapter/HorizontalCoinViewHolder.kt | 39 ++++ .../coins/feature/adapter/ItemAnimator.kt | 92 +++++++++ .../mapper/CategoriesToAdapterItemMapper.kt | 30 +++ .../main/res/layout/item_coin_horizontal.xml | 81 ++++++++ .../res/layout/item_horizontal_category.xml | 7 + 14 files changed, 543 insertions(+), 80 deletions(-) create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalAdapter.kt create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalItemViewHolder.kt delete mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapter.kt create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsListAdapter.kt create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinViewHolder.kt create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ItemAnimator.kt create mode 100644 app/src/main/java/ru/otus/cryptosample/coins/feature/mapper/CategoriesToAdapterItemMapper.kt create mode 100644 app/src/main/res/layout/item_coin_horizontal.xml create mode 100644 app/src/main/res/layout/item_horizontal_category.xml diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListFragment.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListFragment.kt index 093b01f..fa07d11 100644 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListFragment.kt +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListFragment.kt @@ -11,14 +11,18 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import kotlinx.coroutines.launch import ru.otus.cryptosample.CoinsSampleApp -import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapter +import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem +import ru.otus.cryptosample.coins.feature.adapter.CoinsListAdapter +import ru.otus.cryptosample.coins.feature.adapter.ItemAnimator import ru.otus.cryptosample.coins.feature.di.DaggerCoinListComponent import ru.otus.cryptosample.databinding.FragmentCoinListBinding import javax.inject.Inject class CoinListFragment : Fragment() { + private val sharedPool = RecyclerView.RecycledViewPool() private var _binding: FragmentCoinListBinding? = null private val binding get() = _binding!! @@ -28,7 +32,7 @@ class CoinListFragment : Fragment() { private val viewModel: CoinListViewModel by viewModels { factory } - private lateinit var coinsAdapter: CoinsAdapter + private lateinit var coinsAdapter: CoinsListAdapter override fun onAttach(context: Context) { super.onAttach(context) @@ -59,7 +63,7 @@ class CoinListFragment : Fragment() { } private fun setupRecyclerView() { - coinsAdapter = CoinsAdapter() + coinsAdapter = CoinsListAdapter(sharedPool) val gridLayoutManager = GridLayoutManager(requireContext(), 2) gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() { @@ -67,12 +71,14 @@ class CoinListFragment : Fragment() { return when (coinsAdapter.getItemViewType(position)) { 0 -> 2 // Category header spans full width 1 -> 1 // Coin item spans half width + 2 -> 2 // Horizontal items position else -> 1 } } } binding.recyclerView.apply { + itemAnimator = ItemAnimator() layoutManager = gridLayoutManager adapter = coinsAdapter } @@ -98,8 +104,8 @@ class CoinListFragment : Fragment() { } } - private fun renderState(state: CoinsScreenState) { - coinsAdapter.setData(state.categories) + private fun renderState(categories: List) { + coinsAdapter.submitList(categories) } override fun onDestroyView() { diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModel.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModel.kt index 6851a32..dce964a 100644 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModel.kt +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModel.kt @@ -11,14 +11,17 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.update import ru.otus.cryptosample.coins.domain.ConsumeCoinsUseCase +import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem +import ru.otus.cryptosample.coins.feature.mapper.CategoriesToAdapterItemMapper class CoinListViewModel( private val consumeCoinsUseCase: ConsumeCoinsUseCase, private val coinsStateFactory: CoinsStateFactory, + private val mapper: CategoriesToAdapterItemMapper ) : ViewModel() { - private val _state = MutableStateFlow(CoinsScreenState()) - val state: StateFlow = _state.asStateFlow() + private val _state = MutableStateFlow(mapper.map(CoinsScreenState().categories)) + val state: StateFlow> = _state.asStateFlow() private var fullCategories: List = emptyList() private var highlightMovers = false @@ -68,6 +71,6 @@ class CoinListViewModel( }) } - _state.update { it.copy(categories = processedCategories) } + _state.update { mapper.map(processedCategories) } } } diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModelFactory.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModelFactory.kt index 2e309d1..49e4025 100644 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModelFactory.kt +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinListViewModelFactory.kt @@ -5,12 +5,14 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewmodel.CreationExtras import ru.otus.common.di.FeatureScope import ru.otus.cryptosample.coins.domain.ConsumeCoinsUseCase +import ru.otus.cryptosample.coins.feature.mapper.CategoriesToAdapterItemMapper import javax.inject.Inject @FeatureScope class CoinListViewModelFactory @Inject constructor( private val consumeCoinsUseCase: ConsumeCoinsUseCase, private val coinsStateFactory: CoinsStateFactory, + private val mapper: CategoriesToAdapterItemMapper ) : ViewModelProvider.Factory { @@ -24,6 +26,7 @@ class CoinListViewModelFactory @Inject constructor( return CoinListViewModel( consumeCoinsUseCase = consumeCoinsUseCase, coinsStateFactory = coinsStateFactory, + mapper = mapper ) as T } } diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalAdapter.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalAdapter.kt new file mode 100644 index 0000000..b810976 --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalAdapter.kt @@ -0,0 +1,66 @@ +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 ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem.CoinItem +import ru.otus.cryptosample.databinding.ItemCoinHorizontalBinding + +private const val HIGHLIGHT = "HIGHLIGHT" + +class CategoryHorizontalAdapter : + ListAdapter(CoinDiffUtilCallBack()) { + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): HorizontalCoinViewHolder { + return HorizontalCoinViewHolder( + ItemCoinHorizontalBinding.inflate( + LayoutInflater.from( + parent.context, + ), + parent, + false, + ) + ) + } + + override fun onBindViewHolder( + holder: HorizontalCoinViewHolder, + position: Int + ) { + holder.bind(getItem(position).coin) + } + + override fun onBindViewHolder( + holder: HorizontalCoinViewHolder, + position: Int, + payloads: List + ) { + if (payloads.isEmpty()) { + holder.bind(getItem(position).coin) + return + } + + if (payloads.contains(HIGHLIGHT)) { + holder.updateHighlight(getItem(position).coin.highlight) + } + } + + class CoinDiffUtilCallBack : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: CoinItem, + newItem: CoinItem + ): Boolean = oldItem.coin.id == newItem.coin.id + + override fun areContentsTheSame( + oldItem: CoinItem, + newItem: CoinItem + ): Boolean = oldItem.coin == newItem.coin + + override fun getChangePayload(oldItem: CoinItem, newItem: CoinItem): Any? = + if (oldItem.coin.highlight != newItem.coin.highlight) HIGHLIGHT else null + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalItemViewHolder.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalItemViewHolder.kt new file mode 100644 index 0000000..f25e0e6 --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CategoryHorizontalItemViewHolder.kt @@ -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.adapter.CoinsAdapterItem.CoinItem +import ru.otus.cryptosample.databinding.ItemHorizontalCategoryBinding + +class CategoryHorizontalItemViewHolder( + binding: ItemHorizontalCategoryBinding, + sharedPool: RecyclerView.RecycledViewPool +) : + RecyclerView.ViewHolder(binding.root) { + private val _adapter = CategoryHorizontalAdapter() + + init { + binding.horizontalRecycler.apply { + setRecycledViewPool(sharedPool) + layoutManager = + LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false) + adapter = _adapter + setHasFixedSize(true) + itemAnimator = null + } + } + + fun bind(items: List) { + _adapter.submitList(items) + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinViewHolder.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinViewHolder.kt index 729978e..57c3174 100644 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinViewHolder.kt +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinViewHolder.kt @@ -33,4 +33,8 @@ class CoinViewHolder( fireBadge.isVisible = coin.highlight } } + + fun updateHighlight(highlight: Boolean){ + binding.fireBadge.isVisible = highlight + } } diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapter.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapter.kt deleted file mode 100644 index 9d6ab4f..0000000 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapter.kt +++ /dev/null @@ -1,72 +0,0 @@ -package ru.otus.cryptosample.coins.feature.adapter - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.recyclerview.widget.RecyclerView -import ru.otus.cryptosample.coins.feature.CoinCategoryState -import ru.otus.cryptosample.databinding.ItemCategoryHeaderBinding -import ru.otus.cryptosample.databinding.ItemCoinBinding - -class CoinsAdapter : RecyclerView.Adapter() { - - companion object { - private const val VIEW_TYPE_CATEGORY = 0 - private const val VIEW_TYPE_COIN = 1 - } - - private var items = listOf() - - fun setData(categories: List) { - val adapterItems = mutableListOf() - - categories.forEach { category -> - adapterItems.add(CoinsAdapterItem.CategoryHeader(category.name)) - category.coins.forEach { coin -> - adapterItems.add(CoinsAdapterItem.CoinItem(coin)) - } - } - - items = adapterItems - notifyDataSetChanged() - } - - override fun getItemCount(): Int = items.size - - override fun getItemViewType(position: Int): Int { - return when (items[position]) { - is CoinsAdapterItem.CategoryHeader -> VIEW_TYPE_CATEGORY - is CoinsAdapterItem.CoinItem -> VIEW_TYPE_COIN - } - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { - return when (viewType) { - VIEW_TYPE_CATEGORY -> CategoryHeaderViewHolder( - ItemCategoryHeaderBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - VIEW_TYPE_COIN -> CoinViewHolder( - ItemCoinBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - ) - else -> throw IllegalArgumentException("Unknown view type: $viewType") - } - } - - override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - when (val item = items[position]) { - is CoinsAdapterItem.CategoryHeader -> { - (holder as CategoryHeaderViewHolder).bind(item.categoryName) - } - is CoinsAdapterItem.CoinItem -> { - (holder as CoinViewHolder).bind(item.coin) - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapterItem.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapterItem.kt index c483d5e..3e6da15 100644 --- a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapterItem.kt +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsAdapterItem.kt @@ -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 HorizontalCategory(val id: String, val coinItems: List): CoinsAdapterItem() } \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsListAdapter.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsListAdapter.kt new file mode 100644 index 0000000..453a03a --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinsListAdapter.kt @@ -0,0 +1,174 @@ +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.databinding.ItemCategoryHeaderBinding +import ru.otus.cryptosample.databinding.ItemCoinBinding +import ru.otus.cryptosample.databinding.ItemHorizontalCategoryBinding + +private const val VIEW_TYPE_CATEGORY = 0 +private const val VIEW_TYPE_COIN = 1 + +private const val VIEW_TYPE_HORIZONTAL_CATEGORY = 2 + +class CoinsListAdapter(private val sharedPool: RecyclerView.RecycledViewPool) : + ListAdapter(DiffCallback()) { + + override fun getItemViewType(position: Int): Int { + return when (getItem(position)) { + is CoinsAdapterItem.CategoryHeader -> VIEW_TYPE_CATEGORY + is CoinsAdapterItem.CoinItem -> VIEW_TYPE_COIN + is CoinsAdapterItem.HorizontalCategory -> VIEW_TYPE_HORIZONTAL_CATEGORY + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): RecyclerView.ViewHolder { + + val layoutInflater = LayoutInflater.from(parent.context) + return when (viewType) { + VIEW_TYPE_CATEGORY -> CategoryHeaderViewHolder( + ItemCategoryHeaderBinding.inflate( + layoutInflater, + parent, + false + ) + ) + + VIEW_TYPE_COIN -> CoinViewHolder( + ItemCoinBinding.inflate( + layoutInflater, + parent, + false + ) + ) + + VIEW_TYPE_HORIZONTAL_CATEGORY -> { + CategoryHorizontalItemViewHolder( + ItemHorizontalCategoryBinding.inflate( + layoutInflater, + parent, + false + ), + sharedPool + ) + } + + else -> throw IllegalArgumentException("Unknown view type: $viewType") + } + } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + payloads: List + ) { + val item = getItem(position) + + if (payloads.isEmpty()) { + onBindViewHolder(holder, position) + return + } + + val payload = payloads.getOrNull(0) + + payload?.let { + when (it) { + is Payloads.SingleHighLight -> (holder as CoinViewHolder).updateHighlight((item as CoinsAdapterItem.CoinItem).coin.highlight) + + is Payloads.MultiHighlights -> (holder as CategoryHorizontalItemViewHolder).bind( + (item as CoinsAdapterItem.HorizontalCategory).coinItems + ) + + else -> Unit + } + } + + } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + ) { + val item = getItem(position) + when (item) { + is CoinsAdapterItem.CategoryHeader -> (holder as CategoryHeaderViewHolder).bind(item.categoryName) + + is CoinsAdapterItem.CoinItem -> (holder as CoinViewHolder).bind(item.coin) + + is CoinsAdapterItem.HorizontalCategory -> (holder as CategoryHorizontalItemViewHolder).bind( + item.coinItems + ) + } + } + + private class DiffCallback : DiffUtil.ItemCallback() { + + override fun areItemsTheSame( + oldItem: CoinsAdapterItem, + newItem: CoinsAdapterItem + ): Boolean { + return when { + oldItem is CoinsAdapterItem.CoinItem && newItem is CoinsAdapterItem.CoinItem -> { + oldItem.coin.id == newItem.coin.id + } + + oldItem is CoinsAdapterItem.CategoryHeader && newItem is CoinsAdapterItem.CategoryHeader -> { + oldItem.categoryName == newItem.categoryName + } + + oldItem is CoinsAdapterItem.HorizontalCategory && newItem is CoinsAdapterItem.HorizontalCategory -> { + oldItem.id == newItem.id + } + + 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 && newItem is CoinsAdapterItem.CoinItem -> { + if (oldItem.coin.highlight != newItem.coin.highlight) Payloads.SingleHighLight else null + } + + oldItem is CoinsAdapterItem.HorizontalCategory && newItem is CoinsAdapterItem.HorizontalCategory -> { + + if (hasHighLightsChanges(oldItem, newItem)) Payloads.MultiHighlights else null + } + + else -> null + } + } + + private fun hasHighLightsChanges( + oldItem: CoinsAdapterItem.HorizontalCategory, + newItem: CoinsAdapterItem.HorizontalCategory + ): Boolean { + + oldItem.coinItems.forEach { item -> + val new = newItem.coinItems.firstOrNull { it.coin.id == item.coin.id } + if (new?.coin?.highlight != item.coin.highlight) return true + } + + return false + } + + } + + private sealed interface Payloads { + data object SingleHighLight : Payloads + + data object MultiHighlights : Payloads + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinViewHolder.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinViewHolder.kt new file mode 100644 index 0000000..9d8d61e --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinViewHolder.kt @@ -0,0 +1,39 @@ +package ru.otus.cryptosample.coins.feature.adapter + +import androidx.core.content.ContextCompat +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import coil.load +import ru.otus.cryptosample.R +import ru.otus.cryptosample.coins.feature.CoinState +import ru.otus.cryptosample.databinding.ItemCoinHorizontalBinding + +class HorizontalCoinViewHolder(private val binding: ItemCoinHorizontalBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(coin: CoinState) { + with(binding) { + coinName.text = coin.name + coinPrice.text = coin.price + coinChange.text = coin.discount + + val changeColor = if (coin.goesUp) { + ContextCompat.getColor(itemView.context, android.R.color.holo_red_dark) + } else { + ContextCompat.getColor(itemView.context, android.R.color.holo_green_dark) + } + coinChange.setTextColor(changeColor) + + coinIcon.load(coin.image) { + placeholder(R.drawable.generic) + error(R.drawable.generic) + } + + fireBadge.isVisible = coin.highlight + } + } + + fun updateHighlight(highlight: Boolean){ + binding.fireBadge.isVisible = highlight + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ItemAnimator.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ItemAnimator.kt new file mode 100644 index 0000000..d1374d4 --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ItemAnimator.kt @@ -0,0 +1,92 @@ +package ru.otus.cryptosample.coins.feature.adapter + +import android.animation.Animator +import android.view.View +import android.view.animation.LinearInterpolator +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.RecyclerView + +class ItemAnimator : DefaultItemAnimator() { + + override fun animateAdd(holder: RecyclerView.ViewHolder): Boolean { + with(holder.itemView) { + animate().cancel() + reset() + + val realWidth = if (width==0) measuredWidth else width + + alpha = 0f + translationX = -1f * realWidth + scaleX = 0.9f + scaleY = 0.9f + + animate().alpha(1f) + .translationX(0f) + .scaleY(1f) + .scaleX(1f) + .setDuration(300) + .setInterpolator(LinearInterpolator()) + .withStartAction { + dispatchAddStarting(holder) + } + .withEndAction { + reset() + dispatchAddFinished(holder) + } + .setListener(getListener(holder)) + .start() + + } + + return true + } + + override fun animateRemove(holder: RecyclerView.ViewHolder): Boolean { + + with(holder.itemView) { + animate().cancel() + reset() + + animate().alpha(0f) + .translationX(-1f * width) + .scaleY(0.9f) + .scaleX(0.9f) + .setDuration(300) + .setInterpolator(LinearInterpolator()) + .withStartAction { dispatchRemoveStarting(holder) } + .withEndAction { + reset() + dispatchRemoveFinished(holder) + } + .setListener(getListener(holder)) + .start() + } + + return true + } + + private fun getListener(holder: RecyclerView.ViewHolder) = object: Animator.AnimatorListener{ + override fun onAnimationCancel(animation: Animator) { + holder.setIsRecyclable(true) + } + + override fun onAnimationEnd(animation: Animator) { + holder.setIsRecyclable(true) + } + + override fun onAnimationRepeat(animation: Animator) { + holder.setIsRecyclable(false) + } + + override fun onAnimationStart(animation: Animator) { + holder.setIsRecyclable(false) + } + } + + private fun View.reset() { + alpha = 1f + translationX = 0f + scaleY = 1f + scaleX = 1f + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/mapper/CategoriesToAdapterItemMapper.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/mapper/CategoriesToAdapterItemMapper.kt new file mode 100644 index 0000000..4729c1b --- /dev/null +++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/mapper/CategoriesToAdapterItemMapper.kt @@ -0,0 +1,30 @@ +package ru.otus.cryptosample.coins.feature.mapper + +import ru.otus.cryptosample.coins.feature.CoinCategoryState +import ru.otus.cryptosample.coins.feature.adapter.CoinsAdapterItem +import javax.inject.Inject + +class CategoriesToAdapterItemMapper @Inject constructor() { + + fun map(categories: List): List { + + val adapterItems = mutableListOf() + + categories.forEach { category -> + adapterItems.add(CoinsAdapterItem.CategoryHeader(category.name)) + if (category.coins.size > 10) { + adapterItems.add(CoinsAdapterItem.HorizontalCategory(id = category.id, coinItems = category.coins.map { + CoinsAdapterItem.CoinItem( + it + ) + })) + } else { + category.coins.forEach { coin -> + adapterItems.add(CoinsAdapterItem.CoinItem(coin)) + } + } + } + + return adapterItems + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/item_coin_horizontal.xml b/app/src/main/res/layout/item_coin_horizontal.xml new file mode 100644 index 0000000..366751e --- /dev/null +++ b/app/src/main/res/layout/item_coin_horizontal.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_horizontal_category.xml b/app/src/main/res/layout/item_horizontal_category.xml new file mode 100644 index 0000000..6b77dde --- /dev/null +++ b/app/src/main/res/layout/item_horizontal_category.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file