mirror of
https://github.com/PhilKes/NotallyX.git
synced 2025-06-28 12:19:55 +00:00
Merge pull request #238 from PhilKes/fix/user-unenrolls-biometric
Handle user un-enrolling device biometrics while biometric lock is enabled
This commit is contained in:
commit
b55c01c94a
13 changed files with 222 additions and 145 deletions
|
@ -3,19 +3,27 @@ package com.philkes.notallyx.presentation.activity
|
|||
import android.app.Activity
|
||||
import android.app.KeyguardManager
|
||||
import android.content.Intent
|
||||
import android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT
|
||||
import android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.view.View.INVISIBLE
|
||||
import android.view.View.VISIBLE
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.viewbinding.ViewBinding
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.philkes.notallyx.NotallyXApplication
|
||||
import com.philkes.notallyx.R
|
||||
import com.philkes.notallyx.presentation.showToast
|
||||
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
|
||||
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
||||
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
|
||||
import com.philkes.notallyx.utils.security.disableBiometricLock
|
||||
import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt
|
||||
|
||||
abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
||||
|
@ -26,6 +34,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
|||
|
||||
protected lateinit var binding: T
|
||||
protected lateinit var preferences: NotallyXPreferences
|
||||
protected val baseModel: BaseNoteModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -68,8 +77,41 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
|
|||
biometricAuthenticationActivityResultLauncher,
|
||||
R.string.unlock,
|
||||
onSuccess = { unlock() },
|
||||
) {
|
||||
finish()
|
||||
) { errorCode ->
|
||||
when (errorCode) {
|
||||
BIOMETRIC_ERROR_NO_BIOMETRICS -> {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.unlock_with_biometrics_not_setup)
|
||||
.setPositiveButton(R.string.disable) { _, _ ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
disableBiometricLock(baseModel)
|
||||
}
|
||||
show()
|
||||
}
|
||||
.setNegativeButton(R.string.tap_to_set_up) { _, _ ->
|
||||
val intent =
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
Intent(Settings.ACTION_BIOMETRIC_ENROLL)
|
||||
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
Intent(Settings.ACTION_FINGERPRINT_ENROLL)
|
||||
} else {
|
||||
Intent(Settings.ACTION_SECURITY_SETTINGS)
|
||||
}
|
||||
startActivity(intent)
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
BIOMETRIC_ERROR_HW_NOT_PRESENT -> {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
disableBiometricLock(baseModel)
|
||||
showToast(R.string.biometrics_disable_success)
|
||||
}
|
||||
show()
|
||||
}
|
||||
|
||||
else -> finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import android.widget.Toast
|
|||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.widget.doAfterTextChanged
|
||||
|
@ -77,11 +76,10 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
private lateinit var exportFileActivityResultLauncher: ActivityResultLauncher<Intent>
|
||||
private lateinit var exportNotesActivityResultLauncher: ActivityResultLauncher<Intent>
|
||||
|
||||
private val model: BaseNoteModel by viewModels()
|
||||
private val actionModeCancelCallback =
|
||||
object : OnBackPressedCallback(true) {
|
||||
override fun handleOnBackPressed() {
|
||||
model.actionMode.close(true)
|
||||
baseModel.actionMode.close(true)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -159,10 +157,10 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
.setCheckable(true)
|
||||
.setIcon(R.drawable.settings)
|
||||
}
|
||||
model.preferences.labelsHiddenInNavigation.observe(this) { hiddenLabels ->
|
||||
hideLabelsInNavigation(hiddenLabels, model.preferences.maxLabels.value)
|
||||
baseModel.preferences.labelsHiddenInNavigation.observe(this) { hiddenLabels ->
|
||||
hideLabelsInNavigation(hiddenLabels, baseModel.preferences.maxLabels.value)
|
||||
}
|
||||
model.preferences.maxLabels.observe(this) { maxLabels ->
|
||||
baseModel.preferences.maxLabels.observe(this) { maxLabels ->
|
||||
binding.NavigationView.menu.setupLabelsMenuItems(labels, maxLabels)
|
||||
}
|
||||
}
|
||||
|
@ -201,7 +199,10 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
} else null
|
||||
configuration = AppBarConfiguration(binding.NavigationView.menu, binding.DrawerLayout)
|
||||
setupActionBarWithNavController(navController, configuration)
|
||||
hideLabelsInNavigation(model.preferences.labelsHiddenInNavigation.value, maxLabelsToDisplay)
|
||||
hideLabelsInNavigation(
|
||||
baseModel.preferences.labelsHiddenInNavigation.value,
|
||||
maxLabelsToDisplay,
|
||||
)
|
||||
}
|
||||
|
||||
private fun hideLabelsInNavigation(hiddenLabels: Set<String>, maxLabelsToDisplay: Int) {
|
||||
|
@ -218,7 +219,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
}
|
||||
|
||||
private fun setupActionMode() {
|
||||
binding.ActionMode.setNavigationOnClickListener { model.actionMode.close(true) }
|
||||
binding.ActionMode.setNavigationOnClickListener { baseModel.actionMode.close(true) }
|
||||
|
||||
val transition =
|
||||
MaterialFade().apply {
|
||||
|
@ -230,7 +231,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
excludeTarget(binding.NavigationView, true)
|
||||
}
|
||||
|
||||
model.actionMode.enabled.observe(this) { enabled ->
|
||||
baseModel.actionMode.enabled.observe(this) { enabled ->
|
||||
TransitionManager.beginDelayedTransition(binding.RelativeLayout, transition)
|
||||
if (enabled) {
|
||||
binding.Toolbar.visibility = View.GONE
|
||||
|
@ -245,23 +246,23 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
}
|
||||
|
||||
val menu = binding.ActionMode.menu
|
||||
model.folder.observe(this@MainActivity, ModelFolderObserver(menu, model))
|
||||
baseModel.folder.observe(this@MainActivity, ModelFolderObserver(menu, baseModel))
|
||||
}
|
||||
|
||||
private fun moveNotes(folderTo: Folder) {
|
||||
val folderFrom = model.actionMode.getFirstNote().folder
|
||||
val ids = model.moveBaseNotes(folderTo)
|
||||
val folderFrom = baseModel.actionMode.getFirstNote().folder
|
||||
val ids = baseModel.moveBaseNotes(folderTo)
|
||||
Snackbar.make(
|
||||
findViewById(R.id.DrawerLayout),
|
||||
getQuantityString(folderTo.movedToResId(), ids.size),
|
||||
Snackbar.LENGTH_SHORT,
|
||||
)
|
||||
.apply { setAction(R.string.undo) { model.moveBaseNotes(ids, folderFrom) } }
|
||||
.apply { setAction(R.string.undo) { baseModel.moveBaseNotes(ids, folderFrom) } }
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun share() {
|
||||
val baseNote = model.actionMode.getFirstNote()
|
||||
val baseNote = baseModel.actionMode.getFirstNote()
|
||||
val body =
|
||||
when (baseNote.type) {
|
||||
Type.NOTE -> baseNote.body.applySpans(baseNote.spans)
|
||||
|
@ -273,19 +274,19 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
private fun deleteForever() {
|
||||
MaterialAlertDialogBuilder(this)
|
||||
.setMessage(R.string.delete_selected_notes)
|
||||
.setPositiveButton(R.string.delete) { _, _ -> model.deleteSelectedBaseNotes() }
|
||||
.setPositiveButton(R.string.delete) { _, _ -> baseModel.deleteSelectedBaseNotes() }
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun label() {
|
||||
val baseNotes = model.actionMode.selectedNotes.values
|
||||
val baseNotes = baseModel.actionMode.selectedNotes.values
|
||||
lifecycleScope.launch {
|
||||
val labels = model.getAllLabels()
|
||||
val labels = baseModel.getAllLabels()
|
||||
if (labels.isNotEmpty()) {
|
||||
displaySelectLabelsDialog(labels, baseNotes)
|
||||
} else {
|
||||
model.actionMode.close(true)
|
||||
baseModel.actionMode.close(true)
|
||||
navigateWithAnimation(R.id.Labels)
|
||||
}
|
||||
}
|
||||
|
@ -340,15 +341,15 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
noteLabels
|
||||
}
|
||||
baseNotes.zip(updatedBaseNotesLabels).forEach { (baseNote, updatedLabels) ->
|
||||
model.updateBaseNoteLabels(updatedLabels, baseNote.id)
|
||||
baseModel.updateBaseNoteLabels(updatedLabels, baseNote.id)
|
||||
}
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun exportSelectedNotes(mimeType: ExportMimeType) {
|
||||
if (model.actionMode.count.value == 1) {
|
||||
val baseNote = model.actionMode.getFirstNote()
|
||||
if (baseModel.actionMode.count.value == 1) {
|
||||
val baseNote = baseModel.actionMode.getFirstNote()
|
||||
when (mimeType) {
|
||||
ExportMimeType.PDF -> {
|
||||
exportPdfFile(
|
||||
|
@ -392,7 +393,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
.apply { addCategory(Intent.CATEGORY_DEFAULT) }
|
||||
.wrapWithChooser(this@MainActivity)
|
||||
model.selectedExportMimeType = mimeType
|
||||
baseModel.selectedExportMimeType = mimeType
|
||||
exportNotesActivityResultLauncher.launch(intent)
|
||||
}
|
||||
}
|
||||
|
@ -425,7 +426,7 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
putExtra(Intent.EXTRA_TITLE, file.nameWithoutExtension!!)
|
||||
}
|
||||
.wrapWithChooser(this@MainActivity)
|
||||
model.selectedExportFile = file
|
||||
baseModel.selectedExportFile = file
|
||||
exportFileActivityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
|
@ -521,22 +522,26 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
|
||||
private fun setupSearch() {
|
||||
binding.EnterSearchKeyword.apply {
|
||||
setText(model.keyword)
|
||||
setText(baseModel.keyword)
|
||||
doAfterTextChanged { text ->
|
||||
model.keyword = requireNotNull(text).trim().toString()
|
||||
baseModel.keyword = requireNotNull(text).trim().toString()
|
||||
if (
|
||||
model.keyword.isNotEmpty() &&
|
||||
baseModel.keyword.isNotEmpty() &&
|
||||
navController.currentDestination?.id != R.id.Search
|
||||
) {
|
||||
val bundle =
|
||||
Bundle().apply { putSerializable(EXTRA_INITIAL_FOLDER, model.folder.value) }
|
||||
Bundle().apply {
|
||||
putSerializable(EXTRA_INITIAL_FOLDER, baseModel.folder.value)
|
||||
}
|
||||
navController.navigate(R.id.Search, bundle)
|
||||
}
|
||||
}
|
||||
setOnFocusChangeListener { v, hasFocus ->
|
||||
if (hasFocus && navController.currentDestination?.id != R.id.Search) {
|
||||
val bundle =
|
||||
Bundle().apply { putSerializable(EXTRA_INITIAL_FOLDER, model.folder.value) }
|
||||
Bundle().apply {
|
||||
putSerializable(EXTRA_INITIAL_FOLDER, baseModel.folder.value)
|
||||
}
|
||||
navController.navigate(R.id.Search, bundle)
|
||||
}
|
||||
}
|
||||
|
@ -547,13 +552,13 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
|
|||
exportFileActivityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
result.data?.data?.let { uri -> model.exportSelectedFileToUri(uri) }
|
||||
result.data?.data?.let { uri -> baseModel.exportSelectedFileToUri(uri) }
|
||||
}
|
||||
}
|
||||
exportNotesActivityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
result.data?.data?.let { uri -> model.exportSelectedNotesToFolder(uri) }
|
||||
result.data?.data?.let { uri -> baseModel.exportSelectedNotesToFolder(uri) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreference
|
|||
import com.philkes.notallyx.utils.Operations
|
||||
import com.philkes.notallyx.utils.Operations.catchNoBrowserInstalled
|
||||
import com.philkes.notallyx.utils.Operations.reportBug
|
||||
import com.philkes.notallyx.utils.security.decryptDatabase
|
||||
import com.philkes.notallyx.utils.security.disableBiometricLock
|
||||
import com.philkes.notallyx.utils.security.encryptDatabase
|
||||
import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt
|
||||
import com.philkes.notallyx.utils.wrapWithChooser
|
||||
|
@ -618,6 +618,10 @@ class SettingsFragment : Fragment() {
|
|||
model.savePreference(model.preferences.iv, cipher.iv)
|
||||
val passphrase = model.preferences.databaseEncryptionKey.init(cipher)
|
||||
encryptDatabase(requireContext(), passphrase)
|
||||
model.savePreference(
|
||||
model.preferences.fallbackDatabaseEncryptionKey,
|
||||
passphrase,
|
||||
)
|
||||
model.savePreference(model.preferences.biometricLock, BiometricLock.ENABLED)
|
||||
}
|
||||
val app = (activity?.application as NotallyXApplication)
|
||||
|
@ -638,11 +642,7 @@ class SettingsFragment : Fragment() {
|
|||
model.preferences.iv.value!!,
|
||||
onSuccess = { cipher ->
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
val encryptedPassphrase = model.preferences.databaseEncryptionKey.value
|
||||
val passphrase = cipher.doFinal(encryptedPassphrase)
|
||||
model.closeDatabase()
|
||||
decryptDatabase(requireContext(), passphrase)
|
||||
model.savePreference(model.preferences.biometricLock, BiometricLock.DISABLED)
|
||||
requireContext().disableBiometricLock(model, cipher)
|
||||
}
|
||||
showToast(R.string.biometrics_disable_success)
|
||||
},
|
||||
|
|
|
@ -85,7 +85,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
private val searchResultPos = NotNullLiveData(-1)
|
||||
private val searchResultsAmount = NotNullLiveData(-1)
|
||||
|
||||
internal val model: NotallyModel by viewModels()
|
||||
internal val notallyModel: NotallyModel by viewModels()
|
||||
internal lateinit var changeHistory: ChangeHistory
|
||||
|
||||
protected val undos: MutableList<View> = mutableListOf()
|
||||
|
@ -93,9 +93,9 @@ abstract class EditActivity(private val type: Type) :
|
|||
|
||||
override fun finish() {
|
||||
lifecycleScope.launch(Dispatchers.Main) {
|
||||
if (model.isEmpty()) {
|
||||
model.deleteBaseNote()
|
||||
} else if (model.isModified()) {
|
||||
if (notallyModel.isEmpty()) {
|
||||
notallyModel.deleteBaseNote()
|
||||
} else if (notallyModel.isModified()) {
|
||||
saveNote()
|
||||
}
|
||||
super.finish()
|
||||
|
@ -104,21 +104,21 @@ abstract class EditActivity(private val type: Type) :
|
|||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
outState.putLong("id", model.id)
|
||||
if (model.isModified()) {
|
||||
outState.putLong("id", notallyModel.id)
|
||||
if (notallyModel.isModified()) {
|
||||
lifecycleScope.launch { saveNote() }
|
||||
}
|
||||
}
|
||||
|
||||
open suspend fun saveNote() {
|
||||
model.modifiedTimestamp = System.currentTimeMillis()
|
||||
model.saveNote()
|
||||
WidgetProvider.sendBroadcast(application, longArrayOf(model.id))
|
||||
notallyModel.modifiedTimestamp = System.currentTimeMillis()
|
||||
notallyModel.saveNote()
|
||||
WidgetProvider.sendBroadcast(application, longArrayOf(notallyModel.id))
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
model.type = type
|
||||
notallyModel.type = type
|
||||
initialiseBinding()
|
||||
setContentView(binding.root)
|
||||
|
||||
|
@ -126,12 +126,14 @@ abstract class EditActivity(private val type: Type) :
|
|||
val persistedId = savedInstanceState?.getLong("id")
|
||||
val selectedId = intent.getLongExtra(Constants.SelectedBaseNote, 0L)
|
||||
val id = persistedId ?: selectedId
|
||||
model.setState(id)
|
||||
notallyModel.setState(id)
|
||||
|
||||
if (model.isNewNote && intent.action == Intent.ACTION_SEND) {
|
||||
if (notallyModel.isNewNote && intent.action == Intent.ACTION_SEND) {
|
||||
handleSharedNote()
|
||||
} else if (model.isNewNote) {
|
||||
intent.getStringExtra(Constants.SelectedLabel)?.let { model.setLabels(listOf(it)) }
|
||||
} else if (notallyModel.isNewNote) {
|
||||
intent.getStringExtra(Constants.SelectedLabel)?.let {
|
||||
notallyModel.setLabels(listOf(it))
|
||||
}
|
||||
}
|
||||
|
||||
setupToolbars()
|
||||
|
@ -152,7 +154,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
recordAudioActivityResultLauncher =
|
||||
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||
if (result.resultCode == RESULT_OK) {
|
||||
model.addAudio()
|
||||
notallyModel.addAudio()
|
||||
}
|
||||
}
|
||||
addImagesActivityResultLauncher =
|
||||
|
@ -162,11 +164,11 @@ abstract class EditActivity(private val type: Type) :
|
|||
val clipData = result.data?.clipData
|
||||
if (uri != null) {
|
||||
val uris = arrayOf(uri)
|
||||
model.addImages(uris)
|
||||
notallyModel.addImages(uris)
|
||||
} else if (clipData != null) {
|
||||
val uris =
|
||||
Array(clipData.itemCount) { index -> clipData.getItemAt(index).uri }
|
||||
model.addImages(uris)
|
||||
notallyModel.addImages(uris)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +184,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
)
|
||||
}
|
||||
if (!list.isNullOrEmpty()) {
|
||||
model.deleteImages(list)
|
||||
notallyModel.deleteImages(list)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -191,12 +193,12 @@ abstract class EditActivity(private val type: Type) :
|
|||
if (result.resultCode == RESULT_OK) {
|
||||
val list =
|
||||
result.data?.getStringArrayListExtra(SelectLabelsActivity.SELECTED_LABELS)
|
||||
if (list != null && list != model.labels) {
|
||||
model.setLabels(list)
|
||||
if (list != null && list != notallyModel.labels) {
|
||||
notallyModel.setLabels(list)
|
||||
Operations.bindLabels(
|
||||
binding.LabelGroup,
|
||||
model.labels,
|
||||
model.textSize,
|
||||
notallyModel.labels,
|
||||
notallyModel.textSize,
|
||||
paddingTop = true,
|
||||
)
|
||||
}
|
||||
|
@ -214,7 +216,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
)
|
||||
}
|
||||
if (audio != null) {
|
||||
model.deleteAudio(audio)
|
||||
notallyModel.deleteAudio(audio)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -225,11 +227,11 @@ abstract class EditActivity(private val type: Type) :
|
|||
val clipData = result.data?.clipData
|
||||
if (uri != null) {
|
||||
val uris = arrayOf(uri)
|
||||
model.addFiles(uris)
|
||||
notallyModel.addFiles(uris)
|
||||
} else if (clipData != null) {
|
||||
val uris =
|
||||
Array(clipData.itemCount) { index -> clipData.getItemAt(index).uri }
|
||||
model.addFiles(uris)
|
||||
notallyModel.addFiles(uris)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -276,7 +278,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
add(R.string.pin, R.drawable.pin, MenuItem.SHOW_AS_ACTION_ALWAYS) { pin() }
|
||||
bindPinned()
|
||||
|
||||
when (model.folder) {
|
||||
when (notallyModel.folder) {
|
||||
Folder.NOTES -> {
|
||||
add(R.string.delete, R.drawable.delete, MenuItem.SHOW_AS_ACTION_ALWAYS) {
|
||||
delete()
|
||||
|
@ -425,7 +427,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
protected fun createFolderActions() =
|
||||
when (model.folder) {
|
||||
when (notallyModel.folder) {
|
||||
Folder.NOTES ->
|
||||
listOf(
|
||||
Action(R.string.archive, R.drawable.archive, callback = ::archive),
|
||||
|
@ -449,20 +451,26 @@ abstract class EditActivity(private val type: Type) :
|
|||
|
||||
open fun setupListeners() {
|
||||
binding.EnterTitle.initHistory(changeHistory) { text ->
|
||||
model.title = text.trim().toString()
|
||||
notallyModel.title = text.trim().toString()
|
||||
}
|
||||
}
|
||||
|
||||
open fun setStateFromModel() {
|
||||
val (date, datePrefixResId) =
|
||||
when (preferences.notesSorting.value.sortedBy) {
|
||||
NotesSortBy.CREATION_DATE -> Pair(model.timestamp, R.string.creation_date)
|
||||
NotesSortBy.MODIFIED_DATE -> Pair(model.modifiedTimestamp, R.string.modified_date)
|
||||
NotesSortBy.CREATION_DATE -> Pair(notallyModel.timestamp, R.string.creation_date)
|
||||
NotesSortBy.MODIFIED_DATE ->
|
||||
Pair(notallyModel.modifiedTimestamp, R.string.modified_date)
|
||||
else -> Pair(null, null)
|
||||
}
|
||||
binding.Date.displayFormattedTimestamp(date, preferences.dateFormat.value, datePrefixResId)
|
||||
binding.EnterTitle.setText(model.title)
|
||||
Operations.bindLabels(binding.LabelGroup, model.labels, model.textSize, paddingTop = true)
|
||||
binding.EnterTitle.setText(notallyModel.title)
|
||||
Operations.bindLabels(
|
||||
binding.LabelGroup,
|
||||
notallyModel.labels,
|
||||
notallyModel.textSize,
|
||||
paddingTop = true,
|
||||
)
|
||||
|
||||
setColor()
|
||||
}
|
||||
|
@ -475,10 +483,10 @@ abstract class EditActivity(private val type: Type) :
|
|||
val body = charSequence ?: string
|
||||
|
||||
if (body != null) {
|
||||
model.body = Editable.Factory.getInstance().newEditable(body)
|
||||
notallyModel.body = Editable.Factory.getInstance().newEditable(body)
|
||||
}
|
||||
if (title != null) {
|
||||
model.title = title
|
||||
notallyModel.title = title
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -499,7 +507,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
private fun startRecordAudioActivity() {
|
||||
if (model.audioRoot != null) {
|
||||
if (notallyModel.audioRoot != null) {
|
||||
val intent = Intent(this, RecordAudioActivity::class.java)
|
||||
recordAudioActivityResultLauncher.launch(intent)
|
||||
} else showToast(R.string.insert_an_sd_card_audio)
|
||||
|
@ -518,7 +526,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
override fun addImages() {
|
||||
if (model.imageRoot != null) {
|
||||
if (notallyModel.imageRoot != null) {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_GET_CONTENT)
|
||||
.apply {
|
||||
|
@ -533,7 +541,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
override fun attachFiles() {
|
||||
if (model.filesRoot != null) {
|
||||
if (notallyModel.filesRoot != null) {
|
||||
val intent =
|
||||
Intent(Intent.ACTION_GET_CONTENT)
|
||||
.apply {
|
||||
|
@ -549,24 +557,24 @@ abstract class EditActivity(private val type: Type) :
|
|||
|
||||
override fun changeColor() {
|
||||
showColorSelectDialog { selectedColor ->
|
||||
model.color = selectedColor
|
||||
notallyModel.color = selectedColor
|
||||
setColor()
|
||||
}
|
||||
}
|
||||
|
||||
override fun changeLabels() {
|
||||
val intent = Intent(this, SelectLabelsActivity::class.java)
|
||||
intent.putStringArrayListExtra(SelectLabelsActivity.SELECTED_LABELS, model.labels)
|
||||
intent.putStringArrayListExtra(SelectLabelsActivity.SELECTED_LABELS, notallyModel.labels)
|
||||
selectLabelsActivityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
override fun share() {
|
||||
val body =
|
||||
when (type) {
|
||||
Type.NOTE -> model.body
|
||||
Type.LIST -> Operations.getBody(model.items.toMutableList())
|
||||
Type.NOTE -> notallyModel.body
|
||||
Type.LIST -> Operations.getBody(notallyModel.items.toMutableList())
|
||||
}
|
||||
Operations.shareNote(this, model.title, body)
|
||||
Operations.shareNote(this, notallyModel.title, body)
|
||||
}
|
||||
|
||||
private fun delete() {
|
||||
|
@ -584,11 +592,11 @@ abstract class EditActivity(private val type: Type) :
|
|||
private fun moveNote(toFolder: Folder) {
|
||||
val resultIntent =
|
||||
Intent().apply {
|
||||
putExtra(NOTE_ID, model.id)
|
||||
putExtra(FOLDER_FROM, model.folder.name)
|
||||
putExtra(NOTE_ID, notallyModel.id)
|
||||
putExtra(FOLDER_FROM, notallyModel.folder.name)
|
||||
putExtra(FOLDER_TO, toFolder.name)
|
||||
}
|
||||
model.folder = toFolder
|
||||
notallyModel.folder = toFolder
|
||||
setResult(RESULT_OK, resultIntent)
|
||||
finish()
|
||||
}
|
||||
|
@ -598,7 +606,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
.setMessage(R.string.delete_note_forever)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
lifecycleScope.launch {
|
||||
model.deleteBaseNote()
|
||||
notallyModel.deleteBaseNote()
|
||||
super.finish()
|
||||
}
|
||||
}
|
||||
|
@ -607,17 +615,17 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
fun pin() {
|
||||
model.pinned = !model.pinned
|
||||
notallyModel.pinned = !notallyModel.pinned
|
||||
bindPinned()
|
||||
}
|
||||
|
||||
private fun setupImages() {
|
||||
val imageAdapter =
|
||||
PreviewImageAdapter(model.imageRoot) { position ->
|
||||
PreviewImageAdapter(notallyModel.imageRoot) { position ->
|
||||
val intent =
|
||||
Intent(this, ViewImageActivity::class.java).apply {
|
||||
putExtra(ViewImageActivity.POSITION, position)
|
||||
putExtra(Constants.SelectedBaseNote, model.id)
|
||||
putExtra(Constants.SelectedBaseNote, notallyModel.id)
|
||||
}
|
||||
viewImagesActivityResultLauncher.launch(intent)
|
||||
}
|
||||
|
@ -656,7 +664,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
)
|
||||
}
|
||||
|
||||
model.images.observe(this) { list ->
|
||||
notallyModel.images.observe(this) { list ->
|
||||
imageAdapter.submitList(list)
|
||||
binding.ImagePreview.isVisible = list.isNotEmpty()
|
||||
binding.ImagePreviewPosition.isVisible = list.size > 1
|
||||
|
@ -666,13 +674,13 @@ abstract class EditActivity(private val type: Type) :
|
|||
private fun setupFiles() {
|
||||
val fileAdapter =
|
||||
PreviewFileAdapter({ fileAttachment ->
|
||||
if (model.filesRoot == null) {
|
||||
if (notallyModel.filesRoot == null) {
|
||||
return@PreviewFileAdapter
|
||||
}
|
||||
val intent =
|
||||
Intent(Intent.ACTION_VIEW)
|
||||
.apply {
|
||||
val file = File(model.filesRoot, fileAttachment.localName)
|
||||
val file = File(notallyModel.filesRoot, fileAttachment.localName)
|
||||
val uri = this@EditActivity.getUriForFile(file)
|
||||
setDataAndType(uri, fileAttachment.mimeType)
|
||||
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
|
@ -684,7 +692,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
.setMessage(getString(R.string.delete_file, fileAttachment.originalName))
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.setPositiveButton(R.string.delete) { _, _ ->
|
||||
model.deleteFiles(arrayListOf(fileAttachment))
|
||||
notallyModel.deleteFiles(arrayListOf(fileAttachment))
|
||||
}
|
||||
.show()
|
||||
return@PreviewFileAdapter true
|
||||
|
@ -696,7 +704,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
layoutManager =
|
||||
LinearLayoutManager(this@EditActivity, LinearLayoutManager.HORIZONTAL, false)
|
||||
}
|
||||
model.files.observe(this) { list ->
|
||||
notallyModel.files.observe(this) { list ->
|
||||
fileAdapter.submitList(list)
|
||||
val visible = list.isNotEmpty()
|
||||
binding.FilesPreview.apply {
|
||||
|
@ -741,7 +749,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
private fun setupAudios() {
|
||||
val adapter = AudioAdapter { position: Int ->
|
||||
if (position != -1) {
|
||||
val audio = model.audios.value[position]
|
||||
val audio = notallyModel.audios.value[position]
|
||||
val intent = Intent(this, PlayAudioActivity::class.java)
|
||||
intent.putExtra(PlayAudioActivity.AUDIO, audio)
|
||||
playAudioActivityResultLauncher.launch(intent)
|
||||
|
@ -749,7 +757,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
binding.AudioRecyclerView.adapter = adapter
|
||||
|
||||
model.audios.observe(this) { list ->
|
||||
notallyModel.audios.observe(this) { list ->
|
||||
adapter.submitList(list)
|
||||
binding.AudioHeader.isVisible = list.isNotEmpty()
|
||||
binding.AudioRecyclerView.isVisible = list.isNotEmpty()
|
||||
|
@ -757,7 +765,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
|
||||
open protected fun setColor() {
|
||||
val color = Operations.extractColor(model.color, this)
|
||||
val color = Operations.extractColor(notallyModel.color, this)
|
||||
binding.ScrollView.apply {
|
||||
setBackgroundColor(color)
|
||||
setControlsContrastColorForAllViews(color)
|
||||
|
@ -776,9 +784,9 @@ abstract class EditActivity(private val type: Type) :
|
|||
}
|
||||
}
|
||||
|
||||
val title = model.textSize.editTitleSize
|
||||
val date = model.textSize.displayBodySize
|
||||
val body = model.textSize.editBodySize
|
||||
val title = notallyModel.textSize.editTitleSize
|
||||
val date = notallyModel.textSize.displayBodySize
|
||||
val body = notallyModel.textSize.editBodySize
|
||||
|
||||
binding.EnterTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, title)
|
||||
binding.Date.setTextSize(TypedValue.COMPLEX_UNIT_SP, date)
|
||||
|
@ -787,8 +795,8 @@ abstract class EditActivity(private val type: Type) :
|
|||
setupImages()
|
||||
setupFiles()
|
||||
setupAudios()
|
||||
model.addingFiles.setupProgressDialog(this, R.string.adding_files)
|
||||
model.eventBus.observe(this) { event ->
|
||||
notallyModel.addingFiles.setupProgressDialog(this, R.string.adding_files)
|
||||
notallyModel.eventBus.observe(this) { event ->
|
||||
event.handle { errors -> displayFileErrors(errors) }
|
||||
}
|
||||
|
||||
|
@ -798,7 +806,7 @@ abstract class EditActivity(private val type: Type) :
|
|||
private fun bindPinned() {
|
||||
val icon: Int
|
||||
val title: Int
|
||||
if (model.pinned) {
|
||||
if (notallyModel.pinned) {
|
||||
icon = R.drawable.unpin
|
||||
title = R.string.unpin
|
||||
} else {
|
||||
|
|
|
@ -30,12 +30,12 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
|||
private lateinit var listManager: ListManager
|
||||
|
||||
override fun finish() {
|
||||
model.setItems(items.toMutableList())
|
||||
notallyModel.setItems(items.toMutableList())
|
||||
super.finish()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
model.setItems(items.toMutableList())
|
||||
notallyModel.setItems(items.toMutableList())
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
|
@ -92,7 +92,7 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
|||
override fun configureUI() {
|
||||
binding.EnterTitle.setOnNextAction { listManager.moveFocusToNext(-1) }
|
||||
|
||||
if (model.isNewNote || model.items.isEmpty()) {
|
||||
if (notallyModel.isNewNote || notallyModel.items.isEmpty()) {
|
||||
listManager.add(pushChange = false)
|
||||
}
|
||||
}
|
||||
|
@ -118,8 +118,8 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
|||
}
|
||||
adapter =
|
||||
ListItemAdapter(
|
||||
Operations.extractColor(model.color, this),
|
||||
model.textSize,
|
||||
Operations.extractColor(notallyModel.color, this),
|
||||
notallyModel.textSize,
|
||||
elevation,
|
||||
NotallyXPreferences.getInstance(application),
|
||||
listManager,
|
||||
|
@ -133,7 +133,7 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
|||
if (sortCallback is ListItemSortedByCheckedCallback) {
|
||||
sortCallback.setList(items)
|
||||
}
|
||||
items.init(model.items)
|
||||
items.init(notallyModel.items)
|
||||
adapter?.setList(items)
|
||||
binding.RecyclerView.adapter = adapter
|
||||
listManager.adapter = adapter!!
|
||||
|
@ -142,6 +142,6 @@ class EditListActivity : EditActivity(Type.LIST), MoreListActions {
|
|||
|
||||
override fun setColor() {
|
||||
super.setColor()
|
||||
adapter?.setBackgroundColor(Operations.extractColor(model.color, this))
|
||||
adapter?.setBackgroundColor(Operations.extractColor(notallyModel.color, this))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
|||
|
||||
setupEditor()
|
||||
|
||||
if (model.isNewNote) {
|
||||
if (notallyModel.isNewNote) {
|
||||
binding.EnterBody.requestFocus()
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,7 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
|||
return 0
|
||||
}
|
||||
searchResultIndices =
|
||||
model.body.toString().findAllOccurrences(search).onEach { (startIdx, endIdx) ->
|
||||
notallyModel.body.toString().findAllOccurrences(search).onEach { (startIdx, endIdx) ->
|
||||
binding.EnterBody.highlight(startIdx, endIdx, false)
|
||||
}
|
||||
return searchResultIndices!!.size
|
||||
|
@ -136,8 +136,8 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
|||
override fun setupListeners() {
|
||||
super.setupListeners()
|
||||
binding.EnterBody.initHistory(changeHistory) { text ->
|
||||
val textChanged = !model.body.toString().contentEquals(text)
|
||||
model.body = text
|
||||
val textChanged = !notallyModel.body.toString().contentEquals(text)
|
||||
notallyModel.body = text
|
||||
if (textChanged && searchResultIndices?.isNotEmpty() == true) {
|
||||
val amount = highlightSearchResults(search)
|
||||
setSearchResultsAmount(amount)
|
||||
|
@ -151,7 +151,7 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
|||
}
|
||||
|
||||
private fun updateEditText() {
|
||||
binding.EnterBody.text = model.body
|
||||
binding.EnterBody.text = notallyModel.body
|
||||
}
|
||||
|
||||
private fun setupEditor() {
|
||||
|
@ -336,7 +336,9 @@ class EditNoteActivity : EditActivity(Type.NOTE), AddNoteActions {
|
|||
|
||||
fun linkNote(activityResultLauncher: ActivityResultLauncher<Intent>) {
|
||||
val intent =
|
||||
Intent(this, PickNoteActivity::class.java).apply { putExtra(EXCLUDE_NOTE_ID, model.id) }
|
||||
Intent(this, PickNoteActivity::class.java).apply {
|
||||
putExtra(EXCLUDE_NOTE_ID, notallyModel.id)
|
||||
}
|
||||
activityResultLauncher.launch(intent)
|
||||
}
|
||||
|
||||
|
|
|
@ -64,7 +64,7 @@ class SelectLabelsActivity : LockedActivity<ActivityLabelBinding>() {
|
|||
val value = binding.EditText.text.toString().trim()
|
||||
if (value.isNotEmpty()) {
|
||||
val label = Label(value)
|
||||
model.insertLabel(label) { success ->
|
||||
baseModel.insertLabel(label) { success ->
|
||||
if (success) {
|
||||
dialog.dismiss()
|
||||
} else showToast(R.string.label_exists)
|
||||
|
@ -95,7 +95,7 @@ class SelectLabelsActivity : LockedActivity<ActivityLabelBinding>() {
|
|||
)
|
||||
}
|
||||
|
||||
model.labels.observe(this) { labels ->
|
||||
baseModel.labels.observe(this) { labels ->
|
||||
labelAdapter.submitList(labels)
|
||||
if (labels.isEmpty()) {
|
||||
binding.EmptyState.visibility = View.VISIBLE
|
||||
|
|
|
@ -114,6 +114,8 @@ class NotallyXPreferences private constructor(private val app: Application) {
|
|||
val iv = ByteArrayPreference("encryption_iv", preferences, null)
|
||||
val databaseEncryptionKey =
|
||||
EncryptedPassphrasePreference("database_encryption_key", preferences, ByteArray(0))
|
||||
val fallbackDatabaseEncryptionKey =
|
||||
ByteArrayPreference("fallback_database_encryption_key", encryptedPreferences, ByteArray(0))
|
||||
|
||||
val dataOnExternalStorage =
|
||||
BooleanPreference("dataOnExternalStorage", preferences, false, R.string.data_on_external)
|
||||
|
|
|
@ -6,6 +6,8 @@ import android.security.keystore.KeyGenParameterSpec
|
|||
import android.security.keystore.KeyProperties
|
||||
import androidx.annotation.RequiresApi
|
||||
import com.philkes.notallyx.data.NotallyDatabase.Companion.DatabaseName
|
||||
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
|
||||
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
|
||||
import java.io.File
|
||||
import java.security.KeyStore
|
||||
import javax.crypto.Cipher
|
||||
|
@ -98,7 +100,7 @@ fun getInitializedCipherForDecryption(
|
|||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
private fun getCipher(): Cipher {
|
||||
fun getCipher(): Cipher {
|
||||
return Cipher.getInstance(
|
||||
KeyProperties.KEY_ALGORITHM_AES +
|
||||
"/" +
|
||||
|
@ -107,3 +109,14 @@ private fun getCipher(): Cipher {
|
|||
KeyProperties.ENCRYPTION_PADDING_PKCS7
|
||||
)
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.M)
|
||||
fun Context.disableBiometricLock(model: BaseNoteModel, cipher: Cipher? = null) {
|
||||
val encryptedPassphrase = model.preferences.databaseEncryptionKey.value
|
||||
val passphrase =
|
||||
cipher?.doFinal(encryptedPassphrase)
|
||||
?: model.preferences.fallbackDatabaseEncryptionKey.value!!
|
||||
model.closeDatabase()
|
||||
decryptDatabase(this, passphrase)
|
||||
model.savePreference(model.preferences.biometricLock, BiometricLock.DISABLED)
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ fun Activity.showBiometricOrPinPrompt(
|
|||
titleResId: Int,
|
||||
descriptionResId: Int? = null,
|
||||
onSuccess: (cipher: Cipher) -> Unit,
|
||||
onFailure: () -> Unit,
|
||||
onFailure: (errorCode: Int?) -> Unit,
|
||||
) {
|
||||
showBiometricOrPinPrompt(
|
||||
isForDecrypt,
|
||||
|
@ -44,7 +44,7 @@ fun Fragment.showBiometricOrPinPrompt(
|
|||
descriptionResId: Int,
|
||||
cipherIv: ByteArray? = null,
|
||||
onSuccess: (cipher: Cipher) -> Unit,
|
||||
onFailure: () -> Unit,
|
||||
onFailure: (errorCode: Int?) -> Unit,
|
||||
) {
|
||||
showBiometricOrPinPrompt(
|
||||
isForDecrypt,
|
||||
|
@ -66,23 +66,24 @@ private fun showBiometricOrPinPrompt(
|
|||
descriptionResId: Int? = null,
|
||||
cipherIv: ByteArray? = null,
|
||||
onSuccess: (cipher: Cipher) -> Unit,
|
||||
onFailure: () -> Unit,
|
||||
onFailure: (errorCode: Int?) -> Unit,
|
||||
) {
|
||||
when {
|
||||
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
|
||||
// Android 11+ with BiometricPrompt and Authenticators
|
||||
val prompt = BiometricPrompt.Builder(context)
|
||||
.apply {
|
||||
setTitle(context.getString(titleResId))
|
||||
descriptionResId?.let {
|
||||
setDescription(context.getString(descriptionResId))
|
||||
}
|
||||
setAllowedAuthenticators(
|
||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||
val prompt =
|
||||
BiometricPrompt.Builder(context)
|
||||
.apply {
|
||||
setTitle(context.getString(titleResId))
|
||||
descriptionResId?.let {
|
||||
setDescription(context.getString(descriptionResId))
|
||||
}
|
||||
setAllowedAuthenticators(
|
||||
BiometricManager.Authenticators.BIOMETRIC_STRONG or
|
||||
BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||
)
|
||||
}
|
||||
.build()
|
||||
)
|
||||
}
|
||||
.build()
|
||||
val cipher =
|
||||
if (isForDecrypt) {
|
||||
getInitializedCipherForDecryption(iv = cipherIv!!)
|
||||
|
@ -103,12 +104,12 @@ private fun showBiometricOrPinPrompt(
|
|||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(errorCode)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -124,9 +125,9 @@ private fun showBiometricOrPinPrompt(
|
|||
}
|
||||
setNegativeButton(
|
||||
context.getString(R.string.cancel),
|
||||
context.mainExecutor
|
||||
context.mainExecutor,
|
||||
) { _, _ ->
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
|
@ -150,12 +151,12 @@ private fun showBiometricOrPinPrompt(
|
|||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(errorCode)
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -166,7 +167,7 @@ private fun showBiometricOrPinPrompt(
|
|||
ContextCompat.getSystemService(context, FingerprintManager::class.java)
|
||||
if (
|
||||
fingerprintManager?.isHardwareDetected == true &&
|
||||
fingerprintManager.hasEnrolledFingerprints()
|
||||
fingerprintManager.hasEnrolledFingerprints()
|
||||
) {
|
||||
val cipher =
|
||||
if (isForDecrypt) {
|
||||
|
@ -188,7 +189,7 @@ private fun showBiometricOrPinPrompt(
|
|||
|
||||
override fun onAuthenticationFailed() {
|
||||
super.onAuthenticationFailed()
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
|
||||
override fun onAuthenticationError(
|
||||
|
@ -196,7 +197,7 @@ private fun showBiometricOrPinPrompt(
|
|||
errString: CharSequence?,
|
||||
) {
|
||||
super.onAuthenticationError(errorCode, errString)
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(errorCode)
|
||||
}
|
||||
},
|
||||
null,
|
||||
|
@ -225,7 +226,7 @@ private fun promptPinAuthentication(
|
|||
context: Context,
|
||||
activityResultLauncher: ActivityResultLauncher<Intent>,
|
||||
titleResId: Int,
|
||||
onFailure: () -> Unit,
|
||||
onFailure: (errorCode: Int?) -> Unit,
|
||||
) {
|
||||
val keyguardManager = ContextCompat.getSystemService(context, KeyguardManager::class.java)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
|
@ -239,10 +240,10 @@ private fun promptPinAuthentication(
|
|||
if (intent != null) {
|
||||
activityResultLauncher.launch(intent)
|
||||
} else {
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
} else {
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
} else {
|
||||
// For API 21-22, use isKeyguardSecure
|
||||
|
@ -255,10 +256,10 @@ private fun promptPinAuthentication(
|
|||
if (intent != null) {
|
||||
activityResultLauncher.launch(intent)
|
||||
} else {
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
} else {
|
||||
onFailure.invoke()
|
||||
onFailure.invoke(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -76,6 +76,7 @@
|
|||
<string name="deleting_files">Dateien löschen</string>
|
||||
<string name="deleting_images">Bilder löschen</string>
|
||||
<string name="descending">Absteigend</string>
|
||||
<string name="disable">Deaktivieren</string>
|
||||
<string name="disable_auto_backup">Automatisches Backup deaktivieren</string>
|
||||
<string name="disable_external_data">Verschiebe die Daten zurück in den internen Speicher</string>
|
||||
<string name="disable_lock_description">Dies entschlüsselt außerdem die Datenbank</string>
|
||||
|
@ -222,6 +223,7 @@
|
|||
<string name="unknown_error">Unbekannter Fehler</string>
|
||||
<string name="unknown_name">Unbekannter Name</string>
|
||||
<string name="unlock">Entsperre mittels Biometrie/PIN</string>
|
||||
<string name="unlock_with_biometrics_not_setup">Die biometrische Sperre ist aktiviert, allerdings ist für dein Gerät keine Biometrie/PIN mehr eingerichtet.\n\nUm die biometrische zu deaktivieren klicke Deaktivieren, ansonsten richte für dein Geräte Biometrie/PIN ein</string>
|
||||
<string name="unpin">Loslösen</string>
|
||||
<string name="updated_link">Link aktualisiert</string>
|
||||
<string name="view">Ansicht</string>
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
<string name="deleting_files">Deleting files</string>
|
||||
<string name="deleting_images">Deleting images</string>
|
||||
<string name="descending">Descending</string>
|
||||
<string name="disable">Disable</string>
|
||||
<string name="disable_auto_backup">Disable auto backup</string>
|
||||
<string name="disable_external_data">Move data back to internal storage</string>
|
||||
<string name="disable_lock_description">This will also decrypt the database</string>
|
||||
|
@ -258,6 +259,7 @@
|
|||
<string name="unknown_error">Unknown error</string>
|
||||
<string name="unknown_name">Unknown name</string>
|
||||
<string name="unlock">Unlock via Biometric/PIN</string>
|
||||
<string name="unlock_with_biometrics_not_setup">You have previously enabled biometric lock but Biometrics/PIN are not setup for your device anymore.\n\nIf you wish to disable biometric lock press Disable, otherwise setup Biometrics/PIN for your device</string>
|
||||
<string name="unpin">Unpin</string>
|
||||
<string name="updated_link">Updated Link</string>
|
||||
<string name="view">View</string>
|
||||
|
|
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue