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 @@