Implement auto-delete of notifications, closes #71

This commit is contained in:
Philipp Heckel 2022-02-09 16:20:24 -05:00
parent 42188d5152
commit d44358f75c
31 changed files with 227 additions and 50 deletions

View file

@ -12,8 +12,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 31
versionCode 21 versionCode 22
versionName "1.8.1" versionName "1.9.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -4,7 +4,7 @@ import android.app.Application
import android.content.Context import android.content.Context
import io.heckel.ntfy.db.Database import io.heckel.ntfy.db.Database
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
class Application : Application() { class Application : Application() {
private val database by lazy { private val database by lazy {

View file

@ -297,9 +297,15 @@ interface NotificationDao {
@Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId") @Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId")
fun markAllAsDeleted(subscriptionId: Long) fun markAllAsDeleted(subscriptionId: Long)
@Query("UPDATE notification SET deleted = 1 WHERE timestamp < :olderThanTimestamp")
fun markAsDeletedIfOlderThan(olderThanTimestamp: Long)
@Query("UPDATE notification SET deleted = 0 WHERE id = :notificationId") @Query("UPDATE notification SET deleted = 0 WHERE id = :notificationId")
fun undelete(notificationId: String) fun undelete(notificationId: String)
@Query("DELETE FROM notification WHERE timestamp < :olderThanTimestamp")
fun removeIfOlderThan(olderThanTimestamp: Long)
@Query("DELETE FROM notification WHERE subscriptionId = :subscriptionId") @Query("DELETE FROM notification WHERE subscriptionId = :subscriptionId")
fun removeAll(subscriptionId: Long) fun removeAll(subscriptionId: Long)
} }

View file

@ -1,13 +1,12 @@
package io.heckel.ntfy.db package io.heckel.ntfy.db
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Build import android.os.Build
import androidx.annotation.WorkerThread import androidx.annotation.WorkerThread
import androidx.appcompat.app.AppCompatDelegate import androidx.appcompat.app.AppCompatDelegate
import androidx.lifecycle.* import androidx.lifecycle.*
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -116,20 +115,26 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
notificationDao.update(notification) notificationDao.update(notification)
} }
fun markAsDeleted(notificationId: String) {
notificationDao.markAsDeleted(notificationId)
}
fun undeleteNotification(notificationId: String) { fun undeleteNotification(notificationId: String) {
notificationDao.undelete(notificationId) notificationDao.undelete(notificationId)
} }
fun markAsDeleted(notificationId: String) {
notificationDao.markAsDeleted(notificationId)
}
fun markAllAsDeleted(subscriptionId: Long) { fun markAllAsDeleted(subscriptionId: Long) {
notificationDao.markAllAsDeleted(subscriptionId) notificationDao.markAllAsDeleted(subscriptionId)
} }
@Suppress("RedundantSuspendModifier") fun markAsDeletedIfOlderThan(olderThanTimestamp: Long) {
@WorkerThread notificationDao.markAsDeletedIfOlderThan(olderThanTimestamp)
}
fun removeNotificationsIfOlderThan(olderThanTimestamp: Long) {
notificationDao.removeIfOlderThan(olderThanTimestamp)
}
fun removeAllNotifications(subscriptionId: Long) { fun removeAllNotifications(subscriptionId: Long) {
notificationDao.removeAll(subscriptionId) notificationDao.removeAll(subscriptionId)
} }
@ -164,6 +169,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
.apply() .apply()
} }
fun getDeleteWorkerVersion(): Int {
return sharedPrefs.getInt(SHARED_PREFS_DELETE_WORKER_VERSION, 0)
}
fun setDeleteWorkerVersion(version: Int) {
sharedPrefs.edit()
.putInt(SHARED_PREFS_DELETE_WORKER_VERSION, version)
.apply()
}
fun getAutoRestartWorkerVersion(): Int { fun getAutoRestartWorkerVersion(): Int {
return sharedPrefs.getInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0) return sharedPrefs.getInt(SHARED_PREFS_AUTO_RESTART_WORKER_VERSION, 0)
} }
@ -205,6 +220,16 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
.apply() .apply()
} }
fun getAutoDeleteSeconds(): Long {
return sharedPrefs.getLong(SHARED_PREFS_AUTO_DELETE_SECONDS, AUTO_DELETE_DEFAULT_SECONDS)
}
fun setAutoDeleteSeconds(seconds: Long) {
sharedPrefs.edit()
.putLong(SHARED_PREFS_AUTO_DELETE_SECONDS, seconds)
.apply()
}
fun setDarkMode(mode: Int) { fun setDarkMode(mode: Int) {
if (mode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) { if (mode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) {
sharedPrefs.edit() sharedPrefs.edit()
@ -384,11 +409,12 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
companion object { companion object {
const val SHARED_PREFS_ID = "MainPreferences" const val SHARED_PREFS_ID = "MainPreferences"
const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion" const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion"
const val SHARED_PREFS_DELETE_WORKER_VERSION = "DeleteWorkerVersion"
const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion" const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil" const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil"
const val SHARED_PREFS_MIN_PRIORITY = "MinPriority" const val SHARED_PREFS_MIN_PRIORITY = "MinPriority"
const val SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE = "AutoDownload" const val SHARED_PREFS_AUTO_DOWNLOAD_MAX_SIZE = "AutoDownload"
const val SHARED_PREFS_WAKELOCK_ENABLED = "WakelockEnabled" const val SHARED_PREFS_AUTO_DELETE_SECONDS = "AutoDelete"
const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol" const val SHARED_PREFS_CONNECTION_PROTOCOL = "ConnectionProtocol"
const val SHARED_PREFS_DARK_MODE = "DarkMode" const val SHARED_PREFS_DARK_MODE = "DarkMode"
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled" const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
@ -401,9 +427,19 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
const val MUTED_UNTIL_FOREVER = 1L const val MUTED_UNTIL_FOREVER = 1L
const val MUTED_UNTIL_TOMORROW = 2L const val MUTED_UNTIL_TOMORROW = 2L
const val AUTO_DOWNLOAD_NEVER = 0L private const val ONE_MB = 1024 * 1024L
const val AUTO_DOWNLOAD_NEVER = 0L // Values must match values.xml
const val AUTO_DOWNLOAD_ALWAYS = 1L const val AUTO_DOWNLOAD_ALWAYS = 1L
const val AUTO_DOWNLOAD_DEFAULT = 1024 * 1024L // Must match a value in values.xml const val AUTO_DOWNLOAD_DEFAULT = ONE_MB
private const val ONE_DAY_SECONDS = 24 * 60 * 60L
const val AUTO_DELETE_NEVER = 0L // Values must match values.xml
const val AUTO_DELETE_ONE_DAY_SECONDS = ONE_DAY_SECONDS
const val AUTO_DELETE_THREE_DAYS_SECONDS = 3 * ONE_DAY_SECONDS
const val AUTO_DELETE_ONE_WEEK_SECONDS = 7 * ONE_DAY_SECONDS
const val AUTO_DELETE_ONE_MONTH_SECONDS = 30 * ONE_DAY_SECONDS
const val AUTO_DELETE_THREE_MONTHS_SECONDS = 90 * ONE_DAY_SECONDS
const val AUTO_DELETE_DEFAULT_SECONDS = AUTO_DELETE_ONE_MONTH_SECONDS
const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp" const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp"
const val CONNECTION_PROTOCOL_WS = "ws" const val CONNECTION_PROTOCOL_WS = "ws"

View file

@ -4,7 +4,7 @@ import android.os.Build
import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.User import io.heckel.ntfy.db.User
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.topicUrl import io.heckel.ntfy.util.topicUrl
import io.heckel.ntfy.util.topicUrlAuth import io.heckel.ntfy.util.topicUrlAuth
import io.heckel.ntfy.util.topicUrlJson import io.heckel.ntfy.util.topicUrlJson

View file

@ -6,7 +6,7 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.joinTagsMap import io.heckel.ntfy.util.joinTagsMap
import io.heckel.ntfy.util.splitTags import io.heckel.ntfy.util.splitTags
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -5,7 +5,7 @@ import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.workDataOf import androidx.work.workDataOf
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
/** /**
* Download attachment in the background via WorkManager * Download attachment in the background via WorkManager

View file

@ -17,7 +17,7 @@ import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.queryFilename import io.heckel.ntfy.util.queryFilename
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request

View file

@ -4,7 +4,7 @@ import android.content.Context
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.up.Distributor import io.heckel.ntfy.up.Distributor
import io.heckel.ntfy.util.safeLet import io.heckel.ntfy.util.safeLet

View file

@ -11,7 +11,7 @@ import androidx.core.content.ContextCompat
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.Colors
import io.heckel.ntfy.ui.DetailActivity import io.heckel.ntfy.ui.DetailActivity
import io.heckel.ntfy.ui.MainActivity import io.heckel.ntfy.ui.MainActivity

View file

@ -1,7 +1,7 @@
package io.heckel.ntfy.service package io.heckel.ntfy.service
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.util.topicUrl import io.heckel.ntfy.util.topicUrl
import kotlinx.coroutines.* import kotlinx.coroutines.*

View file

@ -16,7 +16,7 @@ import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.ConnectionState import io.heckel.ntfy.db.ConnectionState
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.ui.Colors import io.heckel.ntfy.ui.Colors

View file

@ -5,7 +5,7 @@ import android.content.Intent
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.work.* import androidx.work.*
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
/** /**
* This class only manages the SubscriberService, i.e. it starts or stops it. * This class only manages the SubscriberService, i.e. it starts or stops it.

View file

@ -5,7 +5,7 @@ import android.os.Build
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService.Companion.requestBuilder import io.heckel.ntfy.msg.ApiService.Companion.requestBuilder
import io.heckel.ntfy.msg.NotificationParser import io.heckel.ntfy.msg.NotificationParser
import io.heckel.ntfy.util.topicShortUrl import io.heckel.ntfy.util.topicShortUrl

View file

@ -20,7 +20,7 @@ import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.User import io.heckel.ntfy.db.User
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.util.topicUrl import io.heckel.ntfy.util.topicUrl
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -27,7 +27,7 @@ import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager

View file

@ -20,7 +20,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.stfalcon.imageviewer.StfalconImageViewer import com.stfalcon.imageviewer.StfalconImageViewer
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.* import io.heckel.ntfy.db.*
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.DownloadManager import io.heckel.ntfy.msg.DownloadManager
import io.heckel.ntfy.util.* import io.heckel.ntfy.util.*
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers

View file

@ -7,7 +7,7 @@ import androidx.preference.PreferenceFragmentCompat
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View file

@ -30,12 +30,13 @@ import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.firebase.FirebaseMessenger import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.service.SubscriberService import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.* import io.heckel.ntfy.util.*
import io.heckel.ntfy.work.DeleteWorker
import io.heckel.ntfy.work.PollWorker import io.heckel.ntfy.work.PollWorker
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -158,8 +159,9 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
AppCompatDelegate.setDefaultNightMode(repository.getDarkMode()) AppCompatDelegate.setDefaultNightMode(repository.getDarkMode())
// Background things // Background things
startPeriodicPollWorker() schedulePeriodicPollWorker()
startPeriodicServiceRestartWorker() schedulePeriodicServiceRestartWorker()
schedulePeriodicDeleteWorker()
} }
override fun onResume() { override fun onResume() {
@ -178,7 +180,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
Log.d(TAG, "Battery: ignoring optimizations = $ignoringOptimizations (we want this to be true); instant subscriptions = $hasInstantSubscriptions; remind time reached = $batteryRemindTimeReached; banner = $showBanner") Log.d(TAG, "Battery: ignoring optimizations = $ignoringOptimizations (we want this to be true); instant subscriptions = $hasInstantSubscriptions; remind time reached = $batteryRemindTimeReached; banner = $showBanner")
} }
private fun startPeriodicPollWorker() { private fun schedulePeriodicPollWorker() {
val workerVersion = repository.getPollWorkerVersion() val workerVersion = repository.getPollWorkerVersion()
val workPolicy = if (workerVersion == PollWorker.VERSION) { val workPolicy = if (workerVersion == PollWorker.VERSION) {
Log.d(TAG, "Poll worker version matches: choosing KEEP as existing work policy") Log.d(TAG, "Poll worker version matches: choosing KEEP as existing work policy")
@ -200,7 +202,26 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work) workManager!!.enqueueUniquePeriodicWork(PollWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work)
} }
private fun startPeriodicServiceRestartWorker() {
private fun schedulePeriodicDeleteWorker() {
val workerVersion = repository.getDeleteWorkerVersion()
val workPolicy = if (workerVersion == DeleteWorker.VERSION) {
Log.d(TAG, "Delete worker version matches: choosing KEEP as existing work policy")
ExistingPeriodicWorkPolicy.KEEP
} else {
Log.d(TAG, "Delete worker version DOES NOT MATCH: choosing REPLACE as existing work policy")
repository.setDeleteWorkerVersion(DeleteWorker.VERSION)
ExistingPeriodicWorkPolicy.REPLACE
}
val work = PeriodicWorkRequestBuilder<DeleteWorker>(DELETE_WORKER_INTERVAL_MINUTES, TimeUnit.MINUTES)
.addTag(DeleteWorker.TAG)
.addTag(DeleteWorker.WORK_NAME_PERIODIC_ALL)
.build()
Log.d(TAG, "Delete worker: Scheduling period work every $DELETE_WORKER_INTERVAL_MINUTES minutes")
workManager!!.enqueueUniquePeriodicWork(DeleteWorker.WORK_NAME_PERIODIC_ALL, workPolicy, work)
}
private fun schedulePeriodicServiceRestartWorker() {
val workerVersion = repository.getAutoRestartWorkerVersion() val workerVersion = repository.getAutoRestartWorkerVersion()
val workPolicy = if (workerVersion == SubscriberService.SERVICE_START_WORKER_VERSION) { val workPolicy = if (workerVersion == SubscriberService.SERVICE_START_WORKER_VERSION) {
Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy") Log.d(TAG, "ServiceStartWorker version matches: choosing KEEP as existing work policy")
@ -598,6 +619,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Thanks to varunon9 (https://gist.github.com/varunon9/f2beec0a743c96708eb0ef971a9ff9cd) for this! // Thanks to varunon9 (https://gist.github.com/varunon9/f2beec0a743c96708eb0ef971a9ff9cd) for this!
const val POLL_WORKER_INTERVAL_MINUTES = 2 * 60L const val POLL_WORKER_INTERVAL_MINUTES = 2 * 60L
const val DELETE_WORKER_INTERVAL_MINUTES = 8 * 60L
const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L const val SERVICE_START_WORKER_INTERVAL_MINUTES = 3 * 60L
} }
} }

View file

@ -5,7 +5,6 @@ import android.app.AlertDialog
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -25,7 +24,7 @@ import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.User import io.heckel.ntfy.db.User
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.formatBytes import io.heckel.ntfy.util.formatBytes
import io.heckel.ntfy.util.formatDateShort import io.heckel.ntfy.util.formatDateShort
@ -234,6 +233,32 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
} }
} }
// Auto delete
val autoDeletePrefId = context?.getString(R.string.settings_notifications_auto_delete_key) ?: return
val autoDelete: ListPreference? = findPreference(autoDeletePrefId)
autoDelete?.value = repository.getAutoDeleteSeconds().toString()
autoDelete?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
val seconds = value?.toLongOrNull() ?:return
repository.setAutoDeleteSeconds(seconds)
}
override fun getString(key: String?, defValue: String?): String {
return repository.getAutoDeleteSeconds().toString()
}
}
autoDelete?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref ->
val seconds = pref.value.toLongOrNull() ?: repository.getAutoDeleteSeconds()
when (seconds) {
Repository.AUTO_DELETE_NEVER -> getString(R.string.settings_notifications_auto_delete_summary_never)
Repository.AUTO_DELETE_ONE_DAY_SECONDS -> getString(R.string.settings_notifications_auto_delete_summary_one_day)
Repository.AUTO_DELETE_THREE_DAYS_SECONDS -> getString(R.string.settings_notifications_auto_delete_summary_three_days)
Repository.AUTO_DELETE_ONE_WEEK_SECONDS -> getString(R.string.settings_notifications_auto_delete_summary_one_week)
Repository.AUTO_DELETE_ONE_MONTH_SECONDS -> getString(R.string.settings_notifications_auto_delete_summary_one_month)
Repository.AUTO_DELETE_THREE_MONTHS_SECONDS -> getString(R.string.settings_notifications_auto_delete_summary_three_months)
else -> getString(R.string.settings_notifications_auto_delete_summary_one_month) // Must match default const
}
}
// Dark mode // Dark mode
val darkModePrefId = context?.getString(R.string.settings_appearance_dark_mode_key) ?: return val darkModePrefId = context?.getString(R.string.settings_appearance_dark_mode_key) ?: return
val darkMode: ListPreference? = findPreference(darkModePrefId) val darkMode: ListPreference? = findPreference(darkModePrefId)

View file

@ -5,7 +5,7 @@ import android.content.Intent
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.randomString import io.heckel.ntfy.util.randomString
import io.heckel.ntfy.util.topicUrlUp import io.heckel.ntfy.util.topicUrlUp

View file

@ -2,7 +2,7 @@ package io.heckel.ntfy.up
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
/** /**
* This is the UnifiedPush distributor, an amalgamation of messages to be sent as part of the spec. * This is the UnifiedPush distributor, an amalgamation of messages to be sent as part of the spec.

View file

@ -1,4 +1,4 @@
package io.heckel.ntfy.log package io.heckel.ntfy.util
import android.content.Context import android.content.Context
import android.os.Build import android.os.Build

View file

@ -0,0 +1,52 @@
package io.heckel.ntfy.work
import android.content.Context
import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.util.Log
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
// IMPORTANT:
// Every time the worker is changed, the periodic work has to be REPLACEd.
// This is facilitated in the MainActivity using the VERSION below.
init {
Log.init(ctx) // Init in all entrypoints
}
override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) {
Log.d(TAG, "Deleting expired notifications")
val repository = Repository.getInstance(applicationContext)
val deleteAfterSeconds = repository.getAutoDeleteSeconds()
if (deleteAfterSeconds == Repository.AUTO_DELETE_NEVER) {
Log.d(TAG, "Not deleting any notifications; global setting set to NEVER")
return@withContext Result.success()
}
// Mark as deleted
val markDeletedOlderThanTimestamp = (System.currentTimeMillis()/1000) - deleteAfterSeconds
Log.d(TAG, "Marking notifications older than $markDeletedOlderThanTimestamp as deleted")
repository.markAsDeletedIfOlderThan(markDeletedOlderThanTimestamp)
// Hard delete
val deleteOlderThanTimestamp = (System.currentTimeMillis()/1000) - HARD_DELETE_AFTER_SECONDS
Log.d(TAG, "Hard deleting notifications older than $markDeletedOlderThanTimestamp")
repository.removeNotificationsIfOlderThan(deleteOlderThanTimestamp)
return@withContext Result.success()
}
}
companion object {
const val VERSION = BuildConfig.VERSION_CODE
const val TAG = "NtfyDeleteWorker"
const val WORK_NAME_PERIODIC_ALL = "NtfyDeleteWorkerPeriodic" // Do not change
private const val ONE_DAY_SECONDS = 24 * 60 * 60L
const val HARD_DELETE_AFTER_SECONDS = 4 * 30 * ONE_DAY_SECONDS // 4 months
}
}

View file

@ -4,17 +4,16 @@ import android.content.Context
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.db.Database
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.log.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.util.Log
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.random.Random import kotlin.random.Random
class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) { class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx, params) {
// IMPORTANT WARNING: // IMPORTANT:
// Every time the worker is changed, the periodic work has to be REPLACEd. // Every time the worker is changed, the periodic work has to be REPLACEd.
// This is facilitated in the MainActivity using the VERSION below. // This is facilitated in the MainActivity using the VERSION below.
@ -25,9 +24,7 @@ class PollWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx,
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
return withContext(Dispatchers.IO) { return withContext(Dispatchers.IO) {
Log.d(TAG, "Polling for new notifications") Log.d(TAG, "Polling for new notifications")
val database = Database.getInstance(applicationContext) val repository = Repository.getInstance(applicationContext)
val sharedPrefs = applicationContext.getSharedPreferences(Repository.SHARED_PREFS_ID, Context.MODE_PRIVATE)
val repository = Repository.getInstance(sharedPrefs, database)
val dispatcher = NotificationDispatcher(applicationContext, repository) val dispatcher = NotificationDispatcher(applicationContext, repository)
val api = ApiService() val api = ApiService()

View file

@ -227,10 +227,10 @@
<string name="settings_notifications_min_priority_high">High priority and higher</string> <string name="settings_notifications_min_priority_high">High priority and higher</string>
<string name="settings_notifications_min_priority_max">Only max priority</string> <string name="settings_notifications_min_priority_max">Only max priority</string>
<string name="settings_notifications_auto_download_key">AutoDownload</string> <string name="settings_notifications_auto_download_key">AutoDownload</string>
<string name="settings_notifications_auto_download_title">Auto download attachments</string> <string name="settings_notifications_auto_download_title">Download attachments</string>
<string name="settings_notifications_auto_download_summary_always">Attachments are always downloaded automatically</string> <string name="settings_notifications_auto_download_summary_always">Automatically download all attachments</string>
<string name="settings_notifications_auto_download_summary_never">Attachments are never downloaded automatically</string> <string name="settings_notifications_auto_download_summary_never">Never download attachments automatically</string>
<string name="settings_notifications_auto_download_summary_smaller_than_x">Attachments up to %1$s are downloaded automatically</string> <string name="settings_notifications_auto_download_summary_smaller_than_x">Auto-download attachments up to %1$s</string>
<string name="settings_notifications_auto_download_never">Never download automatically</string> <string name="settings_notifications_auto_download_never">Never download automatically</string>
<string name="settings_notifications_auto_download_always">Always download automatically</string> <string name="settings_notifications_auto_download_always">Always download automatically</string>
<string name="settings_notifications_auto_download_100k">If smaller than 100 KB</string> <string name="settings_notifications_auto_download_100k">If smaller than 100 KB</string>
@ -239,6 +239,20 @@
<string name="settings_notifications_auto_download_5m">If smaller than 5 MB</string> <string name="settings_notifications_auto_download_5m">If smaller than 5 MB</string>
<string name="settings_notifications_auto_download_10m">If smaller than 10 MB</string> <string name="settings_notifications_auto_download_10m">If smaller than 10 MB</string>
<string name="settings_notifications_auto_download_50m">If smaller than 50 MB</string> <string name="settings_notifications_auto_download_50m">If smaller than 50 MB</string>
<string name="settings_notifications_auto_delete_key">AutoDelete</string>
<string name="settings_notifications_auto_delete_title">Delete notifications</string>
<string name="settings_notifications_auto_delete_summary_never">Never automatically delete notifications</string>
<string name="settings_notifications_auto_delete_summary_one_day">Auto-delete notifications after one day</string>
<string name="settings_notifications_auto_delete_summary_three_days">Auto-delete notifications after 3 days</string>
<string name="settings_notifications_auto_delete_summary_one_week">Auto-delete notifications after one week</string>
<string name="settings_notifications_auto_delete_summary_one_month">Auto-delete notifications after one month</string>
<string name="settings_notifications_auto_delete_summary_three_months">Auto-delete notifications after 3 months</string>
<string name="settings_notifications_auto_delete_never">Never</string>
<string name="settings_notifications_auto_delete_one_day">After one day</string>
<string name="settings_notifications_auto_delete_three_days">After 3 days</string>
<string name="settings_notifications_auto_delete_one_week">After one week</string>
<string name="settings_notifications_auto_delete_one_month">After one month</string>
<string name="settings_notifications_auto_delete_three_months">After 3 months</string>
<string name="settings_appearance_header">Appearance</string> <string name="settings_appearance_header">Appearance</string>
<string name="settings_appearance_dark_mode_key">DarkMode</string> <string name="settings_appearance_dark_mode_key">DarkMode</string>
<string name="settings_appearance_dark_mode_title">Dark mode</string> <string name="settings_appearance_dark_mode_title">Dark mode</string>

View file

@ -53,6 +53,22 @@
<item>10485760</item> <item>10485760</item>
<item>52428800</item> <item>52428800</item>
</string-array> </string-array>
<string-array name="settings_notifications_auto_delete_entries">
<item>@string/settings_notifications_auto_delete_never</item>
<item>@string/settings_notifications_auto_delete_one_day</item>
<item>@string/settings_notifications_auto_delete_three_days</item>
<item>@string/settings_notifications_auto_delete_one_week</item>
<item>@string/settings_notifications_auto_delete_one_month</item>
<item>@string/settings_notifications_auto_delete_three_months</item>
</string-array>
<string-array name="settings_notifications_auto_delete_values">
<item>0</item>
<item>86400</item>
<item>259200</item>
<item>604800</item>
<item>2592000</item>
<item>7776000</item>
</string-array>
<string-array name="settings_advanced_connection_protocol_entries"> <string-array name="settings_advanced_connection_protocol_entries">
<item>@string/settings_advanced_connection_protocol_entry_jsonhttp</item> <item>@string/settings_advanced_connection_protocol_entry_jsonhttp</item>
<item>@string/settings_advanced_connection_protocol_entry_ws</item> <item>@string/settings_advanced_connection_protocol_entry_ws</item>

View file

@ -21,6 +21,12 @@
app:entries="@array/settings_notifications_auto_download_entries" app:entries="@array/settings_notifications_auto_download_entries"
app:entryValues="@array/settings_notifications_auto_download_values" app:entryValues="@array/settings_notifications_auto_download_values"
app:defaultValue="1"/> app:defaultValue="1"/>
<ListPreference
app:key="@string/settings_notifications_auto_delete_key"
app:title="@string/settings_notifications_auto_delete_title"
app:entries="@array/settings_notifications_auto_delete_entries"
app:entryValues="@array/settings_notifications_auto_delete_values"
app:defaultValue="2592000"/>
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory <PreferenceCategory
app:title="@string/settings_appearance_header" app:title="@string/settings_appearance_header"

View file

@ -1,7 +1,7 @@
package io.heckel.ntfy.firebase package io.heckel.ntfy.firebase
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
class FirebaseMessenger { class FirebaseMessenger {
fun subscribe(topic: String) { fun subscribe(topic: String) {

View file

@ -9,7 +9,7 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Attachment import io.heckel.ntfy.db.Attachment
import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.log.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64 import io.heckel.ntfy.msg.MESSAGE_ENCODING_BASE64
import io.heckel.ntfy.msg.NotificationDispatcher import io.heckel.ntfy.msg.NotificationDispatcher

View file

@ -0,0 +1,3 @@
Features:
* Dark theme: Improvements around style and contrast (#119, thanks @kzshantonu for reporting)
* Automatically delete notifications (#71, thanks @arjan-s for reporting)