From 04af4e1bbd7b98c3526538a9e7619f8ef359b441 Mon Sep 17 00:00:00 2001 From: Shihaam Abdul Rahman Date: Sun, 17 May 2026 21:39:42 +0500 Subject: [PATCH] add biometrics to confirm digalogbox --- .../sh/sar/basedbank/ui/home/HomeActivity.kt | 1 + .../sh/sar/basedbank/ui/home/MoreFragment.kt | 1 + .../ui/home/SettingsSecurityFragment.kt | 16 +++- .../sar/basedbank/ui/home/TransferFragment.kt | 79 ++++++++++++++----- .../res/layout/fragment_settings_security.xml | 61 +++++++++++--- app/src/main/res/menu/drawer_menu.xml | 12 ++- app/src/main/res/menu/more_nav_menu.xml | 3 + app/src/main/res/values/strings.xml | 5 +- 8 files changed, 140 insertions(+), 38 deletions(-) diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt index 3a72673..93e3365 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/HomeActivity.kt @@ -211,6 +211,7 @@ class HomeActivity : AppCompatActivity() { R.id.nav_settings -> show(SettingsFragment()) else -> Toast.makeText(this, R.string.work_in_progress, Toast.LENGTH_SHORT).show() } + binding.navigationView.setCheckedItem(itemId) val bottomNavIds = setOf(R.id.nav_dashboard, R.id.nav_accounts, R.id.nav_contacts, R.id.nav_transfer, R.id.nav_more) if (binding.bottomNavigation.visibility == View.VISIBLE && itemId in bottomNavIds) { suppressBottomNavCallback = true diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/MoreFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/MoreFragment.kt index 9d62aa9..baf65a3 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/MoreFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/MoreFragment.kt @@ -17,6 +17,7 @@ class MoreFragment : Fragment() { private data class NavItem(val id: Int, @DrawableRes val icon: Int, @StringRes val title: Int) private val items = listOf( + NavItem(R.id.nav_pay_mv_qr, R.drawable.ic_qr_scan, R.string.pay_mv_qr), NavItem(R.id.nav_activities, R.drawable.ic_nav_activities, R.string.nav_activities), NavItem(R.id.nav_transfer_history, R.drawable.ic_nav_transfer_history, R.string.nav_transfer_history), NavItem(R.id.nav_finances, R.drawable.ic_nav_finances, R.string.nav_finances), diff --git a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsSecurityFragment.kt b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsSecurityFragment.kt index e568108..6b7625a 100644 --- a/app/src/main/java/sh/sar/basedbank/ui/home/SettingsSecurityFragment.kt +++ b/app/src/main/java/sh/sar/basedbank/ui/home/SettingsSecurityFragment.kt @@ -36,9 +36,23 @@ class SettingsSecurityFragment : Fragment() { .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS if (canUseBiometrics) { binding.rowBiometrics.visibility = View.VISIBLE - binding.switchBiometrics.isChecked = prefs.getBoolean("biometrics_enabled", false) + + val unlockEnabled = prefs.getBoolean("biometrics_enabled", false) + binding.switchBiometrics.isChecked = unlockEnabled + binding.switchBiometricsTransfer.isChecked = prefs.getBoolean("biometrics_transfer_confirm", false) + binding.switchBiometricsTransfer.isEnabled = unlockEnabled + binding.switchBiometrics.setOnCheckedChangeListener { _, isChecked -> prefs.edit().putBoolean("biometrics_enabled", isChecked).apply() + binding.switchBiometricsTransfer.isEnabled = isChecked + if (!isChecked) { + binding.switchBiometricsTransfer.isChecked = false + prefs.edit().putBoolean("biometrics_transfer_confirm", false).apply() + } + } + + binding.switchBiometricsTransfer.setOnCheckedChangeListener { _, isChecked -> + prefs.edit().putBoolean("biometrics_transfer_confirm", isChecked).apply() } } 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 ab80e3a..b557e1d 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 @@ -19,6 +19,9 @@ import android.widget.LinearLayout import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.biometric.BiometricManager +import androidx.biometric.BiometricPrompt +import androidx.core.content.ContextCompat import androidx.core.widget.addTextChangedListener import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels @@ -562,29 +565,31 @@ class TransferFragment : Fragment() { val mainMsg = "Send $currency $amountStr to $destDisplay?\n\nFrom: ${src.accountBriefName} · ${src.accountNumber}" - val dialogBuilder = AlertDialog.Builder(requireContext()) - .setTitle(R.string.transfer) - .setPositiveButton(R.string.transfer_confirm) { _, _ -> - binding.btnTransfer.isEnabled = false - viewLifecycleOwner.lifecycleScope.launch { - val (ok, msg, receipt) = withContext(Dispatchers.IO) { - if (!isSrcBml) { - doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, destDisplay, amountStr, remarks, bankNameCapture) - } else { - doBmlTransfer(src, resolvedAccountNumber, destDisplay, amount, amountStr, remarks, isSrcCard, isDestMib, currency, allAccounts, allContacts) - } - } - binding.btnTransfer.isEnabled = true - if (ok && receipt != null) { - clearForm() - val activity = requireActivity() as HomeActivity - activity.refreshBalances(src) - activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar)) - } else if (!ok) { - Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show() + val doTransfer: () -> Unit = { + binding.btnTransfer.isEnabled = false + viewLifecycleOwner.lifecycleScope.launch { + val (ok, msg, receipt) = withContext(Dispatchers.IO) { + if (!isSrcBml) { + doMibTransfer(src, resolvedAccountNumber, resolvedRecipientName, destDisplay, amountStr, remarks, bankNameCapture) + } else { + doBmlTransfer(src, resolvedAccountNumber, destDisplay, amount, amountStr, remarks, isSrcCard, isDestMib, currency, allAccounts, allContacts) } } + binding.btnTransfer.isEnabled = true + if (ok && receipt != null) { + clearForm() + val activity = requireActivity() as HomeActivity + activity.refreshBalances(src) + activity.showWithBackStack(TransferReceiptFragment.newInstance(receipt, capturedToAvatar)) + } else if (!ok) { + Toast.makeText(requireContext(), msg, Toast.LENGTH_LONG).show() + } } + } + + val dialogBuilder = AlertDialog.Builder(requireContext()) + .setTitle(R.string.transfer) + .setPositiveButton(R.string.transfer_confirm) { _, _ -> doTransfer() } .setNegativeButton(android.R.string.cancel, null) if (isUsdToMvr) { @@ -606,7 +611,39 @@ class TransferFragment : Fragment() { } else { dialogBuilder.setMessage(mainMsg) } - dialogBuilder.show() + + 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() + ) + } + } } private fun doMibTransfer( diff --git a/app/src/main/res/layout/fragment_settings_security.xml b/app/src/main/res/layout/fragment_settings_security.xml index 3fce8fd..48c2d40 100644 --- a/app/src/main/res/layout/fragment_settings_security.xml +++ b/app/src/main/res/layout/fragment_settings_security.xml @@ -30,23 +30,60 @@ android:id="@+id/rowBiometrics" android:layout_width="match_parent" android:layout_height="wrap_content" - android:orientation="horizontal" - android:gravity="center_vertical" + android:orientation="vertical" android:layout_marginTop="16dp" android:visibility="gone"> - - + android:layout_height="wrap_content" + android:text="@string/settings_biometrics" + android:textAppearance="?attr/textAppearanceTitleSmall" + android:textColor="?attr/colorOnSurfaceVariant" + android:layout_marginBottom="8dp" /> + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/drawer_menu.xml b/app/src/main/res/menu/drawer_menu.xml index cbe55d2..d0d893d 100644 --- a/app/src/main/res/menu/drawer_menu.xml +++ b/app/src/main/res/menu/drawer_menu.xml @@ -5,12 +5,18 @@ - + + + diff --git a/app/src/main/res/menu/more_nav_menu.xml b/app/src/main/res/menu/more_nav_menu.xml index 2755ffc..c59310a 100644 --- a/app/src/main/res/menu/more_nav_menu.xml +++ b/app/src/main/res/menu/more_nav_menu.xml @@ -1,5 +1,8 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dfe9e4a..a3d747d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -103,7 +103,10 @@ Security Change PIN / Pattern - Use Biometrics + Use biometrics + To unlock app + Confirm transfer + Confirm Transfer Auto-lock Off 30s