@@ -147,7 +147,16 @@ class SettingsFragment : Fragment() {
147147 exportBackupActivityResultLauncher =
148148 registerForActivityResult(ActivityResultContracts .StartActivityForResult ()) { result ->
149149 if (result.resultCode == RESULT_OK ) {
150- result.data?.data?.let { uri -> model.exportBackup(uri) }
150+ result.data?.data?.let { uri ->
151+ model.exportBackup(uri) {
152+ // Continue with pending biometric action (enable/disable) after export
153+ pendingBiometricContinuation?.invoke()
154+ pendingBiometricContinuation = null
155+ }
156+ }
157+ } else {
158+ // User canceled export picker; do not keep a stale continuation around
159+ pendingBiometricContinuation = null
151160 }
152161 }
153162 chooseBackupFolderActivityResultLauncher =
@@ -842,70 +851,98 @@ class SettingsFragment : Fragment() {
842851 )
843852 }
844853
854+ // Holds a continuation to run (enable/disable biometric) after user-triggered export completes
855+ private var pendingBiometricContinuation: (() -> Unit )? = null
856+
845857 private fun showEnableBiometricLock () {
846- showBiometricOrPinPrompt(
847- false ,
848- setupLockActivityResultLauncher,
849- R .string.enable_lock_title,
850- R .string.enable_lock_description,
851- onSuccess = { cipher ->
852- val app = (requireActivity().application as NotallyXApplication )
853- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
854- lifecycleScope.launch {
855- try {
856- model.enableBiometricLock(cipher)
857- } catch (e: EncryptionException ) {
858- app.log(TAG , throwable = e)
859- showErrorDialog(
860- e,
861- R .string.biometrics_setup_failure,
862- getString(
863- R .string.biometrics_setup_failure_encrypt,
864- getString(R .string.report_bug),
865- ),
866- )
867- return @launch
858+ showBiometricBackupAdvice {
859+ showBiometricOrPinPrompt(
860+ false ,
861+ setupLockActivityResultLauncher,
862+ R .string.enable_lock_title,
863+ R .string.enable_lock_description,
864+ onSuccess = { cipher ->
865+ val app = (requireActivity().application as NotallyXApplication )
866+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
867+ lifecycleScope.launch {
868+ try {
869+ model.enableBiometricLock(cipher)
870+ } catch (e: EncryptionException ) {
871+ app.log(TAG , throwable = e)
872+ showErrorDialog(
873+ e,
874+ R .string.biometrics_setup_failure,
875+ getString(
876+ R .string.biometrics_setup_failure_encrypt,
877+ getString(R .string.report_bug),
878+ ),
879+ )
880+ return @launch
881+ }
882+ app.locked.value = false
883+ showToast(R .string.biometrics_setup_success)
868884 }
869- app.locked.value = false
870- showToast(R .string.biometrics_setup_success)
871885 }
872- }
873- },
874- ) {
875- showBiometricsNotSetupDialog()
886+ },
887+ ) {
888+ showBiometricsNotSetupDialog()
889+ }
876890 }
877891 }
878892
879893 private fun showDisableBiometricLock () {
880- showBiometricOrPinPrompt(
881- true ,
882- disableLockActivityResultLauncher,
883- R .string.disable_lock_title,
884- R .string.disable_lock_description,
885- model.preferences.iv.value!! ,
886- onSuccess = { cipher ->
887- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
888- val app = (requireActivity().application as NotallyXApplication )
889- lifecycleScope.launch {
890- try {
891- model.disableBiometricLock(cipher)
892- } catch (e: DecryptionException ) {
893- app.log(TAG , throwable = e)
894- showErrorDialog(
895- e,
896- R .string.biometrics_setup_failure,
897- getString(
898- R .string.biometrics_setup_failure_decrypt,
899- getString(R .string.report_bug),
900- ),
901- )
902- return @launch
894+ showBiometricBackupAdvice {
895+ showBiometricOrPinPrompt(
896+ true ,
897+ disableLockActivityResultLauncher,
898+ R .string.disable_lock_title,
899+ R .string.disable_lock_description,
900+ model.preferences.iv.value!! ,
901+ onSuccess = { cipher ->
902+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .M ) {
903+ val app = (requireActivity().application as NotallyXApplication )
904+ lifecycleScope.launch {
905+ try {
906+ model.disableBiometricLock(cipher)
907+ } catch (e: DecryptionException ) {
908+ app.log(TAG , throwable = e)
909+ showErrorDialog(
910+ e,
911+ R .string.biometrics_setup_failure,
912+ getString(
913+ R .string.biometrics_setup_failure_decrypt,
914+ getString(R .string.report_bug),
915+ ),
916+ )
917+ return @launch
918+ }
919+ showToast(R .string.biometrics_disable_success)
903920 }
904- showToast(R .string.biometrics_disable_success)
905921 }
906- }
907- },
908- ) {}
922+ },
923+ ) {}
924+ }
925+ }
926+
927+ private fun showBiometricBackupAdvice (onContinue : () -> Unit ) {
928+ MaterialAlertDialogBuilder (requireContext())
929+ .setMessage(R .string.biometric_backup_advice)
930+ .setPositiveButton(R .string.export) { _, _ ->
931+ // After export finishes, continue with biometric action
932+ pendingBiometricContinuation = onContinue
933+ val intent =
934+ Intent (Intent .ACTION_CREATE_DOCUMENT )
935+ .apply {
936+ type = MIME_TYPE_ZIP
937+ addCategory(Intent .CATEGORY_OPENABLE )
938+ putExtra(Intent .EXTRA_TITLE , " NotallyX Backup.zip" )
939+ }
940+ .wrapWithChooser(requireContext())
941+ exportBackupActivityResultLauncher.launch(intent)
942+ }
943+ .setNeutralButton(R .string.continue_) { _, _ -> onContinue() }
944+ .setNegativeButton(R .string.cancel, null )
945+ .show()
909946 }
910947
911948 private fun showBiometricsNotSetupDialog () {
0 commit comments