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 @@ -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!!
Expand All @@ -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)
Expand Down Expand Up @@ -59,20 +63,22 @@ class CoinListFragment : Fragment() {
}

private fun setupRecyclerView() {
coinsAdapter = CoinsAdapter()
coinsAdapter = CoinsListAdapter(sharedPool)

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
2 -> 2 // Horizontal items position
else -> 1
}
}
}

binding.recyclerView.apply {
itemAnimator = ItemAnimator()
layoutManager = gridLayoutManager
adapter = coinsAdapter
}
Expand All @@ -98,8 +104,8 @@ class CoinListFragment : Fragment() {
}
}

private fun renderState(state: CoinsScreenState) {
coinsAdapter.setData(state.categories)
private fun renderState(categories: List<CoinsAdapterItem>) {
coinsAdapter.submitList(categories)
}

override fun onDestroyView() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<CoinsScreenState> = _state.asStateFlow()
private val _state = MutableStateFlow(mapper.map(CoinsScreenState().categories))
val state: StateFlow<List<CoinsAdapterItem>> = _state.asStateFlow()

private var fullCategories: List<CoinCategoryState> = emptyList()
private var highlightMovers = false
Expand Down Expand Up @@ -68,6 +71,6 @@ class CoinListViewModel(
})
}

_state.update { it.copy(categories = processedCategories) }
_state.update { mapper.map(processedCategories) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand All @@ -24,6 +26,7 @@ class CoinListViewModelFactory @Inject constructor(
return CoinListViewModel(
consumeCoinsUseCase = consumeCoinsUseCase,
coinsStateFactory = coinsStateFactory,
mapper = mapper
) as T
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CoinItem, HorizontalCoinViewHolder>(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<Any?>
) {
if (payloads.isEmpty()) {
holder.bind(getItem(position).coin)
return
}

if (payloads.contains(HIGHLIGHT)) {
holder.updateHighlight(getItem(position).coin.highlight)
}
}

class CoinDiffUtilCallBack : DiffUtil.ItemCallback<CoinItem>() {
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
}
}
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.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<CoinItem>) {
_adapter.submitList(items)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,8 @@ class CoinViewHolder(
fireBadge.isVisible = coin.highlight
}
}

fun updateHighlight(highlight: Boolean){
binding.fireBadge.isVisible = highlight
}
}

This file was deleted.

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 HorizontalCategory(val id: String, val coinItems: List<CoinItem>): CoinsAdapterItem()
}
Loading