diff --git a/app/src/main/kotlin/br/com/stonks/ui/BottomAppBarLayout.kt b/app/src/main/kotlin/br/com/stonks/ui/BottomAppBarLayout.kt index ae9c3e6..dfe5258 100644 --- a/app/src/main/kotlin/br/com/stonks/ui/BottomAppBarLayout.kt +++ b/app/src/main/kotlin/br/com/stonks/ui/BottomAppBarLayout.kt @@ -70,7 +70,7 @@ internal fun BottomAppBarLayout( navController.navigate(MainNavDestination.HOME.route) } NavigationItem( - icon = R.drawable.ic_alert, + icon = R.drawable.ic_radar, label = br.com.stonks.R.string.main_nav_action_stock_alert, selected = false, ) { diff --git a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/states/StockUiEvent.kt b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/states/StockUiEvent.kt index 95d5e47..0d8eb55 100644 --- a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/states/StockUiEvent.kt +++ b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/states/StockUiEvent.kt @@ -1,8 +1,15 @@ package br.com.stonks.feature.stocks.ui.states import br.com.stonks.common.states.UiEvent +import br.com.stonks.feature.stocks.ui.model.AlertUiModel internal sealed class StockUiEvent : UiEvent { - data object RegisterAlert : StockUiEvent() + data class RegisterAlert( + val data: AlertUiModel, + ) : StockUiEvent() + + data class RemoveAlert( + val id: Long, + ) : StockUiEvent() } diff --git a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/AlertLayout.kt b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/AlertLayout.kt new file mode 100644 index 0000000..61663fb --- /dev/null +++ b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/AlertLayout.kt @@ -0,0 +1,272 @@ +package br.com.stonks.feature.stocks.ui.view + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.OutlinedCard +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import br.com.stonks.common.formatters.formatCurrency +import br.com.stonks.designsystem.components.TagLayout +import br.com.stonks.designsystem.tokens.ColorToken +import br.com.stonks.designsystem.tokens.SpacingToken +import br.com.stonks.feature.stocks.R +import br.com.stonks.feature.stocks.domain.types.StockAlertType +import br.com.stonks.feature.stocks.domain.types.StockStatusType +import br.com.stonks.feature.stocks.ui.model.AlertUiModel +import br.com.stonks.feature.stocks.utils.getDescription +import kotlinx.coroutines.launch + +@Composable +private fun AlertForms( + uiModel: AlertUiModel, + modifier: Modifier = Modifier, +) { + Column( + modifier = modifier.fillMaxWidth() + .padding(SpacingToken.xl) + ) { + OutlinedTextField( + value = uiModel.ticket, + onValueChange = { }, + enabled = false, + label = { Text("Ticket") }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text) + ) + + Spacer(modifier = Modifier.height(SpacingToken.lg)) + + OutlinedTextField( + value = uiModel.alertValue.formatCurrency(), + onValueChange = { }, + enabled = false, + label = { Text("Alertar em") }, + modifier = Modifier.fillMaxWidth(), + keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal) + ) + } +} + +@Composable +@OptIn(ExperimentalMaterial3Api::class) +private fun AlertActions( + uiModel: AlertUiModel, + onEditItem: (data: AlertUiModel) -> Unit, + onDeleteItem: (id: Long) -> Unit, +) { + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + var showBottomSheet by remember { mutableStateOf(false) } + + IconButton( + onClick = { onDeleteItem(uiModel.id) }, + ) { + Icon( + painter = painterResource( + id = br.com.stonks.designsystem.R.drawable.ic_trash + ), + contentDescription = stringResource(id = R.string.alert_action_delete), + ) + } + IconButton( + onClick = { showBottomSheet = true }, + ) { + Icon( + painter = painterResource( + id = br.com.stonks.designsystem.R.drawable.ic_edit + ), + contentDescription = stringResource(id = R.string.alert_action_edit), + ) + } + + if (showBottomSheet) { + ModalBottomSheet( + modifier = Modifier, + sheetState = sheetState, + onDismissRequest = { showBottomSheet = false }, + ) { + AlertForms( + uiModel = uiModel, + ) + Row( + modifier = Modifier + .fillMaxWidth() + .padding(SpacingToken.xl), + horizontalArrangement = Arrangement.End, + ) { + OutlinedButton(onClick = { + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) { + onEditItem(uiModel) + showBottomSheet = false + } + } + }) { + Text(stringResource(id = R.string.alert_action_close)) + } + } + } + } +} + +@Composable +private fun AlertLayoutTitle( + uiModel: AlertUiModel, + modifier: Modifier = Modifier, + onEditItem: (data: AlertUiModel) -> Unit, + onDeleteItem: (id: Long) -> Unit, +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + verticalAlignment = Alignment.CenterVertically, + ) { + Row( + modifier = Modifier.weight(1f), + ) { + Text( + text = uiModel.ticket, + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.ExtraBold, + ), + maxLines = 1, + overflow = TextOverflow.Ellipsis, + ) + TagLayout( + modifier = Modifier.padding(start = SpacingToken.md), + containerColor = uiModel.tagColor, + contentColor = ColorToken.NeutralWhite, + label = stringResource(id = uiModel.status.getDescription()), + ) + } + AlertActions( + uiModel = uiModel, + onEditItem = { onEditItem(uiModel) }, + onDeleteItem = { onDeleteItem(uiModel.id) }, + ) + } +} + +@Composable +private fun AlertLayoutContent( + alertValue: Double, + modifier: Modifier = Modifier, +) { + Row( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight() + ) { + Image( + painter = painterResource(id = br.com.stonks.designsystem.R.drawable.ic_broken_image), + contentDescription = null, + modifier = Modifier.size(40.dp), + contentScale = ContentScale.FillWidth + ) + Column( + modifier = Modifier.padding(start = SpacingToken.lg), + ) { + Text( + text = stringResource(id = R.string.alert_price), + style = MaterialTheme.typography.titleSmall, + color = ColorToken.Grayscale300, + ) + Spacer(modifier = Modifier.height(SpacingToken.xs)) + Text( + text = alertValue.formatCurrency(), + style = MaterialTheme.typography.titleSmall.copy( + fontWeight = FontWeight.ExtraBold, + ), + ) + } + } +} + +@Composable +internal fun AlertCard( + uiModel: AlertUiModel, + modifier: Modifier = Modifier, + onEditItem: (data: AlertUiModel) -> Unit, + onDeleteItem: (id: Long) -> Unit, +) { + OutlinedCard( + modifier = modifier + .fillMaxWidth() + .wrapContentHeight(), + ) { + Column( + modifier = Modifier + .background(ColorToken.NeutralWhite) + .padding(SpacingToken.xl) + ) { + AlertLayoutTitle( + uiModel = uiModel, + onEditItem = { onEditItem(uiModel) }, + onDeleteItem = { onDeleteItem(uiModel.id) }, + ) + HorizontalDivider( + modifier = Modifier.padding(vertical = SpacingToken.lg), + color = ColorToken.Grayscale100, + ) + AlertLayoutContent( + alertValue = uiModel.alertValue, + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun AlertLayoutPreview() { + Column( + modifier = Modifier.padding(SpacingToken.xl) + ) { + AlertCard( + uiModel = AlertUiModel( + id = 1L, + ticket = "GOGL34", + alertValue = 71.0, + status = StockStatusType.AVAILABLE, + alert = StockAlertType.HIGH_PRICE, + tagColor = ColorToken.HighlightGreen, + ), + onEditItem = { }, + onDeleteItem = { }, + ) + } +} diff --git a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/StockAlertScreen.kt b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/StockAlertScreen.kt index c60174a..84f3d50 100644 --- a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/StockAlertScreen.kt +++ b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/view/StockAlertScreen.kt @@ -6,10 +6,6 @@ import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview @@ -25,8 +21,10 @@ import br.com.stonks.feature.stocks.domain.types.StockAlertType import br.com.stonks.feature.stocks.domain.types.StockStatusType import br.com.stonks.feature.stocks.ui.model.AlertUiModel import br.com.stonks.feature.stocks.ui.model.StockAlertUiModel +import br.com.stonks.feature.stocks.ui.states.StockUiEvent import br.com.stonks.feature.stocks.ui.states.StockUiState import br.com.stonks.feature.stocks.ui.viewmodel.STOCK_VM_QUALIFIER +import br.com.stonks.feature.stocks.ui.viewmodel.StockViewModel import org.koin.androidx.compose.koinViewModel import org.koin.core.qualifier.named import timber.log.Timber @@ -35,8 +33,8 @@ import timber.log.Timber private fun StockAlertContent( uiModel: StockAlertUiModel, modifier: Modifier = Modifier, - onEditItem: () -> Unit, - onDeleteItem: () -> Unit, + onEditItem: (data: AlertUiModel) -> Unit, + onDeleteItem: (id: Long) -> Unit, ) { LazyColumn( modifier = modifier, @@ -54,8 +52,8 @@ private fun StockAlertContent( items(uiModel.stockAlerts) { alert -> AlertCard( uiModel = alert, - onEditItem = { onEditItem() }, - onDeleteItem = { onDeleteItem() }, + onEditItem = { onEditItem(alert) }, + onDeleteItem = { onDeleteItem(alert.id) }, ) } } @@ -70,7 +68,6 @@ fun StockAlertScreen( ), ) { val uiState = viewModel.uiState.collectAsStateWithLifecycle() - var selected by remember { mutableStateOf(false) } when (uiState.value) { is StockUiState.Loading -> { @@ -81,8 +78,16 @@ fun StockAlertScreen( StockAlertContent( uiModel = (uiState.value as StockUiState.Success).data, modifier = modifier, - onEditItem = { }, - onDeleteItem = { }, + onEditItem = { data -> + (viewModel as StockViewModel).dispatchUiEvent( + StockUiEvent.RegisterAlert(data) + ) + }, + onDeleteItem = { id -> + (viewModel as StockViewModel).dispatchUiEvent( + StockUiEvent.RemoveAlert(id) + ) + }, ) } diff --git a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/viewmodel/StockViewModel.kt b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/viewmodel/StockViewModel.kt index dcccda1..a1de821 100644 --- a/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/viewmodel/StockViewModel.kt +++ b/feature/stocks/src/main/kotlin/br/com/stonks/feature/stocks/ui/viewmodel/StockViewModel.kt @@ -4,6 +4,7 @@ import androidx.lifecycle.viewModelScope import br.com.stonks.common.states.ViewModelState import br.com.stonks.feature.stocks.domain.usecase.StockAlertUseCase import br.com.stonks.feature.stocks.ui.mapper.StockAlertUiMapper +import br.com.stonks.feature.stocks.ui.model.AlertUiModel import br.com.stonks.feature.stocks.ui.states.StockUiEvent import br.com.stonks.feature.stocks.ui.states.StockUiState import kotlinx.coroutines.flow.MutableStateFlow @@ -28,8 +29,8 @@ internal class StockViewModel( override fun dispatchUiEvent(uiEvent: StockUiEvent) { when (uiEvent) { - StockUiEvent.RegisterAlert -> TODO() - else -> TODO() + is StockUiEvent.RegisterAlert -> registerAlert(uiEvent.data) + is StockUiEvent.RemoveAlert -> removeAlert(uiEvent.id) } } @@ -45,4 +46,12 @@ internal class StockViewModel( } } } + + private fun registerAlert(alert: AlertUiModel) { + Timber.e("Register or edit stock alert: $alert") + } + + private fun removeAlert(id: Long) { + Timber.e("Remove stock alert ID: $id") + } } diff --git a/feature/stocks/src/main/res/values/strings.xml b/feature/stocks/src/main/res/values/strings.xml index 932344f..f106667 100644 --- a/feature/stocks/src/main/res/values/strings.xml +++ b/feature/stocks/src/main/res/values/strings.xml @@ -7,6 +7,7 @@ Valor do produto Excluir alerta Editar alerta + Fechar Salvar Cancelar