mirror of
https://github.com/PhilKes/NotallyX.git
synced 2025-06-28 12:19:55 +00:00
Refactor Preferences from objects to Enums
This commit is contained in:
parent
87279902ca
commit
e38e14bdd4
43 changed files with 925 additions and 927 deletions
|
@ -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"
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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() },
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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()) }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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<*>?,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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())
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>() {
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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) }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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>>>()
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)})"
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>>>()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue