diff --git a/core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoMarketCapitalizationDialog.kt b/core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoDialog.kt similarity index 51% rename from core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoMarketCapitalizationDialog.kt rename to core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoDialog.kt index dcc3dfee1..2d671d283 100644 --- a/core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoMarketCapitalizationDialog.kt +++ b/core/presentation/src/main/java/dev/arkbuilders/rate/core/presentation/ui/InfoDialog.kt @@ -15,7 +15,6 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -25,15 +24,25 @@ import dev.arkbuilders.rate.core.presentation.R import dev.arkbuilders.rate.core.presentation.theme.ArkColor @Composable -fun InfoMarketCapitalizationDialog(onDismiss: () -> Unit) { +fun InfoDialog( + title: String, + desc: String, + onDismiss: () -> Unit, +) { Dialog(onDismissRequest = { onDismiss() }) { - InfoMarketCapitalizationDialogContent(onDismiss) + Content(title, desc, onDismiss) } } -@Preview +@Preview(showBackground = true) @Composable -private fun InfoMarketCapitalizationDialogContent(onDismiss: () -> Unit = {}) { +private fun Content( + title: String = "Currency Already Selected", + desc: String = + "Please choose a different currency to complete the pairing. " + + "Identical currencies cannot be paired together.", + onDismiss: () -> Unit = {}, +) { Column( modifier = Modifier @@ -59,7 +68,7 @@ private fun InfoMarketCapitalizationDialogContent(onDismiss: () -> Unit = {}) { onClick = { onDismiss() }, ) { Icon( - modifier = Modifier, + modifier = Modifier.size(12.dp), painter = painterResource(id = R.drawable.ic_close), contentDescription = "", tint = ArkColor.FGQuinary, @@ -68,73 +77,15 @@ private fun InfoMarketCapitalizationDialogContent(onDismiss: () -> Unit = {}) { } Text( modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp), - text = "Market Capitalization", + text = title, fontWeight = FontWeight.SemiBold, fontSize = 18.sp, color = ArkColor.TextPrimary, ) Text( modifier = Modifier.padding(top = 4.dp, start = 16.dp, end = 16.dp, bottom = 36.dp), - text = stringResource(id = R.string.info_dialog_market_capitalization_description), - fontSize = 18.sp, - color = ArkColor.TextTertiary, - ) - } -} - -@Composable -fun InfoValueOfCirculatingDialog(onDismiss: () -> Unit) { - Dialog(onDismissRequest = { onDismiss() }) { - InfoValueOfCirculatingDialogContent(onDismiss) - } -} - -@Preview -@Composable -private fun InfoValueOfCirculatingDialogContent(onDismiss: () -> Unit = {}) { - Column( - modifier = - Modifier - .fillMaxWidth() - .background(Color.White, RoundedCornerShape(12.dp)), - ) { - Box(modifier = Modifier.fillMaxWidth()) { - Icon( - modifier = - Modifier - .padding(top = 20.dp, start = 16.dp) - .align(Alignment.TopStart), - painter = painterResource(id = R.drawable.ic_info_bg), - contentDescription = "", - tint = Color.Unspecified, - ) - IconButton( - modifier = - Modifier - .size(44.dp) - .padding(top = 12.dp, end = 12.dp) - .align(Alignment.TopEnd), - onClick = { onDismiss() }, - ) { - Icon( - modifier = Modifier, - painter = painterResource(id = R.drawable.ic_close), - contentDescription = "", - tint = ArkColor.FGQuinary, - ) - } - } - Text( - modifier = Modifier.padding(top = 12.dp, start = 16.dp, end = 16.dp), - text = "Value of Circulating Currency", - fontWeight = FontWeight.SemiBold, - fontSize = 18.sp, - color = ArkColor.TextPrimary, - ) - Text( - modifier = Modifier.padding(top = 4.dp, start = 16.dp, end = 16.dp, bottom = 36.dp), - text = stringResource(id = R.string.info_dialog_value_of_circulating_description), - fontSize = 18.sp, + text = desc, + fontSize = 14.sp, color = ArkColor.TextTertiary, ) } diff --git a/core/presentation/src/main/res/values/strings.xml b/core/presentation/src/main/res/values/strings.xml index 36cb9383f..aaabf886f 100644 --- a/core/presentation/src/main/res/values/strings.xml +++ b/core/presentation/src/main/res/values/strings.xml @@ -2,7 +2,9 @@ ARK Rate ARK Rate [Debug] + Market Capitalization The total market value of a cryptocurrency\'s circulating supply.\n\nIt is analogous to the free-float capitalization in the stock market.\n\nCap = Current Price x Circulating Supply. + Value of Circulating Currency Currency in circulation refers to the amount of cash–in the form of paper notes or coins–within a country that is physically used to conduct transactions between consumers and businesses. Portfolios @@ -64,6 +66,9 @@ Latest rates refresh Latest alerts check + Currency already selected + Please choose a different currency. Identical currencies cannot be used together. + Create Group Please enter a name for this group. Group name diff --git a/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertScreen.kt b/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertScreen.kt index 719227c14..b042e699b 100644 --- a/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertScreen.kt +++ b/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertScreen.kt @@ -111,6 +111,22 @@ fun AddPairAlertScreen( ) AppSharedFlow.ShowAddedSnackbarPairAlert.flow.emit(visuals) } + + is AddPairAlertScreenEffect.NavigateSearchBase -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.AddPairAlertBase.name, + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) + + is AddPairAlertScreenEffect.NavigateSearchTarget -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.AddPairAlertTarget.name, + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) } } @@ -127,7 +143,17 @@ fun AddPairAlertScreen( }, ) { Box(modifier = Modifier.padding(it)) { - Content(state, navigator, viewModel) + Content( + state = state, + navigateSearchBase = viewModel::onNavigateSearchBase, + navigateSearchTarget = viewModel::onNavigateSearchTarget, + onGroupSelect = viewModel::onGroupSelect, + onPriceOrPercentChanged = viewModel::onPriceOrPercentChanged, + onOneTimeChanged = viewModel::onOneTimeChanged, + onPriceOrPercentInputChanged = viewModel::onPriceOrPercentInputChanged, + onIncreaseToggle = viewModel::onIncreaseToggle, + onSaveClick = viewModel::onSaveClick, + ) } } } @@ -135,8 +161,14 @@ fun AddPairAlertScreen( @Composable private fun Content( state: AddPairAlertScreenState, - navigator: DestinationsNavigator, - viewModel: AddPairAlertViewModel, + navigateSearchBase: () -> Unit, + navigateSearchTarget: () -> Unit, + onGroupSelect: (String) -> Unit, + onPriceOrPercentChanged: (Boolean) -> Unit, + onOneTimeChanged: (Boolean) -> Unit, + onPriceOrPercentInputChanged: (String) -> Unit, + onIncreaseToggle: () -> Unit, + onSaveClick: () -> Unit, ) { var showNewGroupDialog by remember { mutableStateOf(false) } var showGroupsPopup by remember { mutableStateOf(false) } @@ -144,7 +176,7 @@ private fun Content( if (showNewGroupDialog) { GroupCreateDialog(onDismiss = { showNewGroupDialog = false }) { - viewModel.onGroupSelect(it) + onGroupSelect(it) } } @@ -155,12 +187,18 @@ private fun Content( .weight(1f) .verticalScroll(rememberScrollState()), ) { - PriceOrPercent(state, viewModel::onPriceOrPercentChanged) - EditCondition(state, viewModel, navigator) + PriceOrPercent(state, onPriceOrPercentChanged) + EditCondition( + state = state, + navigateSearchBase = navigateSearchBase, + navigateSearchTarget = navigateSearchTarget, + onPriceOrPercentInputChanged = onPriceOrPercentInputChanged, + onIncreaseToggle = onIncreaseToggle, + ) OneTimeOrRecurrent( state.priceOrPercent.isLeft(), state.oneTimeNotRecurrent, - viewModel::onOneTimeChanged, + onOneTimeChanged, ) DropDownWithIcon( modifier = @@ -192,7 +230,7 @@ private fun Content( GroupSelectPopup( groups = state.availableGroups, widthPx = addGroupBtnWidth, - onGroupSelect = { viewModel.onGroupSelect(it) }, + onGroupSelect = onGroupSelect, onNewGroupClick = { showNewGroupDialog = true }, onDismiss = { showGroupsPopup = false }, ) @@ -207,7 +245,7 @@ private fun Content( Modifier .fillMaxWidth() .padding(16.dp), - onClick = { viewModel.onSaveClick() }, + onClick = onSaveClick, enabled = state.finishEnabled, ) { Text( @@ -344,8 +382,10 @@ private fun DropDownBtn( @Composable private fun EditCondition( state: AddPairAlertScreenState, - viewModel: AddPairAlertViewModel, - navigator: DestinationsNavigator, + navigateSearchBase: () -> Unit, + navigateSearchTarget: () -> Unit, + onPriceOrPercentInputChanged: (String) -> Unit, + onIncreaseToggle: () -> Unit, ) { val ctx = LocalContext.current Column( @@ -368,9 +408,7 @@ private fun EditCondition( modifier = Modifier.padding(start = 8.dp), title = state.targetCode, ) { - navigator.navigate( - SearchCurrencyScreenDestination(AppSharedFlowKey.AddPairAlertTarget.name), - ) + navigateSearchTarget() } Text( modifier = Modifier.padding(start = 8.dp), @@ -385,7 +423,7 @@ private fun EditCondition( if (state.oneTimeNotRecurrent && state.priceOrPercent.isLeft()) this else - clickable { viewModel.onIncreaseToggle() } + clickable { onIncreaseToggle() } }, verticalAlignment = Alignment.CenterVertically, ) { @@ -448,7 +486,7 @@ private fun EditCondition( ifLeft = { it }, ifRight = { it }, ), - onValueChange = { viewModel.onPriceOrPercentInputChanged(it) }, + onValueChange = { onPriceOrPercentInputChanged(it) }, ) if (state.priceOrPercent.isLeft()) { Text( @@ -488,9 +526,7 @@ private fun EditCondition( modifier = Modifier.padding(start = 16.dp), title = state.baseCode, ) { - navigator.navigate( - SearchCurrencyScreenDestination(AppSharedFlowKey.AddPairAlertBase.name), - ) + navigateSearchBase() } } } diff --git a/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertViewModel.kt b/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertViewModel.kt index 1801f9c4c..c3c7a4291 100644 --- a/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertViewModel.kt +++ b/feature/pairalert/src/main/java/dev/arkbuilders/rate/feature/pairalert/presentation/add/AddPairAlertViewModel.kt @@ -47,6 +47,14 @@ sealed class AddPairAlertScreenEffect { data object NavigateBack : AddPairAlertScreenEffect() class NotifyPairAdded(val pair: PairAlert) : AddPairAlertScreenEffect() + + data class NavigateSearchTarget( + val prohibitedCodes: List, + ) : AddPairAlertScreenEffect() + + data class NavigateSearchBase( + val prohibitedCodes: List, + ) : AddPairAlertScreenEffect() } class AddPairAlertViewModel( @@ -346,6 +354,18 @@ class AddPairAlertViewModel( reduce { state.copy(finishEnabled = enabled) } } + fun onNavigateSearchBase() = + intent { + val prohibitedCodes = listOf(state.targetCode) + postSideEffect(AddPairAlertScreenEffect.NavigateSearchBase(prohibitedCodes)) + } + + fun onNavigateSearchTarget() = + intent { + val prohibitedCodes = listOf(state.baseCode) + postSideEffect(AddPairAlertScreenEffect.NavigateSearchTarget(prohibitedCodes)) + } + companion object { private val INITIAL_ONE_TIME_SCALE = BigDecimal.valueOf(1.1) private val INITIAL_RECURRENT_SCALE = BigDecimal.valueOf(10) diff --git a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetScreen.kt b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetScreen.kt index 18c56c1ed..c473eedf9 100644 --- a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetScreen.kt +++ b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetScreen.kt @@ -100,6 +100,23 @@ fun AddAssetScreen(navigator: DestinationsNavigator) { ), ) } + + is AddAssetSideEffect.NavigateSearchAdd -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.AddAsset.toString(), + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) + + is AddAssetSideEffect.NavigateSearchSet -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.SetAssetCode.name, + pos = effect.index, + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) } } @@ -115,18 +132,10 @@ fun AddAssetScreen(navigator: DestinationsNavigator) { Content( state = state, onAssetValueChanged = viewModel::onAssetValueChange, - onNewCurrencyClick = { - navigator.navigate( - SearchCurrencyScreenDestination(AppSharedFlowKey.AddAsset.toString()), - ) - }, + onNewCurrencyClick = viewModel::onAddCode, onAssetRemove = viewModel::onAssetRemove, onGroupSelect = viewModel::onGroupSelect, - onCodeChange = { - navigator.navigate( - SearchCurrencyScreenDestination(AppSharedFlowKey.SetAssetCode.name, it), - ) - }, + onCodeChange = viewModel::onSetCode, onAddAsset = viewModel::onAddAsset, ) } diff --git a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetViewModel.kt b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetViewModel.kt index a333b286e..7cbb4bd67 100644 --- a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetViewModel.kt +++ b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/add/AddAssetViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import dev.arkbuilders.rate.core.domain.CurrUtils import dev.arkbuilders.rate.core.domain.model.AmountStr +import dev.arkbuilders.rate.core.domain.model.CurrencyCode import dev.arkbuilders.rate.core.domain.repo.AnalyticsManager import dev.arkbuilders.rate.core.domain.repo.CodeUseStatRepo import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo @@ -37,6 +38,11 @@ sealed class AddAssetSideEffect { AddAssetSideEffect() data object NavigateBack : AddAssetSideEffect() + + data class NavigateSearchAdd(val prohibitedCodes: List) : AddAssetSideEffect() + + data class NavigateSearchSet(val index: Int, val prohibitedCodes: List) : + AddAssetSideEffect() } class AddAssetViewModel( @@ -139,6 +145,19 @@ class AddAssetViewModel( postSideEffect(AddAssetSideEffect.NotifyAssetAdded(currencies)) postSideEffect(AddAssetSideEffect.NavigateBack) } + + fun onSetCode(index: Int) = + intent { + val prohibitedCodes = state.currencies.map { it.code }.toMutableList() + prohibitedCodes.removeAt(index) + postSideEffect(AddAssetSideEffect.NavigateSearchSet(index, prohibitedCodes)) + } + + fun onAddCode() = + intent { + val prohibitedCodes = state.currencies.map { it.code } + postSideEffect(AddAssetSideEffect.NavigateSearchAdd(prohibitedCodes)) + } } @PortfolioScope diff --git a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/edit/EditAssetScreen.kt b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/edit/EditAssetScreen.kt index 664a9d3e2..5901509f9 100644 --- a/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/edit/EditAssetScreen.kt +++ b/feature/portfolio/src/main/java/dev/arkbuilders/rate/feature/portfolio/presentation/edit/EditAssetScreen.kt @@ -48,8 +48,7 @@ import dev.arkbuilders.rate.core.presentation.theme.ArkColor import dev.arkbuilders.rate.core.presentation.ui.AppHorDiv import dev.arkbuilders.rate.core.presentation.ui.AppTopBarBack import dev.arkbuilders.rate.core.presentation.ui.ArkCursorLargeTextField -import dev.arkbuilders.rate.core.presentation.ui.InfoMarketCapitalizationDialog -import dev.arkbuilders.rate.core.presentation.ui.InfoValueOfCirculatingDialog +import dev.arkbuilders.rate.core.presentation.ui.InfoDialog import dev.arkbuilders.rate.core.presentation.ui.LoadingScreen import dev.arkbuilders.rate.feature.portfolio.di.PortfolioComponentHolder import dev.arkbuilders.rate.feature.search.presentation.destinations.SearchCurrencyScreenDestination @@ -114,11 +113,19 @@ private fun Content( } if (showMarketCapitalizationDialog) { - InfoMarketCapitalizationDialog { showMarketCapitalizationDialog = false } + InfoDialog( + title = stringResource(id = CoreRString.info_dialog_market_capitalization), + desc = stringResource(id = CoreRString.info_dialog_market_capitalization_description), + onDismiss = { showMarketCapitalizationDialog = false }, + ) } if (showValueOfCirculatingDialog) { - InfoValueOfCirculatingDialog { showValueOfCirculatingDialog = false } + InfoDialog( + title = stringResource(id = CoreRString.info_dialog_value_of_circulating), + desc = stringResource(id = CoreRString.info_dialog_value_of_circulating_description), + onDismiss = { showValueOfCirculatingDialog = false }, + ) } Column( diff --git a/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickScreen.kt b/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickScreen.kt index d00822d35..9ffcc8638 100644 --- a/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickScreen.kt +++ b/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickScreen.kt @@ -116,6 +116,23 @@ fun AddQuickScreen( ), ) } + + is AddQuickScreenEffect.NavigateSearchAdd -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.AddQuickCode.toString(), + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) + + is AddQuickScreenEffect.NavigateSearchSet -> + navigator.navigate( + SearchCurrencyScreenDestination( + appSharedFlowKeyString = AppSharedFlowKey.SetQuickCode.toString(), + pos = effect.index, + prohibitedCodes = effect.prohibitedCodes.toTypedArray(), + ), + ) } } Scaffold( @@ -135,21 +152,10 @@ fun AddQuickScreen( Content( state = state, onAmountChanged = viewModel::onAssetAmountChange, - onNewCurrencyClick = { - navigator.navigate( - SearchCurrencyScreenDestination(AppSharedFlowKey.AddQuickCode.toString()), - ) - }, + onNewCurrencyClick = viewModel::onAddCode, onCurrencyRemove = viewModel::onCurrencyRemove, onGroupSelect = viewModel::onGroupSelect, - onCodeChange = { index -> - navigator.navigate( - SearchCurrencyScreenDestination( - AppSharedFlowKey.SetQuickCode.toString(), - index, - ), - ) - }, + onCodeChange = viewModel::onSetCode, onAddAsset = viewModel::onAddQuickPair, ) } diff --git a/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickViewModel.kt b/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickViewModel.kt index 3c28d4f54..64dc63ae6 100644 --- a/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickViewModel.kt +++ b/feature/quick/src/main/java/dev/arkbuilders/rate/feature/quick/presentation/add/AddQuickViewModel.kt @@ -42,6 +42,12 @@ sealed class AddQuickScreenEffect { data class NotifyPairAdded(val pair: QuickPair) : AddQuickScreenEffect() data object NavigateBack : AddQuickScreenEffect() + + data class NavigateSearchSet(val index: Int, val prohibitedCodes: List) : + AddQuickScreenEffect() + + data class NavigateSearchAdd(val prohibitedCodes: List) : + AddQuickScreenEffect() } class AddQuickViewModel( @@ -199,6 +205,21 @@ class AddQuickViewModel( state.copy(finishEnabled = finishEnabled) } } + + fun onSetCode(index: Int) = + intent { + val prohibitedCodes = + state.currencies.map { it.code }.toMutableList().apply { + removeAt(index) + } + postSideEffect(AddQuickScreenEffect.NavigateSearchSet(index, prohibitedCodes)) + } + + fun onAddCode() = + intent { + val prohibitedCodes = state.currencies.map { it.code } + postSideEffect(AddQuickScreenEffect.NavigateSearchAdd(prohibitedCodes)) + } } class AddQuickViewModelFactory @AssistedInject constructor( diff --git a/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyInfoItem.kt b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyInfoItem.kt new file mode 100644 index 000000000..0d12a319e --- /dev/null +++ b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyInfoItem.kt @@ -0,0 +1,58 @@ +package dev.arkbuilders.rate.feature.search.presentation + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import dev.arkbuilders.rate.core.presentation.theme.ArkColor +import dev.arkbuilders.rate.core.presentation.ui.CurrIcon + +@Composable +fun SearchCurrencyInfoItem( + model: CurrencySearchModel, + onClick: (CurrencySearchModel) -> Unit, +) { + val contentAlpha = if (model.isProhibited) 0.4f else 1f + Column { + Row( + modifier = + Modifier + .alpha(contentAlpha) + .fillMaxWidth() + .clickable { onClick(model) } + .padding(horizontal = 24.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + CurrIcon(modifier = Modifier.size(40.dp), code = model.code) + Column( + modifier = Modifier.padding(start = 12.dp), + verticalArrangement = Arrangement.Center, + ) { + Text( + text = model.code, + fontWeight = FontWeight.Medium, + color = ArkColor.TextPrimary, + ) + if (model.name.isNotEmpty()) { + Text(text = model.name, color = ArkColor.TextTertiary) + } + } + } + HorizontalDivider( + modifier = Modifier.padding(horizontal = 16.dp), + thickness = 1.dp, + color = ArkColor.BorderSecondary, + ) + } +} diff --git a/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyScreen.kt b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyScreen.kt index e1e0864da..cdb411607 100644 --- a/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyScreen.kt +++ b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchCurrencyScreen.kt @@ -19,11 +19,11 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.navigation.DestinationsNavigator -import dev.arkbuilders.rate.core.domain.model.CurrencyName +import dev.arkbuilders.rate.core.domain.model.CurrencyCode import dev.arkbuilders.rate.core.presentation.CoreRString import dev.arkbuilders.rate.core.presentation.ui.AppHorDiv import dev.arkbuilders.rate.core.presentation.ui.AppTopBarBack -import dev.arkbuilders.rate.core.presentation.ui.CurrencyInfoItem +import dev.arkbuilders.rate.core.presentation.ui.InfoDialog import dev.arkbuilders.rate.core.presentation.ui.ListHeader import dev.arkbuilders.rate.core.presentation.ui.LoadingScreen import dev.arkbuilders.rate.core.presentation.ui.NoResult @@ -37,6 +37,7 @@ import org.orbitmvi.orbit.compose.collectSideEffect fun SearchCurrencyScreen( appSharedFlowKeyString: String, pos: Int? = null, + prohibitedCodes: Array? = null, navigator: DestinationsNavigator, ) { val ctx = LocalContext.current @@ -48,7 +49,7 @@ fun SearchCurrencyScreen( viewModel( factory = component.searchVMFactory() - .create(appSharedFlowKeyString, pos), + .create(appSharedFlowKeyString, pos, prohibitedCodes?.toList()), ) val state by viewModel.collectAsState() @@ -58,6 +59,14 @@ fun SearchCurrencyScreen( } } + if (state.showCodeProhibitedDialog) { + InfoDialog( + title = stringResource(CoreRString.search_currency_already_selected), + desc = stringResource(CoreRString.search_currency_already_selected_desc), + onDismiss = viewModel::onCodeProhibitedDialogDismiss, + ) + } + Scaffold( topBar = { AppTopBarBack( @@ -102,18 +111,18 @@ private fun Input( @Composable private fun Results( filter: String, - frequent: List, - all: List, - topResultsFiltered: List, - onClick: (CurrencyName) -> Unit, + frequent: List, + all: List, + topResultsFiltered: List, + onClick: (CurrencySearchModel) -> Unit, ) { when { filter.isNotEmpty() -> { if (topResultsFiltered.isNotEmpty()) { LazyColumn { item { ListHeader(stringResource(CoreRString.top_results)) } - items(topResultsFiltered) { name -> - CurrencyInfoItem(name) { onClick(it) } + items(topResultsFiltered) { model -> + SearchCurrencyInfoItem(model) { onClick(it) } } } } else { @@ -125,13 +134,13 @@ private fun Results( LazyColumn { if (frequent.isNotEmpty()) { item { ListHeader(stringResource(CoreRString.frequent_currencies)) } - items(frequent) { name -> - CurrencyInfoItem(name) { onClick(it) } + items(frequent) { model -> + SearchCurrencyInfoItem(model) { onClick(it) } } } item { ListHeader(stringResource(CoreRString.all_currencies)) } - items(all) { name -> - CurrencyInfoItem(name) { onClick(it) } + items(all) { model -> + SearchCurrencyInfoItem(model) { onClick(it) } } } } diff --git a/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchViewModel.kt b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchViewModel.kt index c6c5bd020..6c27eb79b 100644 --- a/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchViewModel.kt +++ b/feature/search/src/main/java/dev/arkbuilders/rate/feature/search/presentation/SearchViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModelProvider import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject +import dev.arkbuilders.rate.core.domain.model.CurrencyCode import dev.arkbuilders.rate.core.domain.model.CurrencyName import dev.arkbuilders.rate.core.domain.repo.AnalyticsManager import dev.arkbuilders.rate.core.domain.repo.CurrencyRepo @@ -21,13 +22,20 @@ import org.orbitmvi.orbit.syntax.simple.postSideEffect import org.orbitmvi.orbit.syntax.simple.reduce import org.orbitmvi.orbit.viewmodel.container +data class CurrencySearchModel( + val code: CurrencyCode, + val name: String, + val isProhibited: Boolean, +) + data class SearchScreenState( - val frequent: List = emptyList(), - val all: List = emptyList(), + val frequent: List = emptyList(), + val all: List = emptyList(), val filter: String = "", - val topResults: List = emptyList(), - val topResultsFiltered: List = emptyList(), + val topResults: List = emptyList(), + val topResultsFiltered: List = emptyList(), val initialized: Boolean = false, + val showCodeProhibitedDialog: Boolean = false, ) sealed class SearchScreenEffect { @@ -37,6 +45,7 @@ sealed class SearchScreenEffect { class SearchViewModel( private val appSharedFlowKeyString: String, private val pos: Int?, + private val prohibitedCodes: List?, private val currencyRepo: CurrencyRepo, private val calcFrequentCurrUseCase: CalcFrequentCurrUseCase, private val getTopResultUseCase: GetTopResultUseCase, @@ -50,11 +59,12 @@ class SearchViewModel( analyticsManager.trackScreen("SearchScreen") intent { - val all = currencyRepo.getCurrencyNameUnsafe() + val all = currencyRepo.getCurrencyNameUnsafe().mapToSearchModel() val frequent = calcFrequentCurrUseCase.invoke() .map { currencyRepo.nameByCodeUnsafe(it) } - val topResults = getTopResultUseCase() + .mapToSearchModel() + val topResults = getTopResultUseCase().mapToSearchModel() reduce { state.copy( @@ -78,12 +88,26 @@ class SearchViewModel( reduce { state.copy(filter = input, topResultsFiltered = filtered) } } - fun onClick(name: CurrencyName) = + fun onClick(model: CurrencySearchModel) = intent { - emitResult(name) + prohibitedCodes?.let { + if (model.code in prohibitedCodes) { + reduce { + state.copy(showCodeProhibitedDialog = true) + } + return@intent + } + } + + emitResult(CurrencyName(code = model.code, name = model.name)) postSideEffect(SearchScreenEffect.NavigateBack) } + fun onCodeProhibitedDialogDismiss() = + intent { + reduce { state.copy(showCodeProhibitedDialog = false) } + } + private suspend fun emitResult(name: CurrencyName) { val appFlowKey = AppSharedFlowKey.valueOf(appSharedFlowKeyString) when (appFlowKey) { @@ -108,11 +132,18 @@ class SearchViewModel( AppSharedFlow.AddQuickCode.flow.emit(name.code) } } + + private fun List.mapToSearchModel() = + map { name -> + val isProhibited = prohibitedCodes?.let { name.code in it } ?: false + CurrencySearchModel(code = name.code, name = name.name, isProhibited = isProhibited) + } } class SearchViewModelFactory @AssistedInject constructor( @Assisted private val appSharedFlowKeyString: String, @Assisted private val pos: Int?, + @Assisted private val prohibitedCodes: List?, private val currencyRepo: CurrencyRepo, private val calcFrequentCurrUseCase: CalcFrequentCurrUseCase, private val getTopResultUseCase: GetTopResultUseCase, @@ -122,6 +153,7 @@ class SearchViewModelFactory @AssistedInject constructor( return SearchViewModel( appSharedFlowKeyString, pos, + prohibitedCodes, currencyRepo, calcFrequentCurrUseCase, getTopResultUseCase, @@ -135,6 +167,7 @@ class SearchViewModelFactory @AssistedInject constructor( fun create( appSharedFlowKeyString: String, pos: Int?, + prohibitedCodes: List?, ): SearchViewModelFactory } }