diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..4bec4ea
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,117 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..a55e7a1
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
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..3a40740 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,12 +11,15 @@ 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.di.DaggerCoinListComponent
import ru.otus.cryptosample.databinding.FragmentCoinListBinding
import javax.inject.Inject
+import ru.otus.cryptosample.R
+import ru.otus.cryptosample.coins.feature.adapter.CoinItemAnimator
class CoinListFragment : Fragment() {
@@ -59,14 +62,15 @@ class CoinListFragment : Fragment() {
}
private fun setupRecyclerView() {
- coinsAdapter = CoinsAdapter()
+ coinsAdapter = CoinsAdapter(RecyclerView.RecycledViewPool())
val gridLayoutManager = GridLayoutManager(requireContext(), 2)
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
+ R.layout.item_category_header -> 2 // Category header spans full width
+ R.layout.item_coin -> 1 // Coin item spans half-width
+ R.layout.item_carousel -> 2 // The horizontal row spans full width
else -> 1
}
}
@@ -75,6 +79,15 @@ class CoinListFragment : Fragment() {
binding.recyclerView.apply {
layoutManager = gridLayoutManager
adapter = coinsAdapter
+ itemAnimator = CoinItemAnimator()
+ }
+
+ binding.btnAddCoin.setOnClickListener {
+ viewModel.addRandomCoin()
+ }
+
+ binding.btnRemoveCoins.setOnClickListener {
+ viewModel.removeRandomCoin()
}
}
@@ -99,7 +112,7 @@ class CoinListFragment : Fragment() {
}
private fun renderState(state: CoinsScreenState) {
- coinsAdapter.setData(state.categories)
+ coinsAdapter.setData(state.categories, state.showAll)
}
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..2897d60 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
@@ -38,6 +38,50 @@ class CoinListViewModel(
updateUiState()
}
+ fun addRandomCoin() {
+ if (fullCategories.isEmpty()) return
+
+ val allCoins = fullCategories.flatMap { it.coins }
+ if (allCoins.isEmpty()) return
+
+ val templateCoin = allCoins.random()
+
+ val newCoinId = "new_${System.currentTimeMillis()}"
+ val newCoin = templateCoin.copy(
+ id = newCoinId,
+ name = "New Coin ${(1..100).random()}",
+ price = "$${(1000..50000).random()}",
+ )
+
+ val randomCategory = fullCategories.random()
+
+ fullCategories = fullCategories.map { category ->
+ if (category.id == randomCategory.id) {
+ category.copy(coins = category.coins + newCoin)
+ } else {
+ category
+ }
+ }
+ updateUiState()
+ }
+
+ fun removeRandomCoin() {
+ if (fullCategories.isEmpty()) return
+
+ val allCoins = fullCategories.flatMap { it.coins }
+ if (allCoins.isEmpty()) return
+
+ val coinsToRemove = allCoins.shuffled().take(5.coerceAtMost(allCoins.size))
+ val coinsToRemoveIds = coinsToRemove.map { it.id }.toSet()
+
+ fullCategories = fullCategories.map { category ->
+ category.copy(
+ coins = category.coins.filter { coin -> coin.id !in coinsToRemoveIds }
+ )
+ }
+ updateUiState()
+ }
+
private fun requestCoins() {
consumeCoinsUseCase().map { categories ->
categories.map { category -> coinsStateFactory.create(category) }
@@ -54,20 +98,20 @@ class CoinListViewModel(
}
private fun updateUiState() {
- var processedCategories = if (showAll) {
- fullCategories
- } else {
- fullCategories.map { category ->
- category.copy(coins = category.coins.take(4))
- }
- }
+ var processedCategories = fullCategories
processedCategories = processedCategories.map { category ->
- category.copy(coins = category.coins.map { coin ->
- coin.copy(highlight = highlightMovers && coin.isHotMover)
- })
+ category.copy(
+ coins = category.coins.map { coin ->
+ coin.copy(highlight = highlightMovers && coin.isHotMover)
+ })
}
- _state.update { it.copy(categories = processedCategories) }
+ _state.update {
+ it.copy(
+ categories = processedCategories,
+ showAll = showAll
+ )
+ }
}
}
diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinState.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinState.kt
index cecf2b7..483bbb8 100644
--- a/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinState.kt
+++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/CoinState.kt
@@ -2,7 +2,8 @@ package ru.otus.cryptosample.coins.feature
data class CoinsScreenState(
val categories: List = emptyList(),
-)
+ val showAll: Boolean = true,
+ )
data class CoinCategoryState(
val id: String,
diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinItemAnimator.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinItemAnimator.kt
new file mode 100644
index 0000000..d3c8957
--- /dev/null
+++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/CoinItemAnimator.kt
@@ -0,0 +1,71 @@
+package ru.otus.cryptosample.coins.feature.adapter
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ObjectAnimator
+import android.view.View
+import androidx.recyclerview.widget.DefaultItemAnimator
+import androidx.recyclerview.widget.RecyclerView
+
+class CoinItemAnimator : DefaultItemAnimator() {
+
+ override fun animateAdd(holder: RecyclerView.ViewHolder): Boolean {
+ holder.itemView.alpha = 0f
+ holder.itemView.translationX = -holder.itemView.width.toFloat()
+
+ val fadeIn = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 0f, 1f)
+ val slideIn = ObjectAnimator.ofFloat(holder.itemView, View.TRANSLATION_X, -holder.itemView.width.toFloat(), 0f)
+
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(fadeIn, slideIn)
+ animatorSet.duration = addDuration
+ animatorSet.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ dispatchAddStarting(holder)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ dispatchAddFinished(holder)
+ holder.itemView.alpha = 1f
+ holder.itemView.translationX = 0f
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ holder.itemView.alpha = 1f
+ holder.itemView.translationX = 0f
+ }
+ })
+ animatorSet.start()
+
+ return false
+ }
+
+ override fun animateRemove(holder: RecyclerView.ViewHolder): Boolean {
+ val fadeOut = ObjectAnimator.ofFloat(holder.itemView, View.ALPHA, 1f, 0f)
+ val slideOut = ObjectAnimator.ofFloat(holder.itemView, View.TRANSLATION_X, 0f, holder.itemView.width.toFloat())
+
+ val animatorSet = AnimatorSet()
+ animatorSet.playTogether(fadeOut, slideOut)
+ animatorSet.duration = removeDuration
+ animatorSet.addListener(object : AnimatorListenerAdapter() {
+ override fun onAnimationStart(animation: Animator) {
+ dispatchRemoveStarting(holder)
+ }
+
+ override fun onAnimationEnd(animation: Animator) {
+ dispatchRemoveFinished(holder)
+ holder.itemView.alpha = 1f
+ holder.itemView.translationX = 0f
+ }
+
+ override fun onAnimationCancel(animation: Animator) {
+ holder.itemView.alpha = 1f
+ holder.itemView.translationX = 0f
+ }
+ })
+ animatorSet.start()
+
+ return false
+ }
+}
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..8d91e60 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
@@ -12,6 +12,10 @@ class CoinViewHolder(
private val binding: ItemCoinBinding
) : RecyclerView.ViewHolder(binding.root) {
+ sealed class Payload {
+ data class HighlightChanged(val isHighlighted: Boolean) : Payload()
+ }
+
fun bind(coin: CoinState) {
with(binding) {
coinName.text = coin.name
@@ -33,4 +37,19 @@ class CoinViewHolder(
fireBadge.isVisible = coin.highlight
}
}
+
+ fun bind(coin: CoinState, payloads: List) {
+ if (payloads.isEmpty()) {
+ bind(coin)
+ return
+ }
+
+ payloads.forEach { payload ->
+ when (payload) {
+ is Payload.HighlightChanged -> {
+ binding.fireBadge.isVisible = payload.isHighlighted
+ }
+ }
+ }
+ }
}
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
index 9d6ab4f..158e478 100644
--- 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
@@ -1,72 +1,145 @@
package ru.otus.cryptosample.coins.feature.adapter
-import android.view.LayoutInflater
import android.view.ViewGroup
+import androidx.recyclerview.widget.AsyncListDiffer
+import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
+import ru.otus.cryptosample.R
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() {
-
+class CoinsAdapter(
+ private val sharedPool: RecyclerView.RecycledViewPool
+) : RecyclerView.Adapter() {
+
companion object {
- private const val VIEW_TYPE_CATEGORY = 0
- private const val VIEW_TYPE_COIN = 1
+ private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(
+ oldItem: CoinsAdapterItem,
+ newItem: CoinsAdapterItem
+ ): Boolean {
+ return when (oldItem) {
+ is CoinsAdapterItem.CategoryHeader if newItem is CoinsAdapterItem.CategoryHeader ->
+ oldItem.categoryName == newItem.categoryName
+
+ is CoinsAdapterItem.CoinItem if newItem is CoinsAdapterItem.CoinItem ->
+ oldItem.coin.id == newItem.coin.id
+
+ is CoinsAdapterItem.HorizontalCoinsRow if newItem is CoinsAdapterItem.HorizontalCoinsRow ->
+ oldItem.categoryName == newItem.categoryName
+
+ else -> false
+ }
+ }
+
+ override fun areContentsTheSame(
+ oldItem: CoinsAdapterItem,
+ newItem: CoinsAdapterItem
+ ): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun getChangePayload(
+ oldItem: CoinsAdapterItem,
+ newItem: CoinsAdapterItem
+ ): Any? {
+ if (oldItem is CoinsAdapterItem.CoinItem && newItem is CoinsAdapterItem.CoinItem) {
+ val oldCoin = oldItem.coin
+ val newCoin = newItem.coin
+ if (oldCoin.highlight != newCoin.highlight &&
+ oldCoin.copy(highlight = newCoin.highlight) == newCoin) {
+ return CoinViewHolder.Payload.HighlightChanged(newCoin.highlight)
+ }
+ }
+ return null
+ }
+ }
}
-
- private var items = listOf()
-
- fun setData(categories: List) {
+
+ private val differ = AsyncListDiffer(this, DIFF_CALLBACK)
+
+ fun setData(categories: List, showAll: Boolean) {
val adapterItems = mutableListOf()
-
+
categories.forEach { category ->
adapterItems.add(CoinsAdapterItem.CategoryHeader(category.name))
- category.coins.forEach { coin ->
- adapterItems.add(CoinsAdapterItem.CoinItem(coin))
+
+ val coins = category.coins
+ val shouldUseHorizontalRow = coins.size > 10 && !showAll
+
+ if (shouldUseHorizontalRow) {
+ adapterItems.add(
+ CoinsAdapterItem.HorizontalCoinsRow(
+ categoryName = category.name,
+ coins = coins
+ )
+ )
+ } else {
+ coins.forEach { coin ->
+ adapterItems.add(CoinsAdapterItem.CoinItem(coin))
+ }
}
}
-
- items = adapterItems
- notifyDataSetChanged()
+
+ differ.submitList(adapterItems)
}
-
- override fun getItemCount(): Int = items.size
-
+
+ override fun getItemCount(): Int = differ.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 (differ.currentList[position]) {
+ is CoinsAdapterItem.CategoryHeader -> R.layout.item_category_header
+ is CoinsAdapterItem.CoinItem -> R.layout.item_coin
+ is CoinsAdapterItem.HorizontalCoinsRow -> R.layout.item_carousel
}
}
-
+
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
- VIEW_TYPE_CATEGORY -> CategoryHeaderViewHolder(
- ItemCategoryHeaderBinding.inflate(
- LayoutInflater.from(parent.context),
- parent,
- false
- )
+ R.layout.item_category_header -> CategoryHeaderViewHolder(
+ parent.inflateBinding(ItemCategoryHeaderBinding::inflate)
)
- VIEW_TYPE_COIN -> CoinViewHolder(
- ItemCoinBinding.inflate(
- LayoutInflater.from(parent.context),
- parent,
- false
- )
+ R.layout.item_coin -> CoinViewHolder(
+ parent.inflateBinding(ItemCoinBinding::inflate)
+ )
+ R.layout.item_carousel -> HorizontalCoinsRowViewHolder(
+ parent.inflateBinding(ItemCarouselBinding::inflate),
+ sharedPool
)
else -> throw IllegalArgumentException("Unknown view type: $viewType")
}
}
-
+
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
- when (val item = items[position]) {
+ when (val item = differ.currentList[position]) {
is CoinsAdapterItem.CategoryHeader -> {
(holder as CategoryHeaderViewHolder).bind(item.categoryName)
}
is CoinsAdapterItem.CoinItem -> {
(holder as CoinViewHolder).bind(item.coin)
}
+ is CoinsAdapterItem.HorizontalCoinsRow -> {
+ (holder as HorizontalCoinsRowViewHolder).bind(item.coins)
+ }
+ }
+ }
+
+ override fun onBindViewHolder(
+ holder: RecyclerView.ViewHolder,
+ position: Int,
+ payloads: MutableList
+ ) {
+ if (payloads.isEmpty()) {
+ super.onBindViewHolder(holder, position, payloads)
+ } else {
+ when (val item = differ.currentList[position]) {
+ is CoinsAdapterItem.CoinItem -> {
+ (holder as CoinViewHolder).bind(item.coin, payloads)
+ }
+ else -> super.onBindViewHolder(holder, position, payloads)
+ }
}
}
}
\ 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..e867b08 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
@@ -2,7 +2,14 @@ package ru.otus.cryptosample.coins.feature.adapter
import ru.otus.cryptosample.coins.feature.CoinState
-sealed class CoinsAdapterItem {
- data class CategoryHeader(val categoryName: String) : CoinsAdapterItem()
- data class CoinItem(val coin: CoinState) : CoinsAdapterItem()
+sealed interface CoinsAdapterItem {
+
+ data class CategoryHeader(val categoryName: String) : CoinsAdapterItem
+
+ data class CoinItem(val coin: CoinState) : CoinsAdapterItem
+
+ data class HorizontalCoinsRow(
+ val categoryName: String,
+ val coins: List
+ ) : CoinsAdapterItem
}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsAdapter.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsAdapter.kt
new file mode 100644
index 0000000..bd56590
--- /dev/null
+++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsAdapter.kt
@@ -0,0 +1,72 @@
+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 kotlin.math.roundToInt
+import ru.otus.cryptosample.coins.feature.CoinState
+import ru.otus.cryptosample.databinding.ItemCoinBinding
+
+class HorizontalCoinsAdapter : ListAdapter(DIFF_CALLBACK) {
+
+ companion object {
+ private const val VISIBLE_ITEMS_COUNT = 2.25f
+
+ private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: CoinState, newItem: CoinState): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: CoinState, newItem: CoinState): Boolean {
+ return oldItem == newItem
+ }
+
+ override fun getChangePayload(oldItem: CoinState, newItem: CoinState): Any? {
+ return if (oldItem.highlight != newItem.highlight &&
+ oldItem.copy(highlight = newItem.highlight) == newItem) {
+ CoinViewHolder.Payload.HighlightChanged(newItem.highlight)
+ } else {
+ null
+ }
+ }
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoinViewHolder {
+ val binding = ItemCoinBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+
+ val rv = parent as? RecyclerView
+ val parentWidthPx = rv?.width?.takeIf { it > 0 }
+ ?: parent.measuredWidth.takeIf { it > 0 }
+ ?: parent.resources.displayMetrics.widthPixels
+
+ val paddingStart = rv?.paddingStart ?: 0
+ val paddingEnd = rv?.paddingEnd ?: 0
+ val availableWidth = (parentWidthPx - paddingStart - paddingEnd).coerceAtLeast(0)
+
+ val itemWidth = (availableWidth / VISIBLE_ITEMS_COUNT).roundToInt().coerceAtLeast(1)
+
+ val lp = (binding.root.layoutParams as? RecyclerView.LayoutParams)
+ ?: RecyclerView.LayoutParams(itemWidth, RecyclerView.LayoutParams.WRAP_CONTENT)
+
+ lp.width = itemWidth
+ lp.height = RecyclerView.LayoutParams.WRAP_CONTENT
+ binding.root.layoutParams = lp
+
+ return CoinViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: CoinViewHolder, position: Int) {
+ holder.bind(getItem(position))
+ }
+
+ override fun onBindViewHolder(holder: CoinViewHolder, position: Int, payloads: MutableList) {
+ if (payloads.isEmpty()) {
+ super.onBindViewHolder(holder, position, payloads)
+ } else {
+ holder.bind(getItem(position), payloads)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsRowViewHolder.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsRowViewHolder.kt
new file mode 100644
index 0000000..bcfb79b
--- /dev/null
+++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/HorizontalCoinsRowViewHolder.kt
@@ -0,0 +1,26 @@
+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 HorizontalCoinsRowViewHolder(
+ binding: ItemCarouselBinding,
+ sharedPool: RecyclerView.RecycledViewPool
+) : RecyclerView.ViewHolder(binding.root) {
+
+ private val adapter = HorizontalCoinsAdapter()
+
+ init {
+ binding.horizontalRecyclerView.apply {
+ layoutManager = LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
+ setRecycledViewPool(sharedPool)
+ this.adapter = this@HorizontalCoinsRowViewHolder.adapter
+ }
+ }
+
+ fun bind(coins: List) {
+ adapter.submitList(coins)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ViewBindingExtensions.kt b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ViewBindingExtensions.kt
new file mode 100644
index 0000000..2700714
--- /dev/null
+++ b/app/src/main/java/ru/otus/cryptosample/coins/feature/adapter/ViewBindingExtensions.kt
@@ -0,0 +1,19 @@
+package ru.otus.cryptosample.coins.feature.adapter
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.viewbinding.ViewBinding
+
+/**
+ * Extension-функция для упрощения inflate ViewBinding в адаптерах.
+ *
+ * Пример использования:
+ * ```
+ * parent.inflateBinding(ItemCoinBinding::inflate)
+ * ```
+ */
+inline fun ViewGroup.inflateBinding(
+ crossinline bindingInflater: (LayoutInflater, ViewGroup, Boolean) -> T
+): T {
+ return bindingInflater(LayoutInflater.from(context), this, false)
+}
diff --git a/app/src/main/res/layout/fragment_coin_list.xml b/app/src/main/res/layout/fragment_coin_list.xml
index b222909..5936f40 100644
--- a/app/src/main/res/layout/fragment_coin_list.xml
+++ b/app/src/main/res/layout/fragment_coin_list.xml
@@ -1,7 +1,6 @@
@@ -12,23 +11,44 @@
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
- android:text="Coins"
+ android:text="@string/coins"
+ android:textColor="?attr/colorOnSurface"
android:textSize="20sp"
android:textStyle="bold"
- android:textColor="?attr/colorOnSurface"
- app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
+
+
+
+
+ app:layout_constraintTop_toBottomOf="@+id/btnAddCoin">
diff --git a/app/src/main/res/layout/item_carousel.xml b/app/src/main/res/layout/item_carousel.xml
new file mode 100644
index 0000000..ec2ad37
--- /dev/null
+++ b/app/src/main/res/layout/item_carousel.xml
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 5f2b821..358603a 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -2,4 +2,9 @@
CryptoSample
Coins
Main
+ Coins
+ Highlight movers
+ Show All
+ Add Coin
+ Remove Coins
\ No newline at end of file