unified pay with QR and tranfer confirm dialog box
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 5s
All checks were successful
Auto Tag on Version Change / check-version (push) Successful in 5s
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
@@ -239,8 +239,7 @@
|
||||
<string name="transfer_otp_code_hint">Verification code</string>
|
||||
|
||||
<!-- BML QR Pay -->
|
||||
<string name="bml_qr_pay_now">Pay Now</string>
|
||||
<string name="bml_qr_looking_up">Looking up merchant…</string>
|
||||
<string name="bml_qr_looking_up">Looking up merchant…</string>
|
||||
<string name="bml_qr_lookup_failed">Could not load merchant details</string>
|
||||
<string name="bml_qr_payment_success">Payment Successful</string>
|
||||
<string name="bml_qr_select_account">Select a BML account to pay from</string>
|
||||
|
||||
Reference in New Issue
Block a user