Skip to content

Commit 3948aad

Browse files
committed
Prompt user to create backup before dis-/enabling biometric lock
1 parent 9e5386b commit 3948aad

File tree

4 files changed

+96
-57
lines changed

4 files changed

+96
-57
lines changed

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt

Lines changed: 93 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -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() {

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
373373
}
374374
}
375375

376-
fun exportBackup(uri: Uri) {
376+
fun exportBackup(uri: Uri, onComplete: (() -> Unit)? = null) {
377377
viewModelScope.launch {
378378
val exportedNotes =
379379
withContext(Dispatchers.IO) {
@@ -387,6 +387,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
387387
}
388388
val message = app.getQuantityString(R.plurals.exported_notes, exportedNotes)
389389
app.showToast(message)
390+
onComplete?.invoke()
390391
}
391392
}
392393

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
<string name="backup_periodic">Periodic Backups</string>
4242
<string name="backup_periodic_hint">By enabling this, backups are automatically created in the configured Backups Folder.\nThis may not work if you have power saving mode enabled</string>
4343
<string name="behaviour">Behaviour</string>
44+
<string name="biometric_backup_advice">It is advised to export a full backup of all notes before dis-/enabling biometric lock, in case the encryption via biometric lock fails</string>
4445
<string name="biometric_lock">Lock app with device biometric or PIN</string>
4546
<string name="biometrics_disable_success">Biometric/PIN lock has been disabled</string>
4647
<string name="biometrics_failure">Failed to authenticate via Biometric/PIN</string>

app/translations.xlsx

112 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)