diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c9f59f4b90..576117b0ec 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -76,6 +76,7 @@ compose-animation = { module = "androidx.compose.animation:animation", version.r compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "compose" } compose-material3 = { module = "androidx.compose.material3:material3", version.ref = "compose-material3" } compose-material3-windowsize = { module = "androidx.compose.material3:material3-window-size-class", version.ref = "compose-material3" } +compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose" } compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "compose" } compose-runtime-livedate-temp = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose" } compose-ui = { module = "androidx.compose.ui:ui", version.ref = "compose" } @@ -190,6 +191,7 @@ compose = [ "compose-foundation", "compose-material3", "compose-material3-windowsize", + "compose-material-icons-extended", "compose-runtime", "compose-ui", "compose-activity", diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionEvent.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionEvent.kt index 2f3baad975..763eb689e1 100644 --- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionEvent.kt +++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionEvent.kt @@ -24,6 +24,7 @@ sealed interface EditTransactionEvent { data class OnSetTransactionType(val newTransactionType: TransactionType) : EditTransactionEvent data object OnPayPlannedPayment : EditTransactionEvent data object Delete : EditTransactionEvent + data object Duplicate : EditTransactionEvent data class CreateCategory(val data: CreateCategoryData) : EditTransactionEvent data class EditCategory(val updatedCategory: Category) : EditTransactionEvent data class CreateAccount(val data: CreateAccountData) : EditTransactionEvent diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt index 0370fbd973..3dd90b588c 100644 --- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt +++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionScreen.kt @@ -172,6 +172,9 @@ fun BoxWithConstraintsScope.EditTransactionScreen(screen: EditTransactionScreen) onDelete = { viewModel.onEvent(EditTransactionEvent.Delete) }, + onDuplicate = { + viewModel.onEvent(EditTransactionEvent.Duplicate) + }, onCreateAccount = { viewModel.onEvent(EditTransactionEvent.CreateAccount(it)) }, @@ -223,6 +226,7 @@ private fun BoxWithConstraintsScope.UI( onSave: (closeScreen: Boolean) -> Unit, onSetHasChanges: (hasChanges: Boolean) -> Unit, onDelete: () -> Unit, + onDuplicate: () -> Unit, onCreateAccount: (CreateAccountData) -> Unit, onExchangeRateChange: (Double?) -> Unit = { }, onTagOperation: (EditTransactionEvent.TagEvent) -> Unit = {}, @@ -290,7 +294,9 @@ private fun BoxWithConstraintsScope.UI( }, onChangeTransactionTypeModal = { changeTransactionTypeModalVisible = true - } + }, + showDuplicateButton = true, + onDuplicate = onDuplicate ) Spacer(Modifier.height(32.dp)) @@ -696,6 +702,7 @@ private fun BoxWithConstraintsScope.Preview(isDark: Boolean = false) { onSave = {}, onSetHasChanges = {}, onDelete = {}, + onDuplicate = {}, onCreateAccount = { }, onSetDate = {}, onSetTime = {}, diff --git a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt index 10ce2f4bb4..1b70a8af4e 100644 --- a/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt +++ b/screen/edit-transaction/src/main/java/com/ivy/transaction/EditTransactionViewModel.kt @@ -313,6 +313,7 @@ class EditTransactionViewModel @Inject constructor( is EditTransactionEvent.CreateAccount -> createAccount(event.data) is EditTransactionEvent.CreateCategory -> createCategory(event.data) EditTransactionEvent.Delete -> delete() + EditTransactionEvent.Duplicate -> duplicate() is EditTransactionEvent.EditCategory -> editCategory(event.updatedCategory) is EditTransactionEvent.OnAccountChanged -> onAccountChanged(event.newAccount) is EditTransactionEvent.OnAmountChanged -> onAmountChanged(event.newAmount) @@ -605,6 +606,24 @@ class EditTransactionViewModel @Inject constructor( } } + private fun duplicate() { + viewModelScope.launch { + ioThread { + loadedTransaction() + .copy( + id = UUID.randomUUID(), + dateTime = timeNowLocal() + ) + .toDomain(transactionMapper) + ?.let { + transactionRepo.save(it) + } + refreshWidget(WalletBalanceWidgetReceiver::class.java) + } + closeScreen() + } + } + private fun createCategory(data: CreateCategoryData) { viewModelScope.launch { categoryCreator.createCategory(data) { diff --git a/screen/planned-payments/src/main/java/com/ivy/planned/edit/EditPlannedScreen.kt b/screen/planned-payments/src/main/java/com/ivy/planned/edit/EditPlannedScreen.kt index 91dfe9b9b1..39efe51427 100644 --- a/screen/planned-payments/src/main/java/com/ivy/planned/edit/EditPlannedScreen.kt +++ b/screen/planned-payments/src/main/java/com/ivy/planned/edit/EditPlannedScreen.kt @@ -107,7 +107,9 @@ private fun BoxWithConstraintsScope.UI( }, onChangeTransactionTypeModal = { onEvent(EditPlannedScreenEvent.OnTransactionTypeModalVisible(true)) - } + }, + showDuplicateButton = false, + onDuplicate = {} ) Spacer(Modifier.height(32.dp)) diff --git a/temp/legacy-code/src/main/java/com/ivy/legacy/ui/component/edit/core/Toolbar.kt b/temp/legacy-code/src/main/java/com/ivy/legacy/ui/component/edit/core/Toolbar.kt index af2519ca39..87667f1dba 100644 --- a/temp/legacy-code/src/main/java/com/ivy/legacy/ui/component/edit/core/Toolbar.kt +++ b/temp/legacy-code/src/main/java/com/ivy/legacy/ui/component/edit/core/Toolbar.kt @@ -1,14 +1,27 @@ package com.ivy.wallet.ui.edit.core +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.sharp.CopyAll +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButtonDefaults +import androidx.compose.material3.OutlinedIconButton 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.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.ivy.base.model.TransactionType +import com.ivy.design.l0_system.UI +import com.ivy.design.l0_system.asBrush import com.ivy.navigation.navigation import com.ivy.ui.R import com.ivy.wallet.ui.theme.components.CloseButton @@ -24,6 +37,9 @@ fun Toolbar( onDeleteTrnModal: () -> Unit, onChangeTransactionTypeModal: () -> Unit, + + showDuplicateButton: Boolean, + onDuplicate: () -> Unit, ) { Row( verticalAlignment = Alignment.CenterVertically @@ -66,6 +82,30 @@ fun Toolbar( } if (initialTransactionId != null) { + if (showDuplicateButton) { + OutlinedIconButton( + modifier = Modifier + .size(48.dp) + .background(Color.Transparent, CircleShape) + .testTag("duplicate_button"), + shape = CircleShape, + colors = IconButtonDefaults.outlinedIconButtonColors() + .copy(contentColor = UI.colors.medium), + border = IconButtonDefaults.outlinedIconButtonBorder(enabled = true) + .copy(width = 2.dp, brush = UI.colors.medium.asBrush()), + onClick = onDuplicate + ) { + Icon( + modifier = Modifier.padding(6.dp), + imageVector = Icons.Sharp.CopyAll, + contentDescription = "duplicate_button", + tint = UI.colors.pureInverse + ) + } + + Spacer(Modifier.width(12.dp)) + } + DeleteButton( hasShadow = false ) {