Coverage Summary for Class: SurferDeleteTransactionDialogKt (com.georgeci.moneysurfer.uikit.components.transaction)

Class Class, % Method, % Branch, % Line, % Instruction, %
SurferDeleteTransactionDialogKt 100% (1/1) 66.7% (6/9) 50% (7/14) 92.9% (26/28) 92.5% (245/265)


 package com.georgeci.moneysurfer.uikit.components.transaction
 
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.tooling.preview.Preview
 import com.georgeci.moneysurfer.uikit.icons.SurferIcons
 import com.georgeci.moneysurfer.uikit.modifier.surferTestTagAsId
 import com.georgeci.moneysurfer.uikit.preview.SurferComponentPreview
 import com.georgeci.moneysurfer.uikit.semantics.SurferSemantics
 import com.georgeci.moneysurfer.uikit.theme.AppTheme
 import moneysurfer.uikit.generated.resources.Res
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_cancel
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_confirm
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_message
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_message_generic
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_split_message
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_split_message_generic
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_split_title
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_title
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_transfer_message
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_transfer_message_generic
 import moneysurfer.uikit.generated.resources.uikit_transaction_delete_transfer_title
 import org.jetbrains.compose.resources.StringResource
 import org.jetbrains.compose.resources.stringResource
 
 /**
  * Stable selectors for the shared delete dialog — see docs/testing/testing-strategy.md.
  *
  * The value keeps its `transactionDetails:` prefix even though the dialog is no longer owned by
  * that screen: it is the id two Maestro flows already tap (`08`, `17`), and renaming it would
  * break them for nothing. The prefix names where the flows first meet this dialog, not where the
  * composable lives.
  */
 object SurferDeleteTransactionDialogTestTags {
     const val Confirm = "transactionDetails:confirmDelete"
 }
 
 /**
  * The one "delete this transaction?" dialog. Deleting is the same act wherever it is started —
  * the details screen, edit mode on the creation screen, or a swipe on any of the three
  * transaction lists — so a second dialog with its own wording (or its own idea of what Cancel
  * does) would be two answers to one question.
  *
  * It lives in uikit rather than in the transaction feature because those lists span three feature
  * modules, and uikit is the only place all three can see.
  *
  * @param titleOrNull the row's title, quoted back so the user can see which one is meant; null
  *   falls back to the generic wording rather than quoting an empty string.
  * @param isTransfer whether the row is one leg of a transfer, which changes the copy: a transfer
  *   is deleted whole, so the dialog has to say the money disappears from *both* accounts before
  *   the user agrees to it.
  * @param isSplit whether the row stands for a receipt split across categories, for the same
  *   reason: the delete takes every leg of it, so a user who swiped what looks like one row has to
  *   be told several transactions are about to go. Ignored when [isTransfer] is set — a transfer leg
  *   is never split, and a caller passing both is naming a state the app cannot produce.
  */
 @Composable
 fun SurferDeleteTransactionDialog(
     titleOrNull: String?,
     onConfirm: () -> Unit,
     onDismiss: () -> Unit,
     isTransfer: Boolean = false,
     isSplit: Boolean = false,
 ) {
     val variant = DeleteVariant.of(isTransfer = isTransfer, isSplit = isSplit)
     AlertDialog(
         onDismissRequest = onDismiss,
         icon = {
             Icon(
                 imageVector = SurferIcons.Delete,
                 contentDescription = SurferSemantics.Decorative,
                 tint = AppTheme.materialColors.error,
             )
         },
         title = {
             Text(
                 text = stringResource(variant.title),
                 textAlign = TextAlign.Center,
             )
         },
         text = {
             Text(text = deleteMessage(titleOrNull, variant), textAlign = TextAlign.Center)
         },
         confirmButton = {
             Button(
                 onClick = onConfirm,
                 // The dialog is its own window, so the hosting screen's `surferTestTagAsId()`
                 // does not reach in here — it has to be applied again alongside the tag.
                 modifier = Modifier
                     .surferTestTagAsId()
                     .testTag(SurferDeleteTransactionDialogTestTags.Confirm),
                 colors = ButtonDefaults.buttonColors(
                     containerColor = AppTheme.materialColors.error,
                     contentColor = AppTheme.materialColors.onError,
                 ),
             ) {
                 Text(stringResource(Res.string.uikit_transaction_delete_confirm))
             }
         },
         dismissButton = {
             TextButton(onClick = onDismiss) {
                 Text(stringResource(Res.string.uikit_transaction_delete_cancel))
             }
         },
     )
 }
 
 /**
  * What is actually being deleted, which is what the copy has to say. Two of the three take more
  * than the row the user pointed at with them, and that is the whole reason the wording differs.
  */
 private enum class DeleteVariant(
     val title: StringResource,
     val message: StringResource,
     val genericMessage: StringResource,
 ) {
     Single(
         title = Res.string.uikit_transaction_delete_title,
         message = Res.string.uikit_transaction_delete_message,
         genericMessage = Res.string.uikit_transaction_delete_message_generic,
     ),
     Transfer(
         title = Res.string.uikit_transaction_delete_transfer_title,
         message = Res.string.uikit_transaction_delete_transfer_message,
         genericMessage = Res.string.uikit_transaction_delete_transfer_message_generic,
     ),
     Split(
         title = Res.string.uikit_transaction_delete_split_title,
         message = Res.string.uikit_transaction_delete_split_message,
         genericMessage = Res.string.uikit_transaction_delete_split_message_generic,
     ),
     ;
 
     companion object {
         fun of(isTransfer: Boolean, isSplit: Boolean): DeleteVariant = when {
             isTransfer -> Transfer
             isSplit -> Split
             else -> Single
         }
     }
 }
 
 /** One sentence, quoting the row's title when there is one to quote. */
 @Composable
 private fun deleteMessage(titleOrNull: String?, variant: DeleteVariant): String =
     if (titleOrNull != null) {
         stringResource(variant.message, titleOrNull)
     } else {
         stringResource(variant.genericMessage)
     }
 
 @Preview
 @Composable
 private fun SurferDeleteTransactionDialogPreview() {
     SurferComponentPreview {
         SurferDeleteTransactionDialog(
             titleOrNull = "Starbucks",
             onConfirm = {},
             onDismiss = {},
         )
     }
 }
 
 @Preview
 @Composable
 private fun SurferDeleteTransactionDialogSplitPreview() {
     SurferComponentPreview {
         SurferDeleteTransactionDialog(
             titleOrNull = "Pyaterochka",
             onConfirm = {},
             onDismiss = {},
             isSplit = true,
         )
     }
 }
 
 @Preview
 @Composable
 private fun SurferDeleteTransactionDialogTransferPreview() {
     SurferComponentPreview {
         SurferDeleteTransactionDialog(
             titleOrNull = "Savings top-up",
             onConfirm = {},
             onDismiss = {},
             isTransfer = true,
         )
     }
 }