diff --git a/app/src/main/java/com/philkes/notallyx/presentation/activity/LockedActivity.kt b/app/src/main/java/com/philkes/notallyx/presentation/activity/LockedActivity.kt index 33fcf519..aa2ace62 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/activity/LockedActivity.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/activity/LockedActivity.kt @@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope import androidx.viewbinding.ViewBinding import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.philkes.notallyx.NotallyXApplication @@ -23,6 +24,7 @@ import com.philkes.notallyx.presentation.showToast import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt +import kotlinx.coroutines.launch abstract class LockedActivity : AppCompatActivity() { @@ -82,9 +84,12 @@ abstract class LockedActivity : AppCompatActivity() { .setMessage(R.string.unlock_with_biometrics_not_setup) .setPositiveButton(R.string.disable) { _, _ -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - baseModel.disableBiometricLock() + lifecycleScope.launch { + baseModel.disableBiometricLock() + showToast(R.string.biometrics_disable_success) + } } - show() + hide() } .setNegativeButton(R.string.tap_to_set_up) { _, _ -> val intent = @@ -102,8 +107,10 @@ abstract class LockedActivity : AppCompatActivity() { BIOMETRIC_ERROR_HW_NOT_PRESENT -> { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - baseModel.disableBiometricLock() - showToast(R.string.biometrics_disable_success) + lifecycleScope.launch { + baseModel.disableBiometricLock() + showToast(R.string.biometrics_disable_success) + } } show() } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt b/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt index b64ba2e9..1b75492c 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt @@ -23,6 +23,7 @@ import androidx.core.net.toUri import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.lifecycle.lifecycleScope import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE import com.philkes.notallyx.NotallyXApplication @@ -58,11 +59,16 @@ import com.philkes.notallyx.utils.getExtraBooleanFromBundleOrIntent import com.philkes.notallyx.utils.getLastExceptionLog import com.philkes.notallyx.utils.getLogFile import com.philkes.notallyx.utils.getUriForFile +import com.philkes.notallyx.utils.log import com.philkes.notallyx.utils.reportBug +import com.philkes.notallyx.utils.security.DecryptionException +import com.philkes.notallyx.utils.security.EncryptionException import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt +import com.philkes.notallyx.utils.showErrorDialog import com.philkes.notallyx.utils.viewLogs import com.philkes.notallyx.utils.wrapWithChooser import java.util.Date +import kotlinx.coroutines.launch class SettingsFragment : Fragment() { @@ -663,7 +669,11 @@ class SettingsFragment : Fragment() { R.string.reset_settings_message, R.string.reset_settings, { _, _ -> - model.resetPreferences { _ -> showToast(R.string.reset_settings_success) } + lifecycleScope.launch { + model.resetPreferences { _ -> + showToast(R.string.reset_settings_success) + } + } }, ) } @@ -839,12 +849,27 @@ class SettingsFragment : Fragment() { R.string.enable_lock_title, R.string.enable_lock_description, onSuccess = { cipher -> + val app = (requireActivity().application as NotallyXApplication) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - model.enableBiometricLock(cipher) + lifecycleScope.launch { + try { + model.enableBiometricLock(cipher) + } catch (e: EncryptionException) { + app.log(TAG, throwable = e) + showErrorDialog( + e, + R.string.biometrics_setup_failure, + getString( + R.string.biometrics_setup_failure_encrypt, + getString(R.string.report_bug), + ), + ) + return@launch + } + app.locked.value = false + showToast(R.string.biometrics_setup_success) + } } - val app = (activity?.application as NotallyXApplication) - app.locked.value = false - showToast(R.string.biometrics_setup_success) }, ) { showBiometricsNotSetupDialog() @@ -860,9 +885,25 @@ class SettingsFragment : Fragment() { model.preferences.iv.value!!, onSuccess = { cipher -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - model.disableBiometricLock(cipher) + val app = (requireActivity().application as NotallyXApplication) + lifecycleScope.launch { + try { + model.disableBiometricLock(cipher) + } catch (e: DecryptionException) { + app.log(TAG, throwable = e) + showErrorDialog( + e, + R.string.biometrics_setup_failure, + getString( + R.string.biometrics_setup_failure_decrypt, + getString(R.string.report_bug), + ), + ) + return@launch + } + showToast(R.string.biometrics_disable_success) + } } - showToast(R.string.biometrics_disable_success) }, ) {} } @@ -906,6 +947,7 @@ class SettingsFragment : Fragment() { } companion object { + private const val TAG = "SettingsFragment" const val EXTRA_SHOW_IMPORT_BACKUPS_FOLDER = "notallyx.intent.extra.SHOW_IMPORT_BACKUPS_FOLDER" } diff --git a/app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt b/app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt index 872e9904..af2498b3 100644 --- a/app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt +++ b/app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt @@ -19,6 +19,7 @@ import androidx.room.withTransaction import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.philkes.notallyx.R import com.philkes.notallyx.data.NotallyDatabase +import com.philkes.notallyx.data.NotallyDatabase.Companion.DATABASE_NAME import com.philkes.notallyx.data.dao.BaseNoteDao import com.philkes.notallyx.data.dao.CommonDao import com.philkes.notallyx.data.dao.LabelDao @@ -60,6 +61,7 @@ import com.philkes.notallyx.utils.Cache import com.philkes.notallyx.utils.MIME_TYPE_JSON import com.philkes.notallyx.utils.backup.clearAllFolders import com.philkes.notallyx.utils.backup.clearAllLabels +import com.philkes.notallyx.utils.backup.copyDatabase import com.philkes.notallyx.utils.backup.exportAsZip import com.philkes.notallyx.utils.backup.exportPdfFile import com.philkes.notallyx.utils.backup.exportPlainTextFile @@ -71,10 +73,15 @@ import com.philkes.notallyx.utils.cancelNoteReminders import com.philkes.notallyx.utils.deleteAttachments import com.philkes.notallyx.utils.getBackupDir import com.philkes.notallyx.utils.getExternalImagesDirectory +import com.philkes.notallyx.utils.getExternalMediaDirectory import com.philkes.notallyx.utils.log import com.philkes.notallyx.utils.scheduleNoteReminders +import com.philkes.notallyx.utils.security.DecryptionException +import com.philkes.notallyx.utils.security.EncryptionException import com.philkes.notallyx.utils.security.decryptDatabase import com.philkes.notallyx.utils.security.encryptDatabase +import com.philkes.notallyx.utils.security.isEncryptedDatabase +import com.philkes.notallyx.utils.security.isUnencryptedDatabase import com.philkes.notallyx.utils.toReadablePath import java.io.File import java.util.concurrent.atomic.AtomicInteger @@ -283,24 +290,55 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) { } } - fun enableBiometricLock(cipher: Cipher) { + suspend fun enableBiometricLock(cipher: Cipher) { savePreference(preferences.iv, cipher.iv) val passphrase = preferences.databaseEncryptionKey.init(cipher) - encryptDatabase(app, passphrase) - savePreference(preferences.fallbackDatabaseEncryptionKey, passphrase) - savePreference(preferences.biometricLock, BiometricLock.ENABLED) + withContext(Dispatchers.IO) { + database.close() + val (_, dbFileCopy) = app.copyDatabase(suffix = "-encrypt") + val (_, dbFileBackup) = app.copyDatabase(suffix = "-encrypt-backup") + encryptDatabase(app, dbFileCopy, passphrase) + val originalDbFile = NotallyDatabase.getCurrentDatabaseFile(app) + dbFileCopy.copyTo(originalDbFile, overwrite = true) + if (originalDbFile.isUnencryptedDatabase) { + dbFileBackup.copyTo(originalDbFile, overwrite = true) + val externalBackupFile = + File(app.getExternalMediaDirectory(), "${DATABASE_NAME}_Backup-encrypt") + dbFileBackup.copyTo(externalBackupFile, overwrite = true) + throw EncryptionException( + "Encrypt succeeded but overwritten database is not encrypted" + ) + } + savePreference(preferences.fallbackDatabaseEncryptionKey, passphrase) + savePreference(preferences.biometricLock, BiometricLock.ENABLED) + } } @RequiresApi(Build.VERSION_CODES.M) - fun disableBiometricLock(cipher: Cipher? = null, callback: (() -> Unit)? = null) { + suspend fun disableBiometricLock(cipher: Cipher? = null, callback: (() -> Unit)? = null) { val encryptedPassphrase = preferences.databaseEncryptionKey.value val passphrase = cipher?.doFinal(encryptedPassphrase) ?: preferences.fallbackDatabaseEncryptionKey.value!! - database.close() - decryptDatabase(app, passphrase) - savePreference(preferences.biometricLock, BiometricLock.DISABLED) - callback?.invoke() + withContext(Dispatchers.IO) { + database.close() + val (_, dbFileCopy) = app.copyDatabase(decrypt = false, suffix = "-decrypt") + val (_, dbFileBackup) = app.copyDatabase(decrypt = false, suffix = "-decrypt-backup") + decryptDatabase(app, dbFileCopy, passphrase) + val originalDbFile = NotallyDatabase.getCurrentDatabaseFile(app) + dbFileCopy.copyTo(originalDbFile, overwrite = true) + if (originalDbFile.isEncryptedDatabase) { + dbFileBackup.copyTo(originalDbFile, overwrite = true) + val externalBackupFile = + File(app.getExternalMediaDirectory(), "${DATABASE_NAME}_Backup-decrypt") + dbFileBackup.copyTo(externalBackupFile, overwrite = true) + throw DecryptionException( + "Decrypt succeeded but overwritten database is still encrypted" + ) + } + savePreference(preferences.biometricLock, BiometricLock.DISABLED) + callback?.invoke() + } } fun savePreference(preference: BasePreference, value: T) { @@ -605,7 +643,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) { } } - fun resetPreferences(callback: (restartRequired: Boolean) -> Unit) { + suspend fun resetPreferences(callback: (restartRequired: Boolean) -> Unit) { val backupsFolder = preferences.backupsFolder.value val publicFolder = preferences.dataInPublicFolder.value val isThemeDefault = preferences.theme.value == Theme.FOLLOW_SYSTEM diff --git a/app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt b/app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt index 3d8afe16..bcea436f 100644 --- a/app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt +++ b/app/src/main/java/com/philkes/notallyx/utils/AndroidExtensions.kt @@ -35,6 +35,7 @@ import com.philkes.notallyx.R import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.Type import com.philkes.notallyx.data.model.toText +import com.philkes.notallyx.databinding.DialogErrorBinding import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EXTRA_SELECTED_BASE_NOTE import com.philkes.notallyx.presentation.activity.note.EditListActivity import com.philkes.notallyx.presentation.activity.note.EditNoteActivity @@ -182,6 +183,54 @@ fun ContextWrapper.viewLogs() { fun ContextWrapper.getLogFileUri() = getLogFile().let { if (it.exists()) getUriForFile(it) else null } +fun Fragment.showErrorDialog( + throwable: Throwable, + titleResId: Int, + message: String, + originalStacktrace: String? = null, +) { + val stacktrace = throwable.stackTraceToString() + val layout = + DialogErrorBinding.inflate(layoutInflater, null, false).apply { + ExceptionTitle.text = message + ExceptionDetails.text = stacktrace + CopyButton.setOnClickListener { requireContext().copyToClipBoard(stacktrace) } + } + MaterialAlertDialogBuilder(requireContext()) + .setTitle(titleResId) + .setView(layout.root) + .setPositiveButton(R.string.report_bug) { dialog, _ -> + dialog.cancel() + reportBug(originalStacktrace ?: throwable.stackTraceToString()) + } + .setCancelButton() + .show() +} + +fun Activity.showErrorDialog( + throwable: Throwable, + titleResId: Int, + message: String, + originalStacktrace: String? = null, +) { + val stacktrace = throwable.stackTraceToString() + val layout = + DialogErrorBinding.inflate(layoutInflater, null, false).apply { + ExceptionTitle.text = message + ExceptionDetails.text = stacktrace + CopyButton.setOnClickListener { copyToClipBoard(stacktrace) } + } + MaterialAlertDialogBuilder(this) + .setTitle(titleResId) + .setView(layout.root) + .setPositiveButton(R.string.report_bug) { dialog, _ -> + dialog.cancel() + reportBug(originalStacktrace ?: throwable.stackTraceToString()) + } + .setCancelButton() + .show() +} + private const val MAX_LOGS_FILE_SIZE_KB: Long = 2048 private fun Context.logToFile( diff --git a/app/src/main/java/com/philkes/notallyx/utils/ErrorActivity.kt b/app/src/main/java/com/philkes/notallyx/utils/ErrorActivity.kt index 9ac6befc..2c00b6a0 100644 --- a/app/src/main/java/com/philkes/notallyx/utils/ErrorActivity.kt +++ b/app/src/main/java/com/philkes/notallyx/utils/ErrorActivity.kt @@ -10,8 +10,10 @@ import androidx.lifecycle.lifecycleScope import cat.ereza.customactivityoncrash.CustomActivityOnCrash import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.philkes.notallyx.R +import com.philkes.notallyx.R.string.auto_backup_failed +import com.philkes.notallyx.R.string.crash_export_backup_failed +import com.philkes.notallyx.R.string.report_bug import com.philkes.notallyx.databinding.ActivityErrorBinding -import com.philkes.notallyx.databinding.DialogErrorBinding import com.philkes.notallyx.presentation.getQuantityString import com.philkes.notallyx.presentation.setCancelButton import com.philkes.notallyx.presentation.setupProgressDialog @@ -92,13 +94,12 @@ class ErrorActivity : AppCompatActivity() { result.data?.data?.let { uri -> val preferences = NotallyXPreferences.getInstance(this) val exceptionHandler = CoroutineExceptionHandler { _, throwable -> - // MaterialAlertDialogBuilder(this) - // .setTitle(R.string.auto_backup_failed) - // - // .setMessage(throwable.stackTraceToString()) - // .setCancelButton() - // .show() - showErrorDialog(throwable, stacktrace) + showErrorDialog( + throwable, + auto_backup_failed, + getString(crash_export_backup_failed, this.getString(report_bug)), + originalStacktrace = stacktrace, + ) } lifecycleScope.launch(exceptionHandler) { val exportedNotes = @@ -123,26 +124,6 @@ class ErrorActivity : AppCompatActivity() { exportBackupProgress.setupProgressDialog(this) } - private fun showErrorDialog(throwable: Throwable, originalStacktrace: String?) { - val stacktrace = throwable.stackTraceToString() - val layout = - DialogErrorBinding.inflate(layoutInflater, null, false).apply { - ExceptionTitle.text = - getString(R.string.crash_export_backup_failed, getString(R.string.report_bug)) - ExceptionDetails.text = stacktrace - CopyButton.setOnClickListener { copyToClipBoard(stacktrace) } - } - MaterialAlertDialogBuilder(this) - .setTitle(R.string.auto_backup_failed) - .setView(layout.root) - .setPositiveButton(R.string.report_bug) { dialog, _ -> - dialog.cancel() - reportBug(originalStacktrace) - } - .setCancelButton() - .show() - } - companion object { private const val TAG = "ErrorActivity" } diff --git a/app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt b/app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt index 35ffae40..ffddd1e3 100644 --- a/app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt +++ b/app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt @@ -421,19 +421,24 @@ private fun createZipFromDirectory( } } -fun ContextWrapper.copyDatabase(): Pair { +fun ContextWrapper.copyDatabase( + decrypt: Boolean = true, + suffix: String = "", +): Pair { val database = NotallyDatabase.getDatabase(this, observePreferences = false).value database.checkpoint() val preferences = NotallyXPreferences.getInstance(this) val databaseFile = NotallyDatabase.getCurrentDatabaseFile(this) - return if (preferences.isLockEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + return if ( + decrypt && preferences.isLockEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + ) { val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!) val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value) - val decryptedFile = File(cacheDir, DATABASE_NAME) + val decryptedFile = File(cacheDir, DATABASE_NAME + suffix) decryptDatabase(this, passphrase, databaseFile, decryptedFile) Pair(database, decryptedFile) } else { - val dbFile = File(cacheDir, DATABASE_NAME) + val dbFile = File(cacheDir, DATABASE_NAME + suffix) databaseFile.copyTo(dbFile, overwrite = true) Pair(database, dbFile) } diff --git a/app/src/main/java/com/philkes/notallyx/utils/security/DecryptionException.kt b/app/src/main/java/com/philkes/notallyx/utils/security/DecryptionException.kt new file mode 100644 index 00000000..ffcb69cd --- /dev/null +++ b/app/src/main/java/com/philkes/notallyx/utils/security/DecryptionException.kt @@ -0,0 +1,3 @@ +package com.philkes.notallyx.utils.security + +class DecryptionException(msg: String, cause: Throwable? = null) : Exception(msg, cause) {} diff --git a/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionException.kt b/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionException.kt new file mode 100644 index 00000000..80ee2c80 --- /dev/null +++ b/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionException.kt @@ -0,0 +1,3 @@ +package com.philkes.notallyx.utils.security + +class EncryptionException(msg: String, cause: Throwable? = null) : Exception(msg, cause) {} diff --git a/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionUtils.kt b/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionUtils.kt index 23bc5f51..12755949 100644 --- a/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionUtils.kt +++ b/app/src/main/java/com/philkes/notallyx/utils/security/EncryptionUtils.kt @@ -6,7 +6,6 @@ import android.os.Build import android.security.keystore.KeyGenParameterSpec import android.security.keystore.KeyProperties import androidx.annotation.RequiresApi -import com.philkes.notallyx.data.NotallyDatabase import java.io.File import java.security.KeyStore import javax.crypto.Cipher @@ -18,22 +17,42 @@ private const val ENCRYPTION_KEY_NAME = "notallyx_database_encryption_key" private const val ANDROID_KEYSTORE = "AndroidKeyStore" -fun encryptDatabase(context: ContextWrapper, passphrase: ByteArray) { - val dbFile = NotallyDatabase.getCurrentDatabaseFile(context) - val state = SQLCipherUtils.getDatabaseState(dbFile) - if (state == SQLCipherUtils.State.UNENCRYPTED) { - SQLCipherUtils.encrypt(context, dbFile, passphrase) +fun encryptDatabase(context: ContextWrapper, dbFile: File, passphrase: ByteArray) { + if (dbFile.isUnencryptedDatabase) { + try { + SQLCipherUtils.encrypt(context, dbFile, passphrase) + if (dbFile.isUnencryptedDatabase) { + throw EncryptionException( + "Encrypt was executed, but database is still not encrypted" + ) + } + } catch (e: Exception) { + throw EncryptionException("Encryption of ${dbFile.name} failed", e) + } } } -fun decryptDatabase(context: ContextWrapper, passphrase: ByteArray) { - val dbFile = NotallyDatabase.getCurrentDatabaseFile(context) - val state = SQLCipherUtils.getDatabaseState(dbFile) - if (state == SQLCipherUtils.State.ENCRYPTED) { - SQLCipherUtils.decrypt(context, dbFile, passphrase) +fun decryptDatabase(context: ContextWrapper, dbFile: File, passphrase: ByteArray) { + if (dbFile.isEncryptedDatabase) { + try { + SQLCipherUtils.decrypt(context, dbFile, passphrase) + if (SQLCipherUtils.getDatabaseState(dbFile) == SQLCipherUtils.State.ENCRYPTED) { + throw DecryptionException( + "Decrypt was executed, but database is still not decrypted" + ) + } + } catch (e: Exception) { + throw DecryptionException("Decryption of ${dbFile.name} failed", e) + } } } +val File.isEncryptedDatabase: Boolean + get() = SQLCipherUtils.getDatabaseState(this) == SQLCipherUtils.State.ENCRYPTED + +val File.isUnencryptedDatabase: Boolean + get() = SQLCipherUtils.getDatabaseState(this) == SQLCipherUtils.State.UNENCRYPTED + fun decryptDatabase( context: Context, passphrase: ByteArray, diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 1dafdd5e..bfeb0b13 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -261,7 +261,7 @@ Opakování Vlastní opakování Hodnota - Nahlásit problém/chybu + Nahlásit chybu Nahlásit chybu spolu s protokoly o pádu.\n\n Resetovat nastavení Všechna nastavení budou obnovena na výchozí hodnotu. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5f6672ed..fdb11232 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -262,7 +262,7 @@ Wiederholung Benutzer. Wiederholung Wert - Fehler/Bug melden + Bug melden Bug mit Crash logs melden Einstellungen zurücksetzen Alle Einstellungen werden auf ihre Default-Werte zurückgesetzt diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 45881464..e9e5cfca 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -255,7 +255,7 @@ Repetición Repetición personalizada Valor - Informar de incidente/error + Informar error Informar de error con traza de ejecución Restablecer ajustes Todos los ajustes serán restablecidos a su valor por defecto diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e5be8060..5ca2c0b0 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -255,7 +255,7 @@ Répétition Répétition personnalisée Valeur - Signaler un problème/un bug + Signaler bug Signaler un bug avec rapport de plantage Réinitialiser les paramètres Tous les paramètres seront réinitialisés à leur valeur par défaut diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 54a30397..c28de401 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -246,7 +246,7 @@ Ripetizione Ripetizione personalizzata Valore - Segnala un problema/bug + Segnala bug Segnala un bug con i registri di crash Reimposta Impostazioni Tutte le Impostazioni verranno ripristinate ai valori predefiniti diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index aa1a8790..eed3b488 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -184,7 +184,7 @@ Opnemen… Opnieuw doen Verwijder Link - Rapporteer een probleem/fout + Bug melden Herstellen Herstelde %1$d notitie diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4ffcb5f6..229a725a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -266,7 +266,7 @@ Powtarzanie Niestandardowe powtarzanie Liczba - Zgłoś problem/błąd + Zgłoś błąd Zgłoś błąd z dziennikami awarii Przywróć ustawienia Wszystkie ustawienia zostaną zresetowane do wartości domyślnych diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 1c894b97..d9c0ac84 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -255,7 +255,7 @@ Repetare Repetare personalizată Valoare - Raportează o problemă/eroare + Raportează eroare Raportează o eroare cu jurnalele de erori Resetează Setările Toate setările vor fi resetate la valorile implicite diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index fd292c33..df5c4b5f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -261,7 +261,7 @@ Повтор Свой вариант повтора Значение - Сообщить об ошибке/баге + Сообщить об ошибке Отправить отчёт об ошибке Сбросить настройки Все настройки будут сброшены к значениям по умолчанию diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 57b606e1..96ccfd8e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -261,7 +261,7 @@ 重复 定制重复 - 报告问题/错误 + 报告错误 报告问题/错误 重置设置 所有设置都将重置为默认值 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f5cc9b4c..b835b15e 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -248,7 +248,7 @@ 不重複 提醒 移除連結 - 報告問題/錯誤 + 報告錯誤 使用崩潰日誌報告錯誤 重置設定 所有設定都將重設為預設值 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6129ca81..503270b5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,9 @@ Failed to authenticate via Biometric/PIN No biometric features available on this device Biometrics/PIN are not setup for your device yet + Failed to setup Biometric/PIN lock + Decryption of the database failed.\nPlease use \'%1$s\' to report the error + Encryption of the database failed.\nPlease use \'%1$s\' to report the error Biometric/PIN lock has been enabled Bold Calculating… @@ -267,7 +270,7 @@ Repetition Custom Repetition Value - Report an issue/bug + Report Bug The crash logs are too long to automatically pre-fill the Github issue with it, therefore the crash logs have been copied to your clipboard.\nWhen you click \'%1$s\', your browser will open to create a Github issue for your crash.It is important that you paste the automatically copied crash logs into the Github issue\'s \'(Optional) Relevant log output\' field. Report bug with crash logs Reset Settings diff --git a/app/translations.xlsx b/app/translations.xlsx index 2d73b6aa..39993633 100644 Binary files a/app/translations.xlsx and b/app/translations.xlsx differ diff --git a/documentation/docs/faq.md b/documentation/docs/faq.md index 8982914f..907bf0bc 100644 --- a/documentation/docs/faq.md +++ b/documentation/docs/faq.md @@ -94,7 +94,7 @@ If NotallyX crashes, you'll see a crash screen that allows you to report the iss If the app crashed and you did not see a crash screen: 1. Re-open the app (if possible) 2. From the Overview, tap the sidebar menu icon -3. Tap "Settings" > In "About" section > "Send Feedback" > "Report an issue/bug" +3. Tap "Settings" > In "About" section > "Send Feedback" > "Report Bug" 4. This will open a browser to the Github create Issue site 5. If not already logged in, please login or create a new Github account 6. A bug report template will be pre filled with useful information for the developers @@ -143,5 +143,5 @@ See our [Contribution Guidelines](contributing.md) for information on how to con ### I found a bug. How do I report it? -- From inside the app: Tap "Settings" > In "About" section > "Send Feedback" > "Report an issue/bug" (this will prefill the last crash logs and other useful information) +- From inside the app: Tap "Settings" > In "About" section > "Send Feedback" > "Report Bug" (this will prefill the last crash logs and other useful information) - You can also report bugs directly by [creating a new issue](https://github.com/PhilKes/NotallyX/issues/new/choose) on GitHub. Please include as much detail as possible, including steps to reproduce the issue.