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 "androidx.work:work-runtime:2.9.1"
implementation "androidx.preference:preference-ktx:1.2.1"
//noinspection GradleDependency
implementation "androidx.navigation:navigation-ui-ktx:$navVersion"

View file

@ -5,32 +5,33 @@ import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.Observer
import com.philkes.notallyx.presentation.view.misc.BetterLiveData
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled
import com.philkes.notallyx.presentation.view.misc.Theme
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
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.utils.backup.Export.scheduleAutoBackup
import com.philkes.notallyx.utils.security.UnlockReceiver
class NotallyXApplication : Application() {
private lateinit var biometricLockObserver: Observer<String>
private lateinit var preferences: Preferences
private lateinit var biometricLockObserver: Observer<BiometricLock>
private lateinit var preferences: NotallyXPreferences
private var unlockReceiver: UnlockReceiver? = null
val locked = BetterLiveData(true)
val locked = NotNullLiveData(true)
override fun onCreate() {
super.onCreate()
preferences = Preferences.getInstance(this)
preferences = NotallyXPreferences.getInstance(this)
preferences.theme.observeForever { theme ->
when (theme) {
Theme.dark ->
Theme.DARK ->
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
Theme.light ->
Theme.LIGHT ->
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
Theme.followSystem ->
Theme.FOLLOW_SYSTEM ->
AppCompatDelegate.setDefaultNightMode(
AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM
)
@ -41,7 +42,7 @@ class NotallyXApplication : Application() {
val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) }
biometricLockObserver = Observer {
if (it == enabled) {
if (it == BiometricLock.ENABLED) {
unlockReceiver = UnlockReceiver(this)
registerReceiver(unlockReceiver, filter)
} 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.sqlite.db.SimpleSQLiteQuery
import androidx.sqlite.db.SupportSQLiteDatabase
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.dao.BaseNoteDao
import com.philkes.notallyx.data.dao.CommonDao
import com.philkes.notallyx.data.dao.LabelDao
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.data.model.Converters
import com.philkes.notallyx.data.model.Label
import com.philkes.notallyx.presentation.observeForeverSkipFirst
import com.philkes.notallyx.presentation.view.misc.BetterLiveData
import com.philkes.notallyx.presentation.view.misc.BiometricLock.enabled
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.observeForeverSkipFirst
import com.philkes.notallyx.utils.security.getInitializedCipherForDecryption
import net.sqlcipher.database.SupportFactory
@ -37,20 +37,20 @@ abstract class NotallyDatabase : RoomDatabase() {
getBaseNoteDao().query(SimpleSQLiteQuery("pragma wal_checkpoint(FULL)"))
}
private var observer: Observer<String>? = null
private var biometricLockObserver: Observer<BiometricLock>? = null
companion object {
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
?: synchronized(this) {
val preferences = Preferences.getInstance(app)
val preferences = NotallyXPreferences.getInstance(app)
this.instance =
BetterLiveData(
NotNullLiveData(
createInstance(app, preferences, preferences.biometricLock.value)
)
return this.instance!!
@ -59,31 +59,33 @@ abstract class NotallyDatabase : RoomDatabase() {
private fun createInstance(
app: Application,
preferences: Preferences,
biometrickLock: String,
preferences: NotallyXPreferences,
biometrickLock: BiometricLock,
): NotallyDatabase {
val instanceBuilder =
Room.databaseBuilder(app, NotallyDatabase::class.java, DatabaseName)
.addMigrations(Migration2, Migration3, Migration4, Migration5, Migration6)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (biometrickLock == enabled) {
val initializationVector = preferences.iv!!
if (biometrickLock == BiometricLock.ENABLED) {
val initializationVector = preferences.iv.value!!
val cipher = getInitializedCipherForDecryption(iv = initializationVector)
val encryptedPassphrase = preferences.getDatabasePassphrase()
val encryptedPassphrase = preferences.databaseEncryptionKey.value
val passphrase = cipher.doFinal(encryptedPassphrase)
val factory = SupportFactory(passphrase)
instanceBuilder.openHelperFactory(factory)
}
val instance = instanceBuilder.build()
instance.observer = Observer { newBiometrickLock ->
NotallyDatabase.instance?.value?.observer?.let {
instance.biometricLockObserver = Observer { newBiometrickLock ->
NotallyDatabase.instance?.value?.biometricLockObserver?.let {
preferences.biometricLock.removeObserver(it)
}
val newInstance = createInstance(app, preferences, newBiometrickLock)
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 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.fragment.app.Fragment
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.R
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.getUrl
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.Progress
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.EditTextWithHistoryChange
import java.io.File
@ -198,10 +196,10 @@ fun Menu.add(
fun TextView.displayFormattedTimestamp(
timestamp: Long?,
dateFormat: String,
dateFormat: DateFormat,
prefixResId: Int? = null,
) {
if (dateFormat != DateFormat.none && timestamp != null) {
if (dateFormat != DateFormat.NONE && timestamp != null) {
visibility = View.VISIBLE
text =
"${prefixResId?.let { getString(it) } ?: ""} ${formatTimestamp(timestamp, dateFormat)}"
@ -324,22 +322,6 @@ fun Context.canAuthenticateWithBiometrics(): Int {
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? =
when (uri.scheme) {
ContentResolver.SCHEME_CONTENT -> getContentFileName(uri)
@ -458,10 +440,10 @@ fun Activity.checkNotificationPermission(
} else onSuccess()
}
private fun formatTimestamp(timestamp: Long, dateFormat: String): String {
private fun formatTimestamp(timestamp: Long, dateFormat: DateFormat): String {
val date = Date(timestamp)
return when (dateFormat) {
DateFormat.relative -> PrettyTime().format(date)
DateFormat.RELATIVE -> PrettyTime().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.content.Intent
import android.os.Bundle
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.model.BaseNote
import com.philkes.notallyx.presentation.activity.note.PickNoteActivity
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.widget.WidgetProvider
class ConfigureWidgetActivity : PickNoteActivity() {
@ -27,7 +27,7 @@ class ConfigureWidgetActivity : PickNoteActivity() {
override fun onClick(position: Int) {
if (position != -1) {
val preferences = Preferences.getInstance(application)
val preferences = NotallyXPreferences.getInstance(application)
val baseNote = adapter.getItem(position) as BaseNote
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.viewbinding.ViewBinding
import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.Preferences
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
abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
@ -24,12 +24,12 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
ActivityResultLauncher<Intent>
protected lateinit var binding: T
protected lateinit var preferences: Preferences
protected lateinit var preferences: NotallyXPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
notallyXApplication = (application as NotallyXApplication)
preferences = Preferences.getInstance(application)
preferences = NotallyXPreferences.getInstance(application)
biometricAuthenticationActivityResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
@ -42,7 +42,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
}
override fun onResume() {
if (preferences.biometricLock.value == enabled) {
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
if (hasToAuthenticateWithBiometric()) {
hide()
showLockScreen()
@ -55,7 +55,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
override fun onPause() {
super.onPause()
if (preferences.biometricLock.value == enabled) {
if (preferences.biometricLock.value == BiometricLock.ENABLED) {
hide()
}
}
@ -63,7 +63,7 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
open fun showLockScreen() {
showBiometricOrPinPrompt(
true,
preferences.iv!!,
preferences.iv.value!!,
biometricAuthenticationActivityResultLauncher,
R.string.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.view.Constants
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.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotesView
abstract class NotallyFragment : Fragment(), ListItemListener {
@ -139,11 +139,11 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
BaseNoteAdapter(
model.actionMode.selectedIds,
dateFormat.value,
notesSorting.value.first,
notesSorting.value.sortedBy,
textSize.value,
maxItems,
maxLines,
maxTitle,
maxItems.value,
maxLines.value,
maxTitle.value,
model.imageRoot,
this@NotallyFragment,
)
@ -170,8 +170,8 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
binding?.ImageView?.isVisible = list.isEmpty()
}
model.preferences.notesSorting.observe(viewLifecycleOwner) { (sortBy, sortDirection) ->
notesAdapter?.setSorting(sortBy, sortDirection)
model.preferences.notesSorting.observe(viewLifecycleOwner) { notesSort ->
notesAdapter?.setSorting(notesSort)
}
model.actionMode.closeListener.observe(viewLifecycleOwner) { event ->
@ -187,7 +187,7 @@ abstract class NotallyFragment : Fragment(), ListItemListener {
private fun setupRecyclerView() {
binding?.RecyclerView?.layoutManager =
if (model.preferences.view.value == ViewPref.grid) {
if (model.preferences.notesView.value == NotesView.GRID) {
StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
} 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.setupImportProgressDialog
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.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.Theme
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.backup.Export.scheduleAutoBackup
import com.philkes.notallyx.utils.security.decryptDatabase
@ -76,22 +69,20 @@ class SettingsFragment : Fragment() {
private fun setupBinding(binding: FragmentSettingsBinding) {
model.preferences.apply {
view.observe(viewLifecycleOwner) { value ->
binding.View.setup(com.philkes.notallyx.presentation.view.misc.View, value)
}
notesView.observe(viewLifecycleOwner) { value -> binding.View.setup(notesView, value) }
theme.observe(viewLifecycleOwner) { value -> binding.Theme.setup(Theme, value) }
theme.observe(viewLifecycleOwner) { value -> binding.Theme.setup(theme, value) }
dateFormat.observe(viewLifecycleOwner) { value ->
binding.DateFormat.setup(DateFormat, value)
binding.DateFormat.setup(dateFormat, value)
}
textSize.observe(viewLifecycleOwner) { value ->
binding.TextSize.setup(TextSize, value)
binding.TextSize.setup(textSize, value)
}
notesSorting.observe(viewLifecycleOwner) { (sortBy, sortDirection) ->
binding.NotesSortOrder.setup(NotesSorting, sortBy, sortDirection)
notesSorting.observe(viewLifecycleOwner) { notesSort ->
binding.NotesSortOrder.setup(notesSorting, notesSort)
}
// TODO: Hide for now until checked auto-sort is working reliably
@ -99,29 +90,29 @@ class SettingsFragment : Fragment() {
// 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 ->
binding.AutoBackup.setup(AutoBackup, value)
binding.AutoBackup.setupAutoBackup(autoBackupPath, value)
}
autoBackupPeriodDays.observe(viewLifecycleOwner) { value ->
binding.AutoBackupPeriodDays.setup(AutoBackupPeriodDays, value)
binding.AutoBackupPeriodDays.setup(autoBackupPeriodDays, value)
scheduleAutoBackup(value.toLong(), requireContext())
}
backupPassword.observe(viewLifecycleOwner) { value ->
binding.BackupPassword.setup(BackupPassword, value)
binding.BackupPassword.setupBackupPassword(backupPassword, 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 password = model.preferences.backupPassword.value
layout.InputText.apply {
if (password != emptyPassword) {
if (password != PASSWORD_EMPTY) {
setText(password)
}
transformationMethod = PasswordTransformationMethod.getInstance()
@ -384,24 +375,23 @@ class SettingsFragment : Fragment() {
.show()
}
private fun PreferenceBinding.setup(info: ListInfo, 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
private inline fun <reified T> PreferenceBinding.setup(
enumPreference: EnumPreference<T>,
value: T,
) where T : Enum<T>, T : TextProvider {
Title.setText(enumPreference.titleResId!!)
val context = requireContext()
Value.text = value.getText(context)
val enumEntries = T::class.java.enumConstants!!.toList()
val entries = enumEntries.map { it.getText(requireContext()) }.toTypedArray()
val checked = enumEntries.indexOfFirst { it == value }
root.setOnClickListener {
MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title)
MaterialAlertDialogBuilder(context)
.setTitle(enumPreference.titleResId)
.setSingleChoiceItems(entries, checked) { dialog, which ->
dialog.cancel()
val newValue = entryValues[which]
model.savePreference(info, newValue)
val newValue = enumEntries[which]
model.savePreference(enumPreference, newValue)
}
.setNegativeButton(R.string.cancel, null)
.show()
@ -409,35 +399,73 @@ class SettingsFragment : Fragment() {
}
private fun PreferenceBinding.setup(
info: NotesSorting,
sortBy: String,
sortDirection: SortDirection,
preference: EnumPreference<BiometricLock>,
value: BiometricLock,
) {
Title.setText(info.title)
Title.setText(preference.titleResId!!)
val entries = info.getEntries(requireContext())
val entryValues = info.getEntryValues()
val context = requireContext()
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)
val displayValue = entries[checked]
root.setOnClickListener {
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 {
val layout = NotesSortDialogBinding.inflate(layoutInflater, null, false)
entries.zip(entryValues).forEachIndexed { idx, (choiceText, sortByValue) ->
NotesSortBy.entries.forEachIndexed { idx, notesSortBy ->
ChoiceItemBinding.inflate(layoutInflater).root.apply {
id = idx
text = choiceText
tag = sortByValue
text = requireContext().getString(notesSortBy.textResId)
tag = notesSortBy
layout.NotesSortByRadioGroup.addView(this)
setCompoundDrawablesRelativeWithIntrinsicBounds(
NotesSorting.getSortIconResId(sortByValue),
0,
0,
0,
)
if (sortByValue == sortBy) {
setCompoundDrawablesRelativeWithIntrinsicBounds(notesSortBy.iconResId, 0, 0, 0)
if (notesSortBy == value.sortedBy) {
layout.NotesSortByRadioGroup.check(this.id)
}
}
@ -450,37 +478,43 @@ class SettingsFragment : Fragment() {
tag = sortDir
setCompoundDrawablesRelativeWithIntrinsicBounds(sortDir.iconResId, 0, 0, 0)
layout.NotesSortDirectionRadioGroup.addView(this)
if (sortDir == sortDirection) {
if (sortDir == value.sortDirection) {
layout.NotesSortDirectionRadioGroup.check(this.id)
}
}
}
MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title)
.setTitle(preference.titleResId)
.setView(layout.root)
.setPositiveButton(R.string.save) { dialog, _ ->
dialog.cancel()
val newSortBy = layout.NotesSortByRadioGroup.checkedTag() as String
val newSortBy = layout.NotesSortByRadioGroup.checkedTag() as NotesSortBy
val newSortDirection =
layout.NotesSortDirectionRadioGroup.checkedTag() as SortDirection
model.preferences.savePreference(info, newSortBy, newSortDirection)
model.savePreference(
model.preferences.notesSorting,
NotesSort(newSortBy, newSortDirection),
)
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}
private fun PreferenceBinding.setup(info: BackupPassword, password: String) {
Title.setText(info.title)
private fun PreferenceBinding.setupBackupPassword(
preference: StringPreference,
password: String,
) {
Title.setText(preference.titleResId!!)
Value.transformationMethod =
if (password != emptyPassword) PasswordTransformationMethod.getInstance() else null
Value.text = if (password != emptyPassword) password else getText(R.string.tap_to_set_up)
if (password != PASSWORD_EMPTY) PasswordTransformationMethod.getInstance() else null
Value.text = if (password != PASSWORD_EMPTY) password else getText(R.string.tap_to_set_up)
root.setOnClickListener {
val layout = TextInputDialogBinding.inflate(layoutInflater, null, false)
layout.InputText.apply {
if (password != emptyPassword) {
if (password != PASSWORD_EMPTY) {
setText(password)
}
transformationMethod = PasswordTransformationMethod.getInstance()
@ -491,66 +525,22 @@ class SettingsFragment : Fragment() {
visibility = View.VISIBLE
}
MaterialAlertDialogBuilder(requireContext())
.setTitle(info.title)
.setTitle(preference.titleResId)
.setView(layout.root)
.setPositiveButton(R.string.save) { dialog, _ ->
dialog.cancel()
val updatedPassword = layout.InputText.text.toString()
model.preferences.savePreference(info, updatedPassword)
model.savePreference(preference, updatedPassword)
}
.setNegativeButton(R.string.cancel, null)
.setNeutralButton(R.string.clear) { dialog, _ ->
dialog.cancel()
model.preferences.savePreference(info, emptyPassword)
model.savePreference(preference, PASSWORD_EMPTY)
}
.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() {
showBiometricOrPinPrompt(
false,
@ -559,10 +549,10 @@ class SettingsFragment : Fragment() {
R.string.enable_lock_description,
onSuccess = { cipher ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
model.preferences.iv = cipher.iv
val passphrase = model.preferences.generatePassphrase(cipher)
model.savePreference(model.preferences.iv, cipher.iv)
val passphrase = model.preferences.databaseEncryptionKey.init(cipher)
encryptDatabase(requireContext(), passphrase)
model.savePreference(BiometricLock, enabled)
model.savePreference(model.preferences.biometricLock, BiometricLock.ENABLED)
}
val app = (activity?.application as NotallyXApplication)
app.locked.value = false
@ -579,14 +569,14 @@ class SettingsFragment : Fragment() {
disableLockActivityResultLauncher,
R.string.disable_lock_title,
R.string.disable_lock_description,
model.preferences.iv!!,
model.preferences.iv.value!!,
onSuccess = { cipher ->
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)
model.closeDatabase()
decryptDatabase(requireContext(), passphrase)
model.savePreference(BiometricLock, disabled)
model.savePreference(model.preferences.biometricLock, BiometricLock.DISABLED)
}
showBiometricsDisabledToast()
},
@ -632,10 +622,10 @@ class SettingsFragment : Fragment() {
.show()
}
private fun PreferenceBinding.setup(info: AutoBackup, value: String) {
Title.setText(info.title)
private fun PreferenceBinding.setupAutoBackup(preference: StringPreference, value: String) {
Title.setText(preference.titleResId!!)
if (value == info.emptyPath) {
if (value == BACKUP_PATH_EMPTY) {
Value.setText(R.string.tap_to_set_up)
root.setOnClickListener { displayChooseFolderDialog() }
@ -655,14 +645,17 @@ class SettingsFragment : Fragment() {
}
}
private fun PreferenceSeekbarBinding.setup(info: SeekbarInfo, initialValue: Int) {
Title.setText(info.title)
private fun PreferenceSeekbarBinding.setup(
preference: IntPreference,
value: Int = preference.value,
) {
Title.setText(preference.titleResId!!)
Slider.apply {
valueTo = info.max.toFloat()
valueFrom = info.min.toFloat()
value = initialValue.toFloat()
addOnChangeListener { _, value, _ -> model.savePreference(info, value.toInt()) }
valueTo = preference.max.toFloat()
valueFrom = preference.min.toFloat()
this@apply.value = value.toFloat()
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.setupProgressDialog
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.audio.AudioAdapter
import com.philkes.notallyx.presentation.view.note.preview.PreviewFileAdapter
import com.philkes.notallyx.presentation.view.note.preview.PreviewImageAdapter
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.utils.FileError
import com.philkes.notallyx.utils.Operations
@ -282,13 +280,12 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
open fun setStateFromModel() {
val (date, datePrefixResId) =
when (preferences.notesSorting.value.first) {
autoSortByCreationDate -> Pair(model.timestamp, R.string.creation_date)
autoSortByModifiedDate -> Pair(model.modifiedTimestamp, R.string.modified_date)
when (preferences.notesSorting.value.sortedBy) {
NotesSortBy.CREATION_DATE -> Pair(model.timestamp, R.string.creation_date)
NotesSortBy.MODIFIED_DATE -> Pair(model.modifiedTimestamp, R.string.modified_date)
else -> Pair(null, null)
}
binding.Date.displayFormattedTimestamp(date, preferences.dateFormat.value, datePrefixResId)
binding.EnterTitle.setText(model.title)
Operations.bindLabels(binding.LabelGroup, model.labels, model.textSize)
@ -602,9 +599,9 @@ abstract class EditActivity(private val type: Type) : LockedActivity<ActivityEdi
}
}
val title = TextSize.getEditTitleSize(model.textSize)
val date = TextSize.getDisplayBodySize(model.textSize)
val body = TextSize.getEditBodySize(model.textSize)
val title = model.textSize.editTitleSize
val date = model.textSize.displayBodySize
val body = model.textSize.editBodySize
binding.EnterTitle.setTextSize(TypedValue.COMPLEX_UNIT_SP, title)
binding.Date.setTextSize(TypedValue.COMPLEX_UNIT_SP, date)

View file

@ -4,18 +4,18 @@ import android.os.Build
import android.os.Bundle
import android.view.MenuItem
import android.view.inputmethod.InputMethodManager
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.model.Type
import com.philkes.notallyx.presentation.add
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.ListManager
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.ListItemSortedList
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
class EditListActivity : EditActivity(Type.LIST) {
@ -102,12 +102,12 @@ class EditListActivity : EditActivity(Type.LIST) {
ListItemAdapter(
model.textSize,
elevation,
Preferences.getInstance(application),
NotallyXPreferences.getInstance(application),
listManager,
)
val sortCallback =
when (preferences.listItemSorting.value) {
ListItemSorting.autoSortByChecked -> ListItemSortedByCheckedCallback(adapter)
ListItemSort.AUTO_SORT_BY_CHECKED -> ListItemSortedByCheckedCallback(adapter)
else -> ListItemNoSortCallback(adapter)
}
items = ListItemSortedList(sortCallback)

View file

@ -6,7 +6,6 @@ import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
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.presentation.activity.LockedActivity
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.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 java.util.Collections
import kotlinx.coroutines.Dispatchers
@ -37,18 +37,18 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ListIte
val result = Intent()
setResult(RESULT_CANCELED, result)
val preferences = Preferences.getInstance(application)
val preferences = NotallyXPreferences.getInstance(application)
adapter =
with(preferences) {
BaseNoteAdapter(
Collections.emptySet(),
dateFormat.value,
notesSorting.value.first,
notesSorting.value.sortedBy,
textSize.value,
maxItems,
maxLines,
maxTitle,
maxItems.value,
maxLines.value,
maxTitle.value,
application.getExternalImagesDirectory(),
this@PickNoteActivity,
)
@ -58,7 +58,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ListIte
adapter = this@PickNoteActivity.adapter
setHasFixedSize(true)
layoutManager =
if (preferences.view.value == View.grid) {
if (preferences.notesView.value == NotesView.GRID) {
StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
} 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.BaseNoteModifiedDateSort
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.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
class BaseNoteAdapter(
private val selectedIds: Set<Long>,
private val dateFormat: String,
private val sortedBy: String,
private val textSize: String,
private val dateFormat: DateFormat,
private val sortedBy: NotesSortBy,
private val textSize: TextSize,
private val maxItems: Int,
private val maxLines: Int,
private val maxTitle: Int,
@ -82,12 +84,12 @@ class BaseNoteAdapter(
}
}
fun setSorting(sortBy: String, sortDirection: SortDirection) {
fun setSorting(notesSort: NotesSort) {
val sortCallback =
when (sortBy) {
autoSortByTitle -> BaseNoteTitleSort(this, sortDirection)
autoSortByModifiedDate -> BaseNoteModifiedDateSort(this, sortDirection)
else -> BaseNoteCreationDateSort(this, sortDirection)
when (notesSort.sortedBy) {
NotesSortBy.TITLE -> BaseNoteTitleSort(this, notesSort.sortDirection)
NotesSortBy.MODIFIED_DATE -> BaseNoteModifiedDateSort(this, notesSort.sortDirection)
else -> BaseNoteCreationDateSort(this, notesSort.sortDirection)
}
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.dp
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.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 java.io.File
class BaseNoteVH(
private val binding: RecyclerBaseNoteBinding,
private val dateFormat: String,
private val textSize: String,
private val dateFormat: DateFormat,
private val textSize: TextSize,
private val maxItems: Int,
maxLines: Int,
maxTitle: Int,
@ -46,8 +46,8 @@ class BaseNoteVH(
) : RecyclerView.ViewHolder(binding.root) {
init {
val title = TextSize.getDisplayTitleSize(textSize)
val body = TextSize.getDisplayBodySize(textSize)
val title = textSize.displayTitleSize
val body = textSize.displayBodySize
binding.apply {
Title.setTextSize(TypedValue.COMPLEX_UNIT_SP, title)
@ -75,7 +75,7 @@ class BaseNoteVH(
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)
when (baseNote.type) {
@ -84,8 +84,9 @@ class BaseNoteVH(
}
val (date, datePrefixResId) =
when (sortBy) {
autoSortByCreationDate -> Pair(baseNote.timestamp, R.string.creation_date)
autoSortByModifiedDate -> Pair(baseNote.modifiedTimestamp, R.string.modified_date)
NotesSortBy.CREATION_DATE -> Pair(baseNote.timestamp, R.string.creation_date)
NotesSortBy.MODIFIED_DATE ->
Pair(baseNote.modifiedTimestamp, R.string.modified_date)
else -> Pair(null, null)
}
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 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) :
BaseNoteSort(adapter, sortDirection) {

View file

@ -2,7 +2,7 @@ package com.philkes.notallyx.presentation.view.main.sorting
import androidx.recyclerview.widget.RecyclerView
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) :
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.Header
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(
adapter: RecyclerView.Adapter<*>?,

View file

@ -2,7 +2,7 @@ package com.philkes.notallyx.presentation.view.main.sorting
import androidx.recyclerview.widget.RecyclerView
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) :
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
// 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 {
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 androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.databinding.RecyclerListItemBinding
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(
private val textSize: String,
private val textSize: TextSize,
elevation: Float,
private val preferences: Preferences,
private val preferences: NotallyXPreferences,
private val listManager: ListManager,
) : 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.presentation.createListTextWatcherWithHistory
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.TextSize
import com.philkes.notallyx.presentation.viewmodel.preference.ListItemSort
import com.philkes.notallyx.presentation.viewmodel.preference.TextSize
class ListItemVH(
val binding: RecyclerListItemBinding,
val listManager: ListManager,
touchHelper: ItemTouchHelper,
textSize: String,
textSize: TextSize,
) : RecyclerView.ViewHolder(binding.root) {
private var editTextWatcher: TextWatcher
private var dragHandleInitialY: Float = 0f
init {
val body = TextSize.getEditBodySize(textSize)
val body = textSize.editBodySize
binding.EditText.apply {
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)
updateCheckBox(item)
@ -70,7 +70,7 @@ class ListItemVH(
updateDeleteButton(item)
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
} else {
binding.DragHandle.visibility = VISIBLE

View file

@ -5,11 +5,9 @@ import android.util.Log
import android.view.inputmethod.InputMethodManager
import android.widget.EditText
import androidx.recyclerview.widget.RecyclerView
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.data.model.ListItem
import com.philkes.notallyx.data.model.areAllChecked
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.deleteItem
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.shiftItemOrders
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.ChangeHistory
import com.philkes.notallyx.utils.changehistory.DeleteCheckedChange
@ -41,7 +41,7 @@ import com.philkes.notallyx.utils.changehistory.ListMoveChange
class ListManager(
private val recyclerView: RecyclerView,
private val changeHistory: ChangeHistory,
private val preferences: Preferences,
private val preferences: NotallyXPreferences,
private val inputMethodManager: InputMethodManager,
) {
@ -396,7 +396,7 @@ class ListManager(
}
private fun isAutoSortByCheckedEnabled() =
preferences.listItemSorting.value == ListItemSorting.autoSortByChecked
preferences.listItemSorting.value == ListItemSort.AUTO_SORT_BY_CHECKED
private val Int.isBeforeChildItemOfOtherParent: Boolean
get() {

View file

@ -13,7 +13,6 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope
import androidx.room.withTransaction
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
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.presentation.applySpans
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.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.Cache
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 others = Header(app.getString(R.string.others))
val preferences = Preferences.getInstance(app)
val preferences = NotallyXPreferences.getInstance(app)
val imageRoot = app.getExternalImagesDirectory()
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)
fun savePreference(info: SeekbarInfo, value: Int) = executeAsync {
preferences.savePreference(info, value)
}
fun savePreference(info: ListInfo, value: String) = executeAsync {
preferences.savePreference(info, value)
}
fun disableAutoBackup() {
clearPersistedUriPermissions()
executeAsync { preferences.savePreference(AutoBackup, AutoBackup.emptyPath) }
savePreference(preferences.autoBackupPath, BACKUP_PATH_EMPTY)
}
fun setAutoBackupPath(uri: Uri) {
clearPersistedUriPermissions()
val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
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.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.DataUtil
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.Type
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.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.widget.WidgetProvider
import com.philkes.notallyx.utils.Cache
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 lateinit var baseNoteDao: BaseNoteDao
val textSize = Preferences.getInstance(app).textSize.value
val textSize = NotallyXPreferences.getInstance(app).textSize.value
var isNewNote = true
@ -71,9 +71,9 @@ class NotallyModel(private val app: Application) : AndroidViewModel(app) {
var body: Editable = SpannableStringBuilder()
val items = ArrayList<ListItem>()
val images = BetterLiveData<List<FileAttachment>>(emptyList())
val files = BetterLiveData<List<FileAttachment>>(emptyList())
val audios = BetterLiveData<List<Audio>>(emptyList())
val images = NotNullLiveData<List<FileAttachment>>(emptyList())
val files = NotNullLiveData<List<FileAttachment>>(emptyList())
val audios = NotNullLiveData<List<Audio>>(emptyList())
val addingFiles = MutableLiveData<Progress>()
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.RemoteViewsService
import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.Preferences
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.model.BaseNote
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
class WidgetFactory(
@ -24,7 +23,7 @@ class WidgetFactory(
private var baseNote: BaseNote? = null
private lateinit var database: NotallyDatabase
private val preferences = Preferences.getInstance(app)
private val preferences = NotallyXPreferences.getInstance(app)
init {
NotallyDatabase.getDatabase(app).observeForever { database = it }
@ -64,14 +63,11 @@ class WidgetFactory(
private fun getNoteView(note: BaseNote): RemoteViews {
return RemoteViews(app.packageName, R.layout.widget_note).apply {
setTextViewTextSize(
R.id.Title,
TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayTitleSize(preferences.textSize.value),
)
val textSize = preferences.textSize.value
setTextViewTextSize(R.id.Title, TypedValue.COMPLEX_UNIT_SP, textSize.displayTitleSize)
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)
if (note.body.isNotEmpty()) {
@ -91,7 +87,7 @@ class WidgetFactory(
setTextViewTextSize(
R.id.Title,
TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayTitleSize(preferences.textSize.value),
preferences.textSize.value.displayTitleSize,
)
setTextViewText(R.id.Title, list.title)
@ -116,7 +112,7 @@ class WidgetFactory(
setTextViewTextSize(
R.id.CheckBox,
TypedValue.COMPLEX_UNIT_SP,
TextSize.getDisplayBodySize(preferences.textSize.value),
preferences.textSize.value.displayBodySize,
)
setTextViewText(R.id.CheckBox, item.body)
setInt(

View file

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

View file

@ -2,12 +2,12 @@ package com.philkes.notallyx.utils
import androidx.lifecycle.MutableLiveData
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 {
val enabled = BetterLiveData(false)
val count = BetterLiveData(0)
val enabled = NotNullLiveData(false)
val count = NotNullLiveData(0)
val selectedNotes = HashMap<Long, BaseNote>()
val selectedIds = selectedNotes.keys
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.ListItem
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.FileOutputStream
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()) {
group.visibility = View.GONE
} else {
@ -107,7 +107,7 @@ object Operations {
group.removeAllViews()
val inflater = LayoutInflater.from(group.context)
val labelSize = TextSize.getDisplayBodySize(textSize)
val labelSize = textSize.displayBodySize
for (label in labels) {
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.SystemClock
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.audio.Status.PAUSED
import com.philkes.notallyx.utils.audio.Status.READY
@ -15,7 +15,7 @@ import com.philkes.notallyx.utils.audio.Status.RECORDING
@RequiresApi(24)
class AudioRecordService : Service() {
var status = BetterLiveData(READY)
var status = NotNullLiveData(READY)
private var lastStart = 0L
private var audioDuration = 0L

View file

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

View file

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

View file

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

View file

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

View file

@ -1,6 +1,6 @@
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.assertOrder
import com.philkes.notallyx.utils.changehistory.ListIsChildChange
@ -12,7 +12,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test
fun `changeIsChild changes parent to child and pushes change`() {
setSorting(ListItemSorting.noAutoSort)
setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, isChild = true)
items.assertOrder("A", "B")
@ -22,7 +22,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test
fun `changeIsChild changes child to parent`() {
setSorting(ListItemSorting.noAutoSort)
setSorting(ListItemSort.NO_AUTO_SORT)
listManager.changeIsChild(1, isChild = true)
listManager.changeIsChild(1, isChild = false)
@ -34,7 +34,7 @@ class ListManagerIsChildTest : ListManagerTestBase() {
@Test
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(2, true)

View file

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

View file

@ -3,10 +3,7 @@ package com.philkes.notallyx.recyclerview.listmanager
import android.view.inputmethod.InputMethodManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SortedList
import com.philkes.notallyx.Preferences
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.ListItemVH
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.find
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.createListItem
import com.philkes.notallyx.test.mockAndroidLog
@ -34,7 +34,7 @@ open class ListManagerTestBase {
protected lateinit var inputMethodManager: InputMethodManager
protected lateinit var changeHistory: ChangeHistory
protected lateinit var listItemVH: ListItemVH
protected lateinit var preferences: Preferences
protected lateinit var preferences: NotallyXPreferences
protected lateinit var listItemDragCallback: ListItemDragCallback
protected lateinit var items: ListItemSortedList
@ -49,17 +49,17 @@ open class ListManagerTestBase {
inputMethodManager = mock(InputMethodManager::class.java)
changeHistory = ChangeHistory() {}
listItemVH = mock(ListItemVH::class.java)
preferences = mock(Preferences::class.java)
preferences = mock(NotallyXPreferences::class.java)
listManager = ListManager(recyclerView, changeHistory, preferences, inputMethodManager)
listManager.adapter = adapter as RecyclerView.Adapter<ListItemVH>
// Prepare view holder
`when`(recyclerView.findViewHolderForAdapterPosition(anyInt())).thenReturn(listItemVH)
}
protected fun setSorting(sorting: String) {
protected fun setSorting(sorting: ListItemSort) {
val sortCallback =
when (sorting) {
ListItemSorting.autoSortByChecked -> ListItemSortedByCheckedCallback(adapter)
ListItemSort.AUTO_SORT_BY_CHECKED -> ListItemSortedByCheckedCallback(adapter)
else -> ListItemNoSortCallback(adapter)
}
items = ListItemSortedList(sortCallback)
@ -76,7 +76,10 @@ open class ListManagerTestBase {
)
listManager.initList(items)
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 {

View file

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