Skip to content

Commit 63f4c13

Browse files
committed
Add preference to auto remove deleted notes
1 parent b58dd7d commit 63f4c13

12 files changed

Lines changed: 343 additions & 11 deletions

File tree

app/src/main/java/com/philkes/notallyx/NotallyXApplication.kt

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,29 @@ package com.philkes.notallyx
33
import android.app.Activity
44
import android.app.Application
55
import android.content.Context
6+
import android.content.ContextWrapper
67
import android.content.Intent
78
import android.content.IntentFilter
89
import android.os.Build
910
import android.os.Bundle
11+
import android.util.Log
1012
import androidx.appcompat.app.AppCompatDelegate
1113
import androidx.lifecycle.Observer
14+
import androidx.work.ExistingPeriodicWorkPolicy
15+
import androidx.work.PeriodicWorkRequest
1216
import androidx.work.WorkInfo
1317
import androidx.work.WorkManager
1418
import com.google.android.material.color.DynamicColors
19+
import com.philkes.notallyx.NotallyXApplication.Companion.AUTO_REMOVE_DELETED_NOTES
20+
import com.philkes.notallyx.NotallyXApplication.Companion.TAG
1521
import com.philkes.notallyx.presentation.setEnabledSecureFlag
1622
import com.philkes.notallyx.presentation.view.misc.NotNullLiveData
1723
import com.philkes.notallyx.presentation.viewmodel.preference.BiometricLock
1824
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
1925
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences.Companion.EMPTY_PATH
2026
import com.philkes.notallyx.presentation.viewmodel.preference.Theme
2127
import com.philkes.notallyx.presentation.widget.WidgetProvider
28+
import com.philkes.notallyx.utils.AutoRemoveDeletedNotesWorker
2229
import com.philkes.notallyx.utils.backup.AUTO_BACKUP_WORK_NAME
2330
import com.philkes.notallyx.utils.backup.autoBackupOnSave
2431
import com.philkes.notallyx.utils.backup.autoBackupOnSaveFileExists
@@ -30,6 +37,7 @@ import com.philkes.notallyx.utils.backup.isEqualTo
3037
import com.philkes.notallyx.utils.backup.modifiedNoteBackupExists
3138
import com.philkes.notallyx.utils.backup.scheduleAutoBackup
3239
import com.philkes.notallyx.utils.backup.updateAutoBackup
40+
import com.philkes.notallyx.utils.log
3341
import com.philkes.notallyx.utils.observeOnce
3442
import com.philkes.notallyx.utils.security.UnlockReceiver
3543
import java.util.concurrent.TimeUnit
@@ -94,6 +102,9 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
94102
val backupFolder = preferences.backupsFolder.value
95103
checkUpdatePeriodicBackup(backupFolder, backupFolder, value.periodInDays.toLong())
96104
}
105+
preferences.autoRemoveDeletedNotesAfterDays.observeForever { value ->
106+
checkUpdateAutoRemoveOldDeletedNotes(value)
107+
}
97108

98109
val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_OFF) }
99110
biometricLockObserver = Observer { biometricLock ->
@@ -199,6 +210,15 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
199210
}
200211
}
201212

213+
private fun checkUpdateAutoRemoveOldDeletedNotes(days: Int) {
214+
val workManager = getWorkManagerSafe() ?: return
215+
if (days > 0) {
216+
workManager.scheduleAutoRemoveOldDeletedNotes(this)
217+
} else {
218+
workManager.cancelAutoRemoveOldDeletedNotes()
219+
}
220+
}
221+
202222
private fun getWorkManagerSafe(): WorkManager? {
203223
return try {
204224
WorkManager.getInstance(this)
@@ -229,8 +249,33 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
229249
override fun onActivityDestroyed(activity: Activity) {}
230250

231251
companion object {
232-
private fun isTestRunner(): Boolean {
252+
const val TAG = "NotallyXApplication"
253+
const val AUTO_REMOVE_DELETED_NOTES = "com.philkes.notallyx.AutoRemoveDeletedNotes"
254+
255+
fun isTestRunner(): Boolean {
233256
return Build.FINGERPRINT.equals("robolectric", ignoreCase = true)
234257
}
235258
}
236259
}
260+
261+
fun WorkManager.scheduleAutoRemoveOldDeletedNotes(context: ContextWrapper) {
262+
Log.d(TAG, "Scheduling auto removal of old deleted notes")
263+
val request =
264+
PeriodicWorkRequest.Builder(AutoRemoveDeletedNotesWorker::class.java, 1, TimeUnit.DAYS)
265+
.build()
266+
try {
267+
enqueueUniquePeriodicWork(
268+
AUTO_REMOVE_DELETED_NOTES,
269+
ExistingPeriodicWorkPolicy.KEEP,
270+
request,
271+
)
272+
} catch (e: IllegalStateException) {
273+
// only happens in Unit-Tests
274+
context.log(TAG, "Scheduling auto removal of old deleted notes failed", throwable = e)
275+
}
276+
}
277+
278+
fun WorkManager.cancelAutoRemoveOldDeletedNotes() {
279+
Log.d(TAG, "Cancelling auto removal of old deleted notes")
280+
cancelUniqueWork(AUTO_REMOVE_DELETED_NOTES)
281+
}

app/src/main/java/com/philkes/notallyx/data/NotallyDatabase.kt

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import androidx.room.TypeConverters
1212
import androidx.room.migration.Migration
1313
import androidx.sqlite.db.SimpleSQLiteQuery
1414
import androidx.sqlite.db.SupportSQLiteDatabase
15+
import com.philkes.notallyx.NotallyXApplication.Companion.isTestRunner
1516
import com.philkes.notallyx.data.dao.BaseNoteDao
1617
import com.philkes.notallyx.data.dao.CommonDao
1718
import com.philkes.notallyx.data.dao.LabelDao
@@ -113,13 +114,30 @@ abstract class NotallyDatabase : RoomDatabase() {
113114
}
114115
}
115116

117+
private var testInstance: NotallyDatabase? = null
118+
119+
private fun getTestDatabase(context: ContextWrapper): NotallyDatabase {
120+
return testInstance
121+
?: synchronized(this) {
122+
testInstance =
123+
Room.inMemoryDatabaseBuilder(context, NotallyDatabase::class.java)
124+
.allowMainThreadQueries()
125+
.build()
126+
return testInstance!!
127+
}
128+
}
129+
116130
fun getFreshDatabase(context: ContextWrapper, dataInPublic: Boolean): NotallyDatabase {
117-
return createInstance(
118-
context,
119-
NotallyXPreferences.getInstance(context),
120-
false,
121-
dataInPublic = dataInPublic,
122-
)
131+
return if (isTestRunner()) {
132+
getTestDatabase(context)
133+
} else {
134+
createInstance(
135+
context,
136+
NotallyXPreferences.getInstance(context),
137+
false,
138+
dataInPublic = dataInPublic,
139+
)
140+
}
123141
}
124142

125143
private fun createInstance(

app/src/main/java/com/philkes/notallyx/data/dao/BaseNoteDao.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ interface BaseNoteDao {
107107

108108
@Query("SELECT COUNT(*) FROM BaseNote") fun count(): Int
109109

110+
@Query("DELETE FROM BaseNote") suspend fun deleteAll()
111+
110112
@Query("DELETE FROM BaseNote WHERE id = :id") suspend fun delete(id: Long)
111113

112114
@Query("DELETE FROM BaseNote WHERE id IN (:ids)") suspend fun delete(ids: LongArray)
@@ -134,6 +136,15 @@ interface BaseNoteDao {
134136

135137
@Query("SELECT images FROM BaseNote WHERE id = :id") fun getImages(id: Long): String
136138

139+
@Query("SELECT images FROM BaseNote WHERE id IN (:ids)")
140+
fun getImages(ids: LongArray): List<String>
141+
142+
@Query("SELECT files FROM BaseNote WHERE id IN (:ids)")
143+
fun getFiles(ids: LongArray): List<String>
144+
145+
@Query("SELECT audios FROM BaseNote WHERE id IN (:ids)")
146+
fun getAudios(ids: LongArray): List<String>
147+
137148
@Query("SELECT images FROM BaseNote") fun getAllImages(): List<String>
138149

139150
@Query("SELECT files FROM BaseNote") fun getAllFiles(): List<String>
@@ -153,6 +164,9 @@ interface BaseNoteDao {
153164
@Query("SELECT id FROM BaseNote WHERE folder = 'DELETED'")
154165
suspend fun getDeletedNoteIds(): LongArray
155166

167+
@Query("SELECT id FROM BaseNote WHERE folder = 'DELETED' AND modifiedTimestamp < :before")
168+
suspend fun getDeletedNoteIdsOlderThan(before: Long): LongArray
169+
156170
@Query("SELECT images FROM BaseNote WHERE folder = 'DELETED'")
157171
suspend fun getDeletedNoteImages(): List<String>
158172

@@ -165,6 +179,11 @@ interface BaseNoteDao {
165179
@Query("UPDATE BaseNote SET folder = :folder WHERE id IN (:ids)")
166180
suspend fun move(ids: LongArray, folder: Folder)
167181

182+
@Query(
183+
"UPDATE BaseNote SET folder = :folder, modifiedTimestamp = :timestamp WHERE id IN (:ids)"
184+
)
185+
suspend fun move(ids: LongArray, folder: Folder, timestamp: Long)
186+
168187
@Query("SELECT DISTINCT color FROM BaseNote") fun getAllColorsAsync(): LiveData<List<String>>
169188

170189
@Query("SELECT DISTINCT color FROM BaseNote") suspend fun getAllColors(): List<String>

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/PreferenceBindingExtensions.kt

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.philkes.notallyx.presentation.activity.main.fragment.settings
33
import android.content.Context
44
import android.hardware.biometrics.BiometricManager
55
import android.net.Uri
6+
import android.os.Build
67
import android.text.method.PasswordTransformationMethod
78
import android.view.LayoutInflater
89
import android.view.View
@@ -460,9 +461,15 @@ fun PreferenceSeekbarBinding.setup(
460461
max: Int,
461462
context: Context,
462463
enabled: Boolean = true,
464+
tooltipResId: Int? = null,
463465
onChange: (newValue: Int) -> Unit,
464466
) {
465-
Title.setText(titleResId)
467+
Title.apply {
468+
setText(titleResId)
469+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
470+
tooltipResId?.let { tooltipText = context.getString(tooltipResId) }
471+
}
472+
}
466473
val valueInBoundaries = (if (value < min) min else if (value > max) max else value).toFloat()
467474
Slider.apply {
468475
isEnabled = enabled
@@ -487,9 +494,17 @@ fun PreferenceSeekbarBinding.setup(
487494
preference: IntPreference,
488495
context: Context,
489496
value: Int = preference.value,
497+
tooltipResId: Int? = null,
490498
onChange: (newValue: Int) -> Unit,
491499
) {
492-
setup(value, preference.titleResId!!, preference.min, preference.max, context) { newValue ->
500+
setup(
501+
value,
502+
preference.titleResId!!,
503+
preference.min,
504+
preference.max,
505+
context,
506+
tooltipResId = tooltipResId,
507+
) { newValue ->
493508
onChange(newValue)
494509
}
495510
}
@@ -514,7 +529,23 @@ fun PreferenceSeekbarBinding.setupAutoSaveIdleTime(
514529
}
515530
}
516531
}
517-
setup(preference, context, value, onChange)
532+
setup(preference, context, value, onChange = onChange)
533+
}
534+
535+
fun PreferenceSeekbarBinding.setupAutoEmptyBin(
536+
preference: IntPreference,
537+
context: Context,
538+
value: Int = preference.value,
539+
onChange: (newValue: Int) -> Unit,
540+
) {
541+
Slider.apply {
542+
setLabelFormatter { sliderValue ->
543+
if (sliderValue == 0f) {
544+
context.getString(R.string.disabled)
545+
} else "${sliderValue.toInt()} ${context.getString(R.string.days)}"
546+
}
547+
}
548+
setup(preference, context, value, R.string.auto_remove_deleted_notes_hint, onChange)
518549
}
519550

520551
fun PreferenceBinding.setupStartView(

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ import androidx.core.view.isVisible
2424
import androidx.fragment.app.Fragment
2525
import androidx.fragment.app.activityViewModels
2626
import androidx.lifecycle.lifecycleScope
27+
import androidx.work.WorkManager
2728
import com.google.android.material.dialog.MaterialAlertDialogBuilder
2829
import com.google.android.material.textfield.TextInputLayout.END_ICON_PASSWORD_TOGGLE
2930
import com.philkes.notallyx.NotallyXApplication
3031
import com.philkes.notallyx.R
32+
import com.philkes.notallyx.cancelAutoRemoveOldDeletedNotes
3133
import com.philkes.notallyx.data.imports.FOLDER_OR_FILE_MIMETYPE
3234
import com.philkes.notallyx.data.imports.ImportSource
3335
import com.philkes.notallyx.data.imports.txt.APPLICATION_TEXT_MIME_TYPES
@@ -51,6 +53,7 @@ import com.philkes.notallyx.presentation.viewmodel.preference.PeriodicBackup
5153
import com.philkes.notallyx.presentation.viewmodel.preference.PeriodicBackup.Companion.BACKUP_MAX_MIN
5254
import com.philkes.notallyx.presentation.viewmodel.preference.PeriodicBackup.Companion.BACKUP_PERIOD_DAYS_MIN
5355
import com.philkes.notallyx.presentation.viewmodel.preference.PeriodicBackupsPreference
56+
import com.philkes.notallyx.scheduleAutoRemoveOldDeletedNotes
5457
import com.philkes.notallyx.utils.MIME_TYPE_JSON
5558
import com.philkes.notallyx.utils.MIME_TYPE_ZIP
5659
import com.philkes.notallyx.utils.backup.exportPreferences
@@ -337,6 +340,23 @@ class SettingsFragment : Fragment() {
337340
}
338341
}
339342

343+
autoRemoveDeletedNotesAfterDays.observe(viewLifecycleOwner) { value ->
344+
binding.AutoEmptyBin.setupAutoEmptyBin(
345+
autoRemoveDeletedNotesAfterDays,
346+
requireContext(),
347+
) { newValue ->
348+
model.savePreference(autoRemoveDeletedNotesAfterDays, newValue)
349+
val workManager = WorkManager.getInstance(requireContext())
350+
if (newValue > 0) {
351+
workManager.scheduleAutoRemoveOldDeletedNotes(
352+
requireContext() as ContextWrapper
353+
)
354+
} else {
355+
workManager.cancelAutoRemoveOldDeletedNotes()
356+
}
357+
}
358+
}
359+
340360
binding.MaxLabels.setup(maxLabels, requireContext()) { newValue ->
341361
model.savePreference(maxLabels, newValue)
342362
}

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,11 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
631631
viewModelScope.launch(
632632
Dispatchers.IO
633633
) { // Only reminders of notes in NOTES folder are active
634-
baseNoteDao.move(ids, folder)
634+
if (folder == Folder.DELETED) {
635+
baseNoteDao.move(ids, folder, System.currentTimeMillis())
636+
} else {
637+
baseNoteDao.move(ids, folder)
638+
}
635639
val notes = baseNoteDao.getByIds(ids).toNoteIdReminders()
636640
// Only reminders of notes in NOTES folder are active
637641
when (folder) {

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/preference/NotallyXPreferences.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,15 @@ class NotallyXPreferences private constructor(private val context: Context) {
115115
val periodicBackups = PeriodicBackupsPreference(preferences)
116116
val periodicBackupLastExecution =
117117
LongPreference("periodicBackupLastExecution", preferences, -1L)
118+
val autoRemoveDeletedNotesAfterDays =
119+
IntPreference(
120+
"autoRemoveDeletedNotesAfterDays",
121+
preferences,
122+
0,
123+
0,
124+
365,
125+
R.string.auto_remove_deleted_notes,
126+
)
118127

119128
val backupPassword by lazy {
120129
StringPreference(
@@ -262,6 +271,7 @@ class NotallyXPreferences private constructor(private val context: Context) {
262271
backupOnSave,
263272
autoSaveAfterIdleTime,
264273
imagesHiddenInOverview,
274+
autoRemoveDeletedNotesAfterDays,
265275
)
266276
.forEach { it.refresh() }
267277
}

0 commit comments

Comments
 (0)