From efade3a61e81dfb48c03b6573ce589e47aba9951 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 11:14:00 +0200 Subject: [PATCH 1/9] Improve SettingsScreen.kt --- .../ivy/wallet/ui/settings/SettingsScreen.kt | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt b/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt index a100853f3a..9130841ffc 100644 --- a/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/settings/SettingsScreen.kt @@ -215,6 +215,25 @@ private fun BoxWithConstraintsScope.UI( } } + item { + SettingsSectionDivider(text = "App Settings") + + Spacer(Modifier.height(16.dp)) + + LockAppSwitch( + lockApp = lockApp, + onSetLockApp = onSetLockApp + ) + + Spacer(Modifier.height(12.dp)) + + StartDateOfMonth( + startDateOfMonth = startDateOfMonth + ) { + chooseStartDateOfMonthVisible = true + } + } + item { SettingsSectionDivider(text = "Other") @@ -238,21 +257,6 @@ private fun BoxWithConstraintsScope.UI( ) { ivyActivity.shareIvyWallet() } - - Spacer(Modifier.height(12.dp)) - - LockAppSwitch( - lockApp = lockApp, - onSetLockApp = onSetLockApp - ) - - Spacer(Modifier.height(12.dp)) - - StartDateOfMonth( - startDateOfMonth = startDateOfMonth - ) { - chooseStartDateOfMonthVisible = true - } } item { From f2b3c126731ed0052561545c16d3980123f14b21 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 13:34:44 +0200 Subject: [PATCH 2/9] Improve LoanDetailsScreen.kt scrolling --- .../java/com/ivy/wallet/ui/loandetails/LoanDetailsScreen.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/ivy/wallet/ui/loandetails/LoanDetailsScreen.kt b/app/src/main/java/com/ivy/wallet/ui/loandetails/LoanDetailsScreen.kt index 6e6e4f1839..353279a80d 100644 --- a/app/src/main/java/com/ivy/wallet/ui/loandetails/LoanDetailsScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/loandetails/LoanDetailsScreen.kt @@ -169,6 +169,11 @@ private fun BoxWithConstraintsScope.UI( NoLoanRecordsEmptyState() } } + + item { + //scroll hack + Spacer(Modifier.height(96.dp)) + } } } From 5a58ae67093a5634de56115486654b33ccb2e6ea Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 14:40:11 +0200 Subject: [PATCH 3/9] Improve "Loans" screen and rework IvyComposeTest.kt retry to better --- .../com/ivy/wallet/compose/IvyComposeTest.kt | 20 ++++++----- .../ivy/wallet/compose/helpers/LoansScreen.kt | 16 +++++---- .../ivy/wallet/compose/scenario/LoansTest.kt | 35 +++++++++++-------- ...omposeTesting.kt => TestIdlingResource.kt} | 8 ++--- .../com/ivy/wallet/base/TestingContext.kt | 5 +++ .../com/ivy/wallet/ui/loan/LoansScreen.kt | 9 +++-- 6 files changed, 58 insertions(+), 35 deletions(-) rename app/src/main/java/com/ivy/wallet/base/{ComposeTesting.kt => TestIdlingResource.kt} (93%) create mode 100644 app/src/main/java/com/ivy/wallet/base/TestingContext.kt diff --git a/app/src/androidTest/java/com/ivy/wallet/compose/IvyComposeTest.kt b/app/src/androidTest/java/com/ivy/wallet/compose/IvyComposeTest.kt index 6df8360187..5435f63d67 100644 --- a/app/src/androidTest/java/com/ivy/wallet/compose/IvyComposeTest.kt +++ b/app/src/androidTest/java/com/ivy/wallet/compose/IvyComposeTest.kt @@ -70,15 +70,19 @@ abstract class IvyComposeTest { @After fun tearDown() { - idlingResource?.let { - composeTestRule.unregisterIdlingResource(it) - } + resetTestIdling() TestingContext.inTest = false resetApp() } + private fun resetTestIdling() { + idlingResource?.let { + composeTestRule.unregisterIdlingResource(it) + } + } + private fun resetApp() { clearSharedPrefs() resetDatabase() @@ -105,11 +109,12 @@ abstract class IvyComposeTest { protected fun testWithRetry( attempt: Int = 0, maxAttempts: Int = 3, + firstFailure: Throwable? = null, test: () -> Unit ) { try { test() - } catch (e: AssertionError) { + } catch (e: Throwable) { if (attempt < maxAttempts) { //reset state && retry test resetApp() @@ -118,20 +123,19 @@ abstract class IvyComposeTest { TestIdlingResource.reset() //Restart IvyActivity - val intent = composeTestRule.activity.intent - composeTestRule.activity.finish() - composeTestRule.activity.startActivity(intent) + composeTestRule.activityRule.scenario.recreate() composeTestRule.waitMillis(300) //wait for activity to start testWithRetry( attempt = attempt + 1, maxAttempts = maxAttempts, + firstFailure = if (attempt == 0) e else firstFailure, test = test ) } else { //propagate exception - throw e + throw firstFailure ?: e } } } diff --git a/app/src/androidTest/java/com/ivy/wallet/compose/helpers/LoansScreen.kt b/app/src/androidTest/java/com/ivy/wallet/compose/helpers/LoansScreen.kt index a5d012aa12..0be8a943bf 100644 --- a/app/src/androidTest/java/com/ivy/wallet/compose/helpers/LoansScreen.kt +++ b/app/src/androidTest/java/com/ivy/wallet/compose/helpers/LoansScreen.kt @@ -20,8 +20,9 @@ class LoansScreen( fun assertLoan( name: String, loanType: LoanType, - amount: String, - amountDecimal: String, + amountLeft: String, + amountLeftDecimal: String, + loanAmount: String, amountPaid: String, percentPaid: String, currency: String = "USD" @@ -39,8 +40,8 @@ class LoansScreen( ) .performScrollTo() .assertTextEquals( - name, typeText, amount, amountDecimal, currency, - "$amountPaid $currency / $amount$amountDecimal $currency ($percentPaid%)" + name, typeText, amountLeft, amountLeftDecimal, currency, + "$amountPaid $currency / $loanAmount $currency ($percentPaid%)" ) } @@ -73,11 +74,14 @@ class LoansScreen( clickAdd() } + val loanAmount = amount.split(".").first() + val loanAmountDecimal = amount.split(".").getOrNull(1)?.let { ".$it" } ?: ".00" assertLoan( name = loanName, - amount = amount.split(".").first(), - amountDecimal = amount.split(".").getOrNull(1)?.let { ".$it" } ?: ".00", loanType = loanType, + amountLeft = loanAmount, + amountLeftDecimal = loanAmountDecimal, + loanAmount = loanAmount + loanAmountDecimal, currency = "USD", amountPaid = "0.00", percentPaid = "0.00" diff --git a/app/src/androidTest/java/com/ivy/wallet/compose/scenario/LoansTest.kt b/app/src/androidTest/java/com/ivy/wallet/compose/scenario/LoansTest.kt index 8c44e1e220..fdf04a8e40 100644 --- a/app/src/androidTest/java/com/ivy/wallet/compose/scenario/LoansTest.kt +++ b/app/src/androidTest/java/com/ivy/wallet/compose/scenario/LoansTest.kt @@ -38,8 +38,9 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Loan 1", - amount = "4,800", - amountDecimal = ".32", + amountLeft = "4,800", + amountLeftDecimal = ".32", + loanAmount = "4,800.32", loanType = LoanType.BORROW, currency = "USD", amountPaid = "0.00", @@ -66,8 +67,9 @@ class LoansTest : IvyComposeTest() { } loansScreen.assertLoan( name = "Razer Blade", - amount = "4,800", - amountDecimal = ".00", + amountLeft = "4,800", + amountLeftDecimal = ".00", + loanAmount = "4,800.00", loanType = LoanType.LEND, currency = "USD", amountPaid = "0.00", @@ -111,8 +113,9 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Laptop", loanType = LoanType.BORROW, - amount = "4,000", - amountDecimal = ".25", + amountLeft = "4,000", + amountLeftDecimal = ".25", + loanAmount = "4,000.25", amountPaid = "0.00", percentPaid = "0.00" ) @@ -222,8 +225,9 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Loan 1", loanType = LoanType.BORROW, - amount = "1,000", - amountDecimal = ".00", + amountLeft = "749", + amountLeftDecimal = ".50", + loanAmount = "1,000.00", amountPaid = "250.50", percentPaid = "25.05" ) @@ -284,8 +288,9 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Loan", loanType = LoanType.LEND, - amount = "10,000", - amountDecimal = ".00", + amountLeft = "5,000", + amountLeftDecimal = ".00", + loanAmount = "10,000.00", amountPaid = "5,000.00", percentPaid = "50.00" ) @@ -338,8 +343,9 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Loan", loanType = LoanType.LEND, - amount = "1,250", - amountDecimal = ".50", + amountLeft = "1,250", + amountLeftDecimal = ".50", + loanAmount = "1,250.50", amountPaid = "0.00", percentPaid = "0.00" ) @@ -465,9 +471,10 @@ class LoansTest : IvyComposeTest() { loansScreen.assertLoan( name = "Loan", - amount = "1,000", - amountDecimal = ".00", + amountLeft = "-1,000", + amountLeftDecimal = ".50", amountPaid = "2,000.50", + loanAmount = "1,000.00", percentPaid = "200.05", loanType = LoanType.BORROW ) diff --git a/app/src/main/java/com/ivy/wallet/base/ComposeTesting.kt b/app/src/main/java/com/ivy/wallet/base/TestIdlingResource.kt similarity index 93% rename from app/src/main/java/com/ivy/wallet/base/ComposeTesting.kt rename to app/src/main/java/com/ivy/wallet/base/TestIdlingResource.kt index d53430b140..252f349038 100644 --- a/app/src/main/java/com/ivy/wallet/base/ComposeTesting.kt +++ b/app/src/main/java/com/ivy/wallet/base/TestIdlingResource.kt @@ -4,10 +4,6 @@ import androidx.compose.ui.test.IdlingResource import com.ivy.wallet.BuildConfig import java.util.concurrent.atomic.AtomicInteger -object TestingContext { - var inTest = false -} - object TestIdlingResource { private val counter = AtomicInteger(0) @@ -34,4 +30,8 @@ object TestIdlingResource { fun reset() { counter.set(0) } + + fun get(): Int { + return counter.get() + } } \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/base/TestingContext.kt b/app/src/main/java/com/ivy/wallet/base/TestingContext.kt new file mode 100644 index 0000000000..e95832e17a --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/base/TestingContext.kt @@ -0,0 +1,5 @@ +package com.ivy.wallet.base + +object TestingContext { + var inTest = false +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/loan/LoansScreen.kt b/app/src/main/java/com/ivy/wallet/ui/loan/LoansScreen.kt index 0e5dbcacbd..3ebe06ad13 100644 --- a/app/src/main/java/com/ivy/wallet/ui/loan/LoansScreen.kt +++ b/app/src/main/java/com/ivy/wallet/ui/loan/LoansScreen.kt @@ -210,7 +210,7 @@ private fun LoanItem( ) ) { LoanHeader( - loan = loan, + displayLoan = displayLoan, baseCurrency = baseCurrency, contrastColor = contrastColor, ) @@ -228,10 +228,12 @@ private fun LoanItem( @Composable private fun LoanHeader( - loan: Loan, + displayLoan: DisplayLoan, baseCurrency: String, contrastColor: Color, ) { + val loan = displayLoan.loan + Column( modifier = Modifier .fillMaxWidth() @@ -275,6 +277,7 @@ private fun LoanHeader( Spacer(Modifier.height(4.dp)) + val leftToPay = loan.amount - displayLoan.amountPaid BalanceRow( modifier = Modifier .align(Alignment.CenterHorizontally), @@ -282,7 +285,7 @@ private fun LoanHeader( spacerDecimal = 6.dp, textColor = contrastColor, currency = baseCurrency, - balance = loan.amount, + balance = leftToPay, integerFontSize = 30.sp, decimalFontSize = 18.sp, From 3482a702a5906060d18aab4fe146c42a3fc3d137 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 15:43:25 +0200 Subject: [PATCH 4/9] Rework HomeMoreMenu.kt --- .../com/ivy/wallet/ui/home/HomeMoreMenu.kt | 90 +++++++++++++------ .../res/drawable/home_more_menu_budgets.xml | 27 ++++++ .../drawable/home_more_menu_categories.xml | 27 ++++++ .../res/drawable/home_more_menu_dark_mode.xml | 13 +++ .../drawable/home_more_menu_light_mode.xml | 20 +++++ .../res/drawable/home_more_menu_loans.xml | 34 +++++++ .../home_more_menu_planned_payments.xml | 13 +++ .../res/drawable/home_more_menu_reports.xml | 20 +++++ .../res/drawable/home_more_menu_settings.xml | 20 +++++ .../res/drawable/home_more_menu_share.xml | 20 +++++ 10 files changed, 257 insertions(+), 27 deletions(-) create mode 100644 app/src/main/res/drawable/home_more_menu_budgets.xml create mode 100644 app/src/main/res/drawable/home_more_menu_categories.xml create mode 100644 app/src/main/res/drawable/home_more_menu_dark_mode.xml create mode 100644 app/src/main/res/drawable/home_more_menu_light_mode.xml create mode 100644 app/src/main/res/drawable/home_more_menu_loans.xml create mode 100644 app/src/main/res/drawable/home_more_menu_planned_payments.xml create mode 100644 app/src/main/res/drawable/home_more_menu_reports.xml create mode 100644 app/src/main/res/drawable/home_more_menu_settings.xml create mode 100644 app/src/main/res/drawable/home_more_menu_share.xml diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt index 1c8d63c1d9..edcf6255ba 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt @@ -35,7 +35,6 @@ import com.ivy.wallet.ui.Screen import com.ivy.wallet.ui.theme.* import com.ivy.wallet.ui.theme.components.BufferBattery import com.ivy.wallet.ui.theme.components.CircleButtonFilled -import com.ivy.wallet.ui.theme.components.IvyButton import com.ivy.wallet.ui.theme.components.IvyIcon import com.ivy.wallet.ui.theme.modal.AddModalBackHandling import com.ivy.wallet.ui.theme.wallet.AmountCurrencyB1 @@ -192,6 +191,13 @@ private fun ColumnScope.Content( ) { Spacer(Modifier.height(32.dp)) + val ivyContext = LocalIvyContext.current + SearchButton { + //TODO: Navigate to search screen + } + + Spacer(Modifier.height(16.dp)) + QuickAccess( theme = theme, onSwitchTheme = onSwitchTheme @@ -211,16 +217,41 @@ private fun ColumnScope.Content( OpenSource() Spacer(Modifier.weight(1f)) +} - IvyButton( - modifier = Modifier.padding(start = 24.dp), - text = currency, - iconStart = R.drawable.ic_currency +@Composable +private fun SearchButton( + onClick: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .clip(Shapes.roundedFull) + .background(IvyTheme.colors.pure) + .border(1.dp, Gray, Shapes.roundedFull) + .clickable { + onClick() + }, + verticalAlignment = Alignment.CenterVertically ) { - onCurrencyClick() - } + Spacer(Modifier.width(12.dp)) - Spacer(Modifier.height(40.dp)) + IvyIcon(icon = R.drawable.ic_search) + + Spacer(Modifier.width(12.dp)) + + Text( + modifier = Modifier.padding(bottom = 14.dp, top = 12.dp), + text = "Search transactions", + style = Typo.body2.style( + fontWeight = FontWeight.SemiBold, + color = IvyTheme.colors.pureInverse + ) + ) + + Spacer(Modifier.width(16.dp)) + } } @Composable @@ -341,30 +372,30 @@ private fun QuickAccess( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Top ) { - Spacer(Modifier.width(4.dp)) + Spacer(Modifier.weight(1f)) MoreMenuButton( - icon = R.drawable.ic_settings, + icon = R.drawable.home_more_menu_settings, label = "Settings" ) { ivyContext.navigateTo(Screen.Settings) } - Spacer(Modifier.width(0.dp)) + Spacer(Modifier.weight(1f)) MoreMenuButton( - icon = R.drawable.ic_categories, + icon = R.drawable.home_more_menu_categories, label = "Categories" ) { ivyContext.navigateTo(Screen.Categories) } - Spacer(Modifier.width(0.dp)) + Spacer(Modifier.weight(1f)) MoreMenuButton( icon = when (theme) { - Theme.LIGHT -> R.drawable.ic_lightmode - Theme.DARK -> R.drawable.ic_darkmode + Theme.LIGHT -> R.drawable.home_more_menu_light_mode + Theme.DARK -> R.drawable.home_more_menu_dark_mode }, label = when (theme) { Theme.LIGHT -> "Light mode" @@ -382,16 +413,16 @@ private fun QuickAccess( onSwitchTheme() } - Spacer(Modifier.width(0.dp)) + Spacer(Modifier.weight(1f)) MoreMenuButton( - icon = R.drawable.ic_planned_payments, + icon = R.drawable.home_more_menu_planned_payments, label = "Planned\nPayments" ) { ivyContext.navigateTo(Screen.PlannedPayments) } - Spacer(Modifier.width(12.dp)) + Spacer(Modifier.weight(1f)) } Spacer(Modifier.height(16.dp)) @@ -402,39 +433,44 @@ private fun QuickAccess( horizontalArrangement = Arrangement.Start, verticalAlignment = Alignment.Top ) { - Spacer(Modifier.width(4.dp)) + Spacer(Modifier.weight(1f)) val context = LocalContext.current MoreMenuButton( - icon = R.drawable.ic_share, + icon = R.drawable.home_more_menu_share, label = "Share\nIvy Wallet" ) { (context as IvyActivity).shareIvyWallet() } + Spacer(Modifier.weight(1f)) + MoreMenuButton( - icon = R.drawable.ic_statistics_s, + icon = R.drawable.home_more_menu_reports, label = "Reports", - expandPadding = 11.dp ) { ivyContext.navigateTo(Screen.Report) } + Spacer(Modifier.weight(1f)) + MoreMenuButton( - icon = R.drawable.ic_budget_s, + icon = R.drawable.home_more_menu_budgets, label = "Budgets", - expandPadding = 11.dp ) { ivyContext.navigateTo(Screen.Budget) } + Spacer(Modifier.weight(1f)) + MoreMenuButton( - icon = R.drawable.ic_custom_loan_s, + icon = R.drawable.home_more_menu_loans, label = "Loans", - expandPadding = 11.dp ) { ivyContext.navigateTo(Screen.Loans) } + + Spacer(Modifier.weight(1f)) } } @@ -445,7 +481,7 @@ private fun MoreMenuButton( backgroundColor: Color = IvyTheme.colors.pure, tint: Color = IvyTheme.colors.pureInverse, - expandPadding: Dp = 8.dp, + expandPadding: Dp = 14.dp, onClick: () -> Unit ) { diff --git a/app/src/main/res/drawable/home_more_menu_budgets.xml b/app/src/main/res/drawable/home_more_menu_budgets.xml new file mode 100644 index 0000000000..d2272ec248 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_budgets.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/home_more_menu_categories.xml b/app/src/main/res/drawable/home_more_menu_categories.xml new file mode 100644 index 0000000000..fad08f613e --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_categories.xml @@ -0,0 +1,27 @@ + + + + + diff --git a/app/src/main/res/drawable/home_more_menu_dark_mode.xml b/app/src/main/res/drawable/home_more_menu_dark_mode.xml new file mode 100644 index 0000000000..58cd3ab94a --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_dark_mode.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/home_more_menu_light_mode.xml b/app/src/main/res/drawable/home_more_menu_light_mode.xml new file mode 100644 index 0000000000..392fa4fab6 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_light_mode.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/home_more_menu_loans.xml b/app/src/main/res/drawable/home_more_menu_loans.xml new file mode 100644 index 0000000000..31ff84103e --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_loans.xml @@ -0,0 +1,34 @@ + + + + + + diff --git a/app/src/main/res/drawable/home_more_menu_planned_payments.xml b/app/src/main/res/drawable/home_more_menu_planned_payments.xml new file mode 100644 index 0000000000..6f85708f41 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_planned_payments.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/home_more_menu_reports.xml b/app/src/main/res/drawable/home_more_menu_reports.xml new file mode 100644 index 0000000000..c5062eb779 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_reports.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/home_more_menu_settings.xml b/app/src/main/res/drawable/home_more_menu_settings.xml new file mode 100644 index 0000000000..4a20839040 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_settings.xml @@ -0,0 +1,20 @@ + + + + diff --git a/app/src/main/res/drawable/home_more_menu_share.xml b/app/src/main/res/drawable/home_more_menu_share.xml new file mode 100644 index 0000000000..d6ef47afc6 --- /dev/null +++ b/app/src/main/res/drawable/home_more_menu_share.xml @@ -0,0 +1,20 @@ + + + + From 09f4e676457ff413517eccd38920335fb6b7e70f Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 15:48:20 +0200 Subject: [PATCH 5/9] Improve share copy --- app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt index edcf6255ba..6efbaca923 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt @@ -438,7 +438,7 @@ private fun QuickAccess( val context = LocalContext.current MoreMenuButton( icon = R.drawable.home_more_menu_share, - label = "Share\nIvy Wallet" + label = "Share Ivy" ) { (context as IvyActivity).shareIvyWallet() } From d4a0e3478c6a1ae7ceccfb3e65eb68457166026b Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 16:44:17 +0200 Subject: [PATCH 6/9] Closes #94; Implement simple transactions search by title & desc --- .../java/com/ivy/wallet/ui/IvyActivity.kt | 2 + .../main/java/com/ivy/wallet/ui/Screens.kt | 2 + .../com/ivy/wallet/ui/home/HomeMoreMenu.kt | 9 +- .../com/ivy/wallet/ui/search/SearchScreen.kt | 195 ++++++++++++++++++ .../ivy/wallet/ui/search/SearchViewModel.kt | 82 ++++++++ .../ui/theme/components/IvyBasicTextField.kt | 108 ++++++++++ 6 files changed, 396 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt create mode 100644 app/src/main/java/com/ivy/wallet/ui/search/SearchViewModel.kt create mode 100644 app/src/main/java/com/ivy/wallet/ui/theme/components/IvyBasicTextField.kt diff --git a/app/src/main/java/com/ivy/wallet/ui/IvyActivity.kt b/app/src/main/java/com/ivy/wallet/ui/IvyActivity.kt index a98e248e8c..0bdb51a899 100644 --- a/app/src/main/java/com/ivy/wallet/ui/IvyActivity.kt +++ b/app/src/main/java/com/ivy/wallet/ui/IvyActivity.kt @@ -56,6 +56,7 @@ import com.ivy.wallet.ui.paywall.PaywallScreen import com.ivy.wallet.ui.planned.edit.EditPlannedScreen import com.ivy.wallet.ui.planned.list.PlannedPaymentsScreen import com.ivy.wallet.ui.reports.ReportScreen +import com.ivy.wallet.ui.search.SearchScreen import com.ivy.wallet.ui.settings.SettingsScreen import com.ivy.wallet.ui.statistic.level1.PieChartStatisticScreen import com.ivy.wallet.ui.statistic.level2.ItemStatisticScreen @@ -172,6 +173,7 @@ class IvyActivity : AppCompatActivity() { is Screen.Budget -> BudgetScreen(screen = screen) is Screen.Loans -> LoansScreen(screen = screen) is Screen.LoanDetails -> LoanDetailsScreen(screen = screen) + is Screen.Search -> SearchScreen(screen = screen) is Screen.WebView -> WebViewScreen(screen = screen) null -> { } diff --git a/app/src/main/java/com/ivy/wallet/ui/Screens.kt b/app/src/main/java/com/ivy/wallet/ui/Screens.kt index 7b2f39ab33..f4a0ede074 100644 --- a/app/src/main/java/com/ivy/wallet/ui/Screens.kt +++ b/app/src/main/java/com/ivy/wallet/ui/Screens.kt @@ -70,6 +70,8 @@ sealed class Screen { object Loans : Screen() + object Search : Screen() + data class LoanDetails( val loanId: UUID ) : Screen() diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt index 6efbaca923..8e8c81aa4f 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt @@ -193,7 +193,9 @@ private fun ColumnScope.Content( val ivyContext = LocalIvyContext.current SearchButton { - //TODO: Navigate to search screen + ivyContext.navigateTo( + screen = Screen.Search + ) } Spacer(Modifier.height(16.dp)) @@ -242,7 +244,10 @@ private fun SearchButton( Spacer(Modifier.width(12.dp)) Text( - modifier = Modifier.padding(bottom = 14.dp, top = 12.dp), + modifier = Modifier.padding( + top = 12.dp, + bottom = 14.dp + ), text = "Search transactions", style = Typo.body2.style( fontWeight = FontWeight.SemiBold, 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 new file mode 100644 index 0000000000..87777f1904 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt @@ -0,0 +1,195 @@ +package com.ivy.wallet.ui.search + +import androidx.compose.animation.core.animateDpAsState +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +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.text.input.TextFieldValue +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.google.accompanist.insets.systemBarsPadding +import com.ivy.wallet.R +import com.ivy.wallet.base.* +import com.ivy.wallet.model.TransactionHistoryItem +import com.ivy.wallet.model.entity.Account +import com.ivy.wallet.model.entity.Category +import com.ivy.wallet.ui.IvyAppPreview +import com.ivy.wallet.ui.LocalIvyContext +import com.ivy.wallet.ui.Screen +import com.ivy.wallet.ui.theme.Gray +import com.ivy.wallet.ui.theme.IvyTheme +import com.ivy.wallet.ui.theme.Shapes +import com.ivy.wallet.ui.theme.components.IvyBasicTextField +import com.ivy.wallet.ui.theme.components.IvyIcon +import com.ivy.wallet.ui.theme.modal.DURATION_MODAL_KEYBOARD +import com.ivy.wallet.ui.theme.transaction.transactions + +@Composable +fun SearchScreen(screen: Screen.Search) { + val viewModel: SearchViewModel = viewModel() + + val transactions by viewModel.transactions.collectAsState() + val baseCurrency by viewModel.baseCurrencyCode.collectAsState() + val categories by viewModel.categories.collectAsState() + val accounts by viewModel.accounts.collectAsState() + + onScreenStart { + viewModel.search("") + } + + UI( + transactions = transactions, + baseCurrency = baseCurrency, + categories = categories, + accounts = accounts, + + onSearch = viewModel::search + ) +} + +@Composable +private fun UI( + transactions: List, + baseCurrency: String, + categories: List, + accounts: List, + + onSearch: (String) -> Unit = {} +) { + Column( + modifier = Modifier + .fillMaxSize() + .systemBarsPadding() + ) { + Spacer(Modifier.height(24.dp)) + + val listState = rememberLazyListState() + + var searchQueryTextFieldValue by remember { + mutableStateOf(selectEndTextFieldValue("")) + } + + SearchInput( + searchQueryTextFieldValue = searchQueryTextFieldValue, + onSetSearchQueryTextField = { + searchQueryTextFieldValue = it + onSearch(it.text) + } + ) + + LaunchedEffect(transactions) { + //scroll to top when transactions are changed + listState.animateScrollToItem(index = 0, scrollOffset = 0) + } + + Spacer(Modifier.height(16.dp)) + + val ivyContext = LocalIvyContext.current + LazyColumn( + modifier = Modifier.fillMaxSize(), + state = listState + + ) { + transactions( + ivyContext = ivyContext, + upcoming = emptyList(), + upcomingExpanded = false, + setUpcomingExpanded = { }, + baseCurrency = baseCurrency, + upcomingIncome = 0.0, + upcomingExpenses = 0.0, + categories = categories, + accounts = accounts, + listState = listState, + overdue = emptyList(), + overdueExpanded = false, + setOverdueExpanded = { }, + overdueIncome = 0.0, + overdueExpenses = 0.0, + history = transactions, + onPayOrGet = { }, + emptyStateTitle = "No transactions", + emptyStateText = "You don't have any transactions for \"${searchQueryTextFieldValue.text}\" query." + ) + + item { + val keyboardVisible by keyboardVisibleState() + val keyboardShownInsetDp by animateDpAsState( + targetValue = densityScope { + if (keyboardVisible) keyboardOnlyWindowInsets().bottom.toDp() else 0.dp + }, + animationSpec = tween(DURATION_MODAL_KEYBOARD) + ) + + Spacer(Modifier.height(keyboardShownInsetDp)) + //add keyboard height margin at bototm so the list can scroll to bottom + } + } + } +} + +@Composable +private fun SearchInput( + searchQueryTextFieldValue: TextFieldValue, + onSetSearchQueryTextField: (TextFieldValue) -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + .clip(Shapes.roundedFull) + .background(IvyTheme.colors.pure) + .border(1.dp, Gray, Shapes.roundedFull), + verticalAlignment = Alignment.CenterVertically + ) { + Spacer(Modifier.width(12.dp)) + + IvyIcon(icon = R.drawable.ic_search) + + Spacer(Modifier.width(12.dp)) + + val searchFocus = FocusRequester() + IvyBasicTextField( + modifier = Modifier + .padding( + top = 12.dp, + bottom = 14.dp + ) + .focusRequester(searchFocus), + value = searchQueryTextFieldValue, + hint = "Search transactions", + onValueChanged = { + onSetSearchQueryTextField(it) + } + ) + + onScreenStart { + searchFocus.requestFocus() + } + + Spacer(Modifier.width(16.dp)) + } +} + +@Preview +@Composable +private fun Preview() { + IvyAppPreview { + UI( + transactions = emptyList(), + baseCurrency = "BGN", + categories = emptyList(), + accounts = emptyList() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/search/SearchViewModel.kt b/app/src/main/java/com/ivy/wallet/ui/search/SearchViewModel.kt new file mode 100644 index 0000000000..75f55568c6 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/search/SearchViewModel.kt @@ -0,0 +1,82 @@ +package com.ivy.wallet.ui.search + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.ivy.wallet.base.TestIdlingResource +import com.ivy.wallet.base.getDefaultFIATCurrency +import com.ivy.wallet.base.ioThread +import com.ivy.wallet.logic.currency.ExchangeRatesLogic +import com.ivy.wallet.logic.withDateDividers +import com.ivy.wallet.model.TransactionHistoryItem +import com.ivy.wallet.model.entity.Account +import com.ivy.wallet.model.entity.Category +import com.ivy.wallet.model.entity.Transaction +import com.ivy.wallet.persistence.dao.AccountDao +import com.ivy.wallet.persistence.dao.CategoryDao +import com.ivy.wallet.persistence.dao.SettingsDao +import com.ivy.wallet.persistence.dao.TransactionDao +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SearchViewModel @Inject constructor( + private val transactionDao: TransactionDao, + private val settingsDao: SettingsDao, + private val categoryDao: CategoryDao, + private val accountDao: AccountDao, + private val exchangeRatesLogic: ExchangeRatesLogic +) : ViewModel() { + + private val _baseCurrencyCode = MutableStateFlow(getDefaultFIATCurrency().currencyCode) + val baseCurrencyCode = _baseCurrencyCode.asStateFlow() + + private val _transactions = MutableStateFlow(emptyList()) + val transactions = _transactions.asStateFlow() + + private val _accounts = MutableStateFlow(emptyList()) + val accounts = _accounts.asStateFlow() + + private val _categories = MutableStateFlow(emptyList()) + val categories = _categories.asStateFlow() + + fun search(query: String) { + val normalizedQuery = query.lowercase().trim() + + viewModelScope.launch { + TestIdlingResource.increment() + + _baseCurrencyCode.value = ioThread { + settingsDao.findFirst().currency + } + + _categories.value = ioThread { + categoryDao.findAll() + } + + _accounts.value = ioThread { + accountDao.findAll() + } + + _transactions.value = ioThread { + transactionDao.findAll() + .filter { + it.title.matchesQuery(normalizedQuery) || + it.description.matchesQuery(normalizedQuery) + }.withDateDividers( + exchangeRatesLogic = exchangeRatesLogic, + accountDao = accountDao, + settingsDao = settingsDao + ) + } + + TestIdlingResource.decrement() + } + } + + private fun String?.matchesQuery(query: String): Boolean { + return this?.lowercase()?.trim()?.contains(query) == true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyBasicTextField.kt b/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyBasicTextField.kt new file mode 100644 index 0000000000..4d48c59cd3 --- /dev/null +++ b/app/src/main/java/com/ivy/wallet/ui/theme/components/IvyBasicTextField.kt @@ -0,0 +1,108 @@ +package com.ivy.wallet.ui.theme.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.text.BasicTextField +import androidx.compose.foundation.text.KeyboardActions +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.SolidColor +import androidx.compose.ui.platform.LocalView +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.* +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import com.ivy.wallet.base.hideKeyboard +import com.ivy.wallet.base.isNotNullOrBlank +import com.ivy.wallet.base.selectEndTextFieldValue +import com.ivy.wallet.ui.IvyAppPreview +import com.ivy.wallet.ui.theme.IvyComponentPreview +import com.ivy.wallet.ui.theme.IvyTheme +import com.ivy.wallet.ui.theme.Typo +import com.ivy.wallet.ui.theme.style + +@Composable +fun IvyBasicTextField( + modifier: Modifier = Modifier, + value: TextFieldValue, + textColor: Color = IvyTheme.colors.pureInverse, + hint: String?, + visualTransformation: VisualTransformation = VisualTransformation.None, + keyboardOptions: KeyboardOptions = KeyboardOptions( + autoCorrect = true, + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Done, + capitalization = KeyboardCapitalization.Sentences + ), + keyboardActions: KeyboardActions? = null, + onValueChanged: (TextFieldValue) -> Unit +) { + val isEmpty = value.text.isBlank() + + Box( + modifier = modifier, + contentAlignment = Alignment.CenterStart + ) { + if (isEmpty && hint.isNotNullOrBlank()) { + Text( + modifier = Modifier, + text = hint!!, + style = Typo.body2.style( + color = IvyTheme.colors.gray, + fontWeight = FontWeight.SemiBold, + textAlign = TextAlign.Start + ), + ) + } + + val view = LocalView.current + BasicTextField( + modifier = Modifier + .testTag("base_input"), + value = value, + onValueChange = onValueChanged, + textStyle = Typo.body2.style( + fontWeight = FontWeight.SemiBold, + color = IvyTheme.colors.pureInverse, + textAlign = TextAlign.Start + ), + singleLine = false, + cursorBrush = SolidColor(IvyTheme.colors.pureInverse), + visualTransformation = visualTransformation, + keyboardOptions = keyboardOptions, + keyboardActions = keyboardActions ?: KeyboardActions( + onDone = { + hideKeyboard(view) + } + ) + ) + } +} + +@Preview +@Composable +private fun Preview_Hint() { + IvyComponentPreview { + IvyBasicTextField( + value = selectEndTextFieldValue(""), + hint = "Search transactions", + onValueChanged = {} + ) + } +} + +@Preview +@Composable +private fun Preview_Filled() { + IvyComponentPreview { + IvyBasicTextField( + value = selectEndTextFieldValue("sfds"), + hint = "Okay", + onValueChanged = {} + ) + } +} \ No newline at end of file From bc90f9d962980cc1b028aedae68d7da699329a3e Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 17:51:31 +0200 Subject: [PATCH 7/9] Improve search margins --- app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt | 2 +- app/src/main/java/com/ivy/wallet/ui/search/SearchScreen.kt | 1 + .../com/ivy/wallet/ui/theme/transaction/Transactions.kt | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt index 8e8c81aa4f..7a9b4020b1 100644 --- a/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt +++ b/app/src/main/java/com/ivy/wallet/ui/home/HomeMoreMenu.kt @@ -189,7 +189,7 @@ private fun ColumnScope.Content( onBufferClick: () -> Unit, onCurrencyClick: () -> Unit, ) { - Spacer(Modifier.height(32.dp)) + Spacer(Modifier.height(24.dp)) val ivyContext = LocalIvyContext.current SearchButton { 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 87777f1904..8d5d28759f 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 @@ -118,6 +118,7 @@ private fun UI( overdueExpenses = 0.0, history = transactions, onPayOrGet = { }, + dateDividerMarginTop = 16.dp, emptyStateTitle = "No transactions", emptyStateText = "You don't have any transactions for \"${searchQueryTextFieldValue.text}\" query." ) 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 87365be1f5..e4683abe0d 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 @@ -45,8 +45,8 @@ fun LazyListScope.transactions( lastItemSpacer: Dp? = null, onPayOrGet: (Transaction) -> Unit, emptyStateTitle: String = "No transactions", - - emptyStateText: String + emptyStateText: String, + dateDividerMarginTop: Dp? = null ) { if (upcoming.isNotEmpty()) { item { @@ -134,7 +134,8 @@ fun LazyListScope.transactions( is TransactionHistoryDateDivider -> { HistoryDateDivider( date = it.date, - spacerTop = if (it == history.firstOrNull()) 24.dp else 32.dp, + spacerTop = dateDividerMarginTop + ?: if (it == history.firstOrNull()) 24.dp else 32.dp, baseCurrency = baseCurrency, income = it.income, expenses = it.expenses From 067b14630143e40fe74278ea3eb58711f2925ae8 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 18:13:45 +0200 Subject: [PATCH 8/9] Add clear button to search input --- .../java/com/ivy/wallet/ui/search/SearchScreen.kt | 14 +++++++++++++- app/src/main/res/drawable/ic_outline_clear_24.xml | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_outline_clear_24.xml 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 8d5d28759f..4e814ca2e5 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 @@ -4,6 +4,7 @@ import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.tween import androidx.compose.foundation.background import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.rememberLazyListState @@ -178,7 +179,18 @@ private fun SearchInput( searchFocus.requestFocus() } - Spacer(Modifier.width(16.dp)) + Spacer(Modifier.weight(1f)) + + IvyIcon( + modifier = Modifier + .clickable { + onSetSearchQueryTextField(selectEndTextFieldValue("")) + } + .padding(all = 12.dp), //enlarge click area + icon = R.drawable.ic_outline_clear_24 + ) + + Spacer(Modifier.width(8.dp)) } } diff --git a/app/src/main/res/drawable/ic_outline_clear_24.xml b/app/src/main/res/drawable/ic_outline_clear_24.xml new file mode 100644 index 0000000000..4ebf4a04e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_clear_24.xml @@ -0,0 +1,10 @@ + + + From 3c2acf6bef10757d86908a5f75b49c60a7197908 Mon Sep 17 00:00:00 2001 From: Iliyan Germanov Date: Mon, 6 Dec 2021 18:16:34 +0200 Subject: [PATCH 9/9] Bump version --- .../src/main/java/com/ivy/wallet/buildsrc/dependencies.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt b/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt index 4f2660746e..a0c34e4aaf 100644 --- a/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt +++ b/buildSrc/src/main/java/com/ivy/wallet/buildsrc/dependencies.kt @@ -21,8 +21,8 @@ object Libs { object Project { //Version - const val versionName = "2.3.0-halley" - const val versionCode = 90 + const val versionName = "2.3.1-halley" + const val versionCode = 91 //Compile SDK & Build Tools const val compileSdkVersion = 31