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