diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/BmlQrPayFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/BmlQrPayFragment.kt index faf47d1..4058ddf 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/BmlQrPayFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/BmlQrPayFragment.kt @@ -197,7 +197,7 @@ class BmlQrPayFragment : Fragment() { val currency = info.currency MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.bml_qr_pay_now) + .setTitle(R.string.transfer) .setMessage("Pay $currency ${"%.2f".format(amount)} to ${info.merchantName}?\n\nFrom: ${account.accountBriefName} · ${account.accountNumber}") .setPositiveButton(R.string.transfer_confirm) { _, _ -> executePay(account, debitAccount, info.requestId, amount, currency, info.merchantName) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt index 8744205..b287802 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/TransferFragment.kt @@ -709,8 +709,54 @@ class TransferFragment : Fragment() { // ── Transfer ────────────────────────────────────────────────────────────── + /** Shared confirm dialog with optional biometric gate, used for both transfers and QR payments. */ + private fun showConfirmWithBiometric( + title: String, + message: String? = null, + customView: android.view.View? = null, + biometricSubtitle: String, + onConfirmed: () -> Unit + ) { + val builder = MaterialAlertDialogBuilder(requireContext()) + .setTitle(title) + .setPositiveButton(R.string.transfer_confirm) { _, _ -> onConfirmed() } + .setNegativeButton(android.R.string.cancel, null) + if (customView != null) builder.setView(customView) else builder.setMessage(message) + val dialog = builder.show() + val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE) + val biometricTransferConfirm = prefs.getBoolean("biometrics_transfer_confirm", false) + val canAuth = BiometricManager.from(requireContext()) + .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS + if (biometricTransferConfirm && canAuth) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { + val prompt = BiometricPrompt(this, ContextCompat.getMainExecutor(requireContext()), + object : BiometricPrompt.AuthenticationCallback() { + override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { + dialog.dismiss() + onConfirmed() + } + override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { + if (errorCode != BiometricPrompt.ERROR_CANCELED && + errorCode != BiometricPrompt.ERROR_USER_CANCELED && + errorCode != BiometricPrompt.ERROR_NEGATIVE_BUTTON) { + Toast.makeText(requireContext(), errString, Toast.LENGTH_SHORT).show() + } + } + override fun onAuthenticationFailed() { /* keep dialog open */ } + }) + prompt.authenticate( + BiometricPrompt.PromptInfo.Builder() + .setTitle(getString(R.string.biometric_transfer_title)) + .setSubtitle(biometricSubtitle) + .setNegativeButtonText(getString(android.R.string.cancel)) + .build() + ) + } + } + } + private fun initiateTransfer() { - // BML QR merchant payment — completely separate flow + // BML QR merchant payment — uses shared confirm dialog, no receipt bmlQrInfo?.let { info -> val src = selectedAccount ?: run { Toast.makeText(requireContext(), R.string.transfer_session_unavailable, Toast.LENGTH_SHORT).show() @@ -724,14 +770,12 @@ class TransferFragment : Fragment() { Toast.makeText(requireContext(), R.string.transfer_missing_internal_id, Toast.LENGTH_SHORT).show() return } - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.bml_qr_pay_now) - .setMessage("Pay ${info.currency} ${"%.2f".format(amount)} to ${info.merchantName}?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}") - .setPositiveButton(R.string.transfer_confirm) { _, _ -> - executeBmlQrPayment(src, debitAccount, info, amount) - } - .setNegativeButton(android.R.string.cancel, null) - .show() + showConfirmWithBiometric( + title = getString(R.string.transfer), + message = "Pay ${info.currency} ${"%.2f".format(amount)} to ${info.merchantName}?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}", + biometricSubtitle = "${info.currency} ${"%.2f".format(amount)} → ${info.merchantName}", + onConfirmed = { executeBmlQrPayment(src, debitAccount, info, amount) } + ) return } @@ -820,30 +864,21 @@ class TransferFragment : Fragment() { } } - val dialogBuilder = MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.transfer) - .setPositiveButton(R.string.transfer_confirm) { _, _ -> doTransfer() } - .setNegativeButton(android.R.string.cancel, null) - - if (isUsdToMvr || isSrcCredit) { + val warningView: android.view.View? = if (isUsdToMvr || isSrcCredit) { val ctx = requireContext() val dp = resources.displayMetrics.density - val container = LinearLayout(ctx).apply { + LinearLayout(ctx).apply { orientation = LinearLayout.VERTICAL setPadding((24 * dp).toInt(), (16 * dp).toInt(), (24 * dp).toInt(), 0) - } - container.addView(TextView(ctx).apply { text = mainMsg }) - if (isUsdToMvr) { - container.addView(TextView(ctx).apply { + addView(TextView(ctx).apply { text = mainMsg }) + if (isUsdToMvr) addView(TextView(ctx).apply { text = "⚠ You are transferring from a USD account to an MVR account. The currency will be converted at the bank's rate and this cannot be reversed!" setTextColor(Color.RED) textSize = 16f typeface = Typeface.DEFAULT_BOLD setPadding(0, (16 * dp).toInt(), 0, 0) }) - } - if (isSrcCredit) { - container.addView(TextView(ctx).apply { + if (isSrcCredit) addView(TextView(ctx).apply { text = "⚠ Transferring from a credit card is treated as a cash advance. Cash advance fees will be charged on the 10th of the month." setTextColor(Color.RED) textSize = 16f @@ -851,43 +886,14 @@ class TransferFragment : Fragment() { setPadding(0, (16 * dp).toInt(), 0, 0) }) } - dialogBuilder.setView(container) - } else { - dialogBuilder.setMessage(mainMsg) - } - - val dialog = dialogBuilder.show() - - val prefs = requireContext().getSharedPreferences("prefs", Context.MODE_PRIVATE) - val biometricTransferConfirm = prefs.getBoolean("biometrics_transfer_confirm", false) - val canAuth = BiometricManager.from(requireContext()) - .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS - if (biometricTransferConfirm && canAuth) { - dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener { - val prompt = BiometricPrompt(this, ContextCompat.getMainExecutor(requireContext()), - object : BiometricPrompt.AuthenticationCallback() { - override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) { - dialog.dismiss() - doTransfer() - } - override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { - if (errorCode != BiometricPrompt.ERROR_CANCELED && - errorCode != BiometricPrompt.ERROR_USER_CANCELED && - errorCode != BiometricPrompt.ERROR_NEGATIVE_BUTTON) { - Toast.makeText(requireContext(), errString, Toast.LENGTH_SHORT).show() - } - } - override fun onAuthenticationFailed() { /* keep dialog open */ } - }) - prompt.authenticate( - BiometricPrompt.PromptInfo.Builder() - .setTitle(getString(R.string.biometric_transfer_title)) - .setSubtitle("$currency $amountStr → $destDisplay") - .setNegativeButtonText(getString(android.R.string.cancel)) - .build() - ) - } - } + } else null + showConfirmWithBiometric( + title = getString(R.string.transfer), + message = if (warningView == null) mainMsg else null, + customView = warningView, + biometricSubtitle = "$currency $amountStr → $destDisplay", + onConfirmed = { doTransfer() } + ) } private fun executeBmlQrPayment( diff --git a/app/src/main/res/layout/fragment_bml_qr_pay.xml b/app/src/main/res/layout/fragment_bml_qr_pay.xml index 564b8e2..afdcf17 100644 --- a/app/src/main/res/layout/fragment_bml_qr_pay.xml +++ b/app/src/main/res/layout/fragment_bml_qr_pay.xml @@ -203,7 +203,7 @@ android:id="@+id/btnPay" android:layout_width="match_parent" android:layout_height="wrap_content" - android:text="@string/bml_qr_pay_now" + android:text="@string/transfer" android:enabled="false" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c0d2130..a6a1f09 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -239,8 +239,7 @@ Verification code - Pay Now - Looking up merchant… +Looking up merchant… Could not load merchant details Payment Successful Select a BML account to pay from