From 5812f9b2af3580a8b2271ebc21eb7f75d6f820cc Mon Sep 17 00:00:00 2001 From: Amirhossein <119420193+AmirMousavi-dev@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:38:31 +0330 Subject: [PATCH 1/4] create MarketListOrder and OrderType --- .../ir/composenews/domain/util/MarketListOrder.kt | 13 +++++++++++++ .../java/ir/composenews/domain/util/OrderType.kt | 6 ++++++ 2 files changed, 19 insertions(+) create mode 100644 domain/market/src/main/java/ir/composenews/domain/util/MarketListOrder.kt create mode 100644 domain/market/src/main/java/ir/composenews/domain/util/OrderType.kt diff --git a/domain/market/src/main/java/ir/composenews/domain/util/MarketListOrder.kt b/domain/market/src/main/java/ir/composenews/domain/util/MarketListOrder.kt new file mode 100644 index 00000000..873d0674 --- /dev/null +++ b/domain/market/src/main/java/ir/composenews/domain/util/MarketListOrder.kt @@ -0,0 +1,13 @@ +package ir.composenews.domain.util + +sealed class MarketListOrder(val orderType: OrderType) { + class Symbol(orderType: OrderType) : MarketListOrder(orderType) + class Name(orderType: OrderType) : MarketListOrder(orderType) + class Price(orderType: OrderType) : MarketListOrder(orderType) + + fun copy(orderType: OrderType): MarketListOrder = when (this) { + is Name -> Name(orderType) + is Price -> Price(orderType) + is Symbol -> Symbol(orderType) + } +} \ No newline at end of file diff --git a/domain/market/src/main/java/ir/composenews/domain/util/OrderType.kt b/domain/market/src/main/java/ir/composenews/domain/util/OrderType.kt new file mode 100644 index 00000000..29f3f1c7 --- /dev/null +++ b/domain/market/src/main/java/ir/composenews/domain/util/OrderType.kt @@ -0,0 +1,6 @@ +package ir.composenews.domain.util + +sealed interface OrderType { + data object Ascending : OrderType + data object Descending : OrderType +} \ No newline at end of file From afc3c7635d61c14549d2fe888bc309b03e002e2b Mon Sep 17 00:00:00 2001 From: Amirhossein <119420193+AmirMousavi-dev@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:40:19 +0330 Subject: [PATCH 2/4] create DefaultRadioButton and MarketListOrderSection --- .../component/MarketListOrderSection.kt | 97 +++++++++++++++++++ .../component/DefaultRadioButton.kt | 32 ++++++ 2 files changed, 129 insertions(+) create mode 100644 feature/marketlist/src/main/java/ir/composenews/marketlist/component/MarketListOrderSection.kt create mode 100644 library/designsystem/src/main/java/ir/composenews/designsystem/component/DefaultRadioButton.kt diff --git a/feature/marketlist/src/main/java/ir/composenews/marketlist/component/MarketListOrderSection.kt b/feature/marketlist/src/main/java/ir/composenews/marketlist/component/MarketListOrderSection.kt new file mode 100644 index 00000000..2515d74c --- /dev/null +++ b/feature/marketlist/src/main/java/ir/composenews/marketlist/component/MarketListOrderSection.kt @@ -0,0 +1,97 @@ +package ir.composenews.marketlist.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Surface +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import ir.composenews.designsystem.component.DefaultRadioButton +import ir.composenews.designsystem.theme.ComposeNewsTheme +import ir.composenews.domain.util.MarketListOrder +import ir.composenews.domain.util.OrderType + + +@Composable +fun MarketListOrderSection( + modifier: Modifier = Modifier, + marketListOrder: MarketListOrder = MarketListOrder.Price(OrderType.Descending), + onOrderChange: (MarketListOrder) -> Unit, +) { + Column(modifier = modifier) { + Row(modifier = Modifier.fillMaxWidth()) { + + + DefaultRadioButton(text = "Name", selected = marketListOrder is MarketListOrder.Name) { + onOrderChange( + MarketListOrder.Name(marketListOrder.orderType) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + + Row(modifier = Modifier.fillMaxWidth()) { + DefaultRadioButton( + text = "Symbol", + selected = marketListOrder is MarketListOrder.Symbol + ) { + onOrderChange( + MarketListOrder.Symbol(marketListOrder.orderType) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + + Row(modifier = Modifier.fillMaxWidth()) { + DefaultRadioButton( + text = "Price", + selected = marketListOrder is MarketListOrder.Price + ) { + onOrderChange( + MarketListOrder.Price(marketListOrder.orderType) + ) + } + } + } + } + Spacer(modifier = Modifier.width(16.dp)) + Row(modifier = Modifier.fillMaxWidth()) { + + + DefaultRadioButton( + text = "Ascending", + selected = marketListOrder.orderType is OrderType.Ascending + ) { + onOrderChange( + marketListOrder.copy(OrderType.Ascending) + ) + } + Spacer(modifier = Modifier.width(8.dp)) + + Row(modifier = Modifier.fillMaxWidth()) { + DefaultRadioButton( + text = "Descending", + selected = marketListOrder.orderType is OrderType.Descending + ) { + onOrderChange( + marketListOrder.copy(OrderType.Descending) + ) + } + + } + } + } +} + +@Preview +@Composable +private fun MarketListOrderSectionPrev() { + ComposeNewsTheme { + Surface { + MarketListOrderSection(onOrderChange = {}) + } + } +} + diff --git a/library/designsystem/src/main/java/ir/composenews/designsystem/component/DefaultRadioButton.kt b/library/designsystem/src/main/java/ir/composenews/designsystem/component/DefaultRadioButton.kt new file mode 100644 index 00000000..8571be1d --- /dev/null +++ b/library/designsystem/src/main/java/ir/composenews/designsystem/component/DefaultRadioButton.kt @@ -0,0 +1,32 @@ +package ir.composenews.designsystem.component + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.width +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.RadioButton +import androidx.compose.material3.RadioButtonDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +fun DefaultRadioButton( + text: String, + selected: Boolean, + modifier: Modifier = Modifier, + onSelect: () -> Unit, +) { + Row(modifier = modifier, verticalAlignment = Alignment.CenterVertically) { + RadioButton( + selected = selected, onClick = onSelect, colors = RadioButtonDefaults.colors( + selectedColor = MaterialTheme.colorScheme.primary, + unselectedColor = MaterialTheme.colorScheme.onBackground + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text(text = text, style = MaterialTheme.typography.bodyMedium) + } +} \ No newline at end of file From fec24d4c3ad78cdaad37ca4664efe5354793e469 Mon Sep 17 00:00:00 2001 From: Amirhossein <119420193+AmirMousavi-dev@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:42:11 +0330 Subject: [PATCH 3/4] add sorting to viewModel,MarketListUseCase and MarketListContract --- .../domain/use_case/GetMarketListUseCase.kt | 23 +++++++++++++-- .../marketlist/MarketListContract.kt | 5 ++++ .../marketlist/MarketListViewModel.kt | 28 +++++++++++++++---- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/domain/market/src/main/java/ir/composenews/domain/use_case/GetMarketListUseCase.kt b/domain/market/src/main/java/ir/composenews/domain/use_case/GetMarketListUseCase.kt index 732cd322..707aeba9 100644 --- a/domain/market/src/main/java/ir/composenews/domain/use_case/GetMarketListUseCase.kt +++ b/domain/market/src/main/java/ir/composenews/domain/use_case/GetMarketListUseCase.kt @@ -4,13 +4,32 @@ package ir.composenews.domain.use_case import ir.composenews.domain.model.Market import ir.composenews.domain.repository.MarketRepository +import ir.composenews.domain.util.MarketListOrder +import ir.composenews.domain.util.OrderType import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map import javax.inject.Inject class GetMarketListUseCase @Inject constructor( private val repository: MarketRepository, ) { - operator fun invoke(): Flow> { - return repository.getMarketList() + operator fun invoke( + marketListOrder: MarketListOrder = MarketListOrder.Price(OrderType.Ascending), + ): Flow> { + return repository.getMarketList().map { marketList -> + when (marketListOrder.orderType) { + is OrderType.Ascending -> when (marketListOrder) { + is MarketListOrder.Name -> marketList.sortedBy { it.name } + is MarketListOrder.Price -> marketList.sortedBy { it.priceChangePercentage24h } + is MarketListOrder.Symbol -> marketList.sortedBy { it.symbol } + } + + is OrderType.Descending -> when (marketListOrder) { + is MarketListOrder.Name -> marketList.sortedByDescending { it.name } + is MarketListOrder.Price -> marketList.sortedByDescending { it.priceChangePercentage24h } + is MarketListOrder.Symbol -> marketList.sortedByDescending { it.symbol } + } + } + } } } diff --git a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListContract.kt b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListContract.kt index b233f86b..af9b554a 100644 --- a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListContract.kt +++ b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListContract.kt @@ -1,6 +1,8 @@ package ir.composenews.marketlist import ir.composenews.base.UnidirectionalViewModel +import ir.composenews.domain.util.MarketListOrder +import ir.composenews.domain.util.OrderType import ir.composenews.uimarket.model.MarketModel import kotlinx.collections.immutable.PersistentList import kotlinx.collections.immutable.persistentListOf @@ -13,6 +15,8 @@ interface MarketListContract : val refreshing: Boolean = false, val showFavoriteList: Boolean = false, val showFavoriteEmptyState: Boolean = false, + val showMarketListOrderSection: Boolean = false, + val marketListOrder: MarketListOrder = MarketListOrder.Price(OrderType.Descending), ) sealed class Event { @@ -20,5 +24,6 @@ interface MarketListContract : data class OnFavoriteClick(val market: MarketModel) : Event() data object OnGetMarketList : Event() data object OnRefresh : Event() + data class onOrder(val marketListOrder: MarketListOrder) : Event() } } diff --git a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListViewModel.kt b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListViewModel.kt index 4761e437..0f967f87 100644 --- a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListViewModel.kt +++ b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListViewModel.kt @@ -11,6 +11,8 @@ import ir.composenews.domain.use_case.GetFavoriteMarketListUseCase import ir.composenews.domain.use_case.GetMarketListUseCase import ir.composenews.domain.use_case.SyncMarketListUseCase import ir.composenews.domain.use_case.ToggleFavoriteMarketListUseCase +import ir.composenews.domain.util.MarketListOrder +import ir.composenews.domain.util.OrderType import ir.composenews.uimarket.mapper.toMarket import ir.composenews.uimarket.mapper.toMarketModel import ir.composenews.uimarket.model.MarketModel @@ -39,11 +41,19 @@ class MarketListViewModel @Inject constructor( override fun event(event: MarketListContract.Event) = when (event) { MarketListContract.Event.OnGetMarketList -> getData() - MarketListContract.Event.OnRefresh -> getData(isRefreshing = true) + MarketListContract.Event.OnRefresh -> getData( + isRefreshing = true, + marketListOrder = mutableState.value.marketListOrder + ) + is MarketListContract.Event.OnFavoriteClick -> onFavoriteClick(news = event.market) is MarketListContract.Event.OnSetShowFavoriteList -> onSetShowFavoriteList( showFavoriteList = event.showFavoriteList, ) + + is MarketListContract.Event.onOrder -> { + getData(marketListOrder = event.marketListOrder) + } } private fun onSetShowFavoriteList(showFavoriteList: Boolean) { @@ -52,7 +62,12 @@ class MarketListViewModel @Inject constructor( } } - private fun getData(isRefreshing: Boolean = false) { + private fun getData( + isRefreshing: Boolean = false, + marketListOrder: MarketListOrder = MarketListOrder.Price( + OrderType.Descending + ), + ) { if (isRefreshing) { mutableState.update { it.copy(refreshing = true) @@ -62,12 +77,12 @@ class MarketListViewModel @Inject constructor( if (mutableState.value.showFavoriteList) { getFavoriteMarketList() } else { - getMarketList() + getMarketList(marketListOrder) } } } - private suspend fun getMarketList() { + private suspend fun getMarketList(marketListOrder: MarketListOrder) { mutableBaseState.update { BaseContract.BaseState.OnLoading } try { syncMarketListUseCase() @@ -79,13 +94,14 @@ class MarketListViewModel @Inject constructor( ) } } - getMarketListUseCase() + getMarketListUseCase(marketListOrder) .onEach { result -> mutableState.update { prevState -> prevState.copy( marketList = result.map { it.toMarketModel() }.toPersistentList(), refreshing = false, showFavoriteEmptyState = result.isEmpty(), + marketListOrder = marketListOrder ) } mutableBaseState.update { BaseContract.BaseState.OnSuccess } @@ -120,4 +136,4 @@ class MarketListViewModel @Inject constructor( } } } -} +} \ No newline at end of file From 077e05a355f67bd8af99d9186d508241b5dde36f Mon Sep 17 00:00:00 2001 From: Amirhossein <119420193+AmirMousavi-dev@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:43:24 +0330 Subject: [PATCH 4/4] Add Sorting to MarketListScreen --- .../marketlist/MarketListScreen.kt | 102 +++++++++++------- 1 file changed, 61 insertions(+), 41 deletions(-) diff --git a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt index 6a5c86a2..2aab1181 100644 --- a/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt +++ b/feature/marketlist/src/main/java/ir/composenews/marketlist/MarketListScreen.kt @@ -12,6 +12,7 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -30,7 +31,9 @@ import ir.composenews.designsystem.component.pull_refresh_indicator.pullRefresh import ir.composenews.designsystem.component.pull_refresh_indicator.rememberPullRefreshState import ir.composenews.designsystem.preview.ThemePreviews import ir.composenews.designsystem.theme.ComposeNewsTheme +import ir.composenews.domain.util.MarketListOrder import ir.composenews.marketlist.component.MarketListItem +import ir.composenews.marketlist.component.MarketListOrderSection import ir.composenews.marketlist.preview_provider.MarketListStateProvider import ir.composenews.uimarket.model.MarketModel import ir.composenews.utils.ContentType @@ -64,24 +67,28 @@ fun MarketListRoute( if (contentType == ContentType.DUAL_PANE && !state.refreshing && state.marketList.isNotEmpty() && marketModel == null) { onNavigateToDetailScreen(state.marketList[0]) } - - BaseRoute( - baseViewModel = viewModel, - shimmerView = { - ShimmerMarketListItem() - }, - ) { - MarketListScreen( - marketListState = state, - onNavigateToDetailScreen = onNavigateToDetailScreen, - showFavoriteList = showFavoriteList, - onFavoriteClick = { market -> - event.invoke(MarketListContract.Event.OnFavoriteClick(market = market)) + Column { + BaseRoute( + baseViewModel = viewModel, + shimmerView = { + ShimmerMarketListItem() }, - onRefresh = { - event.invoke(MarketListContract.Event.OnRefresh) - }, - ) + ) { + MarketListScreen( + marketListState = state, + onNavigateToDetailScreen = onNavigateToDetailScreen, + showFavoriteList = showFavoriteList, + onFavoriteClick = { market -> + event.invoke(MarketListContract.Event.OnFavoriteClick(market = market)) + }, + onRefresh = { + event.invoke(MarketListContract.Event.OnRefresh) + }, + onOrder = { marketListOrder -> + event.invoke(MarketListContract.Event.onOrder(marketListOrder)) + } + ) + } } } @@ -93,10 +100,15 @@ private fun MarketListScreen( onNavigateToDetailScreen: (market: MarketModel) -> Unit, onFavoriteClick: (market: MarketModel) -> Unit, onRefresh: () -> Unit, + onOrder: (MarketListOrder) -> Unit, ) { val refreshState = rememberPullRefreshState(refreshing = marketListState.refreshing, onRefresh = onRefresh) + val marketLazyState = rememberLazyListState() + LaunchedEffect(key1 = marketListState.marketList) { + marketLazyState.scrollToItem(0) + } Box( modifier = Modifier .fillMaxWidth() @@ -114,29 +126,35 @@ private fun MarketListScreen( ), ) } else { - LazyColumn(modifier = Modifier.fillMaxWidth()) { - items( - items = marketListState.marketList, - key = { it.name }, - ) { market -> - Column( - modifier = Modifier - .fillMaxWidth() - .animateItemPlacement( - animationSpec = tween(durationMillis = 250), - ), - ) { - MarketListItem( - modifier = Modifier, - market = market, - showFavoriteList = showFavoriteList, - onItemClick = { - onNavigateToDetailScreen(market) - }, - onFavoriteClick = { - onFavoriteClick(market) - }, - ) + Column { + + MarketListOrderSection(marketListOrder = marketListState.marketListOrder) { marketListOrder -> + onOrder(marketListOrder) + } + LazyColumn(modifier = Modifier.fillMaxWidth(), state = marketLazyState) { + items( + items = marketListState.marketList, + key = { it.name }, + ) { market -> + Column( + modifier = Modifier + .fillMaxWidth() + .animateItemPlacement( + animationSpec = tween(durationMillis = 250), + ), + ) { + MarketListItem( + modifier = Modifier, + market = market, + showFavoriteList = showFavoriteList, + onItemClick = { + onNavigateToDetailScreen(market) + }, + onFavoriteClick = { + onFavoriteClick(market) + }, + ) + } } } } @@ -148,6 +166,7 @@ private fun MarketListScreen( Modifier.align(Alignment.TopCenter), ) } + } @ThemePreviews @@ -164,7 +183,8 @@ private fun MarketListScreenPrev( onNavigateToDetailScreen = {}, onFavoriteClick = {}, onRefresh = {}, + {} ) } } -} +} \ No newline at end of file