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