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
4 changes: 3 additions & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,11 @@ dependencies {
implementation libs.androidx.datastore.preferences

implementation libs.dagger
implementation libs.androidx.ui
implementation libs.recyclerview
kapt libs.daggerCompiler

testImplementation libs.junit
androidTestImplementation libs.androidx.test.ext.junit
androidTestImplementation libs.espresso.core
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ 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.FadeSlideItemAnimator
import ru.otus.cryptosample.coins.feature.di.DaggerCoinListComponent
import ru.otus.cryptosample.databinding.FragmentCoinListBinding
import javax.inject.Inject
Expand All @@ -28,7 +30,8 @@ class CoinListFragment : Fragment() {

private val viewModel: CoinListViewModel by viewModels { factory }

private lateinit var coinsAdapter: CoinsAdapter
private var coinsAdapter: CoinsAdapter? = null
private var sharedViewPool: RecyclerView.RecycledViewPool? = null

override fun onAttach(context: Context) {
super.onAttach(context)
Expand Down Expand Up @@ -59,22 +62,24 @@ class CoinListFragment : Fragment() {
}

private fun setupRecyclerView() {
coinsAdapter = CoinsAdapter()
val viewPool = RecyclerView.RecycledViewPool().apply {
setMaxRecycledViews(CoinsAdapter.HORIZONTAL_COIN_VIEW_TYPE, 20)
}
val adapter = CoinsAdapter(viewPool)
sharedViewPool = viewPool
coinsAdapter = adapter

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
else -> 1
}
return if (adapter.isFullSpan(position)) 2 else 1
}
}

binding.recyclerView.apply {
layoutManager = gridLayoutManager
adapter = coinsAdapter
this.adapter = adapter
itemAnimator = FadeSlideItemAnimator()
}
}

Expand All @@ -99,11 +104,21 @@ class CoinListFragment : Fragment() {
}

private fun renderState(state: CoinsScreenState) {
coinsAdapter.setData(state.categories)
if (binding.showAllChip.isChecked != state.showAll) {
binding.showAllChip.isChecked = state.showAll
}
if (binding.highlightChip.isChecked != state.highlightMovers) {
binding.highlightChip.isChecked = state.highlightMovers
}
coinsAdapter?.submitData(state.categories, state.showAll)
}

override fun onDestroyView() {
super.onDestroyView()
binding.recyclerView.adapter = null
sharedViewPool?.clear()
sharedViewPool = null
coinsAdapter = null
_binding = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,15 @@ class CoinListViewModel(
}

fun onHighlightMoversToggled(isChecked: Boolean) {
if (highlightMovers == isChecked) return

highlightMovers = isChecked
updateUiState()
}

fun onShowAllToggled(isChecked: Boolean) {
if (showAll == isChecked) return

showAll = isChecked
updateUiState()
}
Expand All @@ -54,20 +58,18 @@ class CoinListViewModel(
}

private fun updateUiState() {
var processedCategories = if (showAll) {
fullCategories
} else {
fullCategories.map { category ->
category.copy(coins = category.coins.take(4))
}
}

processedCategories = processedCategories.map { category ->
val processedCategories = fullCategories.map { category ->
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,
highlightMovers = highlightMovers,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package ru.otus.cryptosample.coins.feature

data class CoinsScreenState(
val categories: List<CoinCategoryState> = emptyList(),
val showAll: Boolean = true,
val highlightMovers: Boolean = false,
)

data class CoinCategoryState(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package ru.otus.cryptosample.coins.feature.adapter

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

data class HighlightPayload(val isHighlighted: Boolean)

data class HorizontalCoinsPayload(val coins: List<CoinState>)

fun coinHighlightPayload(oldItem: CoinState, newItem: CoinState): HighlightPayload? {
val onlyHighlightChanged = oldItem.highlight != newItem.highlight &&
oldItem.copy(highlight = newItem.highlight) == newItem

return if (onlyHighlightChanged) {
HighlightPayload(newItem.highlight)
} else {
null
}
}

object CoinDiffCallback : DiffUtil.ItemCallback<CoinState>() {
override fun areItemsTheSame(oldItem: CoinState, newItem: CoinState): Boolean =
oldItem.id == newItem.id

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

override fun getChangePayload(oldItem: CoinState, newItem: CoinState): Any? =
coinHighlightPayload(oldItem, newItem)
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package ru.otus.cryptosample.coins.feature.adapter

import android.view.View
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
Expand All @@ -9,8 +10,9 @@ import ru.otus.cryptosample.coins.feature.CoinState
import ru.otus.cryptosample.databinding.ItemCoinBinding

class CoinViewHolder(
private val binding: ItemCoinBinding
) : RecyclerView.ViewHolder(binding.root) {
private val binding: ItemCoinBinding,
root: View = binding.root,
) : RecyclerView.ViewHolder(root) {

fun bind(coin: CoinState) {
with(binding) {
Expand All @@ -30,7 +32,11 @@ class CoinViewHolder(
error(R.drawable.generic)
}

fireBadge.isVisible = coin.highlight
bindHighlight(coin.highlight)
}
}

fun bindHighlight(isHighlighted: Boolean) {
binding.fireBadge.isVisible = isHighlighted
}
}
Loading