diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenEvent.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenEvent.kt index 53582ea293..6c44863661 100644 --- a/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenEvent.kt +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenEvent.kt @@ -9,6 +9,7 @@ sealed interface LoanScreenEvent { data class OnReordered(val reorderedList: List) : LoanScreenEvent data class OnCreateAccount(val accountData: CreateAccountData) : LoanScreenEvent data class OnReOrderModalShow(val show: Boolean) : LoanScreenEvent + data class OnTabChanged(val tab: LoanTab) : LoanScreenEvent data object OnAddLoan : LoanScreenEvent data object OnLoanModalDismiss : LoanScreenEvent data object OnChangeDate : LoanScreenEvent diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenMode.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenMode.kt new file mode 100644 index 0000000000..4d1102c2fc --- /dev/null +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenMode.kt @@ -0,0 +1,6 @@ +package com.ivy.loans.loan + +sealed interface LoanScreenMode { + data object TabularMode : LoanScreenMode + data object NonTabularMode : LoanScreenMode +} \ No newline at end of file diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenState.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenState.kt index 69ec6eb00b..42dcbf8877 100644 --- a/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenState.kt +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoanScreenState.kt @@ -9,6 +9,8 @@ import java.time.Instant data class LoanScreenState( val baseCurrency: String, val loans: ImmutableList, + val completedLoans: ImmutableList, + val pendingLoans: ImmutableList, val accounts: ImmutableList, val selectedAccount: Account?, val loanModalData: LoanModalData?, @@ -16,5 +18,7 @@ data class LoanScreenState( val totalOweAmount: String, val totalOwedAmount: String, val paidOffLoanVisibility: Boolean, - val dateTime: Instant -) + val screenMode: LoanScreenMode, + val dateTime: Instant, + val selectedTab: LoanTab +) \ No newline at end of file diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanTab.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoanTab.kt new file mode 100644 index 0000000000..53d605d89a --- /dev/null +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoanTab.kt @@ -0,0 +1,8 @@ +package com.ivy.loans.loan + +import androidx.compose.runtime.Immutable + +@Immutable +enum class LoanTab { + PENDING, COMPLETED +} \ No newline at end of file diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanViewModel.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoanViewModel.kt index 9a8c8eaef9..af28d91863 100644 --- a/screen/loans/src/main/java/com/ivy/loans/loan/LoanViewModel.kt +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoanViewModel.kt @@ -15,6 +15,7 @@ import com.ivy.data.db.dao.read.LoanRecordDao import com.ivy.data.db.dao.read.SettingsDao import com.ivy.data.db.dao.write.WriteLoanDao import com.ivy.data.model.LoanType +import com.ivy.domain.features.Features import com.ivy.frp.test.TestIdlingResource import com.ivy.legacy.datamodel.Account import com.ivy.legacy.datamodel.Loan @@ -58,15 +59,19 @@ class LoanViewModel @Inject constructor( private val timeConverter: TimeConverter, private val timeProvider: TimeProvider, private val dateTimePicker: DateTimePicker, + private val features: Features ) : ComposeViewModel() { private var baseCurrencyCode by mutableStateOf(getDefaultFIATCurrency().currencyCode) private var loans by mutableStateOf>(persistentListOf()) + private var completedLoans by mutableStateOf>(persistentListOf()) + private var pendingLoans by mutableStateOf>(persistentListOf()) private var accounts by mutableStateOf>(persistentListOf()) private var selectedAccount by mutableStateOf(null) private var loanModalData by mutableStateOf(null) private var reorderModalVisible by mutableStateOf(false) private var dateTime by mutableStateOf(timeProvider.utcNow()) + private var selectedTab by mutableStateOf(LoanTab.PENDING) /** If true paid off loans will be visible */ private var paidOffLoanVisibility by mutableStateOf(true) @@ -93,10 +98,41 @@ class LoanViewModel @Inject constructor( totalOweAmount = getTotalOweAmount(totalOweAmount, defaultCurrencyCode), totalOwedAmount = getTotalOwedAmount(totalOwedAmount, defaultCurrencyCode), paidOffLoanVisibility = getPaidOffLoanVisibility(), - dateTime = dateTime + screenMode = getScreenMode(), + dateTime = dateTime, + selectedTab = getSelectedTab(), + completedLoans = getCompletedLoans(), + pendingLoans = getPendingLoans() ) } + fun setTab(tab: LoanTab) { + selectedTab = tab + } + + @Composable + private fun getSelectedTab(): LoanTab { + return selectedTab + } + + @Composable + private fun getCompletedLoans(): ImmutableList { + return completedLoans + } + + @Composable + private fun getPendingLoans(): ImmutableList { + return pendingLoans + } + + @Composable + private fun getScreenMode(): LoanScreenMode { + return when (features.tabularLoanMode.asEnabledState()) { + true -> LoanScreenMode.TabularMode + else -> LoanScreenMode.NonTabularMode + } + } + @Composable private fun getReorderModalVisible() = reorderModalVisible @@ -160,9 +196,14 @@ class LoanViewModel @Inject constructor( is LoanScreenEvent.OnChangeDate -> { handleChangeDate() } + is LoanScreenEvent.OnChangeTime -> { handleChangeTime() } + + is LoanScreenEvent.OnTabChanged -> { + setTab(event.tab) + } } } @@ -218,6 +259,8 @@ class LoanViewModel @Inject constructor( }.toImmutableList() } filterLoans() + loadPendingLoans() + loadCompletedLoans() TestIdlingResource.decrement() } @@ -332,6 +375,14 @@ class LoanViewModel @Inject constructor( } } + private fun loadCompletedLoans() { + completedLoans = allLoans.filter { loan -> loan.percentPaid == 1.0 }.toImmutableList() + } + + private fun loadPendingLoans() { + pendingLoans = allLoans.filter { loan -> loan.percentPaid < 1.0 }.toImmutableList() + } + private fun createAccount(data: CreateAccountData) { viewModelScope.launch { TestIdlingResource.increment() diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoansScreen.kt b/screen/loans/src/main/java/com/ivy/loans/loan/LoansScreen.kt index 1ff99e96a0..ea6bd7adbd 100644 --- a/screen/loans/src/main/java/com/ivy/loans/loan/LoansScreen.kt +++ b/screen/loans/src/main/java/com/ivy/loans/loan/LoansScreen.kt @@ -14,8 +14,8 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width -import androidx.compose.foundation.verticalScroll -import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -46,11 +46,12 @@ import com.ivy.navigation.LoanDetailsScreen import com.ivy.navigation.LoansScreen import com.ivy.navigation.navigation import com.ivy.ui.R +import com.ivy.ui.rememberScrollPositionListState import com.ivy.wallet.ui.theme.Blue import com.ivy.wallet.ui.theme.Gray -import com.ivy.wallet.ui.theme.Orange import com.ivy.wallet.ui.theme.Red import com.ivy.wallet.ui.theme.components.BalanceRow +import com.ivy.wallet.ui.theme.components.CircleButtonFilled import com.ivy.wallet.ui.theme.components.ItemIconSDefaultIcon import com.ivy.wallet.ui.theme.components.IvyIcon import com.ivy.wallet.ui.theme.components.ProgressBar @@ -59,7 +60,6 @@ import com.ivy.wallet.ui.theme.components.ReorderModalSingleType import com.ivy.wallet.ui.theme.dynamicContrast import com.ivy.wallet.ui.theme.findContrastTextColor import com.ivy.wallet.ui.theme.modal.LoanModal -import com.ivy.wallet.ui.theme.modal.LoanModalData import com.ivy.wallet.ui.theme.toComposeColor import kotlinx.collections.immutable.persistentListOf import java.time.Instant @@ -78,68 +78,119 @@ fun BoxWithConstraintsScope.LoansScreen(screen: LoansScreen) { @Composable private fun BoxWithConstraintsScope.UI( state: LoanScreenState, - onEventHandler: (LoanScreenEvent) -> Unit = {}, + onEventHandler: (LoanScreenEvent) -> Unit = {} ) { val nav = navigation() - val scrollState = ivyWalletCtx().loansScrollState Column( modifier = Modifier .fillMaxSize() .systemBarsPadding() - .verticalScroll(scrollState), ) { Spacer(Modifier.height(32.dp)) Toolbar( - setReorderModalVisible = { - onEventHandler.invoke(LoanScreenEvent.OnReOrderModalShow(show = it)) - }, + isTabularModeOn = state.screenMode == LoanScreenMode.TabularMode, + onDismiss = { nav.back() }, + setReorderModalVisible = { onEventHandler.invoke(LoanScreenEvent.OnReOrderModalShow(show = it)) }, state.totalOweAmount, state.totalOwedAmount ) Spacer(Modifier.height(8.dp)) - for (item in state.loans) { - Spacer(Modifier.height(16.dp)) + val scrollState = rememberScrollPositionListState( + key = "loans_lazy_column", + initialFirstVisibleItemIndex = ivyWalletCtx() + .loanListState?.firstVisibleItemIndex ?: 0, + initialFirstVisibleItemScrollOffset = ivyWalletCtx() + .loanListState?.firstVisibleItemScrollOffset ?: 0 + ) - LoanItem( - displayLoan = item - ) { - nav.navigateTo( - screen = LoanDetailsScreen( - loanId = item.loan.id - ) - ) + if (state.screenMode == LoanScreenMode.TabularMode) { + val loans = if (state.selectedTab == LoanTab.PENDING) { + state.pendingLoans + } else { + state.completedLoans } - } - if (state.loans.isEmpty()) { - Spacer(Modifier.weight(1f)) + LazyColumn(state = scrollState) { + items(loans) { item -> + Spacer(Modifier.height(16.dp)) + + LoanItem( + displayLoan = item + ) { + nav.navigateTo( + screen = LoanDetailsScreen( + loanId = item.loan.id + ) + ) + } + } + } - NoLoansEmptyState( - emptyStateTitle = stringResource(R.string.no_loans), - emptyStateText = stringResource(R.string.no_loans_description) - ) + if (loans.isEmpty()) { + Spacer(Modifier.weight(1f)) - Spacer(Modifier.weight(1f)) - } + NoLoansEmptyState( + emptyStateTitle = stringResource(R.string.no_loans), + emptyStateText = stringResource(R.string.no_loans_description) + ) + Spacer(Modifier.weight(1f)) + } + } else { + LazyColumn(state = scrollState) { + items(state.loans) { item -> + Spacer(Modifier.height(16.dp)) + + LoanItem( + displayLoan = item + ) { + nav.navigateTo( + screen = LoanDetailsScreen( + loanId = item.loan.id + ) + ) + } + } + } + if (state.loans.isEmpty()) { + Spacer(Modifier.weight(1f)) + + NoLoansEmptyState( + emptyStateTitle = stringResource(R.string.no_loans), + emptyStateText = stringResource(R.string.no_loans_description) + ) + + Spacer(Modifier.weight(1f)) + } + } Spacer(Modifier.height(150.dp)) // scroll hack } - LoanBottomBar( - isPaidOffLoanVisible = state.paidOffLoanVisibility, - onAdd = { - onEventHandler.invoke(LoanScreenEvent.OnAddLoan) - }, - onTogglePaidOffLoanVisibility = { - onEventHandler.invoke(LoanScreenEvent.OnTogglePaidOffLoanVisibility) - }, - onClose = { - nav.back() - }, - ) + if (state.screenMode == LoanScreenMode.TabularMode) { + TabularLoanBottomBar( + tab = state.selectedTab, + selectTab = { onEventHandler.invoke(LoanScreenEvent.OnTabChanged(it)) }, + onAdd = { + onEventHandler.invoke(LoanScreenEvent.OnAddLoan) + } + ) + } else { + NonTabularLoanBottomBar( + isPaidOffLoanVisible = state.paidOffLoanVisibility, + onAdd = { + onEventHandler.invoke(LoanScreenEvent.OnAddLoan) + }, + onTogglePaidOffLoanVisibility = { + onEventHandler.invoke(LoanScreenEvent.OnTogglePaidOffLoanVisibility) + }, + onClose = { + nav.back() + }, + ) + } ReorderModalSingleType( visible = state.reorderModalVisible, @@ -164,32 +215,35 @@ private fun BoxWithConstraintsScope.UI( ) } - LoanModal( - accounts = state.accounts, - onCreateAccount = { - onEventHandler.invoke(LoanScreenEvent.OnCreateAccount(accountData = it)) - }, - modal = state.loanModalData, - onCreateLoan = { - onEventHandler.invoke(LoanScreenEvent.OnLoanCreate(createLoanData = it)) - }, - onEditLoan = { _, _ -> }, - dismiss = { - onEventHandler.invoke(LoanScreenEvent.OnLoanModalDismiss) - }, - dateTime = state.dateTime, - onSetDate = { - onEventHandler.invoke(LoanScreenEvent.OnChangeDate) - }, - onSetTime = { - onEventHandler.invoke(LoanScreenEvent.OnChangeTime) - } - ) + if (state.loanModalData != null) { + LoanModal( + accounts = state.accounts, + onCreateAccount = { + onEventHandler.invoke(LoanScreenEvent.OnCreateAccount(accountData = it)) + }, + modal = state.loanModalData, + onCreateLoan = { + onEventHandler.invoke(LoanScreenEvent.OnLoanCreate(createLoanData = it)) + }, + onEditLoan = { _, _ -> }, + dismiss = { + onEventHandler.invoke(LoanScreenEvent.OnLoanModalDismiss) + }, + dateTime = state.dateTime, + onSetDate = { + onEventHandler.invoke(LoanScreenEvent.OnChangeDate) + }, + onSetTime = { + onEventHandler.invoke(LoanScreenEvent.OnChangeTime) + } + ) + } } @Composable -@OptIn(ExperimentalMaterial3Api::class) private fun Toolbar( + isTabularModeOn: Boolean, + onDismiss: () -> Unit, setReorderModalVisible: (Boolean) -> Unit, totalOweAmount: String, totalOwedAmount: String @@ -228,6 +282,15 @@ private fun Toolbar( } } + if (isTabularModeOn) { + CircleButtonFilled( + modifier = Modifier, + icon = R.drawable.ic_dismiss, + onClick = onDismiss + ) + Spacer(Modifier.width(8.dp)) + } + ReorderButton { setReorderModalVisible(true) } @@ -409,9 +472,10 @@ private val testDateTime = LocalDateTime.of(2023, 4, 20, 0, 35) @Preview @Composable -private fun Preview(theme: Theme = Theme.LIGHT) { +private fun PreviewInTabularMode(theme: Theme = Theme.LIGHT) { val state = LoanScreenState( baseCurrency = "BGN", + selectedTab = LoanTab.PENDING, loans = persistentListOf( DisplayLoan( loan = Loan( @@ -425,20 +489,24 @@ private fun Preview(theme: Theme = Theme.LIGHT) { loanTotalAmount = 5500.0, amountPaid = 0.0, percentPaid = 0.4 - ), + ) + ), + completedLoans = persistentListOf( DisplayLoan( loan = Loan( - name = "Loan 2", - icon = "atom", - color = Orange.toArgb(), - amount = 252.36, - type = LoanType.BORROW, + name = "Loan 3", + icon = "bank", + color = Blue.toArgb(), + amount = 7000.0, + type = LoanType.LEND, dateTime = testDateTime ), - loanTotalAmount = 252.36, - amountPaid = 124.23, - percentPaid = 0.2 + loanTotalAmount = 7000.0, + amountPaid = 8000.0, + percentPaid = 0.8 ), + ), + pendingLoans = persistentListOf( DisplayLoan( loan = Loan( name = "Loan 3", @@ -456,19 +524,79 @@ private fun Preview(theme: Theme = Theme.LIGHT) { accounts = persistentListOf(), totalOweAmount = "1000.00 INR", totalOwedAmount = "1500.0 INR", - loanModalData = LoanModalData( - loan = Loan( - name = "", - color = Blue.toArgb(), - amount = 0.0, - type = LoanType.LEND, - dateTime = testDateTime + loanModalData = null, + reorderModalVisible = false, + selectedAccount = null, + paidOffLoanVisibility = true, + screenMode = LoanScreenMode.TabularMode, + dateTime = Instant.now() + ) + IvyWalletPreview(theme) { + UI( + state = state + ) {} + } +} + +@Preview +@Composable +private fun PreviewInNonTabularMode(theme: Theme = Theme.LIGHT) { + val state = LoanScreenState( + baseCurrency = "BGN", + selectedTab = LoanTab.PENDING, + loans = persistentListOf( + DisplayLoan( + loan = Loan( + name = "Loan 3", + icon = "bank", + color = Blue.toArgb(), + amount = 7000.0, + type = LoanType.LEND, + dateTime = testDateTime + ), + loanTotalAmount = 7000.0, + amountPaid = 8000.0, + percentPaid = 0.8 + ), + ), + completedLoans = persistentListOf( + DisplayLoan( + loan = Loan( + name = "Loan 1", + icon = "rocket", + color = Red.toArgb(), + amount = 5000.0, + type = LoanType.BORROW, + dateTime = testDateTime + ), + loanTotalAmount = 5500.0, + amountPaid = 0.0, + percentPaid = 0.4 ), - baseCurrency = "INR" ), + pendingLoans = persistentListOf( + DisplayLoan( + loan = Loan( + name = "Loan 3", + icon = "bank", + color = Blue.toArgb(), + amount = 7000.0, + type = LoanType.LEND, + dateTime = testDateTime + ), + loanTotalAmount = 7000.0, + amountPaid = 8000.0, + percentPaid = 0.8 + ), + ), + accounts = persistentListOf(), + totalOweAmount = "1000.00 INR", + totalOwedAmount = "1500.0 INR", + loanModalData = null, reorderModalVisible = false, selectedAccount = null, paidOffLoanVisibility = true, + screenMode = LoanScreenMode.NonTabularMode, dateTime = Instant.now() ) IvyWalletPreview(theme) { @@ -480,10 +608,19 @@ private fun Preview(theme: Theme = Theme.LIGHT) { /** For screenshot testing */ @Composable -fun LoanScreenUiTest(isDark: Boolean) { +fun LoanScreenTabularModeUiTest(isDark: Boolean) { + val theme = when (isDark) { + true -> Theme.DARK + false -> Theme.LIGHT + } + PreviewInTabularMode(theme) +} + +@Composable +fun LoanScreenNonTabularModeUiTest(isDark: Boolean) { val theme = when (isDark) { true -> Theme.DARK false -> Theme.LIGHT } - Preview(theme) + PreviewInNonTabularMode(theme) } \ No newline at end of file diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/LoanBottomBar.kt b/screen/loans/src/main/java/com/ivy/loans/loan/NonTabularLoanBottomBar.kt similarity index 93% rename from screen/loans/src/main/java/com/ivy/loans/loan/LoanBottomBar.kt rename to screen/loans/src/main/java/com/ivy/loans/loan/NonTabularLoanBottomBar.kt index ae3c8d466d..8958cf673e 100644 --- a/screen/loans/src/main/java/com/ivy/loans/loan/LoanBottomBar.kt +++ b/screen/loans/src/main/java/com/ivy/loans/loan/NonTabularLoanBottomBar.kt @@ -21,7 +21,7 @@ import com.ivy.wallet.ui.theme.components.IvyButton import com.ivy.wallet.ui.theme.components.IvyCircleButton @Composable -internal fun BoxWithConstraintsScope.LoanBottomBar( +internal fun BoxWithConstraintsScope.NonTabularLoanBottomBar( isPaidOffLoanVisible: Boolean, onClose: () -> Unit, onAdd: () -> Unit, @@ -54,7 +54,7 @@ internal fun BoxWithConstraintsScope.LoanBottomBar( @Preview @Composable -private fun PreviewBottomBar() { +private fun PreviewNonTabularBottomBar() { IvyWalletPreview { Column( Modifier @@ -63,7 +63,7 @@ private fun PreviewBottomBar() { ) { } - LoanBottomBar( + NonTabularLoanBottomBar( isPaidOffLoanVisible = false, onAdd = {}, onClose = {}, diff --git a/screen/loans/src/main/java/com/ivy/loans/loan/TabularLoanBottomBar.kt b/screen/loans/src/main/java/com/ivy/loans/loan/TabularLoanBottomBar.kt new file mode 100644 index 0000000000..b7c195fe69 --- /dev/null +++ b/screen/loans/src/main/java/com/ivy/loans/loan/TabularLoanBottomBar.kt @@ -0,0 +1,167 @@ +package com.ivy.loans.loan + +import androidx.annotation.DrawableRes +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxWithConstraintsScope +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.navigationBarsPadding +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.zIndex +import com.ivy.design.l0_system.UI +import com.ivy.design.l0_system.style +import com.ivy.legacy.IvyWalletPreview +import com.ivy.legacy.ivyWalletCtx +import com.ivy.legacy.utils.navigationBarInset +import com.ivy.legacy.utils.toDensityPx +import com.ivy.ui.R +import com.ivy.wallet.ui.theme.Blue +import com.ivy.wallet.ui.theme.GradientPurple +import com.ivy.wallet.ui.theme.Green +import com.ivy.wallet.ui.theme.Purple +import com.ivy.wallet.ui.theme.White +import com.ivy.wallet.ui.theme.components.IvyCircleButton +import com.ivy.wallet.ui.theme.components.IvyIcon +import com.ivy.wallet.ui.theme.pureBlur +import kotlin.math.roundToInt + +val FAB_BUTTON_SIZE = 56.dp +const val ZINDEX = 200f + +@Composable +internal fun BoxWithConstraintsScope.TabularLoanBottomBar( + tab: LoanTab, + selectTab: (LoanTab) -> Unit, + onAdd: () -> Unit +) { + Row( + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + .background(pureBlur()) + .navigationBarsPadding(), + verticalAlignment = Alignment.CenterVertically + ) { + Tab( + icon = R.drawable.ic_custom_loan_s, + name = "Pending", + selected = tab == LoanTab.PENDING, + selectedColor = Purple + ) { + selectTab(LoanTab.PENDING) + } + + Spacer(Modifier.width(FAB_BUTTON_SIZE)) + + Tab( + icon = R.drawable.ic_custom_loan_s, + name = "Completed", + selected = tab == LoanTab.COMPLETED, + selectedColor = Green + ) { + selectTab(LoanTab.COMPLETED) + } + } + + val ivyContext = ivyWalletCtx() + val fabStartX = ivyContext.screenWidth / 2 - FAB_BUTTON_SIZE.toDensityPx() / 2 + val fabStartY = ivyContext.screenHeight - navigationBarInset() - + 30.dp.toDensityPx() - FAB_BUTTON_SIZE.toDensityPx() + + IvyCircleButton( + modifier = Modifier + .layout { measurable, constraints -> + val placeable = measurable.measure(constraints) + layout(placeable.width, placeable.height) { + placeable.place( + x = fabStartX.roundToInt(), + y = fabStartY.roundToInt() + ) + } + } + .size(FAB_BUTTON_SIZE) + .zIndex(ZINDEX), + backgroundPadding = 8.dp, + icon = R.drawable.ic_add, + backgroundGradient = GradientPurple, + hasShadow = true, + tint = White + ) { + onAdd() + } +} + +@Composable +private fun RowScope.Tab( + @DrawableRes icon: Int, + name: String, + selected: Boolean, + selectedColor: Color, + onClick: () -> Unit, +) { + Row( + modifier = Modifier + .weight(1f) + .clip(UI.shapes.rFull) + .clickable(onClick = onClick) + .padding(top = 12.dp, bottom = 16.dp) + .testTag(name.lowercase()), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically + ) { + IvyIcon( + icon = icon, + tint = if (selected) selectedColor else UI.colors.pureInverse + ) + + if (selected) { + Spacer(modifier = Modifier.width(4.dp)) + + Text( + text = name, + style = UI.typo.c.style( + fontWeight = FontWeight.Bold, + color = selectedColor + ) + ) + } + } +} + +@Preview +@Composable +private fun PreviewTabularBottomBar() { + IvyWalletPreview { + Column( + Modifier + .fillMaxSize() + .background(Blue) + ) { + } + + TabularLoanBottomBar( + tab = LoanTab.PENDING, + selectTab = {}, + onAdd = {} + ) + } +} diff --git a/screen/loans/src/test/java/com/ivy/loans/LoanScreenPaparazziTest.kt b/screen/loans/src/test/java/com/ivy/loans/LoanScreenPaparazziTest.kt new file mode 100644 index 0000000000..2c3188fb95 --- /dev/null +++ b/screen/loans/src/test/java/com/ivy/loans/LoanScreenPaparazziTest.kt @@ -0,0 +1,30 @@ +package com.ivy.loans + +import com.google.testing.junit.testparameterinjector.TestParameter +import com.google.testing.junit.testparameterinjector.TestParameterInjector +import com.ivy.loans.loan.LoanScreenNonTabularModeUiTest +import com.ivy.loans.loan.LoanScreenTabularModeUiTest +import com.ivy.ui.testing.PaparazziScreenshotTest +import com.ivy.ui.testing.PaparazziTheme +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(TestParameterInjector::class) +class LoanScreenPaparazziTest( + @TestParameter + private val theme: PaparazziTheme, +) : PaparazziScreenshotTest() { + @Test + fun `snapshot loanScreen tabular composable`() { + snapshot(theme) { + LoanScreenTabularModeUiTest(theme == PaparazziTheme.Dark) + } + } + + @Test + fun `snapshot loanScreen non tabular composable`() { + snapshot(theme) { + LoanScreenNonTabularModeUiTest(theme == PaparazziTheme.Dark) + } + } +} \ No newline at end of file diff --git a/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Dark].png b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Dark].png new file mode 100644 index 0000000000..742de51d17 Binary files /dev/null and b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Dark].png differ diff --git a/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Light].png b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Light].png new file mode 100644 index 0000000000..086172b9d9 Binary files /dev/null and b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen non tabular composable[Light].png differ diff --git a/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Dark].png b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Dark].png new file mode 100644 index 0000000000..2f4783c9ce Binary files /dev/null and b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Dark].png differ diff --git a/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Light].png b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Light].png new file mode 100644 index 0000000000..a6cfd486b4 Binary files /dev/null and b/screen/loans/src/test/snapshots/images/com.ivy.loans_LoanScreenPaparazziTest_snapshot loanScreen tabular composable[Light].png differ diff --git a/shared/domain/src/main/java/com/ivy/domain/features/Features.kt b/shared/domain/src/main/java/com/ivy/domain/features/Features.kt index fedb4c25f7..144268c025 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/Features.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/Features.kt @@ -7,6 +7,7 @@ interface Features { val showTitleSuggestions: BoolFeature val showCategorySearchBar: BoolFeature val hideTotalBalance: BoolFeature + val tabularLoanMode: BoolFeature val allFeatures: List } diff --git a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt index a946c8a23e..37a5584bb1 100644 --- a/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt +++ b/shared/domain/src/main/java/com/ivy/domain/features/IvyFeatures.kt @@ -46,6 +46,13 @@ class IvyFeatures @Inject constructor() : Features { defaultValue = false ) + override val tabularLoanMode = BoolFeature( + key = "tabular_loan_ui", + name = "Tabular Loan UI", + description = "Show Completed and Pending loans in separate tabs", + defaultValue = false + ) + override val allFeatures: List get() = listOf( sortCategoriesAlphabetically, @@ -53,6 +60,7 @@ class IvyFeatures @Inject constructor() : Features { compactCategoriesMode, showTitleSuggestions, showCategorySearchBar, - hideTotalBalance + hideTotalBalance, + tabularLoanMode ) } diff --git a/temp/legacy-code/src/main/java/com/ivy/legacy/IvyWalletCtx.kt b/temp/legacy-code/src/main/java/com/ivy/legacy/IvyWalletCtx.kt index ceefa80674..1a7ce96af4 100644 --- a/temp/legacy-code/src/main/java/com/ivy/legacy/IvyWalletCtx.kt +++ b/temp/legacy-code/src/main/java/com/ivy/legacy/IvyWalletCtx.kt @@ -1,7 +1,6 @@ package com.ivy.legacy import android.net.Uri -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -85,7 +84,7 @@ class IvyWalletCtx @Inject constructor() : IvyContext() { var accountsListState: LazyListState? = null @Deprecated("Legacy code. Don't use it, please.") - var loansScrollState: ScrollState = ScrollState(0) + var loanListState: LazyListState? = null @Deprecated("Legacy code. Don't use it, please.") var mainTab by mutableStateOf(com.ivy.legacy.data.model.MainTab.HOME) diff --git a/temp/legacy-code/src/main/java/com/ivy/legacy/legacy/ui/theme/IvyColors.kt b/temp/legacy-code/src/main/java/com/ivy/legacy/legacy/ui/theme/IvyColors.kt index 664f2ffaa5..b2f7da5ff7 100644 --- a/temp/legacy-code/src/main/java/com/ivy/legacy/legacy/ui/theme/IvyColors.kt +++ b/temp/legacy-code/src/main/java/com/ivy/legacy/legacy/ui/theme/IvyColors.kt @@ -62,6 +62,8 @@ val IvyDark = Color(0xFF352680) @Deprecated("Old design system. Use `:ivy-design` and Material3") val Purple1Dark = Color(0xFF622680) +val Purple = Color(0xFFA020F0) + @Deprecated("Old design system. Use `:ivy-design` and Material3") val GreenDark = Color(0xFF0A664F) @@ -84,6 +86,8 @@ val Transparent = Color(0x00000000) @Deprecated("Old design system. Use `:ivy-design` and Material3") val GradientRed = Gradient(Red, Color(0xFFFF99AB)) +val GradientPurple = Gradient(Purple, Color(0xFFED3EF7)) + @Deprecated("Old design system. Use `:ivy-design` and Material3") val GradientGreen = Gradient(Green, Color(0xFF49F2C8))