diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 589b0be423..8df68562af 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,6 +1,7 @@ ## Pull Request (PR) Checklist Please check if your pull request fulfills the following requirements: - [ ] The PR is submitted to the `develop` branch. +- [ ] I understand the **[Ivy Developer Guidelines](../docs/Developer-Guidelines.md)**. - [ ] I've read the **[Contribution Guidelines](https://github.com/ILIYANGERMANOV/ivy-wallet/blob/main/CONTRIBUTING.md)**. - [ ] The code builds and is tested on an actual Android device. - [ ] I confirm that I've run the code locally and everything works as expected. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3e1b678c84..e19d0c79df 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,11 @@ # Contributing to Ivy Wallet +## [Ivy Developer Guidelines](docs/Developer-Guidelines.md) + +A short and helpful guide on Android Architecture, Functional Reactive Programming (FRP) and Ivy best practices - [Ivy Developer Guidelines](docs/Developer-Guidelines.md). + +> Tip: Read it -> make proposals -> make the project better! :rocket: + ## 1. Fork the repo **[How To Fork Guide by GitHub](https://docs.github.com/en/get-started/quickstart/fork-a-repo)** diff --git a/README.md b/README.md index 0488d60f0f..6a4ed4040b 100644 --- a/README.md +++ b/README.md @@ -36,20 +36,11 @@ tell us how we can create a better environment for developers & creators to work ### [Ivy Telegram News](https://t.me/ivywallet) -## What's next? :rocket: +## [Ivy Developer Guidelines](docs/Developer-Guidelines.md) -We need your help! Let us know what you think :) +A short and helpful guide on Android Architecture, Functional Reactive Programming (FRP) and Ivy best practices - [Ivy Developer Guidelines](docs/Developer-Guidelines.md). -Our plan is: - -1. Make Ivy Wallet free! :heavy_check_mark: -1. Create [Ivy Telegram Community](https://t.me/+ETavgioAvWg4NThk). :heavy_check_mark: -1. Create a crypto donations mechanism (BTC, ETH, ADA, SOL...) -1. Use the donations to setup a dev fund where contributors can earn money by helping the project. -1. Create a proposal and voting system using Cardano (ADA) smart contracts. -1. So far, so good! Let us know what do you think should be next? - -Correct us, if we're wrong! Share your opinion. Be the change. :star: +> Tip: Read it -> make proposals -> make the project better! :rocket: ## The Ivy Wallet App diff --git a/app/src/main/java/com/ivy/wallet/domain/action/exchange/ExchangeAct.kt b/app/src/main/java/com/ivy/wallet/domain/action/exchange/ExchangeAct.kt index 7906d11a7b..60869af81f 100644 --- a/app/src/main/java/com/ivy/wallet/domain/action/exchange/ExchangeAct.kt +++ b/app/src/main/java/com/ivy/wallet/domain/action/exchange/ExchangeAct.kt @@ -13,15 +13,13 @@ class ExchangeAct @Inject constructor( private val exchangeRateDao: ExchangeRateDao, ) : FPAction>() { override suspend fun Input.compose(): suspend () -> Option = suspend { - io { - exchange( - data = data, - amount = amount, - getExchangeRate = exchangeRateDao::findByBaseCurrencyAndCurrency then { - it?.toDomain() - } - ) - } + exchange( + data = data, + amount = amount, + getExchangeRate = exchangeRateDao::findByBaseCurrencyAndCurrency then { + it?.toDomain() + } + ) } data class Input( diff --git a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/PlannedPaymentsLogic.kt b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/PlannedPaymentsLogic.kt index dbd8be39bf..7620778aac 100644 --- a/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/PlannedPaymentsLogic.kt +++ b/app/src/main/java/com/ivy/wallet/domain/deprecated/logic/PlannedPaymentsLogic.kt @@ -149,6 +149,7 @@ class PlannedPaymentsLogic( suspend fun payOrGet( transaction: Transaction, syncTransaction: Boolean = true, + skipTransaction: Boolean = false, onUpdateUI: (paidTransaction: Transaction) -> Unit ) { if (transaction.dueDate == null || transaction.dateTime != null) return @@ -166,7 +167,11 @@ class PlannedPaymentsLogic( } ioThread { - transactionDao.save(paidTransaction.toEntity()) + if (skipTransaction) + transactionDao.flagDeleted(paidTransaction.id) + else + transactionDao.save(paidTransaction.toEntity()) + if (plannedPaymentRule != null && plannedPaymentRule.oneTime) { //delete paid oneTime planned payment rules @@ -177,7 +182,7 @@ class PlannedPaymentsLogic( onUpdateUI(paidTransaction) ioThread { - if (syncTransaction) { + if (syncTransaction && !skipTransaction) { transactionUploader.sync(paidTransaction) } diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeTab.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeTab.kt index c2518c2968..84f02f3b6b 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeTab.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeTab.kt @@ -98,6 +98,7 @@ fun BoxWithConstraintsScope.HomeTab(screen: Main) { onDismissCustomerJourneyCard = viewModel::dismissCustomerJourneyCard, onSelectNextMonth = viewModel::nextMonth, onSelectPreviousMonth = viewModel::previousMonth, + onSkipTransaction = viewModel::skipTransaction ) } @@ -147,6 +148,7 @@ private fun BoxWithConstraintsScope.UI( onDismissCustomerJourneyCard: (CustomerJourneyCardData) -> Unit = {}, onSelectNextMonth: () -> Unit = {}, onSelectPreviousMonth: () -> Unit = {}, + onSkipTransaction: (Transaction) -> Unit = {}, ) { val ivyContext = ivyWalletCtx() @@ -255,7 +257,8 @@ private fun BoxWithConstraintsScope.UI( customerJourneyCards = customerJourneyCards, onPayOrGet = onPayOrGet, - onDismiss = onDismissCustomerJourneyCard + onDismiss = onDismissCustomerJourneyCard, + onSkipTransaction = onSkipTransaction ) } @@ -352,7 +355,8 @@ fun HomeLazyColumn( history: List, onPayOrGet: (Transaction) -> Unit, - onDismiss: (CustomerJourneyCardData) -> Unit + onDismiss: (CustomerJourneyCardData) -> Unit, + onSkipTransaction: (Transaction) -> Unit = {}, ) { val ivyContext = ivyWalletCtx() val nav = navigation() @@ -448,7 +452,8 @@ fun HomeLazyColumn( emptyStateText = stringRes( R.string.no_transactions_description, period.toDisplayLong(ivyContext.startDayOfMonth) - ) + ), + onSkipTransaction = onSkipTransaction ) } } diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt index c92762e17f..5dd8861c41 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeViewModel.kt @@ -1,5 +1,6 @@ package com.ivy.wallet.ui.home +import android.util.Log import androidx.lifecycle.viewModelScope import com.ivy.design.l0_system.Theme import com.ivy.design.navigation.Navigation @@ -302,11 +303,14 @@ class HomeViewModel @Inject constructor( load(period = period) } - fun payOrGet(transaction: Transaction) { + fun payOrGet(transaction: Transaction, skipTransaction: Boolean = false) { viewModelScope.launch { TestIdlingResource.increment() - plannedPaymentsLogic.payOrGet(transaction = transaction) { + plannedPaymentsLogic.payOrGet( + transaction = transaction, + skipTransaction = skipTransaction + ) { load() } @@ -314,6 +318,10 @@ class HomeViewModel @Inject constructor( } } + fun skipTransaction(transaction: Transaction) { + payOrGet(transaction, true) + } + fun dismissCustomerJourneyCard(card: CustomerJourneyCardData) { customerJourneyLogic.dismissCard(card) load() diff --git a/app/src/main/java/com/ivy/wallet/ui/planned/list/PlannedPaymentsLazyColumn.kt b/app/src/main/java/com/ivy/wallet/ui/planned/list/PlannedPaymentsLazyColumn.kt index ce62f9d763..5ba0a7bae5 100644 --- a/app/src/main/java/com/ivy/wallet/ui/planned/list/PlannedPaymentsLazyColumn.kt +++ b/app/src/main/java/com/ivy/wallet/ui/planned/list/PlannedPaymentsLazyColumn.kt @@ -1,11 +1,13 @@ package com.ivy.wallet.ui.planned.list import androidx.compose.foundation.layout.* +import androidx.compose.ui.res.stringResource import androidx.compose.foundation.lazy.* import androidx.compose.material.Text import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp @@ -111,7 +113,7 @@ private fun LazyListScope.plannedPaymentItems( SectionDivider( expanded = oneTimeExpanded, setExpanded = setOneTimeExpanded, - title = "One time payments", + title = stringResource(R.string.one_time_payments), titleColor = UI.colors.pureInverse, baseCurrency = currency, income = oneTimeIncome, @@ -143,7 +145,7 @@ private fun LazyListScope.plannedPaymentItems( SectionDivider( expanded = recurringExpanded, setExpanded = setRecurringExpanded, - title = "Recurring payments", + title = stringResource(R.string.recurring_payments), titleColor = UI.colors.pureInverse, baseCurrency = currency, income = recurringIncome, @@ -212,7 +214,7 @@ private fun LazyItemScope.NoPlannedPaymentsEmptyState() { Spacer(Modifier.height(24.dp)) Text( - text = "No planned payments", + text = stringResource(R.string.no_planned_payments), style = UI.typo.b1.style( color = Gray, fontWeight = FontWeight.ExtraBold @@ -222,7 +224,7 @@ private fun LazyItemScope.NoPlannedPaymentsEmptyState() { Spacer(Modifier.height(8.dp)) Text( - text = "You don't have any planed payments.\nPress the '⚡' bottom at the bottom to add one.", + text = stringResource(R.string.no_planned_payments_description), style = UI.typo.b2.style( color = Gray, fontWeight = FontWeight.Medium, diff --git a/app/src/main/java/com/ivy/wallet/ui/reports/ReportScreen.kt b/app/src/main/java/com/ivy/wallet/ui/reports/ReportScreen.kt index f85b27a6c4..ae29676550 100644 --- a/app/src/main/java/com/ivy/wallet/ui/reports/ReportScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/reports/ReportScreen.kt @@ -13,6 +13,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -22,11 +23,13 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.insets.systemBarsPadding import com.ivy.design.api.navigation import com.ivy.design.l0_system.UI +import androidx.compose.ui.res.stringResource import com.ivy.design.l0_system.style import com.ivy.wallet.R import com.ivy.wallet.domain.data.TransactionType import com.ivy.wallet.domain.data.core.Account import com.ivy.wallet.domain.data.core.Category +import com.ivy.wallet.stringRes import com.ivy.wallet.ui.IvyWalletPreview import com.ivy.wallet.ui.PieChartStatistic import com.ivy.wallet.ui.Report @@ -80,7 +83,7 @@ private fun BoxWithConstraintsScope.UI( contentAlignment = Alignment.Center ) { Text( - text = "Generating report...", + text = stringResource(R.string.generating_report), style = UI.typo.b1.style( fontWeight = FontWeight.ExtraBold, color = Orange @@ -114,7 +117,7 @@ private fun BoxWithConstraintsScope.UI( modifier = Modifier.padding( start = 32.dp ), - text = "Reports", + text = stringResource(R.string.reports), style = UI.typo.h2.style( fontWeight = FontWeight.ExtraBold ) @@ -207,9 +210,8 @@ private fun BoxWithConstraintsScope.UI( onPayOrGet = { onEventHandler.invoke(ReportScreenEvent.OnPayOrGet(transaction = it)) }, - emptyStateTitle = "No transactions", - - emptyStateText = "You don't have any transactions for your filter." + emptyStateTitle = stringRes(R.string.no_transactions), + emptyStateText = stringRes(R.string.no_transactions_for_your_filter) ) } else { item { @@ -263,7 +265,7 @@ private fun NoFilterEmptyState( Spacer(Modifier.height(8.dp)) Text( - text = "No Filter", + text = stringResource(R.string.no_filter), style = UI.typo.b1.style( color = Gray, fontWeight = FontWeight.ExtraBold @@ -274,7 +276,7 @@ private fun NoFilterEmptyState( Text( modifier = Modifier.padding(horizontal = 32.dp), - text = "To generate a report you must first set a valid filter.", + text = stringResource(R.string.invalid_filter_warning), style = UI.typo.b2.style( color = Gray, fontWeight = FontWeight.Medium, @@ -286,7 +288,7 @@ private fun NoFilterEmptyState( IvyButton( iconStart = R.drawable.ic_filter_xs, - text = "Set Filter" + text = stringResource(R.string.set_filter) ) { setFilterOverlayVisible(true) } @@ -311,7 +313,7 @@ private fun Toolbar( //Export CSV IvyOutlinedButton( - text = "Export", + text = stringResource(R.string.export), iconTint = Green, textColor = Green, solidBackground = true, diff --git a/app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt b/app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt index 2385784cf2..1dde2d3f6b 100644 --- a/app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt @@ -1,5 +1,6 @@ package com.ivy.wallet.ui.search +import androidx.compose.ui.res.stringResource import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background @@ -14,6 +15,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -25,6 +27,7 @@ import com.ivy.wallet.R import com.ivy.wallet.domain.data.TransactionHistoryItem import com.ivy.wallet.domain.data.core.Account import com.ivy.wallet.domain.data.core.Category +import com.ivy.wallet.stringRes import com.ivy.wallet.ui.IvyWalletPreview import com.ivy.wallet.ui.Search import com.ivy.wallet.ui.ivyWalletCtx @@ -123,8 +126,8 @@ private fun UI( history = transactions, onPayOrGet = { }, dateDividerMarginTop = 16.dp, - emptyStateTitle = "No transactions", - emptyStateText = "You don't have any transactions for \"${searchQueryTextFieldValue.text}\" query." + emptyStateTitle = stringRes(R.string.no_transactions), + emptyStateText = stringRes(R.string.no_transactions_for_query, searchQueryTextFieldValue.text) ) item { @@ -169,7 +172,7 @@ private fun SearchInput( .padding(vertical = 12.dp) .focusRequester(searchFocus), value = searchQueryTextFieldValue, - hint = "Search transactions", + hint = stringResource(R.string.search_transactions), onValueChanged = { onSetSearchQueryTextField(it) } diff --git a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt index cef77bbaf0..21db1574c2 100644 --- a/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/statistic/level1/PieChartStatisticScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -337,13 +338,23 @@ private fun CategoryAmountCard( ) { Spacer(Modifier.width(20.dp)) - ItemIconMDefaultIcon( + ItemIconM( modifier = Modifier.background(categoryColor, CircleShape), iconName = category?.icon, - defaultIcon = R.drawable.ic_custom_category_m, - tint = findContrastTextColor(categoryColor) + tint = findContrastTextColor(categoryColor), + iconContentScale = ContentScale.None, + Default = { + ItemIconMDefaultIcon( + modifier = Modifier.background(categoryColor, CircleShape), + iconName = category?.icon, + defaultIcon = R.drawable.ic_custom_category_m, + tint = findContrastTextColor(categoryColor) + ) + } ) + + Spacer(Modifier.width(16.dp)) Column( diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/components/ItemIcon.kt b/app/src/main/java/com/ivy/wallet/ui/theme/components/ItemIcon.kt index d1c6f0598f..bfc4dcf6fa 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/components/ItemIcon.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/components/ItemIcon.kt @@ -26,6 +26,7 @@ fun ItemIconL( modifier: Modifier = Modifier, iconName: String?, tint: Color = UI.colors.pureInverse, + iconContentScale: ContentScale? = null, Default: (@Composable () -> Unit)? = null ) { ItemIcon( @@ -34,6 +35,7 @@ fun ItemIconL( size = "l", iconName = iconName, tint = tint, + iconContentScale = iconContentScale, Default = Default ) } @@ -65,6 +67,7 @@ fun ItemIconM( modifier: Modifier = Modifier, iconName: String?, tint: Color = UI.colors.pureInverse, + iconContentScale: ContentScale? = null, Default: (@Composable () -> Unit)? = null ) { ItemIcon( @@ -73,6 +76,7 @@ fun ItemIconM( size = "m", iconName = iconName, tint = tint, + iconContentScale = iconContentScale, Default = Default ) } @@ -104,6 +108,7 @@ fun ItemIconS( modifier: Modifier = Modifier, iconName: String?, tint: Color = UI.colors.pureInverse, + iconContentScale: ContentScale? = null, Default: (@Composable () -> Unit)? = null ) { ItemIcon( @@ -112,6 +117,7 @@ fun ItemIconS( size = "s", iconName = iconName, tint = tint, + iconContentScale = iconContentScale, Default = Default ) } @@ -122,6 +128,7 @@ private fun ItemIcon( iconName: String?, size: String, tint: Color = UI.colors.pureInverse, + iconContentScale: ContentScale? = null, Default: (@Composable () -> Unit)? = null ) { val context = LocalContext.current @@ -157,7 +164,7 @@ private fun ItemIcon( painter = painterResource(id = iconInfo.iconId), colorFilter = ColorFilter.tint(tint), alignment = Alignment.Center, - contentScale = if (iconInfo.newFormat) + contentScale = iconContentScale ?: if (iconInfo.newFormat) ContentScale.Fit else ContentScale.None, contentDescription = iconName ?: "item icon" ) diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/modal/BudgetModal.kt b/app/src/main/java/com/ivy/wallet/ui/theme/modal/BudgetModal.kt index 53dfa94db7..b2096f4ab2 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/modal/BudgetModal.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/modal/BudgetModal.kt @@ -21,8 +21,10 @@ import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.compose.ui.res.stringResource import com.ivy.design.l0_system.UI import com.ivy.design.l0_system.style +import com.ivy.wallet.R import com.ivy.wallet.domain.data.core.Account import com.ivy.wallet.domain.data.core.Budget import com.ivy.wallet.domain.data.core.Category @@ -119,7 +121,7 @@ fun BoxWithConstraintsScope.BudgetModal( verticalAlignment = Alignment.CenterVertically ) { ModalTitle( - text = if (modal?.budget != null) "Edit budget" else "Create budget" + text = if (modal?.budget != null) stringResource(R.string.edit_budget) else stringResource(R.string.create_budget) ) if (initialBudget != null) { @@ -137,7 +139,7 @@ fun BoxWithConstraintsScope.BudgetModal( Spacer(Modifier.height(24.dp)) ModalNameInput( - hint = "Budget name", + hint = stringResource(R.string.budget_name), autoFocusKeyboard = modal?.autoFocusKeyboard ?: true, textFieldValue = nameTextFieldValue, setTextFieldValue = { @@ -158,7 +160,7 @@ fun BoxWithConstraintsScope.BudgetModal( Spacer(Modifier.height(24.dp)) ModalAmountSection( - label = "BUDGET AMOUNT", + label = stringResource(R.string.budget_amount_uppercase), currency = modal?.baseCurrency ?: "", amount = amount, amountPaddingTop = 24.dp, @@ -183,8 +185,8 @@ fun BoxWithConstraintsScope.BudgetModal( DeleteModal( visible = deleteModalVisible, - title = "Confirm deletion", - description = "Are you sure that you want to delete \"${nameTextFieldValue.text}\" budget?", + title = stringResource(R.string.confirm_deletion), + description = stringResource(R.string.confirm_budget_deletion_warning, nameTextFieldValue.text), dismiss = { deleteModalVisible = false } ) { if (initialBudget != null) { diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/transaction/TransactionCard.kt b/app/src/main/java/com/ivy/wallet/ui/theme/transaction/TransactionCard.kt index cb1af72260..803a52d76f 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/transaction/TransactionCard.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/transaction/TransactionCard.kt @@ -49,9 +49,12 @@ fun LazyItemScope.TransactionCard( transaction: Transaction, onPayOrGet: (Transaction) -> Unit, + onSkipTransaction: (Transaction) -> Unit = {}, onClick: (Transaction) -> Unit, ) { + val isLightTheme = UI.colors.pure == White + Spacer(Modifier.height(12.dp)) Column( @@ -164,20 +167,43 @@ fun LazyItemScope.TransactionCard( Spacer(Modifier.height(16.dp)) val isExpense = transaction.type == TransactionType.EXPENSE - IvyButton( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 24.dp), - text = if (isExpense) stringResource(R.string.pay) else stringResource(R.string.get), - wrapContentMode = false, - backgroundGradient = if (isExpense) gradientExpenses() else GradientGreen, - textStyle = UI.typo.b2.style( - color = if (isExpense) UI.colors.pure else White, - fontWeight = FontWeight.Bold - ) - ) { - onPayOrGet(transaction) + Row { + IvyButton( + modifier = Modifier + .weight(1f) + .padding(start = 24.dp), + text = stringResource(R.string.skip), + wrapContentMode = false, + backgroundGradient = if (isLightTheme) Gradient(White, White) else Gradient( + Black, + Black + ), + textStyle = UI.typo.b2.style( + color = if (isLightTheme) Black else White, + fontWeight = FontWeight.Bold + ) + ) { + onSkipTransaction(transaction) + } + + Spacer(Modifier.width(8.dp)) + + IvyButton( + modifier = Modifier + .weight(1f) + .padding(end = 24.dp), + text = if (isExpense) stringResource(R.string.pay) else stringResource(R.string.get), + wrapContentMode = false, + backgroundGradient = if (isExpense) gradientExpenses() else GradientGreen, + textStyle = UI.typo.b2.style( + color = if (isExpense) UI.colors.pure else White, + fontWeight = FontWeight.Bold + ) + ) { + onPayOrGet(transaction) + } } + } Spacer(Modifier.height(20.dp)) diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/transaction/Transactions.kt b/app/src/main/java/com/ivy/wallet/ui/theme/transaction/Transactions.kt index 75976ee907..5bc8aa7ae0 100644 --- a/app/src/main/java/com/ivy/wallet/ui/theme/transaction/Transactions.kt +++ b/app/src/main/java/com/ivy/wallet/ui/theme/transaction/Transactions.kt @@ -22,6 +22,7 @@ import com.ivy.wallet.domain.data.TransactionHistoryItem import com.ivy.wallet.domain.data.core.Account import com.ivy.wallet.domain.data.core.Category import com.ivy.wallet.domain.data.core.Transaction +import com.ivy.wallet.stringRes import com.ivy.wallet.ui.EditTransaction import com.ivy.wallet.ui.IvyWalletCtx import com.ivy.wallet.ui.theme.Gray @@ -50,16 +51,17 @@ fun LazyListScope.transactions( history: List, lastItemSpacer: Dp? = null, onPayOrGet: (Transaction) -> Unit, - emptyStateTitle: String = "No transactions", + emptyStateTitle: String = stringRes(R.string.no_transactions), emptyStateText: String, - dateDividerMarginTop: Dp? = null + dateDividerMarginTop: Dp? = null, + onSkipTransaction: (Transaction) -> Unit = {}, ) { if (upcoming.isNotEmpty()) { item { SectionDivider( expanded = upcomingExpanded, setExpanded = setUpcomingExpanded, - title = "Upcoming", + title = stringRes(R.string.upcoming), titleColor = Orange, baseCurrency = baseCurrency, income = upcomingIncome, @@ -74,7 +76,8 @@ fun LazyListScope.transactions( categories = categories, accounts = accounts, transaction = it, - onPayOrGet = onPayOrGet + onPayOrGet = onPayOrGet, + onSkipTransaction = onSkipTransaction ) { trn -> onTransactionClick( nav = nav, @@ -91,7 +94,7 @@ fun LazyListScope.transactions( SectionDivider( expanded = overdueExpanded, setExpanded = setOverdueExpanded, - title = "Overdue", + title = stringRes(R.string.overdue), titleColor = Red, baseCurrency = baseCurrency, income = overdueIncome, @@ -106,7 +109,8 @@ fun LazyListScope.transactions( categories = categories, accounts = accounts, transaction = it, - onPayOrGet = onPayOrGet + onPayOrGet = onPayOrGet, + onSkipTransaction = onSkipTransaction ) { trn -> onTransactionClick( nav = nav, diff --git a/app/src/main/java/com/ivy/wallet/utils/UtilExt.kt b/app/src/main/java/com/ivy/wallet/utils/UtilExt.kt index 84fbf2f012..be8395383a 100644 --- a/app/src/main/java/com/ivy/wallet/utils/UtilExt.kt +++ b/app/src/main/java/com/ivy/wallet/utils/UtilExt.kt @@ -62,20 +62,24 @@ fun MutableList?.orEmpty(): MutableList { return this ?: mutableListOf() } -fun String.nullifyEmpty() = if (this.isBlank()) null else this +fun String.nullifyEmpty() = this.ifBlank { null } fun getDefaultFIATCurrency(): Currency = Currency.getInstance(Locale.getDefault()) ?: Currency.getInstance("USD") ?: Currency.getInstance("usd") ?: Currency.getAvailableCurrencies().firstOrNull() ?: Currency.getInstance("EUR") -fun String.toUpperCaseLocal() = this.toUpperCase(Locale.getDefault()) +fun String.toUpperCaseLocal() = this.uppercase(Locale.getDefault()) -fun String.toLowerCaseLocal() = this.toLowerCase(Locale.getDefault()) +fun String.toLowerCaseLocal() = this.lowercase(Locale.getDefault()) -fun String.uppercaseLocal(): String = this.toUpperCase(Locale.getDefault()) +fun String.uppercaseLocal(): String = this.uppercase(Locale.getDefault()) -fun String.capitalizeLocal(): String = this.capitalize(Locale.getDefault()) +fun String.capitalizeLocal(): String = this.replaceFirstChar { + if (it.isLowerCase()) it.titlecase( + Locale.getDefault() + ) else it.toString() +} fun String.capitalizeWords(): String { return split(" ").joinToString(" ") { it.capitalizeLocal() } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..a4ef3f1458 --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,430 @@ + + + الحسابات + %1$s المجموع: %2$s + الدخل هذا الشهر + الإنفاق هذا الشهر + (مستبعد) + دخل + إنفاق + التطبيق مغلق + الغِ القفل لدخول التطبيق + إلغاء القفل + الرصيد الحالي + الرصيد بعد الدفعات المحددة + اتصل + مزامنة المعاملات + يتم مزامنة المعاملات… + مزامنة البنك مفعلة: + حذف العميل + أضف ميزانية + لا يوجد ميزانيات + لا يوجد لديك اي ميزانيات محددة. اضغط على "أضف ميزانية" لإضافة واحدة. + الميزانيات + للفئات %1$s %2$s + %1$s %2$s ميزانية التطبيق + معلومات الميزانية: %1$s / %2$s + %2$s معلومات الميزانية: %1$s + أضف فئة + الإنفاق + عدد الإنفاق + الدخل + عدد المدخولات + مخطط الرصيد + الرصيد %1$s + المخططات + المدة: + الفئات + csv تصدير ملف + تصدير ملف csv بالخيارات القياسية + الرجاء استخدام الخيارات القياسية و التأكد من وجود الرأس + كيفية الإستيراد + افتح + الخطوات + تعليمات + فيديو + اقرأ المقال + CSV رفع ملف + تصدير البيانات + CSV/ZIP رفع ملف + التصدير الى ملف + مجموعة الحروف: UTF-8\nفاصلة الارقام: Decimal point \'.\'\nمحدد الحروف: فاصلة \'\ط\\,\' + Excel تصدير ملف + CSV حول الملف الى صيغة + ملاحظة: اذا كان المف المصدر لا ينتهي ب ,xls. اضفها عنة طريق اعادة نسمية الملف يدويا. + محول مجاني لملفات CSV + تحقق في مجلدات "االترويج" و "الرسائل الغير مرغوبة" في بريدك الالكتروني + حمل الملف المرفق في بريدك الالكتروني المحتوي على النص: \"transactions_export…\" + إذا كان لديك أكثر من عملة يجب عليك ان تنزل كل ملف ة تقوم بإيراده في التطيق. + الاستيراد من + الرجاء الإنتظار + يتم استيراد ملف الcsv + تم بنجاح + فشل + تم الإستيراد + %1$d معاملات + %1$d حسابات + %1$d فئات + فشل + %1$d صفوف من الملف لم يتعرف عليهم + الإنهاء + إضافة وصف + الوصف + مخطط في + إضافة المال الى + الدفع ب + من + الحساب + الى + إضافة حساب + عنوان الدخل + عنوان الإنفاق + عنوان التحويل + إنفاق + أضف تاريخ المحدد للدفع + ادفع + اجلب + تأكيد الحذف + حذف هذه العملية سيؤدي الى حذفها من تاريخ العمليات و تحديث الرصيد وفقاً لذلك. + تأكيد تغيير الحساب + ملاحظة: انت تحاول ان تغير حساب مرتبط بقرض من حساب بعملة مختلفة, \n سجلات القرض سوف يتم اعادة حسابها وفقا لأسعار الصرف اليوم + تأكيد + الرجاء الانتظارو يتم إعادة حساب سجلات القرض + تم الانشاء في + أهلا + أهلا %1$s + تدفق المال: %1$s%2$s %3$s + ابحث المعاملات + مفتوح المصدر Ivy Wallet + هدف التوفير + الوصول السريع + الاعدادات + وضع النهار + الوضغ الليلي + الوضع التلقائي + المدفوعات\n المخططة + شارك التطبيق + التقارير + القروض + تحديد العملة + لا يوجد معاملات + لا يوجد لديك معاملات في %1$s \n"+" يمكنك اضافة معاملة بالضغط على + اضافة قرض + لا يوجد قروض + لا يوجد لديك اي قروض.\n اضغط على "اضافة قرض +" للإضافة + ملاحظة: حذف هذا القرض سيزيله نهائيا و يحذف جميع القروض التابعة له + الرجاء الانتظار, يتم اعادة حساب كل سجلات القروض + مدفوع + %1$s %2$s متبقي + فائدة القرض + %1$s %2$s مدفوعة + إضافة سجل + الفائدة + لا يوجد سجلات + لا يوجد لديك اي سجلات لهذا القرض.\n اضغط على "اضافة سجل +" للإضافة + إضافة دخل + إضافة إنفاق + غير محدد + %1$s\%% + تحويلات الحساب + %1$sلا يوجد لديك اي معاملات في.\n يمكنك إضافة معاملة بالسحب لاسفل و الضغط على زر "أضف دخل" أو "أضف إنفاق" في الاعلى. + ملاحظة: حذف هذا الحساب سيزيله نهائيا و يزيل كل المعاملات المتعلقه به. + ملاحظة: حذف هذه الفئة سيزيلها نهائيا + تعديل + المعاملات + الرئيسية + إضافة مدفوعات مخططة + إضافة دخل + إضافة إنفاق + تحويل لحساب + تخطي + إضافة جديد + من %1$s + إلى %1$s + المجال + الخصوصية و\n تجميع البيانات + اسحب للموافقة على الشروط و الأحكام + تمت الموافقة على الشروط و الأحكام + اسحب للموافقة على سياسة الخصوصية + تمت الموافقة على سياسة الخصوصية + الشروط و الأحكام + سياسة الخصوصية + تتبع دخلك, إنفاقك و ميزانيتك مع Ivy.\n\n تصميم أنيق, دفعات مخططة و متكررة, أدِر حسابات متعددة, نظم المعاملات في فئات, إحصائيات معبرة, صدّر المعاملات الى ملف csv و أكثر. + أدخل اسمك\n لتخصيص محفظتك + ما اسمك؟ + إدخال + إضافة حسابات + مقترح + التالي + إضافة فئات + مقترحات + تحديد + مدير أموالك الشخصي + مفتوح_المصدر# + حدث خطأ, حاول مجددًا:\n %1$s + يتم تسجيل الدخول… + تم بنجاح! + Google تسجيل الدخول بحساب + حساب غير متصل على الانترنت + Ivy Cloud مزامنة بياناتك على + سلامة و حماية البيانات غير مضمونة! + أو الدخول بحساب غير متصل على الانترنت + سوف يتم حفظ بياناتك محليا على جهازك و لن يتم مزامنتها مع السحابة. انت تخاطر بخسارة ملفاتك إذا ألغيت تثبيت التطبيق أو غيرت جهازك. يمكنك دائما تفعيل المزامنة لاحقا في حال تغيير رأيك. + الشروط و الأحكام + بتسجيل الدخول انت توافق على %1$s و %2$s. + CSV إستيراد ملف + Ivy من تطبيق اخر او من + استيراد ملف نسخ احتياطي من تطبيق اخر يمكن ان يستغرق حتى 5 دقائق. يمكنك دائمًا استيراد البيانات لاحقًا اذا اردت. + استيراد ملف نسخ احتياطي + ابدأ من الصفر + حذف هذه الدفعه المخططة سيزيل كل المدفوعات الغير مدفوغة او المدفوعات المتأخرة القادمة المتعلقة بها. + تحديد نوع العملية + مخطط البدء في + تكرر كل %1$d %2$s + محذوف + مخطط في + null + "تبدأ %1$s " + إضافة دفعة + المدفوعات لمرة واحدة + الدفعات متكررة + لا توجد مدفوعات مخططة + لا يوجد لديك دفعات مخططة.\n اضغط على \'⚡\' في الاسفل لإضافة واحدة. + الدفعات المخططة + اليوم + أمس + غدًا + محدد الدفع في %1$s + قادم + متأخر + الانفاق + الدخل + تعديل الحساب + حساب جديد + اسم الحساب + تضمين الحساب + ادخل رصيد الحساب + اختر العملة + الحاسبة + العملية الحسابية (+-/*=) + تعديل الفئة + انشاء فئى + اسم الفئة + اتر الفئة + (Markdown تدعم صيغة) ادخل اي تفاصيل هنا + إزالة التصفيات + تصفية + تطبيق التصفيات + النوع + الدخل + الفترة زمنية + حدد النطاق الزمني + حسابات (%1$d) + فئات (%1$d) + ازالة الكل + تحديد الكل + القيمة (اختياري) + الكلمات المفتاحية (اختياري) + تحتوي + اضف كلمة مفتاحية + استبعد + لا يوجد معاملات لتصفياتك + بدون تصفيات + لإنشاء تقرير يجب عليك تحديد تصفيات صالحة اولا. + تحديد التصفيات + تصدير + لا يوجد لديك عمليات تطابق "%1$s" + التصدير و الاستيراد + نسخ إحتياطي للبيانات + استيراد البيانات + إعدادات التطبيق + أقفل التطبيق + إظهار التنبيهات + إخفاء الرصيد + إضغط على الرصيد المخفي لإظهاره لمدة 5ث + أخرى + Google Play قيمنا على + Ivy Wallet شارك + المنتج + المنطقة الخطرة + احذف كل بيانات المستخدم + هل تريد حذف كل البيانات؟ + تحذير! هذا الفعل سوف يحذف كل البيانات ل%1$s نهائيا و لن يمكن اسنعادتها. + حسابك + تأكيد الحذف النهائي ل\'%1$s\' + كل بياناتك + تحذير اخير! بعد الضغط على "حذف" بياناتك سوف تحذف للأبد + يتم تصدير البيانات + الرجاء الانتظار, يتم تصدير البيانات + تاريخ بداية الشهر + على تيليجرام Ivy + مركز المساعدة + الخطة + طلب ميزة + اتصل بالدعم + المساهمون في التطبيق + الحساب + تسجيل الخروج + تسجيل الدخول + يتم المزامنة… + تمت مزامنة البيانات للسحابة + أضغط للمزامنة + فشلت عملية المزامنة. أضغط للمزامنة + مجهول + CSV التصدير لملف + المتبقي للإنفاق + تجاوزت عن الميزانية ب + تجاوزت حاجز الإنفاق ب + تحديد نوع المعاملة + تحويل + تم تحديده + (USD, EUR, GBP, BTC, الخ) ابحث + محدد مسبقًا + عملة رقمية + سعر الصرف + اختر اللون + إعادة ترتيب + الكلمة المفتاحية + تعديل الميزانية + إنشاء ميزانية + اسم الميزانية + قيمة الميزانية + هل انت متأكد من حذف الميزانية "%1$s"؟ + تعديل هدف التوفير + اختر أيقونة + اختر شهر + أو نطاق مخصص + أضف تاريخ + أو في آخر + أو كل الوقت + إلغاء تحديد كل الأوقات + تحديد كل الأوقات + أختر تاريخ بداية الشهر + تدعم العملات الرقمية + حذف + حفظ + أضافة + إنشاء + تعديل القرض + قرض جديد + اسم القرض + الحساب المرتبط + إنشاء معاملة أساسية + أدخل قيمة القرض + "ملاحظة: انت تحاول ان تغير حساب مرتبط بقرض من حساب بعملة مختلفة, \n سجلات القرض سوف يتم اعادة حسابها وفقا لأسعار الصرف اليوم" + نوع القرض + اقتراض مال + إقراض مال + تعديل السجل + سجل جديد + ملاحظة + وضع علامة: فائدة + أعد حساب القيمة وفقا لأسعار الصرف اليوم + أدخل قيمة السجل + هل انت متأكد من حذف السجل "%1$s"؟ + "ملاحظة: انت تحاول ان تغير حساب مرتبط بقرض من حساب بعملة مختلفة, \n سجلات القرض سوف يتم اعادة حسابها وفقا لأسعار الصرف اليوم" + تعديل الاسم + خطط ل + مرة واحدة + مرات متعددة + تبدأ في + تتكرر كل + إرسال + ماذا تحتاج؟ + (Markdown تدعم صيغة) اشرحها في جملة واحدة + آخر 12 شهر + آخر 6 أشهر + آخر 4 أسابيع + آخر 7 أيام + اليوم, %1$s + أمس, %1$s + غدًا, %1$s + منتهي الصلاحية + تم تأكيدالهوية. + فشل تأكيدالهوية. + هل قمت بأي معاملات اليوم؟ 🏁 + هل قمت بمتابعة إنفاقك اليوم؟ 💸 + هل قمت بتسجيل معاملاتك اليوم؟ 🏁 + نقدي + البنك + Revolut + أطعمة ومشروبات + الفواتير والرسوم + المواصلات + البقالة + الترفيه + التسوق + هدايا + الصحة + استثمار + السيارة + العمل + مطعم + العائلة + الحياة الاجتماعية + طلب الطعام + السفر + اللياقة البدنية + تطوير الذات + ملابس + الجمال + التعليم + الحيوان الأليف + رياضات + اضبط رصيدك المبدئي + انتقل الى الحسابات + اضغط على حساب -> اضغط على الرصيد -> ادخل رصيد الحساب. هذا كل شيء]]> + قم بإنشاء أول دفعة مخططة + قم بأتمتة المعاملات المتكررة مثل الاشتراكات, الايجار, الراتب, الخ.. كن في معرفة بأموالك من خلال معرفة المبلغ الذي يتعين عليك دفعه/الحصول عليه مقدمًا + هل كنت تعلم؟ + Ivy Wallet \nلديها تطبيقات مصغرة (ويدجت) رائعة تتيح لك إضافة دخل/إنفاق/تحويل بضغطة واحدة فقط من الشاشة الرئيسية \n\n ملاحظ: اذا لم يعمل زر \"إضافة تطبيق مصغر\" يرجى اضافته يدويا عن طريق قائمة التطبيقات المصغرة في شاشتك الرئيسية + إضافة تطبيق مصغر + تحديد ميزانية + Ivy Wallet \nلا تساعدك على تتبع نفقاتك فحسب ، بل تساعدك أيضًا في إنشاء مستقبلك المالي بشكل استباقي من خلال تحديد الميزانيات والالتزام بها. + يمكنك أن ترى مخطط النفقات الخاصة بك حسب الفئات! جربه ، اضغط على زر الإنفاق الرمادي/الأسود الموجود أسفل رصيدك مباشرة. + إحصائيات الإنفاق + Ivy Wallet قيم + أعطنا تقييمك! ساعد Ivy Wallet في أن تصبح أفضل وتنمو من خلال كتابة مراجعة لنا. المجاملات والأفكار والنقاد مرحب بهم! نفعل أفضل ما بوسعنا\n فريق التطبيق + ساعدنا على النمو حتى نتمكن من استثمار المزيد في التطوير وجعل التطبيق أفضل لك. عبر خلال مشاركة التطبيق ، ستجعل اثنين من المطورين سعداء وتساعد أيضًا صديقًا على إدارة امواله. + شارك مع الاصدقاء + يمكنك إنشاء تقارير للحصول على رؤى عميقة حول دخلك وإنفاقك. قم بتصفية معاملاتك حسب النوع والفترة الزمنية والفئة والحسابات والمبلغ والكلمات المفتاحية والمزيد للحصول على عرض أفضل لأموالك. + إنشاء سجل + هل تريد تحسين Ivy Wallet؟ اكتب لنا مراجعة. هذه هي الطريقة الوحيدة لتطوير ما تريده وتحتاجه. كما أنها تساعدنا في الحصول على مرتبة أعلى في PlayStore حتى نتمكن من إنفاق الأموال على المنتج بدلاً من التسويق. \n\n نحن نبذل قصارى جهدنا. \ n Ivy Team + نحتاج مساعدتك! + نحن مجرد مصمم ومطور نعمل على التطبيق بعد وظائفنا. في الوقت الحالي ، نستثمر الكثير من الوقت والمال لتوليد الخسائر والإرهاق فقط. إذا كنت تريد منا الاستمرار في تطوير Ivy Wallet ، فيرجى مشاركتها مع الأصدقاء والعائلة. \n\n ملاحظة. تساعد مراجعات Google PlayStore أيضًا كثيرًا! + مفتوح المصدر Ivy Wallet + كود التطبيق مفتوح ويمكن للجميع رؤيته. نعتقد أن الشفافية والأخلاق ضرورية لكل منتج برمجي. إذا كنت تحب عملنا وترغب في تحسين التطبيق ، يمكنك المساهمة في حساب Github العام الخاص بنا. + ساهم + أضبط الرصيد + تأكيد الهوية مطلوب + أثبت أن لديك حق الوصول إلى هذا الجهاز لإلغاء قفل التطبيق. + الميزانية الإجمالية + ميزانية بفئة + ميزانية بفئات (%1$s) + مٌقتَرَض + مُعَار + يناير + فبراير + مارس + ابريل + مايو + يونيو + يوليو + اغسطس + سبتمبر + اكتوبر + نوفمبر + ديسمبر + أيام + يوم + أسابيع + أسبوع + شهور + شهر + سنوات + سنة + التحويلات كدخل و إنفاق + معاملة تحويلات الحساب كإنفاق/دخل في صفحة الحسابات + البيت + يتم إنشاء التقرير … + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index fecc97edec..73e3f6a708 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -426,4 +426,5 @@ Treats account transfers as income or expense in Accounts Screen Дом + Generating report… \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml new file mode 100644 index 0000000000..8fad25f1c9 --- /dev/null +++ b/app/src/main/res/values-es/strings.xml @@ -0,0 +1,430 @@ + + + Cuentas + Total: %1$s %2$s + INGRESOS ESTE MES + GASTOS ESTE MES + (excluido) + INGRESOS + GASTOS + APP BLOQUEADA + Autentifícate para ingresar a la app + Desbloquear + SALDO ACTUAL + SALDO DESPUÉS DE PAGOS PLANIFICADOS + Conectar + Sincronizar transacciones + Sincronizando transacciones… + Sincronización bancaria habilitada: + Eliminar cliente + Agregar presupuesto + Sin presupuestos + No tienes ningún presupuesto establecido..\nToque "+ Agregar presupuesto" para agregar uno. + Presupuestos + %1$s %2$s por categorías + %1$s %2$s presupuesto de aplicación + información de presupuesto: %1$s / %2$s + información de presupuesto: %1$s%2$s + Agregar categoría + Gastos + Cuenta de gastos + Ingresos + Cuenta de ingresos + Gráfico de balance + BALANCE %1$s + Gráficos + Periodo: + Categorías + Exportar archivo CSV + Exportar archivo CSV con opciones estándar + Utilice las opciones estándar y asegúrese de incluir encabezados. + Como importar + abierto + Pasos + Cómo + Video + Articulo + Subir archivo CSV + Exportar Datos + Subir archivo CSV/ZIP + Exportar a un archivo + Conjunto de caracteres: UTF-8\nSeparador decimal: Punto decimal \'.\'\nCarácter delimitador: Coma \',\' + Exportar archivo de Excel + Convertir XLS a CSV + !NOTA: Si el archivo exportado no tiene la extensión ".xls", agréguelo cambiando el nombre del archivo manualmente. + Convertidor CSV en línea GRATIS + Revisa las carpetas de "Promociones" y "Spam" de tus correos electrónicos + Descargue el archivo \"transactions_export…\" adjunto al correo electrónico. + Si tiene más de una moneda, deberá descargar cada archivo \"transactions_export…\" e importarlo en Ivy. + Importar de + Espere por favor + Importando el archivo CSV + Listo + Fracaso + Importado + %1$d transacciones + %1$d cuentas + %1$d categorías + Fallido + %1$d filas del archivo CSV no reconocidas + Finalizar + Agregar descripción + Descripción + Planificado para + Añadir dinero a + Pagar con + Desde + Cuenta + Para + Añadir cuenta + Título de ingreso + Título del gasto + Título de transferencia + Gastos + Añadir fecha prevista de pago + Pagar + Obtener + Confirmar la eliminación + Eliminar esta transacción la eliminará del historial de transacciones y actualizará el saldo en consecuencia. + Confirmar cambio de cuenta + Nota: Está intentando cambiar la cuenta asociada con el préstamo con una cuenta de moneda diferente, \nTodos los registros del préstamo se volverán a calcular en función de las tasas de cambio de hoy + Confirmar + Espere, recalculando todos los registros de préstamo + Creado el + Hola + Hola %1$s + Flujo de fondos: %1$s%2$s %3$s + Buscar transacciones + Ivy Wallet es de código abierto + meta de ahorro + Acceso rápido + Ajustes + Modo claro + Modo oscuro + Modo automático + Pagos\nPlanificados + Comparte Ivy + Reportes + Préstamos + Establecer moneda + Sin transacciones + No tienes ninguna transacción para %1$s.\nPuedes agregar una tocando el botón \"+\". + Agregar préstamo + Sin préstamos + No tienes ningún préstamo.\nToca \"+ Agregar préstamo\" para agregar uno. + Nota: Al eliminar este préstamo, se eliminará de forma permanente y se eliminarán todos los registros de préstamos asociados. + Espere, recalculando todos los registros de préstamo + Pagado + %1$s %2$s restantes + Intereses del préstamo + %1$s %2$s pagados + Agregar registro + Interés + Sin registros + No tiene ningún registro para este préstamo. Toque "Agregar registro" para crear uno. + Añadir ingresos + Agregar gasto + Sin especificar + %1$s\%% + Transferencias de cuenta + No tiene ninguna transacción para %1$s.\nPuede agregar una desplazándose hacia abajo y tocando el botón "Agregar ingresos" o "Agregar gastos" en la parte superior. + Nota: Eliminar esta cuenta la eliminará de forma permanente y eliminará todas las transacciones asociadas con ella. + Nota: Eliminar esta categoría la eliminará de forma permanente. + Editar + transacciones + Inicio + Agregar pago planificado + AGREGAR INGRESOS + AGREGAR GASTO + TRANSFERENCIAS DE CUENTA + Saltar + Agregar nuevo + Desde %1$s + Para %1$s + Rango + Privacidad y\nrecopilación de datos + Desliza para aceptar nuestros Términos y condiciones + De acuerdo con nuestros Términos y condiciones + Desliza para aceptar nuestra política de privacidad + De acuerdo con nuestra política de privacidad + Términos y condiciones + Política de privacidad + Realice un seguimiento de sus ingresos, gastos y presupuesto con Ivy.\n\nInterfaz de usuario intuitiva, pagos recurrentes y planificados, administre múltiples cuentas, organice transacciones en categorías, estadísticas significativas, exporte a CSV y mucho más. + Introduce tu nombre\npara personalizar tu\nbilletera + ¿Cuál es tu nombre? + Ingresar + Agregar cuentas + Sugerencia + Siguiente + Añadir categorías + Sugerencias + Establecer + Tu gestor personal de dinero + #opensource + Error. Intenta de nuevo: %1$s + Iniciando sesión… + ¡Éxito! + Iniciar sesión con Google + cuenta sin conexión + SINCRONIZA TUS DATOS EN LA NUBE DE IVY + ¡La integridad y la protección de los datos no están garantizadas! + O ENTRAR CON UNA CUENTA OFFLINE + Sus datos se guardarán localmente (solo en su teléfono) y no se sincronizarán con la nube. Corre el riesgo de perderlo si desinstala la aplicación o cambia su dispositivo. Siempre puedes activar la sincronización más tarde si decides. + + Al iniciar sesión, acepta nuestros %1$s y %2$s. + Importar archivo CSV + de Ivy u otra aplicación + La importación de un archivo de copia de seguridad desde otro puede tardar hasta 5 minutos. Siempre puede importar sus datos más tarde si lo desea. + Importar archivo de copia de seguridad + Empezar de nuevo + Al eliminar este pago planificado, se eliminarán todas las transacciones próximas o vencidas no pagadas asociadas con él. + Establecer tipo de pago + Comienzos previstos en + REPITE CADA %1$d %2$s + eliminado + "PLANIFICADO PARA " + nulo + "EMPIEZA %1$s " + Agregar pago + Pagos únicos + Pagos recurrentes + Sin pagos planificados + No tiene ningún pago planificado.\nPresione la parte inferior \'⚡\' en la parte inferior para agregar uno. + Pagos planificados + Hoy + Ayer + Mañana + Vencimiento el %1$s + Próximos + Atrasado + gastos + ingreso + Editar cuenta + Nueva cuenta + Nombre de la cuenta + Incluir cuenta + Ingrese el saldo de la cuenta + Elegir Moneda + Calculadora + Cálculo (+-/*=) + Editar categoría + Crear categoría + Nombre de la categoría + Elegir la categoría + Ingrese cualquier detalle aquí (admite Markdown) + Borrar filtros + Filtrar + Aplicar filtros + Por tipo + Ingresos + Periodo de tiempo + Seleccionar rango de tiempo + cuentas (%1$d) + Categorías (%1$d) + Limpiar todo + Seleccionar todo + Cantidad (opcional) + Palabras clave (opcional) + INCLUYE + Agregar una palabra clave + EXCLUYE + No tienes transacciones para tu filtro. + Sin filtro + Para generar un informe, primero debe establecer un filtro válido. + Establecer filtro + Exportar + No tienes transacciones para la consulta "%1$s". + + Los datos de copia de seguridad + Datos de importación + Ajustes de Aplicación + Bloquear la aplicación + Mostrar notificaciones + Ocultar saldo + Haz click en el saldo oculto para mostrar el saldo por 5 segundos + Otro + Califícanos en Google Play + Compartir Ivy Wallet + Producto + Zona peligrosa + Eliminar todos los datos del usuario + ¿Eliminar todos los datos de usuario? + ¡ADVERTENCIA! Esta acción eliminará todos los datos de %1$s PERMANENTEMENTE y no podrá recuperarlos. + tu cuenta + Confirmar la eliminación permanente de \'%1$s\' + todos tus datos + ¡ÚLTIMA ADVERTENCIA! Después de hacer clic en "Eliminar", sus datos desaparecerán para siempre. + Exportar datos + Por favor espere, exportando datos + Fecha de inicio del mes + Ivy Telegram + Centro de ayuda + Hoja de ruta + Solicitar una característica + Soporte de contacto + Colaboradores del proyecto + CUENTA + Cerrar sesión + Iniciar sesión + Sincronizando… + Datos sincronizados con la nube + Toca para sincronizar + Error de sincronización. Toca para sincronizar + Anónimo + Exportar a CSV + Queda para gastar + Presupuesto excedido por + Búfer excedido por + Establecer tipo de transacción + Transferir + Seleccionado + Buscar (USD, EUR, GBP, BTC, etc.) + Preseleccionado + Cripto + Tipo de cambio + Elegir color + Reordenar + Palabra clave + Editar presupuesto + Crear presupuesto + Nombre del presupuesto + CANTIDAD DE PRESUPUESTO + ¿Está seguro de que desea eliminar el presupuesto "%1$s"? + Editar objetivo de ahorro + Elegir icono + Elegir mes + o rango personalizado + Agregar fecha + o en el ultimo + o todo el tiempo + Deseleccionar todo el tiempo + Seleccionar todo el tiempo + Elija la fecha de inicio del mes + soporta cripto + Borrar + Guardar + Agregar + Crear + Editar préstamo + Nuevo préstamo + Nombre del préstamo + Cuenta asociada + Crear una transacción principal + INGRESE EL MONTO DEL PRÉSTAMO + "Nota: está intentando cambiar la cuenta asociada con el préstamo con una cuenta de moneda diferente, \nTodos los registros del préstamo se volverán a calcular en función de las tasas de cambio de hoy" + Tipo de préstamo + Prestar dinero + Prestar dinero + Editar registro + Nuevo récord + Nota + Marcar como interés + Recalcular la cantidad con las tasas de cambio de divisas de hoy + INGRESE LA CANTIDAD DE REGISTRO + ¿Está seguro de que desea eliminar el registro "%1$s"? + "Nota: está intentando cambiar la cuenta asociada con el registro de préstamo con una cuenta de moneda diferente\nEl monto se volverá a calcular en función de las tasas de cambio de hoy" + Editar nombre + Plan para + Una vez + Múltiples veces + Comienza el + Se repite cada + Entregar + ¿Qué necesitas? + Explícalo en una frase. (admite Markdown) + últimos 12 meses + últimos 6 meses + últimas 4 semanas + Los últimos 7 días + Hoy, %1$s + Ayer, %1$s + Mañana, %1$s + Caducado + ¡La autenticación se realizó correctamente! + La autenticación falló. + ¿Hiciste alguna transacción hoy? 🏁 + ¿Hiciste un seguimiento de tus gastos hoy? 💸 + ¿Has registrado tus transacciones hoy? 🏁 + Efectivo + Banco + Revolut + + + Transporte + Comestibles + Entretenimiento + Compras + Regalos + Salud + Inversiones + Coche + Trabajo + Restaurante + Familia + Vida social + Ordenes de comida + Viaje + Condición física + Autodesarrollo + Ropa + Belleza + Educación + Mascota + Deportes + Ajusta tu saldo inicial + a las cuentas + Toque una cuenta -> Toque su saldo -> Ingrese el saldo actual. ¡Eso es todo!]]> + Crea tu primer pago planificado + Automatice el seguimiento de transacciones recurrentes como sus suscripciones, alquiler, salario, etc. Manténgase al tanto de sus finanzas al saber cuánto tiene que pagar/obtener por adelantado. + ¿Sabías? + Ivy Wallet tiene un widget genial que le permite agregar transacciones de INGRESOS/GASTOS/TRANSFERENCIAS con 1 clic desde su hogar\n\nNota: si el botón "Agregar widget" no funciona, agréguelo manualmente desde su lanzador\' menú de widgets. + Añadir widget + Establecer un presupuesto + Ivy Wallet no solo lo ayuda a realizar un seguimiento pasivo de sus gastos, sino que también crea proactivamente su futuro financiero estableciendo presupuestos y ajustándose a ellos. + ¡Puedes ver tu estructura de gastos por categorías! Pruébelo, toque el botón Gastos gris/negro justo debajo de su saldo. + Gráfico circular de gastos + Danos tu opinión de Ivy Wallet + ¡Danos tu opinión! Ayude a Ivy Wallet a mejorar y crecer escribiéndonos una reseña. ¡Los elogios, las ideas y las críticas son bienvenidos! Hacemos nuestro mejor esfuerzo.\n\nSaludos,\nEquipo Ivy + Ayúdanos a crecer para que podamos invertir más en desarrollo y mejorar la aplicación para ti. Al compartir Ivy Wallet, hará felices a dos desarrolladores y también ayudará a un amigo a tomar el control de sus finanzas. + Compartir con amigos + Puede generar informes para obtener información detallada sobre sus ingresos y gastos. Filtre sus transacciones por tipo, período de tiempo, categoría, cuentas, cantidad, palabras clave y más para obtener una mejor visión de sus finanzas. + Hacer un reporte + ¿Quieres mejorar Ivy Wallet? Escríbenos una reseña. Esa es la única forma en que podemos desarrollar lo que quiere y necesita. También nos ayuda a clasificarnos más alto en PlayStore para que podamos gastar dinero en el producto en lugar de en marketing.\n\nHacemos lo mejor que podemos.\nIvy Team + ¡Necesitamos tu ayuda! + Solo somos un diseñador y un desarrollador trabajando en la aplicación después de nuestros trabajos de 9 a 5. Actualmente, invertimos mucho tiempo y dinero para generar solo pérdidas y agotamiento. Si desea que sigamos desarrollando Ivy Wallet, compártalo con amigos y familiares.\n\nP.D. ¡Las reseñas de Google PlayStore también ayudan mucho! + ¡Ivy Wallet es de código abierto! + El código de Ivy Wallet está abierto y todos pueden verlo. Creemos que la transparencia y la ética son imprescindibles para todo producto de software. Si te gusta nuestro trabajo y quieres mejorar la aplicación, puedes contribuir en nuestro repositorio público de Github. + Contribuir + Ajustar el saldo + Autenticación requerida + Demuestra que tienes acceso a este dispositivo para desbloquear la aplicación. + Presupuesto total + Presupuesto de la categoría + Presupuesto multicategoría (%1$s) + PRESTADA + PRESTADO + Enero + Febrero + Marzo + Abril + Mayo + Junio + Julio + Agosto + Septiembre + Octubre + Noviembre + Diciembre + días + día + semanas + semana + meses + mes + años + año + + Trata las transferencias de cuentas como ingresos o gastos en la pantalla Cuentas + Hogar + Generando reporte… + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 67a4c0cd5e..b68e23970d 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -426,4 +426,5 @@ Tratta i trasferimenti di conto come entrate o uscite nella schermata dei conti Casa + Generazione report… diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 21387e1633..1bc92a2236 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -189,7 +189,7 @@ One time payments Recurring payments No planned payments - You don\'t have any planned payments.\nPress the \'⚡\' bottom at the bottom to add one. + You don\'t have any planned payments.\nPress the \'⚡\' button at the bottom to add one. Planned payments Today Yesterday @@ -426,4 +426,5 @@ Treats account transfers as income or expense in Accounts Screen Home + Generating report… diff --git a/docs/Developer-Guidelines.md b/docs/Developer-Guidelines.md index aa1bfc720a..d61f5b9312 100644 --- a/docs/Developer-Guidelines.md +++ b/docs/Developer-Guidelines.md @@ -1,31 +1,257 @@ # Ivy Developer Guidelines -A short guide that'll evolve our time with one and only goal - to make you a better developer. +A short guide _(that'll evolve with time)_ with one and only goal - to **make you a better developer.** [![PRs welcome!](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/ILIYANGERMANOV/ivy-wallet/blob/main/CONTRIBUTING.md) -> Feedback: Welcome! +> Feedback: Welcome! -> Proposals: Highly appreciated. +> Proposals: Highly appreciated. :rocket: -## I. Domain (Business Logic) +## Ivy Architecture (FRP) -We classify business logic as any domain-specific logic that: is neither UI nor Android stuff nor IO (persistence or network calls). +The Ivy Architecture follows the Functional Reactive Programming (FRP) principles. A good example for them is [The Elm Architecture.](https://guide.elm-lang.org/architecture/) -Now knowing what the `domain` isn't, lets define what it is +### Architecture graph +```mermaid +graph TD; -# _WIP...._ +android(Android System) +user(User) +view(UI) +event(Event) +viewmodel(ViewModel) +action(Action) +pure(Pure) -### 1. Functional Programming (pure) +event -- Propagated --> viewmodel +viewmodel -- Triggers --> action +viewmodel -- "UI State (Flow)" --> view +action -- "Abstacts IO" --> pure +action -- "Composition" --> action +pure -- "Composition" --> pure +pure -- "Computes" --> action +action -- "Data" --> viewmodel -### 2. Actions (use-cases) +user -- Interracts --> view +view -- Produces --> event +android -- Produces --> event +``` -### 3. ViewModel +### 0. Data Model -## II. UI +The Data Model in Ivy drives clear separation between `domain` pure data required for business logic w/o added complexity, `entity` database data, `dto` _(data transfer object)_ JSON representation for network requests and `ui` data which we'll displayed. -### `:ivy-design` +Learn more at [Android Developers Architecture: Entity](https://www.youtube.com/watch?v=cfak1jDKM_4). -## III. Data model +**Data Model** +```mermaid +graph TD; -## IV. IO (network + persistence) \ No newline at end of file +data(Data) +entity(Entity) +dto(DTO) +ui_data(UI Data) + +ui(UI) +network(Network) +db(Database) +viewmodel(ViewModel) +domain("Domain (Action, Pure)") + +network -- Fetch --> dto -- Send --> network +dto --> data + +db -- Retrieve --> entity -- Persist --> db +entity --> data + +data --> entity +data --> dto + +data -- Computation input --> domain +domain -- Computation output --> viewmodel +viewmodel -- Transform --> ui_data +ui_data -- "UI State (Flow)" --> ui + +``` + +**Example** +- `DisplayTransaction` + - UI specific fields +- `Transaction` + - pure domain data +- `TransactionEntity` + - has `isSynced`, `isDeletedFlags` db specific fields (Room DB anontations) +- `TransactionDTO` + - exactly what the API expects/returns (JSON) + +> Motivation: This separation **reduces complexity** and **provides flexibility** for changes. + +### 1. Event (UI interaction or system) +An `Event` is generated from either user interaction with the UI or a system subscription _(e.g. Screen start, Time, Random, Battery level)_. + +```mermaid +graph TD; +user(User) +world(Outside World) +system(System Event) +ui(UI) +event(Event) + +user -- Interracts --> ui +world -- Triggers --> system + +ui -- Produces --> event +system -- Produces --> event +``` + +> Note: There are infinite user inputs and outside world signals. + +### 2. ViewModel (mediator) +Triggers `Action` for incoming `Event`, transforms the result to `UI State` and propagates it to the UI via `Flow`. + +```mermaid +graph TD; + +event(Event) +viewmodel(ViewModel) +action(Actions) +ui(UI) + +event -- Incoming --> viewmodel +viewmodel -- "Action Input" --> action +action -- "Action Output" --> viewmodel +viewmodel -- "UI State (Flow)" --> ui + + +``` + +### 3. Action (domain logic with side-effects) + +Actions accept `Action Input`, handles `threading`, abstract `side-effects` (IO) and executes specific domain logic by **compising** `pure` functions or other `actions`. + +**Action Types** +- `FPAction()`: declaritve FP style _(preferable)_ +- `Action()`: imperative OOP style + +**Action Lifecycle:** + +```mermaid +graph TD; + +input(Action Input) +output(Action Output) +pure(Pure Functions) +action(Action) + +io(IO) +dao(Datbase) +network(Network) +side-effect(Side-Effect) + +side-effect -- any --> io +dao -- DAOs --> io +network -- Retrofit --> io +io -- DI --> action + +action -- Composition --> action +action -- Threading --> action + +input --> action +action -- abstracted IO --> pure -- Result --> action +action -- Final Result --> output +``` + +> Actions are very similar to the "use-cases" from the standard "Clean Code" architecture. + +> You can compose actions and pure functions by using **`then`**. + +### 4. Pure (domain logic with pure code) + +The `pure` layer as the name suggests must consist of only pure functions without side-effects. If the business logic requires, **side-effects must be abstracted**. + +**Code Example** +```Kotlin +//domain.action +class ExchangeAct @Inject constructor( + private val exchangeRateDao: ExchangeRateDao, +) : FPAction>() { + override suspend fun Input.compose(): suspend () -> Option = suspend { + exchange( + data = data, + amount = amount, + getExchangeRate = exchangeRateDao::findByBaseCurrencyAndCurrency then { + it?.toDomain() + } + ) + } + + data class Input( + val data: ExchangeData, + val amount: BigDecimal + ) +} + + +//domain.pure +@Pure +suspend fun exchange( + data: ExchangeData, + amount: BigDecimal, + + @SideEffect + getExchangeRate: suspend (baseCurrency: String, toCurrency: String) -> ExchangeRate?, +): Option { + //PURE IMPLEMENTATION +} +``` + +### 5. UI (@Composable) + +Renders the `UI State` that the user sees, handles `user input` and transforms it to `events` which are propagated to the `ViewModel`. **Do NOT perform any business logic or computations.** + +```mermaid +graph TD; + +user(User) +uiState("UI State (Flow)") +ui("UI (@Composable)") +event(Event) +viewmodel(ViewModel) + +user -- Interracts --> ui +ui -- Produces --> event +event -- "onEvent()" --> viewmodel +viewmodel -- "Action(s)" --> uiState +uiState -- "Flow#collectAsState()" --> ui +``` + +> Exception: The UI layer may perform in-app navigation **`navigation().navigate(..)`** to reduce boiler-plate and complexity. + +### 6. IO (side-effects) + +Responsible for the implementation of IO operations like persistnece, network requests, randomness, date & time, etc. + +- **Room DB**, local persistence +- **Shares Preferences**, local persistence + - key-value pairs persistence + - _will be migrated to DataStore_ +- **Retrofit**, Network Requests (REST) + - send requests + - parse response JSON with GSON + - transform network errors to `NetworkException` +- **Randomness** + - `UUID` generation +- **Date & Time** + - current Date & Time (`timeNowUtc`, `dateNowUtc`) + - Date & Time formatting using user's `Locale` + +### 7. Andoid System (side-effects) + +Responsible for the interaction with the Android System like launching `Intent`, sending `notification`, receiving `push messages`, `biometrics`, etc. + +--- + +_Version 1.0.0_ + +_Feedback and proposals are highly appreciated! Let's spark techincal discussion and make Ivy and the Android World better! :rocket:_ \ No newline at end of file