Refactor Preferences from objects to Enums

This commit is contained in:
PhilKes 2024-10-31 13:17:04 +01:00
parent 87279902ca
commit e38e14bdd4
43 changed files with 925 additions and 927 deletions

View file

@ -105,6 +105,7 @@ dependencies {
implementation 'net.lingala.zip4j:zip4j:2.11.5' implementation 'net.lingala.zip4j:zip4j:2.11.5'
implementation "androidx.work:work-runtime:2.9.1" implementation "androidx.work:work-runtime:2.9.1"
implementation "androidx.preference:preference-ktx:1.2.1"
//noinspection GradleDependency //noinspection GradleDependency
implementation "androidx.navigation:navigation-ui-ktx:$navVersion" implementation "androidx.navigation:navigation-ui-ktx:$navVersion"

View file

@ -5,32 +5,33 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import com.philkes.notallyx.presentation.view.misc.BetterLiveData import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.view.misc.Theme import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.Theme
import com.philkes.notallyx.presentation.widget.WidgetProvider import com.philkes.notallyx.presentation.widget.WidgetProvider
import com.philkes.notallyx.utils.backup.Export.scheduleAutoBackup import com.philkes.notallyx.utils.backup.Export.scheduleAutoBackup
import com.philkes.notallyx.utils.security.UnlockReceiver import com.philkes.notallyx.utils.security.UnlockReceiver
class NotallyXApplication : Application() { class NotallyXApplication : Application() {
private lateinit var biometricLockObserver: Observer<String> private lateinit var biometricLockObserver: Observer<BiometricLock>
private lateinit var preferences: Preferences private lateinit var preferences: NotallyXPreferences
private var unlockReceiver: UnlockReceiver? = null private var unlockReceiver: UnlockReceiver? = null
val locked = BetterLiveData(true) val locked = NotNullLiveData(true)
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
preferences = Preferences.getInstance(this) preferences = NotallyXPreferences.getInstance(this)
preferences.theme.observeForever { theme -> preferences.theme.observeForever { theme ->
when (theme) { when (theme) {
Theme.dark -> Theme.DARK ->
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
Theme.light -> Theme.LIGHT ->
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO) AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
Theme.followSystem -> Theme.FOLLOW_SYSTEM ->
AppCompatDelegate.setDefaultNightMode( AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
) )
@ -41,7 +42,7 @@ class NotallyXApplication : Application() {
val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) } val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) }
biometricLockObserver = Observer { biometricLockObserver = Observer {
if (it == enabled) { if (it == BiometricLock.ENABLED) {
unlockReceiver = UnlockReceiver(this) unlockReceiver = UnlockReceiver(this)
registerReceiver(unlockReceiver, filter) registerReceiver(unlockReceiver, filter)
} else if (unlockReceiver != null) { } else if (unlockReceiver != null) {

View file

@ -1,228 +0,0 @@
package com.philkes.notallyx
import android.app.Application
import android.os.Build
import android.preference.PreferenceManager
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.data.model.toPreservedByteArray
import com.philkes.notallyx.data.model.toPreservedString
import com.philkes.notallyx.presentation.view.misc.AutoBackup
import com.philkes.notallyx.presentation.view.misc.AutoBackupMax
import com.philkes.notallyx.presentation.view.misc.AutoBackupPeriodDays
import com.philkes.notallyx.presentation.view.misc.BackupPassword
import com.philkes.notallyx.presentation.view.misc.BetterLiveData
import com.philkes.notallyx.presentation.view.misc.BiometricLock
import com.philkes.notallyx.presentation.view.misc.DateFormat
import com.philkes.notallyx.presentation.view.misc.ListInfo
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.misc.MaxItems
import com.philkes.notallyx.presentation.view.misc.MaxLines
import com.philkes.notallyx.presentation.view.misc.MaxTitle
import com.philkes.notallyx.presentation.view.misc.NotesSorting
import com.philkes.notallyx.presentation.view.misc.SeekbarInfo
import com.philkes.notallyx.presentation.view.misc.SortDirection
import com.philkes.notallyx.presentation.view.misc.TextInfo
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.view.misc.Theme
import com.philkes.notallyx.presentation.view.misc.View
import java.security.SecureRandom
import javax.crypto.Cipher
private const val DATABASE_ENCRYPTION_KEY = "database_encryption_key"
private const val ENCRYPTION_IV = "encryption_iv"
/**
* Custom implementation of androidx.preference library Way faster, simpler and smaller, logic of
* storing preferences has been decoupled from their UI. It is backed by SharedPreferences but it
* should be trivial to shift to another source if needed.
*/
class Preferences private constructor(app: Application) {
private val preferences = PreferenceManager.getDefaultSharedPreferences(app)
private val editor = preferences.edit()
private val encryptedPreferences by lazy {
EncryptedSharedPreferences.create(
app,
"secret_shared_prefs",
MasterKey.Builder(app).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
}
// Main thread (unfortunately)
val view = BetterLiveData(getListPref(View))
val theme = BetterLiveData(getListPref(Theme))
val dateFormat = BetterLiveData(getListPref(DateFormat))
val notesSorting = BetterLiveData(getNotesSorting(NotesSorting))
val textSize = BetterLiveData(getListPref(TextSize))
val listItemSorting = BetterLiveData(getListPref(ListItemSorting))
var maxItems = getSeekbarPref(MaxItems)
var maxLines = getSeekbarPref(MaxLines)
var maxTitle = getSeekbarPref(MaxTitle)
val autoBackupPath = BetterLiveData(getTextPref(AutoBackup))
var autoBackupPeriodDays = BetterLiveData(getSeekbarPref(AutoBackupPeriodDays))
var autoBackupMax = getSeekbarPref(AutoBackupMax)
val backupPassword by lazy { BetterLiveData(getEncryptedTextPref(BackupPassword)) }
val biometricLock = BetterLiveData(getListPref(BiometricLock))
var iv: ByteArray?
get() = preferences.getString(ENCRYPTION_IV, null)?.toPreservedByteArray
set(value) {
editor.putString(ENCRYPTION_IV, value?.toPreservedString)
editor.commit()
}
fun getDatabasePassphrase(): ByteArray {
val string = preferences.getString(DATABASE_ENCRYPTION_KEY, "")!!
return string.toPreservedByteArray
}
fun generatePassphrase(cipher: Cipher): ByteArray {
val random =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SecureRandom.getInstanceStrong()
} else {
SecureRandom()
}
val result = ByteArray(64)
random.nextBytes(result)
// filter out zero byte values, as SQLCipher does not like them
while (result.contains(0)) {
random.nextBytes(result)
}
val encryptedPassphrase = cipher.doFinal(result)
editor.putString(DATABASE_ENCRYPTION_KEY, encryptedPassphrase.toPreservedString)
editor.commit()
return result
}
private fun getListPref(info: ListInfo) =
requireNotNull(preferences.getString(info.key, info.defaultValue))
private fun getNotesSorting(info: NotesSorting): Pair<String, SortDirection> {
val sortBy = requireNotNull(preferences.getString(info.key, info.defaultValue))
val sortDirection =
requireNotNull(preferences.getString(info.directionKey, info.defaultValueDirection))
return Pair(sortBy, SortDirection.valueOf(sortDirection))
}
private fun getTextPref(info: TextInfo) =
requireNotNull(preferences.getString(info.key, info.defaultValue))
private fun getEncryptedTextPref(info: TextInfo) =
requireNotNull(encryptedPreferences!!.getString(info.key, info.defaultValue))
private fun getSeekbarPref(info: SeekbarInfo) =
requireNotNull(preferences.getInt(info.key, info.defaultValue))
fun getWidgetData(id: Int) = preferences.getLong("widget:$id", 0)
fun getWidgetNoteType(id: Int) =
preferences.getString("widgetNoteType:$id", null)?.let { Type.valueOf(it) }
fun deleteWidget(id: Int) {
editor.remove("widget:$id")
editor.remove("widgetNoteType:$id")
editor.commit()
}
fun updateWidget(id: Int, noteId: Long, noteType: Type) {
editor.putLong("widget:$id", noteId)
editor.putString("widgetNoteType:$id", noteType.name)
editor.commit()
}
fun getUpdatableWidgets(noteIds: LongArray? = null): List<Pair<Int, Long>> {
val updatableWidgets = ArrayList<Pair<Int, Long>>()
val pairs = preferences.all
pairs.keys.forEach { key ->
val token = "widget:"
if (key.startsWith(token)) {
val end = key.substringAfter(token)
val id = end.toIntOrNull()
if (id != null) {
val value = pairs[key] as? Long
if (value != null) {
if (noteIds == null || noteIds.contains(value)) {
updatableWidgets.add(Pair(id, value))
}
}
}
}
}
return updatableWidgets
}
fun savePreference(info: SeekbarInfo, value: Int) {
editor.putInt(info.key, value)
editor.commit()
when (info) {
MaxItems -> maxItems = getSeekbarPref(MaxItems)
MaxLines -> maxLines = getSeekbarPref(MaxLines)
MaxTitle -> maxTitle = getSeekbarPref(MaxTitle)
AutoBackupMax -> autoBackupMax = getSeekbarPref(AutoBackupMax)
AutoBackupPeriodDays ->
autoBackupPeriodDays.postValue(getSeekbarPref(AutoBackupPeriodDays))
}
}
fun savePreference(info: NotesSorting, sortBy: String, sortDirection: SortDirection) {
editor.putString(info.key, sortBy)
editor.putString(info.directionKey, sortDirection.name)
editor.commit()
notesSorting.postValue(getNotesSorting(info))
}
fun savePreference(info: ListInfo, value: String) {
editor.putString(info.key, value)
editor.commit()
when (info) {
View -> view.postValue(getListPref(info))
Theme -> theme.postValue(getListPref(info))
DateFormat -> dateFormat.postValue(getListPref(info))
TextSize -> textSize.postValue(getListPref(info))
ListItemSorting -> listItemSorting.postValue(getListPref(info))
BiometricLock -> biometricLock.postValue(getListPref(info))
else -> return
}
}
fun savePreference(info: TextInfo, value: String) {
val editor = if (info is BackupPassword) encryptedPreferences!!.edit() else this.editor
editor.putString(info.key, value)
editor.commit()
when (info) {
AutoBackup -> autoBackupPath.postValue(getTextPref(info))
BackupPassword -> backupPassword.postValue(getEncryptedTextPref(info))
}
}
fun showDateCreated(): Boolean {
return dateFormat.value != DateFormat.none
}
companion object {
@Volatile private var instance: Preferences? = null
fun getInstance(app: Application): Preferences {
return instance
?: synchronized(this) {
val instance = Preferences(app)
Companion.instance = instance
return instance
}
}
}
}

View file

@ -10,16 +10,16 @@ import androidx.room.TypeConverters
import androidx.room.migration.Migration import androidx.room.migration.Migration
import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteDatabase import androidx.sqlite.db.SupportSQLiteDatabase
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.dao.BaseNoteDao import com.philkes.notallyx.data.dao.BaseNoteDao
import com.philkes.notallyx.data.dao.CommonDao import com.philkes.notallyx.data.dao.CommonDao
import com.philkes.notallyx.data.dao.LabelDao import com.philkes.notallyx.data.dao.LabelDao
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Converters import com.philkes.notallyx.data.model.Converters
import com.philkes.notallyx.data.model.Label import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.presentation.observeForeverSkipFirst import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.presentation.view.misc.BetterLiveData import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.observeForeverSkipFirst
import com.philkes.notallyx.utils.security.getInitializedCipherForDecryption import com.philkes.notallyx.utils.security.getInitializedCipherForDecryption
import net.sqlcipher.database.SupportFactory import net.sqlcipher.database.SupportFactory
@ -37,20 +37,20 @@ abstract class NotallyDatabase : RoomDatabase() {
getBaseNoteDao().query(SimpleSQLiteQuery("pragma wal_checkpoint(FULL)")) getBaseNoteDao().query(SimpleSQLiteQuery("pragma wal_checkpoint(FULL)"))
} }
private var observer: Observer<String>? = null private var biometricLockObserver: Observer<BiometricLock>? = null
companion object { companion object {
const val DatabaseName = "NotallyDatabase" const val DatabaseName = "NotallyDatabase"
@Volatile private var instance: BetterLiveData<NotallyDatabase>? = null @Volatile private var instance: NotNullLiveData<NotallyDatabase>? = null
fun getDatabase(app: Application): BetterLiveData<NotallyDatabase> { fun getDatabase(app: Application): NotNullLiveData<NotallyDatabase> {
return instance return instance
?: synchronized(this) { ?: synchronized(this) {
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
this.instance = this.instance =
BetterLiveData( NotNullLiveData(
createInstance(app, preferences, preferences.biometricLock.value) createInstance(app, preferences, preferences.biometricLock.value)
) )
return this.instance!! return this.instance!!
@ -59,31 +59,33 @@ abstract class NotallyDatabase : RoomDatabase() {
private fun createInstance( private fun createInstance(
app: Application, app: Application,
preferences: Preferences, preferences: NotallyXPreferences,
biometrickLock: String, biometrickLock: BiometricLock,
): NotallyDatabase { ): NotallyDatabase {
val instanceBuilder = val instanceBuilder =
Room.databaseBuilder(app, NotallyDatabase::class.java, DatabaseName) Room.databaseBuilder(app, NotallyDatabase::class.java, DatabaseName)
.addMigrations(Migration2, Migration3, Migration4, Migration5, Migration6) .addMigrations(Migration2, Migration3, Migration4, Migration5, Migration6)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (biometrickLock == enabled) { if (biometrickLock == BiometricLock.ENABLED) {
val initializationVector = preferences.iv!! val initializationVector = preferences.iv.value!!
val cipher = getInitializedCipherForDecryption(iv = initializationVector) val cipher = getInitializedCipherForDecryption(iv = initializationVector)
val encryptedPassphrase = preferences.getDatabasePassphrase() val encryptedPassphrase = preferences.databaseEncryptionKey.value
val passphrase = cipher.doFinal(encryptedPassphrase) val passphrase = cipher.doFinal(encryptedPassphrase)
val factory = SupportFactory(passphrase) val factory = SupportFactory(passphrase)
instanceBuilder.openHelperFactory(factory) instanceBuilder.openHelperFactory(factory)
} }
val instance = instanceBuilder.build() val instance = instanceBuilder.build()
instance.observer = Observer { newBiometrickLock -> instance.biometricLockObserver = Observer { newBiometrickLock ->
NotallyDatabase.instance?.value?.observer?.let { NotallyDatabase.instance?.value?.biometricLockObserver?.let {
preferences.biometricLock.removeObserver(it) preferences.biometricLock.removeObserver(it)
} }
val newInstance = createInstance(app, preferences, newBiometrickLock) val newInstance = createInstance(app, preferences, newBiometrickLock)
NotallyDatabase.instance?.postValue(newInstance) NotallyDatabase.instance?.postValue(newInstance)
preferences.biometricLock.observeForeverSkipFirst(newInstance.observer!!) preferences.biometricLock.observeForeverSkipFirst(
newInstance.biometricLockObserver!!
)
} }
preferences.biometricLock.observeForeverSkipFirst(instance.observer!!) preferences.biometricLock.observeForeverSkipFirst(instance.biometricLockObserver!!)
return instance return instance
} }
return instanceBuilder.build() return instanceBuilder.build()

View file

@ -46,9 +46,7 @@ import androidx.appcompat.app.AppCompatActivity.INPUT_METHOD_SERVICE
import androidx.appcompat.app.AppCompatActivity.KEYGUARD_SERVICE import androidx.appcompat.app.AppCompatActivity.KEYGUARD_SERVICE
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.imports.ImportProgress import com.philkes.notallyx.data.imports.ImportProgress
@ -57,10 +55,10 @@ import com.philkes.notallyx.data.model.Folder
import com.philkes.notallyx.data.model.SpanRepresentation import com.philkes.notallyx.data.model.SpanRepresentation
import com.philkes.notallyx.data.model.getUrl import com.philkes.notallyx.data.model.getUrl
import com.philkes.notallyx.databinding.DialogProgressBinding import com.philkes.notallyx.databinding.DialogProgressBinding
import com.philkes.notallyx.presentation.view.misc.DateFormat
import com.philkes.notallyx.presentation.view.misc.EditTextWithHistory import com.philkes.notallyx.presentation.view.misc.EditTextWithHistory
import com.philkes.notallyx.presentation.view.misc.Progress import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.view.note.listitem.ListManager import com.philkes.notallyx.presentation.view.note.listitem.ListManager
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
import com.philkes.notallyx.utils.changehistory.ChangeHistory import com.philkes.notallyx.utils.changehistory.ChangeHistory
import com.philkes.notallyx.utils.changehistory.EditTextWithHistoryChange import com.philkes.notallyx.utils.changehistory.EditTextWithHistoryChange
import java.io.File import java.io.File
@ -198,10 +196,10 @@ fun Menu.add(
fun TextView.displayFormattedTimestamp( fun TextView.displayFormattedTimestamp(
timestamp: Long?, timestamp: Long?,
dateFormat: String, dateFormat: DateFormat,
prefixResId: Int? = null, prefixResId: Int? = null,
) { ) {
if (dateFormat != DateFormat.none && timestamp != null) { if (dateFormat != DateFormat.NONE && timestamp != null) {
visibility = View.VISIBLE visibility = View.VISIBLE
text = text =
"${prefixResId?.let { getString(it) } ?: ""} ${formatTimestamp(timestamp, dateFormat)}" "${prefixResId?.let { getString(it) } ?: ""} ${formatTimestamp(timestamp, dateFormat)}"
@ -324,22 +322,6 @@ fun Context.canAuthenticateWithBiometrics(): Int {
return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE return BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE
} }
fun <T> LiveData<T>.observeForeverSkipFirst(observer: Observer<T>) {
var isFirstEvent = true
this.observeForever { value ->
if (isFirstEvent) {
isFirstEvent = false
} else {
observer.onChanged(value)
}
}
}
fun String.fileNameWithoutExtension(): String {
return this.substringAfterLast("/") // Remove the path
.substringBeforeLast(".") // Remove the extension
}
fun Context.getFileName(uri: Uri): String? = fun Context.getFileName(uri: Uri): String? =
when (uri.scheme) { when (uri.scheme) {
ContentResolver.SCHEME_CONTENT -> getContentFileName(uri) ContentResolver.SCHEME_CONTENT -> getContentFileName(uri)
@ -458,10 +440,10 @@ fun Activity.checkNotificationPermission(
} else onSuccess() } else onSuccess()
} }
private fun formatTimestamp(timestamp: Long, dateFormat: String): String { private fun formatTimestamp(timestamp: Long, dateFormat: DateFormat): String {
val date = Date(timestamp) val date = Date(timestamp)
return when (dateFormat) { return when (dateFormat) {
DateFormat.relative -> PrettyTime().format(date) DateFormat.RELATIVE -> PrettyTime().format(date)
else -> java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL).format(date) else -> java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL).format(date)
} }
} }

View file

@ -3,9 +3,9 @@ package com.philkes.notallyx.presentation.activity
import android.appwidget.AppWidgetManager import android.appwidget.AppWidgetManager
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity import com.philkes.notallyx.presentation.activity.note.PickNoteActivity
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.widget.WidgetProvider import com.philkes.notallyx.presentation.widget.WidgetProvider
class ConfigureWidgetActivity : PickNoteActivity() { class ConfigureWidgetActivity : PickNoteActivity() {
@ -27,7 +27,7 @@ class ConfigureWidgetActivity : PickNoteActivity() {
override fun onClick(position: Int) { override fun onClick(position: Int) {
if (position != -1) { if (position != -1) {
val preferences = Preferences.getInstance(application) val preferences = NotallyXPreferences.getInstance(application)
val baseNote = adapter.getItem(position) as BaseNote val baseNote = adapter.getItem(position) as BaseNote
preferences.updateWidget(id, baseNote.id, baseNote.type) preferences.updateWidget(id, baseNote.id, baseNote.type)

View file

@ -12,9 +12,9 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import com.philkes.notallyx.NotallyXApplication import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
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() {
@ -24,12 +24,12 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
ActivityResultLauncher<Intent> ActivityResultLauncher<Intent>
protected lateinit var binding: T protected lateinit var binding: T
protected lateinit var preferences: Preferences protected lateinit var preferences: NotallyXPreferences
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
notallyXApplication = (application as NotallyXApplication) notallyXApplication = (application as NotallyXApplication)
preferences = Preferences.getInstance(application) preferences = NotallyXPreferences.getInstance(application)
biometricAuthenticationActivityResultLauncher = biometricAuthenticationActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@ -42,7 +42,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
} }
override fun onResume() { override fun onResume() {
if (preferences.biometricLock.value == enabled) { if (preferences.biometricLock.value == BiometricLock.ENABLED) {
if (hasToAuthenticateWithBiometric()) { if (hasToAuthenticateWithBiometric()) {
hide() hide()
showLockScreen() showLockScreen()
@ -55,7 +55,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
override fun onPause() { override fun onPause() {
super.onPause() super.onPause()
if (preferences.biometricLock.value == enabled) { if (preferences.biometricLock.value == BiometricLock.ENABLED) {
hide() hide()
} }
} }
@ -63,7 +63,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
open fun showLockScreen() { open fun showLockScreen() {
showBiometricOrPinPrompt( showBiometricOrPinPrompt(
true, true,
preferences.iv!!, preferences.iv.value!!,
biometricAuthenticationActivityResultLauncher, biometricAuthenticationActivityResultLauncher,
R.string.unlock, R.string.unlock,
onSuccess = { unlock() }, onSuccess = { unlock() },

View file

@ -31,9 +31,9 @@ import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.movedToResId import com.philkes.notallyx.presentation.movedToResId
import com.philkes.notallyx.presentation.view.Constants import com.philkes.notallyx.presentation.view.Constants
import com.philkes.notallyx.presentation.view.main.BaseNoteAdapter import com.philkes.notallyx.presentation.view.main.BaseNoteAdapter
import com.philkes.notallyx.presentation.view.misc.View as ViewPref
import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotesView
abstract class NotallyFragment : Fragment(), ListItemListener { abstract class NotallyFragment : Fragment(), ListItemListener {
@ -139,11 +139,11 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
BaseNoteAdapter( BaseNoteAdapter(
model.actionMode.selectedIds, model.actionMode.selectedIds,
dateFormat.value, dateFormat.value,
notesSorting.value.first, notesSorting.value.sortedBy,
textSize.value, textSize.value,
maxItems, maxItems.value,
maxLines, maxLines.value,
maxTitle, maxTitle.value,
model.imageRoot, model.imageRoot,
this@NotallyFragment, this@NotallyFragment,
) )
@ -170,8 +170,8 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
binding?.ImageView?.isVisible = list.isEmpty() binding?.ImageView?.isVisible = list.isEmpty()
} }
model.preferences.notesSorting.observe(viewLifecycleOwner) { (sortBy, sortDirection) -> model.preferences.notesSorting.observe(viewLifecycleOwner) { notesSort ->
notesAdapter?.setSorting(sortBy, sortDirection) notesAdapter?.setSorting(notesSort)
} }
model.actionMode.closeListener.observe(viewLifecycleOwner) { event -> model.actionMode.closeListener.observe(viewLifecycleOwner) { event ->
@ -187,7 +187,7 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
private fun setupRecyclerView() { private fun setupRecyclerView() {
binding?.RecyclerView?.layoutManager = binding?.RecyclerView?.layoutManager =
if (model.preferences.view.value == ViewPref.grid) { if (model.preferences.notesView.value == NotesView.GRID) {
StaggeredGridLayoutManager(2, RecyclerView.VERTICAL) StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
} else LinearLayoutManager(requireContext()) } else LinearLayoutManager(requireContext())
} }

View file

@ -35,27 +35,20 @@ import com.philkes.notallyx.presentation.canAuthenticateWithBiometrics
import com.philkes.notallyx.presentation.checkedTag import com.philkes.notallyx.presentation.checkedTag
import com.philkes.notallyx.presentation.setupImportProgressDialog import com.philkes.notallyx.presentation.setupImportProgressDialog
import com.philkes.notallyx.presentation.setupProgressDialog import com.philkes.notallyx.presentation.setupProgressDialog
import com.philkes.notallyx.presentation.view.misc.AutoBackup
import com.philkes.notallyx.presentation.view.misc.AutoBackupMax
import com.philkes.notallyx.presentation.view.misc.AutoBackupPeriodDays
import com.philkes.notallyx.presentation.view.misc.BackupPassword
import com.philkes.notallyx.presentation.view.misc.BackupPassword.emptyPassword
import com.philkes.notallyx.presentation.view.misc.BiometricLock
import com.philkes.notallyx.presentation.view.misc.BiometricLock.disabled
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled
import com.philkes.notallyx.presentation.view.misc.DateFormat
import com.philkes.notallyx.presentation.view.misc.ListInfo
import com.philkes.notallyx.presentation.view.misc.MaxItems
import com.philkes.notallyx.presentation.view.misc.MaxLines
import com.philkes.notallyx.presentation.view.misc.MaxTitle
import com.philkes.notallyx.presentation.view.misc.MenuDialog import com.philkes.notallyx.presentation.view.misc.MenuDialog
import com.philkes.notallyx.presentation.view.misc.NotesSorting
import com.philkes.notallyx.presentation.view.misc.SeekbarInfo
import com.philkes.notallyx.presentation.view.misc.SortDirection
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.view.misc.TextWithIconAdapter import com.philkes.notallyx.presentation.view.misc.TextWithIconAdapter
import com.philkes.notallyx.presentation.view.misc.Theme
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.BACKUP_PATH_EMPTY
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.PASSWORD_EMPTY
import com.philkes.notallyx.presentation.viewmodel.preference.EnumPreference
import com.philkes.notallyx.presentation.viewmodel.preference.IntPreference
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSort
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortBy
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortPreference
import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
import com.philkes.notallyx.presentation.viewmodel.preference.StringPreference
import com.philkes.notallyx.presentation.viewmodel.preference.TextProvider
import com.philkes.notallyx.utils.Operations import com.philkes.notallyx.utils.Operations
import com.philkes.notallyx.utils.backup.Export.scheduleAutoBackup import com.philkes.notallyx.utils.backup.Export.scheduleAutoBackup
import com.philkes.notallyx.utils.security.decryptDatabase import com.philkes.notallyx.utils.security.decryptDatabase
@ -76,22 +69,20 @@ class SettingsFragment : Fragment() {
private fun setupBinding(binding: FragmentSettingsBinding) { private fun setupBinding(binding: FragmentSettingsBinding) {
model.preferences.apply { model.preferences.apply {
view.observe(viewLifecycleOwner) { value -> notesView.observe(viewLifecycleOwner) { value -> binding.View.setup(notesView, value) }
binding.View.setup(com.philkes.notallyx.presentation.view.misc.View, value)
}
theme.observe(viewLifecycleOwner) { value -> binding.Theme.setup(Theme, value) } theme.observe(viewLifecycleOwner) { value -> binding.Theme.setup(theme, value) }
dateFormat.observe(viewLifecycleOwner) { value -> dateFormat.observe(viewLifecycleOwner) { value ->
binding.DateFormat.setup(DateFormat, value) binding.DateFormat.setup(dateFormat, value)
} }
textSize.observe(viewLifecycleOwner) { value -> textSize.observe(viewLifecycleOwner) { value ->
binding.TextSize.setup(TextSize, value) binding.TextSize.setup(textSize, value)
} }
notesSorting.observe(viewLifecycleOwner) { (sortBy, sortDirection) -> notesSorting.observe(viewLifecycleOwner) { notesSort ->
binding.NotesSortOrder.setup(NotesSorting, sortBy, sortDirection) binding.NotesSortOrder.setup(notesSorting, notesSort)
} }
// TODO: Hide for now until checked auto-sort is working reliably // TODO: Hide for now until checked auto-sort is working reliably
@ -99,29 +90,29 @@ class SettingsFragment : Fragment() {
// binding.CheckedListItemSorting.setup(ListItemSorting, value) // binding.CheckedListItemSorting.setup(ListItemSorting, value)
// } // }
binding.MaxItems.setup(MaxItems, maxItems) binding.MaxItems.setup(maxItems)
binding.MaxLines.setup(MaxLines, maxLines) binding.MaxLines.setup(maxLines)
binding.MaxTitle.setup(MaxTitle, maxTitle) binding.MaxTitle.setup(maxTitle)
binding.AutoBackupMax.setup(AutoBackupMax, autoBackupMax) binding.AutoBackupMax.setup(autoBackupMax)
autoBackupPath.observe(viewLifecycleOwner) { value -> autoBackupPath.observe(viewLifecycleOwner) { value ->
binding.AutoBackup.setup(AutoBackup, value) binding.AutoBackup.setupAutoBackup(autoBackupPath, value)
} }
autoBackupPeriodDays.observe(viewLifecycleOwner) { value -> autoBackupPeriodDays.observe(viewLifecycleOwner) { value ->
binding.AutoBackupPeriodDays.setup(AutoBackupPeriodDays, value) binding.AutoBackupPeriodDays.setup(autoBackupPeriodDays, value)
scheduleAutoBackup(value.toLong(), requireContext()) scheduleAutoBackup(value.toLong(), requireContext())
} }
backupPassword.observe(viewLifecycleOwner) { value -> backupPassword.observe(viewLifecycleOwner) { value ->
binding.BackupPassword.setup(BackupPassword, value) binding.BackupPassword.setupBackupPassword(backupPassword, value)
} }
biometricLock.observe(viewLifecycleOwner) { value -> biometricLock.observe(viewLifecycleOwner) { value ->
binding.BiometricLock.setup(BiometricLock, value) binding.BiometricLock.setup(biometricLock, value)
} }
} }
@ -209,7 +200,7 @@ class SettingsFragment : Fragment() {
val layout = TextInputDialogBinding.inflate(layoutInflater, null, false) val layout = TextInputDialogBinding.inflate(layoutInflater, null, false)
val password = model.preferences.backupPassword.value val password = model.preferences.backupPassword.value
layout.InputText.apply { layout.InputText.apply {
if (password != emptyPassword) { if (password != PASSWORD_EMPTY) {
setText(password) setText(password)
} }
transformationMethod = PasswordTransformationMethod.getInstance() transformationMethod = PasswordTransformationMethod.getInstance()
@ -384,24 +375,23 @@ class SettingsFragment : Fragment() {
.show() .show()
} }
private fun PreferenceBinding.setup(info: ListInfo, value: String) { private inline fun <reified T> PreferenceBinding.setup(
Title.setText(info.title) enumPreference: EnumPreference<T>,
value: T,
val entries = info.getEntries(requireContext()) ) where T : Enum<T>, T : TextProvider {
val entryValues = info.getEntryValues() Title.setText(enumPreference.titleResId!!)
val context = requireContext()
val checked = entryValues.indexOf(value) Value.text = value.getText(context)
val displayValue = entries[checked] val enumEntries = T::class.java.enumConstants!!.toList()
val entries = enumEntries.map { it.getText(requireContext()) }.toTypedArray()
Value.text = displayValue val checked = enumEntries.indexOfFirst { it == value }
root.setOnClickListener { root.setOnClickListener {
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(context)
.setTitle(info.title) .setTitle(enumPreference.titleResId)
.setSingleChoiceItems(entries, checked) { dialog, which -> .setSingleChoiceItems(entries, checked) { dialog, which ->
dialog.cancel() dialog.cancel()
val newValue = entryValues[which] val newValue = enumEntries[which]
model.savePreference(info, newValue) model.savePreference(enumPreference, newValue)
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
@ -409,35 +399,73 @@ class SettingsFragment : Fragment() {
} }
private fun PreferenceBinding.setup( private fun PreferenceBinding.setup(
info: NotesSorting, preference: EnumPreference<BiometricLock>,
sortBy: String, value: BiometricLock,
sortDirection: SortDirection,
) { ) {
Title.setText(info.title) Title.setText(preference.titleResId!!)
val entries = info.getEntries(requireContext()) val context = requireContext()
val entryValues = info.getEntryValues() Value.text = value.getText(context)
val enumEntries = BiometricLock.entries
val entries = enumEntries.map { context.getString(it.textResId) }.toTypedArray()
val checked = enumEntries.indexOfFirst { it == value }
val checked = entryValues.indexOf(sortBy) root.setOnClickListener {
val displayValue = entries[checked] MaterialAlertDialogBuilder(requireContext())
.setTitle(preference.titleResId)
.setSingleChoiceItems(entries, checked) { dialog, which ->
dialog.cancel()
val newValue = enumEntries[which]
if (newValue == BiometricLock.ENABLED) {
when (requireContext().canAuthenticateWithBiometrics()) {
BiometricManager.BIOMETRIC_SUCCESS -> showEnableBiometricLock()
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
showNoBiometricsSupportToast()
Value.text = "$displayValue (${requireContext().getString(sortDirection.textResId)})" BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
showBiometricsNotSetupDialog()
}
} else {
when (requireContext().canAuthenticateWithBiometrics()) {
BiometricManager.BIOMETRIC_SUCCESS -> showDisableBiometricLock()
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
showNoBiometricsSupportToast()
model.savePreference(
model.preferences.biometricLock,
BiometricLock.DISABLED,
)
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
showBiometricsNotSetupDialog()
model.savePreference(
model.preferences.biometricLock,
BiometricLock.DISABLED,
)
}
}
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
private fun PreferenceBinding.setup(preference: NotesSortPreference, value: NotesSort) {
Title.setText(preference.titleResId!!)
Value.text = value.getText(requireContext())
root.setOnClickListener { root.setOnClickListener {
val layout = NotesSortDialogBinding.inflate(layoutInflater, null, false) val layout = NotesSortDialogBinding.inflate(layoutInflater, null, false)
entries.zip(entryValues).forEachIndexed { idx, (choiceText, sortByValue) -> NotesSortBy.entries.forEachIndexed { idx, notesSortBy ->
ChoiceItemBinding.inflate(layoutInflater).root.apply { ChoiceItemBinding.inflate(layoutInflater).root.apply {
id = idx id = idx
text = choiceText text = requireContext().getString(notesSortBy.textResId)
tag = sortByValue tag = notesSortBy
layout.NotesSortByRadioGroup.addView(this) layout.NotesSortByRadioGroup.addView(this)
setCompoundDrawablesRelativeWithIntrinsicBounds( setCompoundDrawablesRelativeWithIntrinsicBounds(notesSortBy.iconResId, 0, 0, 0)
NotesSorting.getSortIconResId(sortByValue), if (notesSortBy == value.sortedBy) {
0,
0,
0,
)
if (sortByValue == sortBy) {
layout.NotesSortByRadioGroup.check(this.id) layout.NotesSortByRadioGroup.check(this.id)
} }
} }
@ -450,37 +478,43 @@ class SettingsFragment : Fragment() {
tag = sortDir tag = sortDir
setCompoundDrawablesRelativeWithIntrinsicBounds(sortDir.iconResId, 0, 0, 0) setCompoundDrawablesRelativeWithIntrinsicBounds(sortDir.iconResId, 0, 0, 0)
layout.NotesSortDirectionRadioGroup.addView(this) layout.NotesSortDirectionRadioGroup.addView(this)
if (sortDir == sortDirection) { if (sortDir == value.sortDirection) {
layout.NotesSortDirectionRadioGroup.check(this.id) layout.NotesSortDirectionRadioGroup.check(this.id)
} }
} }
} }
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title) .setTitle(preference.titleResId)
.setView(layout.root) .setView(layout.root)
.setPositiveButton(R.string.save) { dialog, _ -> .setPositiveButton(R.string.save) { dialog, _ ->
dialog.cancel() dialog.cancel()
val newSortBy = layout.NotesSortByRadioGroup.checkedTag() as String val newSortBy = layout.NotesSortByRadioGroup.checkedTag() as NotesSortBy
val newSortDirection = val newSortDirection =
layout.NotesSortDirectionRadioGroup.checkedTag() as SortDirection layout.NotesSortDirectionRadioGroup.checkedTag() as SortDirection
model.preferences.savePreference(info, newSortBy, newSortDirection) model.savePreference(
model.preferences.notesSorting,
NotesSort(newSortBy, newSortDirection),
)
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.show() .show()
} }
} }
private fun PreferenceBinding.setup(info: BackupPassword, password: String) { private fun PreferenceBinding.setupBackupPassword(
Title.setText(info.title) preference: StringPreference,
password: String,
) {
Title.setText(preference.titleResId!!)
Value.transformationMethod = Value.transformationMethod =
if (password != emptyPassword) PasswordTransformationMethod.getInstance() else null if (password != PASSWORD_EMPTY) PasswordTransformationMethod.getInstance() else null
Value.text = if (password != emptyPassword) password else getText(R.string.tap_to_set_up) Value.text = if (password != PASSWORD_EMPTY) password else getText(R.string.tap_to_set_up)
root.setOnClickListener { root.setOnClickListener {
val layout = TextInputDialogBinding.inflate(layoutInflater, null, false) val layout = TextInputDialogBinding.inflate(layoutInflater, null, false)
layout.InputText.apply { layout.InputText.apply {
if (password != emptyPassword) { if (password != PASSWORD_EMPTY) {
setText(password) setText(password)
} }
transformationMethod = PasswordTransformationMethod.getInstance() transformationMethod = PasswordTransformationMethod.getInstance()
@ -491,66 +525,22 @@ class SettingsFragment : Fragment() {
visibility = View.VISIBLE visibility = View.VISIBLE
} }
MaterialAlertDialogBuilder(requireContext()) MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title) .setTitle(preference.titleResId)
.setView(layout.root) .setView(layout.root)
.setPositiveButton(R.string.save) { dialog, _ -> .setPositiveButton(R.string.save) { dialog, _ ->
dialog.cancel() dialog.cancel()
val updatedPassword = layout.InputText.text.toString() val updatedPassword = layout.InputText.text.toString()
model.preferences.savePreference(info, updatedPassword) model.savePreference(preference, updatedPassword)
} }
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.clear) { dialog, _ -> .setNeutralButton(R.string.clear) { dialog, _ ->
dialog.cancel() dialog.cancel()
model.preferences.savePreference(info, emptyPassword) model.savePreference(preference, PASSWORD_EMPTY)
} }
.show() .show()
} }
} }
private fun PreferenceBinding.setup(info: BiometricLock, value: String) {
Title.setText(info.title)
val entries = info.getEntries(requireContext())
val entryValues = info.getEntryValues()
val checked = entryValues.indexOf(value)
val displayValue = entries[checked]
Value.text = displayValue
root.setOnClickListener {
MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title)
.setSingleChoiceItems(entries, checked) { dialog, which ->
dialog.cancel()
val newValue = entryValues[which]
if (newValue == enabled) {
when (requireContext().canAuthenticateWithBiometrics()) {
BiometricManager.BIOMETRIC_SUCCESS -> showEnableBiometricLock()
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ->
showNoBiometricsSupportToast()
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED ->
showBiometricsNotSetupDialog()
}
} else {
when (requireContext().canAuthenticateWithBiometrics()) {
BiometricManager.BIOMETRIC_SUCCESS -> showDisableBiometricLock()
BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> {
showNoBiometricsSupportToast()
model.preferences.biometricLock.value = disabled
}
BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> {
showBiometricsNotSetupDialog()
model.preferences.biometricLock.value = disabled
}
}
}
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
private fun showEnableBiometricLock() { private fun showEnableBiometricLock() {
showBiometricOrPinPrompt( showBiometricOrPinPrompt(
false, false,
@ -559,10 +549,10 @@ class SettingsFragment : Fragment() {
R.string.enable_lock_description, R.string.enable_lock_description,
onSuccess = { cipher -> onSuccess = { cipher ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
model.preferences.iv = cipher.iv model.savePreference(model.preferences.iv, cipher.iv)
val passphrase = model.preferences.generatePassphrase(cipher) val passphrase = model.preferences.databaseEncryptionKey.init(cipher)
encryptDatabase(requireContext(), passphrase) encryptDatabase(requireContext(), passphrase)
model.savePreference(BiometricLock, enabled) model.savePreference(model.preferences.biometricLock, BiometricLock.ENABLED)
} }
val app = (activity?.application as NotallyXApplication) val app = (activity?.application as NotallyXApplication)
app.locked.value = false app.locked.value = false
@ -579,14 +569,14 @@ class SettingsFragment : Fragment() {
disableLockActivityResultLauncher, disableLockActivityResultLauncher,
R.string.disable_lock_title, R.string.disable_lock_title,
R.string.disable_lock_description, R.string.disable_lock_description,
model.preferences.iv!!, 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.getDatabasePassphrase() val encryptedPassphrase = model.preferences.databaseEncryptionKey.value
val passphrase = cipher.doFinal(encryptedPassphrase) val passphrase = cipher.doFinal(encryptedPassphrase)
model.closeDatabase() model.closeDatabase()
decryptDatabase(requireContext(), passphrase) decryptDatabase(requireContext(), passphrase)
model.savePreference(BiometricLock, disabled) model.savePreference(model.preferences.biometricLock, BiometricLock.DISABLED)
} }
showBiometricsDisabledToast() showBiometricsDisabledToast()
}, },
@ -632,10 +622,10 @@ class SettingsFragment : Fragment() {
.show() .show()
} }
private fun PreferenceBinding.setup(info: AutoBackup, value: String) { private fun PreferenceBinding.setupAutoBackup(preference: StringPreference, value: String) {
Title.setText(info.title) Title.setText(preference.titleResId!!)
if (value == info.emptyPath) { if (value == BACKUP_PATH_EMPTY) {
Value.setText(R.string.tap_to_set_up) Value.setText(R.string.tap_to_set_up)
root.setOnClickListener { displayChooseFolderDialog() } root.setOnClickListener { displayChooseFolderDialog() }
@ -655,14 +645,17 @@ class SettingsFragment : Fragment() {
} }
} }
private fun PreferenceSeekbarBinding.setup(info: SeekbarInfo, initialValue: Int) { private fun PreferenceSeekbarBinding.setup(
Title.setText(info.title) preference: IntPreference,
value: Int = preference.value,
) {
Title.setText(preference.titleResId!!)
Slider.apply { Slider.apply {
valueTo = info.max.toFloat() valueTo = preference.max.toFloat()
valueFrom = info.min.toFloat() valueFrom = preference.min.toFloat()
value = initialValue.toFloat() this@apply.value = value.toFloat()
addOnChangeListener { _, value, _ -> model.savePreference(info, value.toInt()) } addOnChangeListener { _, value, _ -> model.savePreference(preference, value.toInt()) }
} }
} }

View file

@ -40,14 +40,12 @@ import com.philkes.notallyx.presentation.getParcelableExtraCompat
import com.philkes.notallyx.presentation.getQuantityString import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.setupProgressDialog import com.philkes.notallyx.presentation.setupProgressDialog
import com.philkes.notallyx.presentation.view.Constants import com.philkes.notallyx.presentation.view.Constants
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByCreationDate
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByModifiedDate
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.view.note.ErrorAdapter import com.philkes.notallyx.presentation.view.note.ErrorAdapter
import com.philkes.notallyx.presentation.view.note.audio.AudioAdapter import com.philkes.notallyx.presentation.view.note.audio.AudioAdapter
import com.philkes.notallyx.presentation.view.note.preview.PreviewFileAdapter import com.philkes.notallyx.presentation.view.note.preview.PreviewFileAdapter
import com.philkes.notallyx.presentation.view.note.preview.PreviewImageAdapter import com.philkes.notallyx.presentation.view.note.preview.PreviewImageAdapter
import com.philkes.notallyx.presentation.viewmodel.NotallyModel import com.philkes.notallyx.presentation.viewmodel.NotallyModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortBy
import com.philkes.notallyx.presentation.widget.WidgetProvider import com.philkes.notallyx.presentation.widget.WidgetProvider
import com.philkes.notallyx.utils.FileError import com.philkes.notallyx.utils.FileError
import com.philkes.notallyx.utils.Operations import com.philkes.notallyx.utils.Operations
@ -282,13 +280,12 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
open fun setStateFromModel() { open fun setStateFromModel() {
val (date, datePrefixResId) = val (date, datePrefixResId) =
when (preferences.notesSorting.value.first) { when (preferences.notesSorting.value.sortedBy) {
autoSortByCreationDate -> Pair(model.timestamp, R.string.creation_date) NotesSortBy.CREATION_DATE -> Pair(model.timestamp, R.string.creation_date)
autoSortByModifiedDate -> Pair(model.modifiedTimestamp, R.string.modified_date) NotesSortBy.MODIFIED_DATE -> Pair(model.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(model.title)
Operations.bindLabels(binding.LabelGroup, model.labels, model.textSize) Operations.bindLabels(binding.LabelGroup, model.labels, model.textSize)
@ -602,9 +599,9 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
} }
} }
val title = TextSize.getEditTitleSize(model.textSize) val title = model.textSize.editTitleSize
val date = TextSize.getDisplayBodySize(model.textSize) val date = model.textSize.displayBodySize
val body = TextSize.getEditBodySize(model.textSize) val body = model.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)

View file

@ -4,18 +4,18 @@ import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.view.MenuItem import android.view.MenuItem
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Type import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.add import com.philkes.notallyx.presentation.add
import com.philkes.notallyx.presentation.setOnNextAction import com.philkes.notallyx.presentation.setOnNextAction
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.note.listitem.ListItemAdapter import com.philkes.notallyx.presentation.view.note.listitem.ListItemAdapter
import com.philkes.notallyx.presentation.view.note.listitem.ListManager import com.philkes.notallyx.presentation.view.note.listitem.ListManager
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemNoSortCallback import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemNoSortCallback
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedByCheckedCallback import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedByCheckedCallback
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList
import com.philkes.notallyx.presentation.view.note.listitem.sorting.toMutableList import com.philkes.notallyx.presentation.view.note.listitem.sorting.toMutableList
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.utils.changehistory.ChangeHistory import com.philkes.notallyx.utils.changehistory.ChangeHistory
class EditListActivity : EditActivity(Type.LIST) { class EditListActivity : EditActivity(Type.LIST) {
@ -102,12 +102,12 @@ class EditListActivity : EditActivity(Type.LIST) {
ListItemAdapter( ListItemAdapter(
model.textSize, model.textSize,
elevation, elevation,
Preferences.getInstance(application), NotallyXPreferences.getInstance(application),
listManager, listManager,
) )
val sortCallback = val sortCallback =
when (preferences.listItemSorting.value) { when (preferences.listItemSorting.value) {
ListItemSorting.autoSortByChecked -> ListItemSortedByCheckedCallback(adapter) ListItemSort.AUTO_SORT_BY_CHECKED -> ListItemSortedByCheckedCallback(adapter)
else -> ListItemNoSortCallback(adapter) else -> ListItemNoSortCallback(adapter)
} }
items = ListItemSortedList(sortCallback) items = ListItemSortedList(sortCallback)

View file

@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
@ -14,9 +13,10 @@ import com.philkes.notallyx.data.model.Header
import com.philkes.notallyx.databinding.ActivityPickNoteBinding import com.philkes.notallyx.databinding.ActivityPickNoteBinding
import com.philkes.notallyx.presentation.activity.LockedActivity import com.philkes.notallyx.presentation.activity.LockedActivity
import com.philkes.notallyx.presentation.view.main.BaseNoteAdapter import com.philkes.notallyx.presentation.view.main.BaseNoteAdapter
import com.philkes.notallyx.presentation.view.misc.View
import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.NotesView
import com.philkes.notallyx.utils.IO.getExternalImagesDirectory import com.philkes.notallyx.utils.IO.getExternalImagesDirectory
import java.util.Collections import java.util.Collections
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -37,18 +37,18 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ListIte
val result = Intent() val result = Intent()
setResult(RESULT_CANCELED, result) setResult(RESULT_CANCELED, result)
val preferences = Preferences.getInstance(application) val preferences = NotallyXPreferences.getInstance(application)
adapter = adapter =
with(preferences) { with(preferences) {
BaseNoteAdapter( BaseNoteAdapter(
Collections.emptySet(), Collections.emptySet(),
dateFormat.value, dateFormat.value,
notesSorting.value.first, notesSorting.value.sortedBy,
textSize.value, textSize.value,
maxItems, maxItems.value,
maxLines, maxLines.value,
maxTitle, maxTitle.value,
application.getExternalImagesDirectory(), application.getExternalImagesDirectory(),
this@PickNoteActivity, this@PickNoteActivity,
) )
@ -58,7 +58,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ListIte
adapter = this@PickNoteActivity.adapter adapter = this@PickNoteActivity.adapter
setHasFixedSize(true) setHasFixedSize(true)
layoutManager = layoutManager =
if (preferences.view.value == View.grid) { if (preferences.notesView.value == NotesView.GRID) {
StaggeredGridLayoutManager(2, RecyclerView.VERTICAL) StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
} else LinearLayoutManager(this@PickNoteActivity) } else LinearLayoutManager(this@PickNoteActivity)
} }

View file

@ -13,17 +13,19 @@ import com.philkes.notallyx.databinding.RecyclerHeaderBinding
import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteCreationDateSort import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteCreationDateSort
import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteModifiedDateSort import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteModifiedDateSort
import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteTitleSort import com.philkes.notallyx.presentation.view.main.sorting.BaseNoteTitleSort
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByModifiedDate
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByTitle
import com.philkes.notallyx.presentation.view.misc.SortDirection
import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSort
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortBy
import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
import java.io.File import java.io.File
class BaseNoteAdapter( class BaseNoteAdapter(
private val selectedIds: Set<Long>, private val selectedIds: Set<Long>,
private val dateFormat: String, private val dateFormat: DateFormat,
private val sortedBy: String, private val sortedBy: NotesSortBy,
private val textSize: String, private val textSize: TextSize,
private val maxItems: Int, private val maxItems: Int,
private val maxLines: Int, private val maxLines: Int,
private val maxTitle: Int, private val maxTitle: Int,
@ -82,12 +84,12 @@ class BaseNoteAdapter(
} }
} }
fun setSorting(sortBy: String, sortDirection: SortDirection) { fun setSorting(notesSort: NotesSort) {
val sortCallback = val sortCallback =
when (sortBy) { when (notesSort.sortedBy) {
autoSortByTitle -> BaseNoteTitleSort(this, sortDirection) NotesSortBy.TITLE -> BaseNoteTitleSort(this, notesSort.sortDirection)
autoSortByModifiedDate -> BaseNoteModifiedDateSort(this, sortDirection) NotesSortBy.MODIFIED_DATE -> BaseNoteModifiedDateSort(this, notesSort.sortDirection)
else -> BaseNoteCreationDateSort(this, sortDirection) else -> BaseNoteCreationDateSort(this, notesSort.sortDirection)
} }
replaceSorting(sortCallback) replaceSorting(sortCallback)
} }

View file

@ -28,17 +28,17 @@ import com.philkes.notallyx.presentation.applySpans
import com.philkes.notallyx.presentation.displayFormattedTimestamp import com.philkes.notallyx.presentation.displayFormattedTimestamp
import com.philkes.notallyx.presentation.dp import com.philkes.notallyx.presentation.dp
import com.philkes.notallyx.presentation.getQuantityString import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByCreationDate
import com.philkes.notallyx.presentation.view.misc.NotesSorting.autoSortByModifiedDate
import com.philkes.notallyx.presentation.view.misc.TextSize
import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener import com.philkes.notallyx.presentation.view.note.listitem.ListItemListener
import com.philkes.notallyx.presentation.viewmodel.preference.DateFormat
import com.philkes.notallyx.presentation.viewmodel.preference.NotesSortBy
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
import com.philkes.notallyx.utils.Operations import com.philkes.notallyx.utils.Operations
import java.io.File import java.io.File
class BaseNoteVH( class BaseNoteVH(
private val binding: RecyclerBaseNoteBinding, private val binding: RecyclerBaseNoteBinding,
private val dateFormat: String, private val dateFormat: DateFormat,
private val textSize: String, private val textSize: TextSize,
private val maxItems: Int, private val maxItems: Int,
maxLines: Int, maxLines: Int,
maxTitle: Int, maxTitle: Int,
@ -46,8 +46,8 @@ class BaseNoteVH(
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
init { init {
val title = TextSize.getDisplayTitleSize(textSize) val title = textSize.displayTitleSize
val body = TextSize.getDisplayBodySize(textSize) val body = textSize.displayBodySize
binding.apply { binding.apply {
Title.setTextSize(TypedValue.COMPLEX_UNIT_SP, title) Title.setTextSize(TypedValue.COMPLEX_UNIT_SP, title)
@ -75,7 +75,7 @@ class BaseNoteVH(
binding.root.isChecked = checked binding.root.isChecked = checked
} }
fun bind(baseNote: BaseNote, imageRoot: File?, checked: Boolean, sortBy: String) { fun bind(baseNote: BaseNote, imageRoot: File?, checked: Boolean, sortBy: NotesSortBy) {
updateCheck(checked) updateCheck(checked)
when (baseNote.type) { when (baseNote.type) {
@ -84,8 +84,9 @@ class BaseNoteVH(
} }
val (date, datePrefixResId) = val (date, datePrefixResId) =
when (sortBy) { when (sortBy) {
autoSortByCreationDate -> Pair(baseNote.timestamp, R.string.creation_date) NotesSortBy.CREATION_DATE -> Pair(baseNote.timestamp, R.string.creation_date)
autoSortByModifiedDate -> Pair(baseNote.modifiedTimestamp, R.string.modified_date) NotesSortBy.MODIFIED_DATE ->
Pair(baseNote.modifiedTimestamp, R.string.modified_date)
else -> Pair(null, null) else -> Pair(null, null)
} }
binding.Date.displayFormattedTimestamp(date, dateFormat, datePrefixResId) binding.Date.displayFormattedTimestamp(date, dateFormat, datePrefixResId)

View file

@ -2,7 +2,7 @@ package com.philkes.notallyx.presentation.view.main.sorting
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.view.misc.SortDirection import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
class BaseNoteCreationDateSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) : class BaseNoteCreationDateSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) :
BaseNoteSort(adapter, sortDirection) { BaseNoteSort(adapter, sortDirection) {

View file

@ -2,7 +2,7 @@ package com.philkes.notallyx.presentation.view.main.sorting
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.view.misc.SortDirection import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
class BaseNoteModifiedDateSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) : class BaseNoteModifiedDateSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) :
BaseNoteSort(adapter, sortDirection) { BaseNoteSort(adapter, sortDirection) {

View file

@ -5,7 +5,7 @@ import androidx.recyclerview.widget.SortedListAdapterCallback
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Header import com.philkes.notallyx.data.model.Header
import com.philkes.notallyx.data.model.Item import com.philkes.notallyx.data.model.Item
import com.philkes.notallyx.presentation.view.misc.SortDirection import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
abstract class BaseNoteSort( abstract class BaseNoteSort(
adapter: RecyclerView.Adapter<*>?, adapter: RecyclerView.Adapter<*>?,

View file

@ -2,7 +2,7 @@ package com.philkes.notallyx.presentation.view.main.sorting
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.view.misc.SortDirection import com.philkes.notallyx.presentation.viewmodel.preference.SortDirection
class BaseNoteTitleSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) : class BaseNoteTitleSort(adapter: RecyclerView.Adapter<*>?, sortDirection: SortDirection) :
BaseNoteSort(adapter, sortDirection) { BaseNoteSort(adapter, sortDirection) {

View file

@ -1,197 +0,0 @@
package com.philkes.notallyx.presentation.view.misc
import android.content.Context
import com.philkes.notallyx.R
import java.text.DateFormat
import java.util.Date
import org.ocpsoft.prettytime.PrettyTime
sealed interface ListInfo {
val title: Int
val key: String
val defaultValue: String
fun getEntryValues(): Array<String>
fun getEntries(context: Context): Array<String>
fun convertToValues(ids: Array<Int>, context: Context): Array<String> {
return Array(ids.size) { index ->
val id = ids[index]
context.getString(id)
}
}
}
object View : ListInfo {
const val list = "list"
const val grid = "grid"
override val title = R.string.view
override val key = "view"
override val defaultValue = list
override fun getEntryValues() = arrayOf(list, grid)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.list, R.string.grid)
return convertToValues(ids, context)
}
}
object Theme : ListInfo {
const val dark = "dark"
const val light = "light"
const val followSystem = "followSystem"
override val title = R.string.theme
override val key = "theme"
override val defaultValue = followSystem
override fun getEntryValues() = arrayOf(dark, light, followSystem)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.dark, R.string.light, R.string.follow_system)
return convertToValues(ids, context)
}
}
object DateFormat : ListInfo {
const val none = "none"
const val relative = "relative"
const val absolute = "absolute"
override val title = R.string.date_format
override val key = "dateFormat"
override val defaultValue = relative
override fun getEntryValues() = arrayOf(none, relative, absolute)
override fun getEntries(context: Context): Array<String> {
val none = context.getString(R.string.none)
val date = Date(System.currentTimeMillis() - 86400000)
val relative = PrettyTime().format(date)
val absolute = DateFormat.getDateInstance(DateFormat.FULL).format(date)
return arrayOf(none, relative, absolute)
}
}
object TextSize : ListInfo {
const val small = "small"
const val medium = "medium"
const val large = "large"
override val title = R.string.text_size
override val key = "textSize"
override val defaultValue = medium
override fun getEntryValues() = arrayOf(small, medium, large)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.small, R.string.medium, R.string.large)
return convertToValues(ids, context)
}
fun getEditBodySize(textSize: String): Float {
return when (textSize) {
small -> 14f
medium -> 16f
large -> 18f
else -> throw IllegalArgumentException("Invalid : $textSize")
}
}
fun getEditTitleSize(textSize: String): Float {
return when (textSize) {
small -> 18f
medium -> 20f
large -> 22f
else -> throw IllegalArgumentException("Invalid : $textSize")
}
}
fun getDisplayBodySize(textSize: String): Float {
return when (textSize) {
small -> 12f
medium -> 14f
large -> 16f
else -> throw IllegalArgumentException("Invalid : $textSize")
}
}
fun getDisplayTitleSize(textSize: String): Float {
return when (textSize) {
small -> 14f
medium -> 16f
large -> 18f
else -> throw IllegalArgumentException("Invalid : $textSize")
}
}
}
object ListItemSorting : ListInfo {
const val noAutoSort = "noAutoSort"
const val autoSortByChecked = "autoSortByChecked"
override val title = R.string.checked_list_item_sorting
override val key = "checkedListItemSorting"
override val defaultValue = noAutoSort
override fun getEntryValues() = arrayOf(noAutoSort, autoSortByChecked)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.no_auto_sort, R.string.auto_sort_by_checked)
return convertToValues(ids, context)
}
}
object NotesSorting : ListInfo {
const val autoSortByCreationDate = "autoSortByCreationDate"
const val autoSortByModifiedDate = "autoSortByModifiedDate"
const val autoSortByTitle = "autoSortByTitle"
override val title = R.string.notes_sorted_by
override val key = "notesSorting"
const val directionKey = "notesSortingDirection"
override val defaultValue = autoSortByCreationDate
val defaultValueDirection = SortDirection.DESC.name
override fun getEntryValues() =
arrayOf(autoSortByCreationDate, autoSortByModifiedDate, autoSortByTitle)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.creation_date, R.string.modified_date, R.string.title)
return convertToValues(ids, context)
}
fun getSortIconResId(sortBy: String): Int {
return when (sortBy) {
autoSortByModifiedDate -> R.drawable.edit_calendar
autoSortByTitle -> R.drawable.sort_by_alpha
else -> R.drawable.calendar_add_on
}
}
}
enum class SortDirection(val textResId: Int, val iconResId: Int) {
ASC(R.string.ascending, R.drawable.arrow_upward),
DESC(R.string.descending, R.drawable.arrow_downward),
}
object BiometricLock : ListInfo {
const val enabled = "enabled"
const val disabled = "disabled"
override val title = R.string.biometric_lock
override val key = "biometricLock"
override val defaultValue = disabled
override fun getEntryValues() = arrayOf(enabled, disabled)
override fun getEntries(context: Context): Array<String> {
val ids = arrayOf(R.string.enabled, R.string.disabled)
return convertToValues(ids, context)
}
}

View file

@ -3,7 +3,7 @@ package com.philkes.notallyx.presentation.view.misc
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
// LiveData that doesn't accept null values // LiveData that doesn't accept null values
class BetterLiveData<T>(value: T) : MutableLiveData<T>(value) { open class NotNullLiveData<T>(value: T) : MutableLiveData<T>(value) {
override fun getValue(): T { override fun getValue(): T {
return requireNotNull(super.getValue()) return requireNotNull(super.getValue())

View file

@ -1,69 +0,0 @@
package com.philkes.notallyx.presentation.view.misc
import com.philkes.notallyx.R
sealed interface SeekbarInfo {
val title: Int
val key: String
val defaultValue: Int
val min: Int
val max: Int
}
object MaxItems : SeekbarInfo {
override val title = R.string.max_items_to_display
override val key = "maxItemsToDisplayInList.v1"
override val defaultValue = 4
override val min = 1
override val max = 10
}
object MaxLines : SeekbarInfo {
override val title = R.string.max_lines_to_display
override val key = "maxLinesToDisplayInNote.v1"
override val defaultValue = 8
override val min = 1
override val max = 10
}
object MaxTitle : SeekbarInfo {
override val title = R.string.max_lines_to_display_title
override val key = "maxLinesToDisplayInTitle"
override val defaultValue = 1
override val min = 1
override val max = 10
}
object AutoBackupMax : SeekbarInfo {
override val title = R.string.max_backups
override val key = "maxBackups"
override val defaultValue = 3
override val min = 1
override val max = 10
}
object AutoBackupPeriodDays : SeekbarInfo {
override val title = R.string.backup_period_days
override val key = "autoBackupPeriodDays"
override val defaultValue = 1
override val min = 1
override val max = 31
}

View file

@ -1,29 +0,0 @@
package com.philkes.notallyx.presentation.view.misc
import com.philkes.notallyx.R
sealed interface TextInfo {
val title: Int
val key: String
val defaultValue: String
}
object AutoBackup : TextInfo {
const val emptyPath = "emptyPath"
override val title = R.string.auto_backup
override val key = "autoBackup"
override val defaultValue = emptyPath
}
object BackupPassword : TextInfo {
const val emptyPassword = "None"
override val title = R.string.backup_password
override val key = "backupPassword"
override val defaultValue = emptyPassword
}

View file

@ -4,14 +4,15 @@ import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.databinding.RecyclerListItemBinding import com.philkes.notallyx.databinding.RecyclerListItemBinding
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
class ListItemAdapter( class ListItemAdapter(
private val textSize: String, private val textSize: TextSize,
elevation: Float, elevation: Float,
private val preferences: Preferences, private val preferences: NotallyXPreferences,
private val listManager: ListManager, private val listManager: ListManager,
) : RecyclerView.Adapter<ListItemVH>() { ) : RecyclerView.Adapter<ListItemVH>() {

View file

@ -14,22 +14,22 @@ import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.databinding.RecyclerListItemBinding import com.philkes.notallyx.databinding.RecyclerListItemBinding
import com.philkes.notallyx.presentation.createListTextWatcherWithHistory import com.philkes.notallyx.presentation.createListTextWatcherWithHistory
import com.philkes.notallyx.presentation.setOnNextAction import com.philkes.notallyx.presentation.setOnNextAction
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.misc.SwipeLayout.SwipeActionsListener import com.philkes.notallyx.presentation.view.misc.SwipeLayout.SwipeActionsListener
import com.philkes.notallyx.presentation.view.misc.TextSize import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
class ListItemVH( class ListItemVH(
val binding: RecyclerListItemBinding, val binding: RecyclerListItemBinding,
val listManager: ListManager, val listManager: ListManager,
touchHelper: ItemTouchHelper, touchHelper: ItemTouchHelper,
textSize: String, textSize: TextSize,
) : RecyclerView.ViewHolder(binding.root) { ) : RecyclerView.ViewHolder(binding.root) {
private var editTextWatcher: TextWatcher private var editTextWatcher: TextWatcher
private var dragHandleInitialY: Float = 0f private var dragHandleInitialY: Float = 0f
init { init {
val body = TextSize.getEditBodySize(textSize) val body = textSize.editBodySize
binding.EditText.apply { binding.EditText.apply {
setTextSize(TypedValue.COMPLEX_UNIT_SP, body) setTextSize(TypedValue.COMPLEX_UNIT_SP, body)
@ -62,7 +62,7 @@ class ListItemVH(
} }
} }
fun bind(item: ListItem, firstItem: Boolean, autoSort: String) { fun bind(item: ListItem, firstItem: Boolean, autoSort: ListItemSort) {
updateEditText(item) updateEditText(item)
updateCheckBox(item) updateCheckBox(item)
@ -70,7 +70,7 @@ class ListItemVH(
updateDeleteButton(item) updateDeleteButton(item)
updateSwipe(item.isChild, !firstItem && !item.checked) updateSwipe(item.isChild, !firstItem && !item.checked)
if (item.checked && autoSort == ListItemSorting.autoSortByChecked) { if (item.checked && autoSort == ListItemSort.AUTO_SORT_BY_CHECKED) {
binding.DragHandle.visibility = INVISIBLE binding.DragHandle.visibility = INVISIBLE
} else { } else {
binding.DragHandle.visibility = VISIBLE binding.DragHandle.visibility = VISIBLE

View file

@ -5,11 +5,9 @@ import android.util.Log
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.EditText import android.widget.EditText
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.model.ListItem import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.areAllChecked import com.philkes.notallyx.data.model.areAllChecked
import com.philkes.notallyx.data.model.plus import com.philkes.notallyx.data.model.plus
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList
import com.philkes.notallyx.presentation.view.note.listitem.sorting.deleteItem import com.philkes.notallyx.presentation.view.note.listitem.sorting.deleteItem
import com.philkes.notallyx.presentation.view.note.listitem.sorting.filter import com.philkes.notallyx.presentation.view.note.listitem.sorting.filter
@ -24,6 +22,8 @@ import com.philkes.notallyx.presentation.view.note.listitem.sorting.setCheckedWi
import com.philkes.notallyx.presentation.view.note.listitem.sorting.setIsChild import com.philkes.notallyx.presentation.view.note.listitem.sorting.setIsChild
import com.philkes.notallyx.presentation.view.note.listitem.sorting.shiftItemOrders import com.philkes.notallyx.presentation.view.note.listitem.sorting.shiftItemOrders
import com.philkes.notallyx.presentation.view.note.listitem.sorting.toReadableString import com.philkes.notallyx.presentation.view.note.listitem.sorting.toReadableString
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.utils.changehistory.ChangeCheckedForAllChange import com.philkes.notallyx.utils.changehistory.ChangeCheckedForAllChange
import com.philkes.notallyx.utils.changehistory.ChangeHistory import com.philkes.notallyx.utils.changehistory.ChangeHistory
import com.philkes.notallyx.utils.changehistory.DeleteCheckedChange import com.philkes.notallyx.utils.changehistory.DeleteCheckedChange
@ -41,7 +41,7 @@ import com.philkes.notallyx.utils.changehistory.ListMoveChange
class ListManager( class ListManager(
private val recyclerView: RecyclerView, private val recyclerView: RecyclerView,
private val changeHistory: ChangeHistory, private val changeHistory: ChangeHistory,
private val preferences: Preferences, private val preferences: NotallyXPreferences,
private val inputMethodManager: InputMethodManager, private val inputMethodManager: InputMethodManager,
) { ) {
@ -396,7 +396,7 @@ class ListManager(
} }
private fun isAutoSortByCheckedEnabled() = private fun isAutoSortByCheckedEnabled() =
preferences.listItemSorting.value == ListItemSorting.autoSortByChecked preferences.listItemSorting.value == ListItemSort.AUTO_SORT_BY_CHECKED
private val Int.isBeforeChildItemOfOtherParent: Boolean private val Int.isBeforeChildItemOfOtherParent: Boolean
get() { get() {

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.room.withTransaction import androidx.room.withTransaction
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.dao.BaseNoteDao import com.philkes.notallyx.data.dao.BaseNoteDao
@ -38,10 +37,10 @@ import com.philkes.notallyx.data.model.SearchResult
import com.philkes.notallyx.data.model.Type import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.applySpans import com.philkes.notallyx.presentation.applySpans
import com.philkes.notallyx.presentation.getQuantityString import com.philkes.notallyx.presentation.getQuantityString
import com.philkes.notallyx.presentation.view.misc.AutoBackup
import com.philkes.notallyx.presentation.view.misc.ListInfo
import com.philkes.notallyx.presentation.view.misc.Progress import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.view.misc.SeekbarInfo import com.philkes.notallyx.presentation.viewmodel.preference.BasePreference
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.BACKUP_PATH_EMPTY
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.utils.ActionMode import com.philkes.notallyx.utils.ActionMode
import com.philkes.notallyx.utils.Cache import com.philkes.notallyx.utils.Cache
import com.philkes.notallyx.utils.IO.deleteAttachments import com.philkes.notallyx.utils.IO.deleteAttachments
@ -105,7 +104,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
private val pinned = Header(app.getString(R.string.pinned)) private val pinned = Header(app.getString(R.string.pinned))
private val others = Header(app.getString(R.string.others)) private val others = Header(app.getString(R.string.others))
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
val imageRoot = app.getExternalImagesDirectory() val imageRoot = app.getExternalImagesDirectory()
val fileRoot = app.getExternalFilesDirectory() val fileRoot = app.getExternalFilesDirectory()
@ -181,24 +180,20 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
private fun transform(list: List<BaseNote>) = transform(list, pinned, others) private fun transform(list: List<BaseNote>) = transform(list, pinned, others)
fun savePreference(info: SeekbarInfo, value: Int) = executeAsync {
preferences.savePreference(info, value)
}
fun savePreference(info: ListInfo, value: String) = executeAsync {
preferences.savePreference(info, value)
}
fun disableAutoBackup() { fun disableAutoBackup() {
clearPersistedUriPermissions() clearPersistedUriPermissions()
executeAsync { preferences.savePreference(AutoBackup, AutoBackup.emptyPath) } savePreference(preferences.autoBackupPath, BACKUP_PATH_EMPTY)
} }
fun setAutoBackupPath(uri: Uri) { fun setAutoBackupPath(uri: Uri) {
clearPersistedUriPermissions() clearPersistedUriPermissions()
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
app.contentResolver.takePersistableUriPermission(uri, flags) app.contentResolver.takePersistableUriPermission(uri, flags)
executeAsync { preferences.savePreference(AutoBackup, uri.toString()) } savePreference(preferences.autoBackupPath, uri.toString())
}
fun <T> savePreference(preference: BasePreference<T>, value: T) {
executeAsync { preference.save(value) }
} }
/** /**

View file

@ -16,7 +16,6 @@ import androidx.core.text.getSpans
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.DataUtil import com.philkes.notallyx.data.DataUtil
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
@ -30,8 +29,9 @@ import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.SpanRepresentation import com.philkes.notallyx.data.model.SpanRepresentation
import com.philkes.notallyx.data.model.Type import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.applySpans import com.philkes.notallyx.presentation.applySpans
import com.philkes.notallyx.presentation.view.misc.BetterLiveData import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.presentation.view.misc.Progress import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.widget.WidgetProvider import com.philkes.notallyx.presentation.widget.WidgetProvider
import com.philkes.notallyx.utils.Cache import com.philkes.notallyx.utils.Cache
import com.philkes.notallyx.utils.Event import com.philkes.notallyx.utils.Event
@ -51,7 +51,7 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
private val database = NotallyDatabase.getDatabase(app) private val database = NotallyDatabase.getDatabase(app)
private lateinit var baseNoteDao: BaseNoteDao private lateinit var baseNoteDao: BaseNoteDao
val textSize = Preferences.getInstance(app).textSize.value val textSize = NotallyXPreferences.getInstance(app).textSize.value
var isNewNote = true var isNewNote = true
@ -71,9 +71,9 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
var body: Editable = SpannableStringBuilder() var body: Editable = SpannableStringBuilder()
val items = ArrayList<ListItem>() val items = ArrayList<ListItem>()
val images = BetterLiveData<List<FileAttachment>>(emptyList()) val images = NotNullLiveData<List<FileAttachment>>(emptyList())
val files = BetterLiveData<List<FileAttachment>>(emptyList()) val files = NotNullLiveData<List<FileAttachment>>(emptyList())
val audios = BetterLiveData<List<Audio>>(emptyList()) val audios = NotNullLiveData<List<Audio>>(emptyList())
val addingFiles = MutableLiveData<Progress>() val addingFiles = MutableLiveData<Progress>()
val eventBus = MutableLiveData<Event<List<FileError>>>() val eventBus = MutableLiveData<Event<List<FileError>>>()

View file

@ -0,0 +1,157 @@
package com.philkes.notallyx.presentation.viewmodel.preference
import android.app.Application
import androidx.core.content.edit
import androidx.preference.PreferenceManager
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.BACKUP_PATH_EMPTY
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.PASSWORD_EMPTY
class NotallyXPreferences private constructor(app: Application) {
private val preferences = PreferenceManager.getDefaultSharedPreferences(app)
private val encryptedPreferences by lazy {
EncryptedSharedPreferences.create(
app,
"secret_shared_prefs",
MasterKey.Builder(app).setKeyScheme(MasterKey.KeyScheme.AES256_GCM).build(),
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM,
)
}
// Main thread (unfortunately)
val theme = createEnumPreference(preferences, "theme", Theme.FOLLOW_SYSTEM, R.string.theme)
val textSize =
createEnumPreference(preferences, "textSize", TextSize.MEDIUM, R.string.text_size)
val dateFormat =
createEnumPreference(preferences, "dateFormat", DateFormat.RELATIVE, R.string.date_format)
val notesView = createEnumPreference(preferences, "view", NotesView.LIST, R.string.view)
val notesSorting = NotesSortPreference(preferences)
val listItemSorting =
createEnumPreference(
preferences,
"checkedListItemSorting",
ListItemSort.NO_AUTO_SORT,
R.string.checked_list_item_sorting,
)
val maxItems =
IntPreference(
"maxItemsToDisplayInList.v1",
preferences,
4,
1,
10,
R.string.max_items_to_display,
)
val maxLines =
IntPreference(
"maxLinesToDisplayInNote.v1",
preferences,
8,
1,
10,
R.string.max_lines_to_display,
)
val maxTitle =
IntPreference(
"maxLinesToDisplayInTitle",
preferences,
1,
1,
10,
R.string.max_lines_to_display_title,
)
val autoBackupPath =
StringPreference("autoBackup", preferences, BACKUP_PATH_EMPTY, R.string.auto_backup)
val autoBackupPeriodDays =
IntPreference("autoBackupPeriodDays", preferences, 1, 1, 31, R.string.backup_period_days)
val autoBackupMax = IntPreference("maxBackups", preferences, 3, 1, 10, R.string.max_backups)
val backupPassword by lazy {
StringPreference(
"backupPassword",
encryptedPreferences,
PASSWORD_EMPTY,
R.string.backup_password,
)
}
val biometricLock =
createEnumPreference(
preferences,
"biometricLock",
BiometricLock.DISABLED,
R.string.biometric_lock,
)
val iv = ByteArrayPreference("encryption_iv", preferences, null)
val databaseEncryptionKey =
EncryptedPassphrasePreference("database_encryption_key", preferences, ByteArray(0))
fun getWidgetData(id: Int) = preferences.getLong("widget:$id", 0)
fun getWidgetNoteType(id: Int) =
preferences.getString("widgetNoteType:$id", null)?.let { Type.valueOf(it) }
fun deleteWidget(id: Int) {
preferences.edit(true) {
remove("widget:$id")
remove("widgetNoteType:$id")
}
}
fun updateWidget(id: Int, noteId: Long, noteType: Type) {
preferences.edit(true) {
putLong("widget:$id", noteId)
putString("widgetNoteType:$id", noteType.name)
commit()
}
}
fun getUpdatableWidgets(noteIds: LongArray? = null): List<Pair<Int, Long>> {
val updatableWidgets = ArrayList<Pair<Int, Long>>()
val pairs = preferences.all
pairs.keys.forEach { key ->
val token = "widget:"
if (key.startsWith(token)) {
val end = key.substringAfter(token)
val id = end.toIntOrNull()
if (id != null) {
val value = pairs[key] as? Long
if (value != null) {
if (noteIds == null || noteIds.contains(value)) {
updatableWidgets.add(Pair(id, value))
}
}
}
}
}
return updatableWidgets
}
fun showDateCreated(): Boolean {
return dateFormat.value != DateFormat.NONE
}
companion object {
@Volatile private var instance: NotallyXPreferences? = null
fun getInstance(app: Application): NotallyXPreferences {
return instance
?: synchronized(this) {
val instance = NotallyXPreferences(app)
Companion.instance = instance
return instance
}
}
}
}

View file

@ -0,0 +1,68 @@
package com.philkes.notallyx.presentation.viewmodel.preference
import android.content.Context
import android.content.SharedPreferences
import android.content.SharedPreferences.Editor
import com.philkes.notallyx.R
class NotesSortPreference(sharedPreferences: SharedPreferences) :
BasePreference<NotesSort>(sharedPreferences, NotesSort(), R.string.notes_sorted_by) {
override fun getValue(sharedPreferences: SharedPreferences): NotesSort {
val sortedByName = sharedPreferences.getString(SORTED_BY_KEY, null)
val sortedBy =
sortedByName?.let {
try {
NotesSortBy.fromValue(sortedByName)
} catch (e: Exception) {
defaultValue.sortedBy
}
} ?: defaultValue.sortedBy
val sortDirectionName = sharedPreferences.getString(SORT_DIRECTION_KEY, null)
val sortDirection =
sortDirectionName?.let {
try {
SortDirection.valueOf(sortDirectionName)
} catch (e: Exception) {
defaultValue.sortDirection
}
} ?: defaultValue.sortDirection
return NotesSort(sortedBy, sortDirection)
}
override fun Editor.put(value: NotesSort) {
putString(SORTED_BY_KEY, value.sortedBy.value)
putString(SORT_DIRECTION_KEY, value.sortDirection.name)
}
companion object {
const val SORTED_BY_KEY = "notesSorting"
const val SORT_DIRECTION_KEY = "notesSortingDirection"
}
}
enum class SortDirection(val textResId: Int, val iconResId: Int) {
ASC(R.string.ascending, R.drawable.arrow_upward),
DESC(R.string.descending, R.drawable.arrow_downward),
}
enum class NotesSortBy(val textResId: Int, val iconResId: Int, val value: String) {
TITLE(R.string.title, R.drawable.sort_by_alpha, "autoSortByTitle"),
CREATION_DATE(R.string.creation_date, R.drawable.calendar_add_on, "autoSortByCreationDate"),
MODIFIED_DATE(R.string.modified_date, R.drawable.edit_calendar, "autoSortByModifiedDate");
companion object {
fun fromValue(value: String): NotesSortBy? {
return entries.find { it.value == value }
}
}
}
data class NotesSort(
val sortedBy: NotesSortBy = NotesSortBy.CREATION_DATE,
val sortDirection: SortDirection = SortDirection.DESC,
) : TextProvider {
override fun getText(context: Context): String {
return "${context.getString(sortedBy.textResId)} (${context.getString(sortDirection.textResId)})"
}
}

View file

@ -0,0 +1,322 @@
package com.philkes.notallyx.presentation.viewmodel.preference
import android.content.Context
import android.content.SharedPreferences
import android.os.Build
import androidx.core.content.edit
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.toPreservedByteArray
import com.philkes.notallyx.data.model.toPreservedString
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import java.security.SecureRandom
import java.util.Date
import java.util.Locale
import javax.crypto.Cipher
import org.ocpsoft.prettytime.PrettyTime
/**
* Every Preference can be observed like a [NotNullLiveData].
*
* @param titleResId Optional string resource id, if preference can be set via the UI.
*/
abstract class BasePreference<T>(
private val sharedPreferences: SharedPreferences,
protected val defaultValue: T,
val titleResId: Int? = null,
) {
private var data: NotNullLiveData<T>? = null
private var cachedValue: T? = null
val value: T
get() {
if (cachedValue == null) {
cachedValue = getValue(sharedPreferences)
}
return cachedValue!!
}
protected abstract fun getValue(sharedPreferences: SharedPreferences): T
private fun getData(): NotNullLiveData<T> {
if (data == null) {
data = NotNullLiveData(value)
}
return data as NotNullLiveData<T>
}
internal fun save(value: T) {
sharedPreferences.edit(true) { put(value) }
cachedValue = value
getData().postValue(value)
}
protected abstract fun SharedPreferences.Editor.put(value: T)
fun observe(lifecycleOwner: LifecycleOwner, observer: Observer<T>) {
getData().observe(lifecycleOwner, observer)
}
fun observeForever(observer: Observer<T>) {
getData().observeForever(observer)
}
fun removeObserver(observer: Observer<T>) {
getData().removeObserver(observer)
}
fun removeObservers(lifecycleOwner: LifecycleOwner) {
getData().removeObservers(lifecycleOwner)
}
}
fun <T> BasePreference<T>.observeForeverSkipFirst(observer: Observer<T>) {
var isFirstEvent = true
this.observeForever { value ->
if (isFirstEvent) {
isFirstEvent = false
} else {
observer.onChanged(value)
}
}
}
interface TextProvider {
fun getText(context: Context): String
}
interface StaticTextProvider : TextProvider {
val textResId: Int
override fun getText(context: Context): String {
return context.getString(textResId)
}
}
class EnumPreference<T>(
sharedPreferences: SharedPreferences,
private val key: String,
defaultValue: T,
private val enumClass: Class<T>,
titleResId: Int? = null,
) : BasePreference<T>(sharedPreferences, defaultValue, titleResId) where
T : Enum<T>,
T : TextProvider {
override fun getValue(sharedPreferences: SharedPreferences): T {
val storedValue = sharedPreferences.getString(key, null)
return try {
storedValue?.let { java.lang.Enum.valueOf(enumClass, it.fromCamelCaseToEnumName()) }
?: defaultValue
} catch (e: IllegalArgumentException) {
defaultValue
}
}
override fun SharedPreferences.Editor.put(value: T) {
putString(key, value.name.toCamelCase())
}
}
inline fun <reified T> createEnumPreference(
sharedPreferences: SharedPreferences,
key: String,
defaultValue: T,
titleResId: Int? = null,
): EnumPreference<T> where T : Enum<T>, T : TextProvider {
return EnumPreference(sharedPreferences, key, defaultValue, T::class.java, titleResId)
}
class IntPreference(
private val key: String,
sharedPreferences: SharedPreferences,
defaultValue: Int,
val min: Int,
val max: Int,
titleResId: Int? = null,
) : BasePreference<Int>(sharedPreferences, defaultValue, titleResId) {
override fun getValue(sharedPreferences: SharedPreferences): Int {
return sharedPreferences.getInt(key, defaultValue)
}
override fun SharedPreferences.Editor.put(value: Int) {
putInt(key, value)
}
}
class StringPreference(
private val key: String,
sharedPreferences: SharedPreferences,
defaultValue: String,
titleResId: Int? = null,
) : BasePreference<String>(sharedPreferences, defaultValue, titleResId) {
override fun getValue(sharedPreferences: SharedPreferences): String {
return sharedPreferences.getString(key, defaultValue)!!
}
override fun SharedPreferences.Editor.put(value: String) {
putString(key, value)
}
}
class ByteArrayPreference(
private val key: String,
sharedPreferences: SharedPreferences,
defaultValue: ByteArray?,
titleResId: Int? = null,
) : BasePreference<ByteArray?>(sharedPreferences, defaultValue, titleResId) {
override fun getValue(sharedPreferences: SharedPreferences): ByteArray? {
return sharedPreferences.getString(key, null)?.toPreservedByteArray ?: defaultValue
}
override fun SharedPreferences.Editor.put(value: ByteArray?) {
putString(key, value?.toPreservedString)
}
}
class EncryptedPassphrasePreference(
private val key: String,
sharedPreferences: SharedPreferences,
defaultValue: ByteArray,
titleResId: Int? = null,
) : BasePreference<ByteArray>(sharedPreferences, defaultValue, titleResId) {
override fun getValue(sharedPreferences: SharedPreferences): ByteArray {
return sharedPreferences.getString(key, null)?.toPreservedByteArray ?: defaultValue
}
override fun SharedPreferences.Editor.put(value: ByteArray) {
putString(key, value.toPreservedString)
}
fun init(cipher: Cipher): ByteArray {
val random =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
SecureRandom.getInstanceStrong()
} else {
SecureRandom()
}
val result = ByteArray(64)
random.nextBytes(result)
// filter out zero byte values, as SQLCipher does not like them
while (result.contains(0)) {
random.nextBytes(result)
}
val encryptedPassphrase = cipher.doFinal(result)
save(encryptedPassphrase)
return result
}
}
enum class NotesView(override val textResId: Int) : StaticTextProvider {
LIST(R.string.list),
GRID(R.string.grid),
}
enum class Theme(override val textResId: Int) : StaticTextProvider {
DARK(R.string.dark),
LIGHT(R.string.light),
FOLLOW_SYSTEM(R.string.follow_system),
}
enum class DateFormat : TextProvider {
NONE,
RELATIVE,
ABSOLUTE;
override fun getText(context: Context): String {
val date = Date(System.currentTimeMillis() - 86400000)
return when (this) {
NONE -> context.getString(R.string.none)
RELATIVE -> PrettyTime().format(date)
ABSOLUTE -> java.text.DateFormat.getDateInstance(java.text.DateFormat.FULL).format(date)
}
}
}
enum class TextSize(override val textResId: Int) : StaticTextProvider {
SMALL(R.string.small),
MEDIUM(R.string.medium),
LARGE(R.string.large);
val editBodySize: Float
get() {
return when (this) {
SMALL -> 14f
MEDIUM -> 16f
LARGE -> 18f
}
}
val editTitleSize: Float
get() {
return when (this) {
SMALL -> 18f
MEDIUM -> 20f
LARGE -> 22f
}
}
val displayBodySize: Float
get() {
return when (this) {
SMALL -> 12f
MEDIUM -> 14f
LARGE -> 16f
}
}
val displayTitleSize: Float
get() {
return when (this) {
SMALL -> 14f
MEDIUM -> 16f
LARGE -> 18f
}
}
}
enum class ListItemSort(override val textResId: Int) : StaticTextProvider {
NO_AUTO_SORT(R.string.no_auto_sort),
AUTO_SORT_BY_CHECKED(R.string.auto_sort_by_checked),
}
enum class BiometricLock(override val textResId: Int) : StaticTextProvider {
ENABLED(R.string.enabled),
DISABLED(R.string.disabled),
}
object Constants {
const val BACKUP_PATH_EMPTY = "emptyPath"
const val PASSWORD_EMPTY = "None"
}
fun String.toCamelCase(): String {
return this.lowercase()
.split("_")
.mapIndexed { index, word ->
if (index == 0) word
else
word.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString()
}
}
.joinToString("")
}
fun String.fromCamelCaseToEnumName(): String {
return this.fold(StringBuilder()) { acc, char ->
if (char.isUpperCase() && acc.isNotEmpty()) {
acc.append("_")
}
acc.append(char.uppercase())
}
.toString()
}

View file

@ -8,12 +8,11 @@ import android.view.View
import android.widget.RemoteViews import android.widget.RemoteViews
import android.widget.RemoteViewsService import android.widget.RemoteViewsService
import com.philkes.notallyx.NotallyXApplication import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Type import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.view.misc.TextSize import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.widget.WidgetProvider.Companion.getSelectNoteIntent import com.philkes.notallyx.presentation.widget.WidgetProvider.Companion.getSelectNoteIntent
class WidgetFactory( class WidgetFactory(
@ -24,7 +23,7 @@ class WidgetFactory(
private var baseNote: BaseNote? = null private var baseNote: BaseNote? = null
private lateinit var database: NotallyDatabase private lateinit var database: NotallyDatabase
private val preferences = Preferences.getInstance(app) private val preferences = NotallyXPreferences.getInstance(app)
init { init {
NotallyDatabase.getDatabase(app).observeForever { database = it } NotallyDatabase.getDatabase(app).observeForever { database = it }
@ -64,14 +63,11 @@ class WidgetFactory(
private fun getNoteView(note: BaseNote): RemoteViews { private fun getNoteView(note: BaseNote): RemoteViews {
return RemoteViews(app.packageName, R.layout.widget_note).apply { return RemoteViews(app.packageName, R.layout.widget_note).apply {
setTextViewTextSize( val textSize = preferences.textSize.value
R.id.Title, setTextViewTextSize(R.id.Title, TypedValue.COMPLEX_UNIT_SP, textSize.displayTitleSize)
TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayTitleSize(preferences.textSize.value),
)
setTextViewText(R.id.Title, note.title) setTextViewText(R.id.Title, note.title)
val bodyTextSize = TextSize.getDisplayBodySize(preferences.textSize.value) val bodyTextSize = textSize.displayBodySize
setTextViewTextSize(R.id.Note, TypedValue.COMPLEX_UNIT_SP, bodyTextSize) setTextViewTextSize(R.id.Note, TypedValue.COMPLEX_UNIT_SP, bodyTextSize)
if (note.body.isNotEmpty()) { if (note.body.isNotEmpty()) {
@ -91,7 +87,7 @@ class WidgetFactory(
setTextViewTextSize( setTextViewTextSize(
R.id.Title, R.id.Title,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayTitleSize(preferences.textSize.value), preferences.textSize.value.displayTitleSize,
) )
setTextViewText(R.id.Title, list.title) setTextViewText(R.id.Title, list.title)
@ -116,7 +112,7 @@ class WidgetFactory(
setTextViewTextSize( setTextViewTextSize(
R.id.CheckBox, R.id.CheckBox,
TypedValue.COMPLEX_UNIT_SP, TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayBodySize(preferences.textSize.value), preferences.textSize.value.displayBodySize,
) )
setTextViewText(R.id.CheckBox, item.body) setTextViewText(R.id.CheckBox, item.body)
setInt( setInt(

View file

@ -9,7 +9,6 @@ import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.widget.RemoteViews import android.widget.RemoteViews
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.dao.BaseNoteDao import com.philkes.notallyx.data.dao.BaseNoteDao
@ -21,6 +20,7 @@ import com.philkes.notallyx.presentation.activity.ConfigureWidgetActivity
import com.philkes.notallyx.presentation.activity.note.EditListActivity import com.philkes.notallyx.presentation.activity.note.EditListActivity
import com.philkes.notallyx.presentation.activity.note.EditNoteActivity import com.philkes.notallyx.presentation.activity.note.EditNoteActivity
import com.philkes.notallyx.presentation.view.Constants import com.philkes.notallyx.presentation.view.Constants
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -110,7 +110,7 @@ class WidgetProvider : AppWidgetProvider() {
override fun onDeleted(context: Context, appWidgetIds: IntArray) { override fun onDeleted(context: Context, appWidgetIds: IntArray) {
val app = context.applicationContext as Application val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
appWidgetIds.forEach { id -> preferences.deleteWidget(id) } appWidgetIds.forEach { id -> preferences.deleteWidget(id) }
} }
@ -121,7 +121,7 @@ class WidgetProvider : AppWidgetProvider() {
appWidgetIds: IntArray, appWidgetIds: IntArray,
) { ) {
val app = context.applicationContext as Application val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
appWidgetIds.forEach { id -> appWidgetIds.forEach { id ->
val noteId = preferences.getWidgetData(id) val noteId = preferences.getWidgetData(id)
@ -134,7 +134,7 @@ class WidgetProvider : AppWidgetProvider() {
fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean = false) { fun updateWidgets(context: Context, noteIds: LongArray? = null, locked: Boolean = false) {
val app = context.applicationContext as Application val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
val manager = AppWidgetManager.getInstance(context) val manager = AppWidgetManager.getInstance(context)
val updatableWidgets = preferences.getUpdatableWidgets(noteIds) val updatableWidgets = preferences.getUpdatableWidgets(noteIds)

View file

@ -2,12 +2,12 @@ package com.philkes.notallyx.utils
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.data.model.BaseNote import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.view.misc.BetterLiveData import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
class ActionMode { class ActionMode {
val enabled = BetterLiveData(false) val enabled = NotNullLiveData(false)
val count = BetterLiveData(0) val count = NotNullLiveData(0)
val selectedNotes = HashMap<Long, BaseNote>() val selectedNotes = HashMap<Long, BaseNote>()
val selectedIds = selectedNotes.keys val selectedIds = selectedNotes.keys
val closeListener = MutableLiveData<Event<Set<Long>>>() val closeListener = MutableLiveData<Event<Set<Long>>>()

View file

@ -19,7 +19,7 @@ import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Color import com.philkes.notallyx.data.model.Color
import com.philkes.notallyx.data.model.ListItem import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.databinding.LabelBinding import com.philkes.notallyx.databinding.LabelBinding
import com.philkes.notallyx.presentation.view.misc.TextSize import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
import java.io.File import java.io.File
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.OutputStreamWriter import java.io.OutputStreamWriter
@ -99,7 +99,7 @@ object Operations {
} }
} }
fun bindLabels(group: ChipGroup, labels: List<String>, textSize: String) { fun bindLabels(group: ChipGroup, labels: List<String>, textSize: TextSize) {
if (labels.isEmpty()) { if (labels.isEmpty()) {
group.visibility = View.GONE group.visibility = View.GONE
} else { } else {
@ -107,7 +107,7 @@ object Operations {
group.removeAllViews() group.removeAllViews()
val inflater = LayoutInflater.from(group.context) val inflater = LayoutInflater.from(group.context)
val labelSize = TextSize.getDisplayBodySize(textSize) val labelSize = textSize.displayBodySize
for (label in labels) { for (label in labels) {
val view = LabelBinding.inflate(inflater, group, true).root val view = LabelBinding.inflate(inflater, group, true).root

View file

@ -6,7 +6,7 @@ import android.media.MediaRecorder
import android.os.Build import android.os.Build
import android.os.SystemClock import android.os.SystemClock
import androidx.annotation.RequiresApi import androidx.annotation.RequiresApi
import com.philkes.notallyx.presentation.view.misc.BetterLiveData import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.utils.IO.getTempAudioFile import com.philkes.notallyx.utils.IO.getTempAudioFile
import com.philkes.notallyx.utils.audio.Status.PAUSED import com.philkes.notallyx.utils.audio.Status.PAUSED
import com.philkes.notallyx.utils.audio.Status.READY import com.philkes.notallyx.utils.audio.Status.READY
@ -15,7 +15,7 @@ import com.philkes.notallyx.utils.audio.Status.RECORDING
@RequiresApi(24) @RequiresApi(24)
class AudioRecordService : Service() { class AudioRecordService : Service() {
var status = BetterLiveData(READY) var status = NotNullLiveData(READY)
private var lastStart = 0L private var lastStart = 0L
private var audioDuration = 0L private var audioDuration = 0L

View file

@ -6,8 +6,8 @@ import android.net.Uri
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.work.Worker import androidx.work.Worker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.philkes.notallyx.Preferences import com.philkes.notallyx.presentation.viewmodel.preference.Constants.BACKUP_PATH_EMPTY
import com.philkes.notallyx.presentation.view.misc.AutoBackup import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.utils.backup.Export.exportAsZip import com.philkes.notallyx.utils.backup.Export.exportAsZip
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -17,11 +17,11 @@ class AutoBackupWorker(private val context: Context, params: WorkerParameters) :
override fun doWork(): Result { override fun doWork(): Result {
val app = context.applicationContext as Application val app = context.applicationContext as Application
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
val backupPath = preferences.autoBackupPath.value val backupPath = preferences.autoBackupPath.value
val maxBackups = preferences.autoBackupMax val maxBackups = preferences.autoBackupMax.value
if (backupPath != AutoBackup.emptyPath) { if (backupPath != BACKUP_PATH_EMPTY) {
val uri = Uri.parse(backupPath) val uri = Uri.parse(backupPath)
val folder = requireNotNull(DocumentFile.fromTreeUri(app, uri)) val folder = requireNotNull(DocumentFile.fromTreeUri(app, uri))

View file

@ -8,13 +8,13 @@ import androidx.lifecycle.MutableLiveData
import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.PeriodicWorkRequest import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.NotallyDatabase import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.model.Converters import com.philkes.notallyx.data.model.Converters
import com.philkes.notallyx.data.model.FileAttachment import com.philkes.notallyx.data.model.FileAttachment
import com.philkes.notallyx.presentation.view.misc.BackupPassword.emptyPassword
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled
import com.philkes.notallyx.presentation.view.misc.Progress import com.philkes.notallyx.presentation.view.misc.Progress
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.viewmodel.preference.Constants.PASSWORD_EMPTY
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.utils.IO.SUBFOLDER_AUDIOS import com.philkes.notallyx.utils.IO.SUBFOLDER_AUDIOS
import com.philkes.notallyx.utils.IO.SUBFOLDER_FILES import com.philkes.notallyx.utils.IO.SUBFOLDER_FILES
import com.philkes.notallyx.utils.IO.SUBFOLDER_IMAGES import com.philkes.notallyx.utils.IO.SUBFOLDER_IMAGES
@ -43,28 +43,28 @@ object Export {
backupProgress?.postValue(Progress(indeterminate = true)) backupProgress?.postValue(Progress(indeterminate = true))
val database = NotallyDatabase.getDatabase(app).value val database = NotallyDatabase.getDatabase(app).value
database.checkpoint() database.checkpoint()
val preferences = Preferences.getInstance(app) val preferences = NotallyXPreferences.getInstance(app)
val backupPassword = preferences.backupPassword.value val backupPassword = preferences.backupPassword.value
val tempFile = File.createTempFile("export", "tmp", app.cacheDir) val tempFile = File.createTempFile("export", "tmp", app.cacheDir)
val zipFile = val zipFile =
ZipFile( ZipFile(
tempFile, tempFile,
if (backupPassword != emptyPassword) backupPassword.toCharArray() else null, if (backupPassword != PASSWORD_EMPTY) backupPassword.toCharArray() else null,
) )
val zipParameters = val zipParameters =
ZipParameters().apply { ZipParameters().apply {
isEncryptFiles = backupPassword != emptyPassword isEncryptFiles = backupPassword != PASSWORD_EMPTY
compressionLevel = CompressionLevel.NO_COMPRESSION compressionLevel = CompressionLevel.NO_COMPRESSION
encryptionMethod = EncryptionMethod.AES encryptionMethod = EncryptionMethod.AES
} }
if ( if (
preferences.biometricLock.value == enabled && preferences.biometricLock.value == BiometricLock.ENABLED &&
Build.VERSION.SDK_INT >= Build.VERSION_CODES.M Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
) { ) {
val cipher = getInitializedCipherForDecryption(iv = preferences.iv!!) val cipher = getInitializedCipherForDecryption(iv = preferences.iv.value!!)
val passphrase = cipher.doFinal(preferences.getDatabasePassphrase()) val passphrase = cipher.doFinal(preferences.databaseEncryptionKey.value)
val decryptedFile = File.createTempFile("decrypted", "tmp", app.cacheDir) val decryptedFile = File.createTempFile("decrypted", "tmp", app.cacheDir)
decryptDatabase(app, passphrase, decryptedFile) decryptDatabase(app, passphrase, decryptedFile)
zipFile.addFile(decryptedFile, zipParameters.copy(NotallyDatabase.DatabaseName)) zipFile.addFile(decryptedFile, zipParameters.copy(NotallyDatabase.DatabaseName))

View file

@ -1,7 +1,7 @@
package com.philkes.notallyx.recyclerview.listmanager package com.philkes.notallyx.recyclerview.listmanager
import com.philkes.notallyx.data.model.ListItem import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.presentation.view.misc.ListItemSorting import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.test.assert import com.philkes.notallyx.test.assert
import com.philkes.notallyx.test.assertChildren import com.philkes.notallyx.test.assertChildren
import com.philkes.notallyx.test.assertOrder import com.philkes.notallyx.test.assertOrder
@ -21,7 +21,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
// region add // region add
@Test @Test
fun `add item at default position`() { fun `add item at default position`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val item = createListItem("Test") val item = createListItem("Test")
val newItem = item.clone() as ListItem val newItem = item.clone() as ListItem
listManager.add(item = item) listManager.add(item = item)
@ -33,7 +33,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `add default item before child item`() { fun `add default item before child item`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.add(1) listManager.add(1)
@ -45,7 +45,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `add default item after child item`() { fun `add default item after child item`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.add(2) listManager.add(2)
@ -57,7 +57,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `add checked item with correct order`() { fun `add checked item with correct order`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val itemToAdd = ListItem("Test", true, false, null, mutableListOf()) val itemToAdd = ListItem("Test", true, false, null, mutableListOf())
val newItem = itemToAdd.clone() as ListItem val newItem = itemToAdd.clone() as ListItem
listManager.add(0, item = itemToAdd) listManager.add(0, item = itemToAdd)
@ -71,7 +71,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `add checked item with correct order when auto-sort enabled`() { fun `add checked item with correct order when auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
val itemToAdd = ListItem("Test", true, false, null, mutableListOf()) val itemToAdd = ListItem("Test", true, false, null, mutableListOf())
val newItem = itemToAdd.clone() as ListItem val newItem = itemToAdd.clone() as ListItem
listManager.add(position = 0, item = itemToAdd) listManager.add(position = 0, item = itemToAdd)
@ -85,7 +85,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `add item with children`() { fun `add item with children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val childItem1 = ListItem("Child1", false, true, null, mutableListOf()) val childItem1 = ListItem("Child1", false, true, null, mutableListOf())
val childItem2 = ListItem("Child2", false, true, null, mutableListOf()) val childItem2 = ListItem("Child2", false, true, null, mutableListOf())
val parentItem = val parentItem =
@ -114,7 +114,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete first item from list unforced does not delete`() { fun `delete first item from list unforced does not delete`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val deletedItem = listManager.delete(position = 0, force = false, allowFocusChange = false) val deletedItem = listManager.delete(position = 0, force = false, allowFocusChange = false)
assertNull(deletedItem) assertNull(deletedItem)
@ -124,7 +124,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete first item from list forced`() { fun `delete first item from list forced`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val deletedItem = listManager.delete(position = 0, force = true) val deletedItem = listManager.delete(position = 0, force = true)
assertEquals("A", deletedItem!!.body) assertEquals("A", deletedItem!!.body)
@ -134,7 +134,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete item with children from list`() { fun `delete item with children from list`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
val deletedItem = listManager.delete(position = 0, force = true)!! val deletedItem = listManager.delete(position = 0, force = true)!!
@ -147,7 +147,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete at invalid position`() { fun `delete at invalid position`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val deletedItem = listManager.delete(10) val deletedItem = listManager.delete(10)
assertNull(deletedItem) assertNull(deletedItem)
@ -160,7 +160,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete checked items`() { fun `delete checked items`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
@ -174,7 +174,7 @@ class ListManagerAddDeleteTest : ListManagerTestBase() {
@Test @Test
fun `delete checked items with auto-sort enabled`() { fun `delete checked items with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)

View file

@ -1,6 +1,6 @@
package com.philkes.notallyx.recyclerview.listmanager package com.philkes.notallyx.recyclerview.listmanager
import com.philkes.notallyx.presentation.view.misc.ListItemSorting import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.test.assert import com.philkes.notallyx.test.assert
import com.philkes.notallyx.test.assertChecked import com.philkes.notallyx.test.assertChecked
import com.philkes.notallyx.test.assertOrder import com.philkes.notallyx.test.assertOrder
@ -14,7 +14,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked checks item`() { fun `changeChecked checks item`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeChecked(0, checked = true, pushChange = true) listManager.changeChecked(0, checked = true, pushChange = true)
"A".assertIsChecked() "A".assertIsChecked()
@ -24,7 +24,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked unchecks item and updates order`() { fun `changeChecked unchecks item and updates order`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
listManager.changeChecked(0, checked = false, pushChange = true) listManager.changeChecked(0, checked = false, pushChange = true)
@ -35,7 +35,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked does nothing when state is unchanged`() { fun `changeChecked does nothing when state is unchanged`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
listManager.changeChecked(0, checked = true, pushChange = true) listManager.changeChecked(0, checked = true, pushChange = true)
@ -46,7 +46,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked on child item`() { fun `changeChecked on child item`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeChecked(1, checked = true, pushChange = true) listManager.changeChecked(1, checked = true, pushChange = true)
@ -61,7 +61,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked on parent item checks all children`() { fun `changeChecked on parent item checks all children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
@ -79,7 +79,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked on child item and does not move when auto-sort is enabled`() { fun `changeChecked on child item and does not move when auto-sort is enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeChecked(1, checked = true, pushChange = true) listManager.changeChecked(1, checked = true, pushChange = true)
@ -94,7 +94,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked true on parent item checks all children and moves when auto-sort is enabled`() { fun `changeChecked true on parent item checks all children and moves when auto-sort is enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
@ -115,7 +115,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked false on parent item unchecks all children and moves when auto-sort is enabled`() { fun `changeChecked false on parent item unchecks all children and moves when auto-sort is enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeChecked(0, checked = true, pushChange = true) listManager.changeChecked(0, checked = true, pushChange = true)
@ -137,7 +137,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked false on parent item after add another item when auto-sort is enabled`() { fun `changeChecked false on parent item after add another item when auto-sort is enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeChecked(0, checked = true, pushChange = true) listManager.changeChecked(0, checked = true, pushChange = true)
@ -155,7 +155,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeChecked false on child item also unchecks parent`() { fun `changeChecked false on child item also unchecks parent`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(1, checked = true, pushChange = true) listManager.changeChecked(1, checked = true, pushChange = true)
@ -176,7 +176,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeCheckedForAll true checks all unchecked items`() { fun `changeCheckedForAll true checks all unchecked items`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeChecked(1, true) listManager.changeChecked(1, true)
listManager.changeChecked(3, true) listManager.changeChecked(3, true)
@ -189,7 +189,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeCheckedForAll false unchecks all unchecked items`() { fun `changeCheckedForAll false unchecks all unchecked items`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeChecked(1, true) listManager.changeChecked(1, true)
listManager.changeChecked(3, true) listManager.changeChecked(3, true)
@ -203,7 +203,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeCheckedForAll true correct order with auto-sort enabled`() { fun `changeCheckedForAll true correct order with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
@ -217,7 +217,7 @@ class ListManagerCheckedTest : ListManagerTestBase() {
@Test @Test
fun `changeCheckedForAll false correct order with auto-sort enabled`() { fun `changeCheckedForAll false correct order with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)

View file

@ -1,6 +1,6 @@
package com.philkes.notallyx.recyclerview.listmanager package com.philkes.notallyx.recyclerview.listmanager
import com.philkes.notallyx.presentation.view.misc.ListItemSorting import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.test.assert import com.philkes.notallyx.test.assert
import com.philkes.notallyx.test.assertOrder import com.philkes.notallyx.test.assertOrder
import com.philkes.notallyx.utils.changehistory.ListIsChildChange import com.philkes.notallyx.utils.changehistory.ListIsChildChange
@ -12,7 +12,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test @Test
fun `changeIsChild changes parent to child and pushes change`() { fun `changeIsChild changes parent to child and pushes change`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, isChild = true) listManager.changeIsChild(1, isChild = true)
items.assertOrder("A", "B") items.assertOrder("A", "B")
@ -22,7 +22,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test @Test
fun `changeIsChild changes child to parent`() { fun `changeIsChild changes child to parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, isChild = true) listManager.changeIsChild(1, isChild = true)
listManager.changeIsChild(1, isChild = false) listManager.changeIsChild(1, isChild = false)
@ -34,7 +34,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test @Test
fun `changeIsChild adds all child items when item becomes a parent`() { fun `changeIsChild adds all child items when item becomes a parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)

View file

@ -1,7 +1,7 @@
package com.philkes.notallyx.recyclerview.listmanager package com.philkes.notallyx.recyclerview.listmanager
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.note.listitem.sorting.lastIndex import com.philkes.notallyx.presentation.view.note.listitem.sorting.lastIndex
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.test.assert import com.philkes.notallyx.test.assert
import com.philkes.notallyx.test.assertOrder import com.philkes.notallyx.test.assertOrder
import com.philkes.notallyx.test.createListItem import com.philkes.notallyx.test.createListItem
@ -18,7 +18,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move parent without children`() { fun `move parent without children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val newPosition = listManager.move(3, 1) val newPosition = listManager.move(3, 1)
items.assertOrder("A", "D", "B") items.assertOrder("A", "D", "B")
@ -28,7 +28,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move parent with children into other parent`() { fun `move parent with children into other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
@ -46,7 +46,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move parent with children to bottom`() { fun `move parent with children to bottom`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
items.printList("Before") items.printList("Before")
@ -63,7 +63,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move parent with children to top`() { fun `move parent with children to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
listManager.add(0, createListItem("G")) listManager.add(0, createListItem("G"))
@ -81,7 +81,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move child to other parent`() { fun `move child to other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
val newPosition = listManager.move(3, 1) val newPosition = listManager.move(3, 1)
@ -94,7 +94,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move child above other child`() { fun `move child above other child`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
listManager.changeIsChild(5, true, false) listManager.changeIsChild(5, true, false)
@ -109,7 +109,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `move child to top`() { fun `move child to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
val newPosition = listManager.move(3, 0) val newPosition = listManager.move(3, 0)
@ -122,7 +122,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `dont move parent into own children`() { fun `dont move parent into own children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeIsChild(4, true) listManager.changeIsChild(4, true)
@ -135,7 +135,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `dont move parent under checked item if auto-sort enabled`() { fun `dont move parent under checked item if auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeChecked(5, true) listManager.changeChecked(5, true)
val newPosition = listManager.move(2, 5) val newPosition = listManager.move(2, 5)
@ -150,7 +150,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove parent without children`() { fun `undoMove parent without children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
val newPosition = listManager.move(1, 4)!! val newPosition = listManager.move(1, 4)!!
val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove
@ -161,7 +161,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove parent with children into other parent`() { fun `undoMove parent with children into other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
val newPosition = listManager.move(1, 3)!! val newPosition = listManager.move(1, 3)!!
val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove
@ -175,7 +175,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove move parent with children to bottom`() { fun `undoMove move parent with children to bottom`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
val newPosition = listManager.move(0, items.lastIndex)!! val newPosition = listManager.move(0, items.lastIndex)!!
@ -191,7 +191,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove parent with children to top`() { fun `undoMove parent with children to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
items.printList("Before") items.printList("Before")
@ -208,7 +208,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove child to other parent`() { fun `undoMove child to other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
val newPosition = listManager.move(3, 1)!! val newPosition = listManager.move(3, 1)!!
val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove
@ -222,7 +222,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove child above other child`() { fun `undoMove child above other child`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
listManager.changeIsChild(5, true, false) listManager.changeIsChild(5, true, false)
@ -239,7 +239,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `undoMove child to top`() { fun `undoMove child to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
val newPosition = listManager.move(3, 0)!! val newPosition = listManager.move(3, 0)!!
val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove val itemBeforeMove = (changeHistory.lookUp() as ListMoveChange).itemBeforeMove
@ -256,7 +256,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag parent without children`() { fun `endDrag parent without children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listItemDragCallback.simulateDrag(3, 1, "D".itemCount) listItemDragCallback.simulateDrag(3, 1, "D".itemCount)
items.assertOrder("A", "D", "B") items.assertOrder("A", "D", "B")
@ -265,7 +265,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag parent with children into other parent`() { fun `endDrag parent with children into other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
listManager.changeIsChild(5, true, false) listManager.changeIsChild(5, true, false)
@ -300,7 +300,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag parent with children to bottom`() { fun `endDrag parent with children to bottom`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(2, true, false) listManager.changeIsChild(2, true, false)
items.printList("Before") items.printList("Before")
@ -316,7 +316,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag parent with children to top`() { fun `endDrag parent with children to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true, false) listManager.changeIsChild(1, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
items.printList("Before") items.printList("Before")
@ -332,7 +332,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag child to other parent`() { fun `endDrag child to other parent`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
listItemDragCallback.simulateDrag(3, 1, "D".itemCount) listItemDragCallback.simulateDrag(3, 1, "D".itemCount)
@ -344,7 +344,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag child above other child`() { fun `endDrag child above other child`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
listManager.changeIsChild(4, true, false) listManager.changeIsChild(4, true, false)
listManager.changeIsChild(5, true, false) listManager.changeIsChild(5, true, false)
@ -358,7 +358,7 @@ class ListManagerMoveTest : ListManagerTestBase() {
@Test @Test
fun `endDrag child to top`() { fun `endDrag child to top`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(3, true, false) listManager.changeIsChild(3, true, false)
listItemDragCallback.simulateDrag(3, 0, "D".itemCount) listItemDragCallback.simulateDrag(3, 0, "D".itemCount)

View file

@ -3,10 +3,7 @@ package com.philkes.notallyx.recyclerview.listmanager
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList import androidx.recyclerview.widget.SortedList
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.model.ListItem import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.presentation.view.misc.BetterLiveData
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.note.listitem.ListItemDragCallback import com.philkes.notallyx.presentation.view.note.listitem.ListItemDragCallback
import com.philkes.notallyx.presentation.view.note.listitem.ListItemVH import com.philkes.notallyx.presentation.view.note.listitem.ListItemVH
import com.philkes.notallyx.presentation.view.note.listitem.ListManager import com.philkes.notallyx.presentation.view.note.listitem.ListManager
@ -15,6 +12,9 @@ import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSort
import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList import com.philkes.notallyx.presentation.view.note.listitem.sorting.ListItemSortedList
import com.philkes.notallyx.presentation.view.note.listitem.sorting.find import com.philkes.notallyx.presentation.view.note.listitem.sorting.find
import com.philkes.notallyx.presentation.view.note.listitem.sorting.indexOfFirst import com.philkes.notallyx.presentation.view.note.listitem.sorting.indexOfFirst
import com.philkes.notallyx.presentation.viewmodel.preference.EnumPreference
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.test.assertChildren import com.philkes.notallyx.test.assertChildren
import com.philkes.notallyx.test.createListItem import com.philkes.notallyx.test.createListItem
import com.philkes.notallyx.test.mockAndroidLog import com.philkes.notallyx.test.mockAndroidLog
@ -34,7 +34,7 @@ open class ListManagerTestBase {
protected lateinit var inputMethodManager: InputMethodManager protected lateinit var inputMethodManager: InputMethodManager
protected lateinit var changeHistory: ChangeHistory protected lateinit var changeHistory: ChangeHistory
protected lateinit var listItemVH: ListItemVH protected lateinit var listItemVH: ListItemVH
protected lateinit var preferences: Preferences protected lateinit var preferences: NotallyXPreferences
protected lateinit var listItemDragCallback: ListItemDragCallback protected lateinit var listItemDragCallback: ListItemDragCallback
protected lateinit var items: ListItemSortedList protected lateinit var items: ListItemSortedList
@ -49,17 +49,17 @@ open class ListManagerTestBase {
inputMethodManager = mock(InputMethodManager::class.java) inputMethodManager = mock(InputMethodManager::class.java)
changeHistory = ChangeHistory() {} changeHistory = ChangeHistory() {}
listItemVH = mock(ListItemVH::class.java) listItemVH = mock(ListItemVH::class.java)
preferences = mock(Preferences::class.java) preferences = mock(NotallyXPreferences::class.java)
listManager = ListManager(recyclerView, changeHistory, preferences, inputMethodManager) listManager = ListManager(recyclerView, changeHistory, preferences, inputMethodManager)
listManager.adapter = adapter as RecyclerView.Adapter<ListItemVH> listManager.adapter = adapter as RecyclerView.Adapter<ListItemVH>
// Prepare view holder // Prepare view holder
`when`(recyclerView.findViewHolderForAdapterPosition(anyInt())).thenReturn(listItemVH) `when`(recyclerView.findViewHolderForAdapterPosition(anyInt())).thenReturn(listItemVH)
} }
protected fun setSorting(sorting: String) { protected fun setSorting(sorting: ListItemSort) {
val sortCallback = val sortCallback =
when (sorting) { when (sorting) {
ListItemSorting.autoSortByChecked -> ListItemSortedByCheckedCallback(adapter) ListItemSort.AUTO_SORT_BY_CHECKED -> ListItemSortedByCheckedCallback(adapter)
else -> ListItemNoSortCallback(adapter) else -> ListItemNoSortCallback(adapter)
} }
items = ListItemSortedList(sortCallback) items = ListItemSortedList(sortCallback)
@ -76,7 +76,10 @@ open class ListManagerTestBase {
) )
listManager.initList(items) listManager.initList(items)
listItemDragCallback = ListItemDragCallback(1.0f, listManager) listItemDragCallback = ListItemDragCallback(1.0f, listManager)
`when`(preferences.listItemSorting).thenReturn(BetterLiveData(sorting)) val listItemSortingPreference = mock(EnumPreference::class.java)
`when`(listItemSortingPreference.value).thenReturn(sorting)
`when`(preferences.listItemSorting)
.thenReturn(listItemSortingPreference as EnumPreference<ListItemSort>)
} }
protected operator fun List<ListItem>.get(body: String): ListItem { protected operator fun List<ListItem>.get(body: String): ListItem {

View file

@ -1,8 +1,8 @@
package com.philkes.notallyx.recyclerview.listmanager package com.philkes.notallyx.recyclerview.listmanager
import com.philkes.notallyx.presentation.view.misc.ListItemSorting
import com.philkes.notallyx.presentation.view.note.listitem.sorting.lastIndex import com.philkes.notallyx.presentation.view.note.listitem.sorting.lastIndex
import com.philkes.notallyx.presentation.view.note.listitem.sorting.map import com.philkes.notallyx.presentation.view.note.listitem.sorting.map
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.test.assertChecked import com.philkes.notallyx.test.assertChecked
import com.philkes.notallyx.test.assertIds import com.philkes.notallyx.test.assertIds
import com.philkes.notallyx.test.assertOrder import com.philkes.notallyx.test.assertOrder
@ -12,7 +12,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo moves`() { fun `undo and redo moves`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.move(0, 4) listManager.move(0, 4)
listManager.move(2, 3) listManager.move(2, 3)
listManager.move(4, 1) listManager.move(4, 1)
@ -34,7 +34,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo changeChecked`() { fun `undo and redo changeChecked`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
listManager.changeChecked(3, true) listManager.changeChecked(3, true)
listManager.changeChecked(0, false) listManager.changeChecked(0, false)
@ -55,7 +55,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo changeChecked if auto-sort enabled`() { fun `undo and redo changeChecked if auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
listManager.changeChecked(3, true) listManager.changeChecked(3, true)
listManager.changeChecked(0, false) listManager.changeChecked(0, false)
@ -79,7 +79,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo changeChecked false on child item`() { fun `undo and redo changeChecked false on child item`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(1, checked = true, pushChange = true) listManager.changeChecked(1, checked = true, pushChange = true)
@ -96,7 +96,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo changeIsChild`() { fun `undo and redo changeIsChild`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(2, true) listManager.changeIsChild(2, true)
listManager.changeIsChild(4, true) listManager.changeIsChild(4, true)
@ -120,7 +120,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo add parents with children`() { fun `undo and redo add parents with children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.addWithChildren(0, "Parent1", "Child1") listManager.addWithChildren(0, "Parent1", "Child1")
listManager.addWithChildren(4, "Parent2") listManager.addWithChildren(4, "Parent2")
listManager.addWithChildren(0, "Parent3") listManager.addWithChildren(0, "Parent3")
@ -150,7 +150,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo delete parents with children`() { fun `undo and redo delete parents with children`() {
setSorting(ListItemSorting.noAutoSort) setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeIsChild(4, true) listManager.changeIsChild(4, true)
@ -174,7 +174,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo check all with auto-sort enabled`() { fun `undo and redo check all with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
@ -191,7 +191,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo uncheck all with auto-sort enabled`() { fun `undo and redo uncheck all with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
@ -208,7 +208,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo delete checked with auto-sort enabled`() { fun `undo and redo delete checked with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeChecked(2, true) listManager.changeChecked(2, true)
listManager.changeChecked(0, true) listManager.changeChecked(0, true)
@ -225,7 +225,7 @@ class ListManagerWithChangeHistoryTest : ListManagerTestBase() {
@Test @Test
fun `undo and redo various changes with auto-sort enabled`() { fun `undo and redo various changes with auto-sort enabled`() {
setSorting(ListItemSorting.autoSortByChecked) setSorting(ListItemSort.AUTO_SORT_BY_CHECKED)
listManager.changeIsChild(1, true) listManager.changeIsChild(1, true)
listManager.changeIsChild(3, true) listManager.changeIsChild(3, true)
listManager.changeIsChild(4, true) listManager.changeIsChild(4, true)