WIP: Subscription settings

This commit is contained in:
Philipp Heckel 2022-05-05 16:56:06 -04:00
parent c9643a2173
commit 9f6dd91088
14 changed files with 596 additions and 49 deletions

View file

@ -0,0 +1,314 @@
{
"formatVersion": 1,
"database": {
"version": 11,
"identityHash": "9a26b356f0d51f2c63fd3e4570b7e645",
"entities": [
{
"tableName": "Subscription",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `topic` TEXT NOT NULL, `instant` INTEGER NOT NULL, `mutedUntil` INTEGER NOT NULL, `minPriority` INTEGER NOT NULL, `autoDelete` INTEGER NOT NULL, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "baseUrl",
"columnName": "baseUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "topic",
"columnName": "topic",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "instant",
"columnName": "instant",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "mutedUntil",
"columnName": "mutedUntil",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minPriority",
"columnName": "minPriority",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "autoDelete",
"columnName": "autoDelete",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "upAppId",
"columnName": "upAppId",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "upConnectorToken",
"columnName": "upConnectorToken",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": false
},
"indices": [
{
"name": "index_Subscription_baseUrl_topic",
"unique": true,
"columnNames": [
"baseUrl",
"topic"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_baseUrl_topic` ON `${TABLE_NAME}` (`baseUrl`, `topic`)"
},
{
"name": "index_Subscription_upConnectorToken",
"unique": true,
"columnNames": [
"upConnectorToken"
],
"orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Subscription_upConnectorToken` ON `${TABLE_NAME}` (`upConnectorToken`)"
}
],
"foreignKeys": []
},
{
"tableName": "Notification",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `encoding` TEXT NOT NULL, `notificationId` INTEGER NOT NULL, `priority` INTEGER NOT NULL DEFAULT 3, `tags` TEXT NOT NULL, `click` TEXT NOT NULL, `actions` TEXT, `deleted` INTEGER NOT NULL, `attachment_name` TEXT, `attachment_type` TEXT, `attachment_size` INTEGER, `attachment_expires` INTEGER, `attachment_url` TEXT, `attachment_contentUri` TEXT, `attachment_progress` INTEGER, PRIMARY KEY(`id`, `subscriptionId`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "subscriptionId",
"columnName": "subscriptionId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "encoding",
"columnName": "encoding",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "notificationId",
"columnName": "notificationId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "priority",
"columnName": "priority",
"affinity": "INTEGER",
"notNull": true,
"defaultValue": "3"
},
{
"fieldPath": "tags",
"columnName": "tags",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "click",
"columnName": "click",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "actions",
"columnName": "actions",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "deleted",
"columnName": "deleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "attachment.name",
"columnName": "attachment_name",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attachment.type",
"columnName": "attachment_type",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attachment.size",
"columnName": "attachment_size",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "attachment.expires",
"columnName": "attachment_expires",
"affinity": "INTEGER",
"notNull": false
},
{
"fieldPath": "attachment.url",
"columnName": "attachment_url",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attachment.contentUri",
"columnName": "attachment_contentUri",
"affinity": "TEXT",
"notNull": false
},
{
"fieldPath": "attachment.progress",
"columnName": "attachment_progress",
"affinity": "INTEGER",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id",
"subscriptionId"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "User",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `username` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))",
"fields": [
{
"fieldPath": "baseUrl",
"columnName": "baseUrl",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "username",
"columnName": "username",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "password",
"columnName": "password",
"affinity": "TEXT",
"notNull": true
}
],
"primaryKey": {
"columnNames": [
"baseUrl"
],
"autoGenerate": false
},
"indices": [],
"foreignKeys": []
},
{
"tableName": "Log",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timestamp` INTEGER NOT NULL, `tag` TEXT NOT NULL, `level` INTEGER NOT NULL, `message` TEXT NOT NULL, `exception` TEXT)",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "timestamp",
"columnName": "timestamp",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "tag",
"columnName": "tag",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "level",
"columnName": "level",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "message",
"columnName": "message",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "exception",
"columnName": "exception",
"affinity": "TEXT",
"notNull": false
}
],
"primaryKey": {
"columnNames": [
"id"
],
"autoGenerate": true
},
"indices": [],
"foreignKeys": []
}
],
"views": [],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9a26b356f0d51f2c63fd3e4570b7e645')"
]
}
}

View file

@ -6,6 +6,7 @@ import com.google.gson.Gson
import com.google.gson.GsonBuilder import com.google.gson.GsonBuilder
import com.google.gson.stream.JsonReader import com.google.gson.stream.JsonReader
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.topicUrl import io.heckel.ntfy.util.topicUrl
import java.io.InputStreamReader import java.io.InputStreamReader
@ -94,6 +95,8 @@ class Backuper(val context: Context) {
topic = s.topic, topic = s.topic,
instant = s.instant, instant = s.instant,
mutedUntil = s.mutedUntil, mutedUntil = s.mutedUntil,
minPriority = s.minPriority ?: Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = s.autoDelete ?: Repository.AUTO_DELETE_USE_GLOBAL,
upAppId = s.upAppId, upAppId = s.upAppId,
upConnectorToken = s.upConnectorToken upConnectorToken = s.upConnectorToken
)) ))
@ -214,6 +217,8 @@ class Backuper(val context: Context) {
topic = s.topic, topic = s.topic,
instant = s.instant, instant = s.instant,
mutedUntil = s.mutedUntil, mutedUntil = s.mutedUntil,
minPriority = s.minPriority,
autoDelete = s.autoDelete,
upAppId = s.upAppId, upAppId = s.upAppId,
upConnectorToken = s.upConnectorToken upConnectorToken = s.upConnectorToken
) )
@ -317,6 +322,8 @@ data class Subscription(
val topic: String, val topic: String,
val instant: Boolean, val instant: Boolean,
val mutedUntil: Long, val mutedUntil: Long,
val minPriority: Int?,
val autoDelete: Long?,
val upAppId: String?, val upAppId: String?,
val upConnectorToken: String? val upConnectorToken: String?
) )

View file

@ -15,17 +15,18 @@ data class Subscription(
@ColumnInfo(name = "baseUrl") val baseUrl: String, @ColumnInfo(name = "baseUrl") val baseUrl: String,
@ColumnInfo(name = "topic") val topic: String, @ColumnInfo(name = "topic") val topic: String,
@ColumnInfo(name = "instant") val instant: Boolean, @ColumnInfo(name = "instant") val instant: Boolean,
@ColumnInfo(name = "mutedUntil") val mutedUntil: Long, // TODO notificationSound, notificationSchedule @ColumnInfo(name = "mutedUntil") val mutedUntil: Long,
@ColumnInfo(name = "minPriority") val minPriority: Int,
@ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds
@ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name @ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name
@ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token @ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token
// TODO autoDownloadAttachments, minPriority
@Ignore val totalCount: Int = 0, // Total notifications @Ignore val totalCount: Int = 0, // Total notifications
@Ignore val newCount: Int = 0, // New notifications @Ignore val newCount: Int = 0, // New notifications
@Ignore val lastActive: Long = 0, // Unix timestamp @Ignore val lastActive: Long = 0, // Unix timestamp
@Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE @Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE
) { ) {
constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, upAppId: String, upConnectorToken: String) : constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, upAppId: String, upConnectorToken: String) :
this(id, baseUrl, topic, instant, mutedUntil, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE) this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE)
} }
enum class ConnectionState { enum class ConnectionState {
@ -38,6 +39,8 @@ data class SubscriptionWithMetadata(
val topic: String, val topic: String,
val instant: Boolean, val instant: Boolean,
val mutedUntil: Long, val mutedUntil: Long,
val autoDelete: Long,
val minPriority: Int,
val upAppId: String?, val upAppId: String?,
val upConnectorToken: String?, val upConnectorToken: String?,
val totalCount: Int, val totalCount: Int,
@ -139,7 +142,7 @@ data class LogEntry(
this(0, timestamp, tag, level, message, exception) this(0, timestamp, tag, level, message, exception)
} }
@androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 10) @androidx.room.Database(entities = [Subscription::class, Notification::class, User::class, LogEntry::class], version = 11)
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class Database : RoomDatabase() { abstract class Database : RoomDatabase() {
abstract fun subscriptionDao(): SubscriptionDao abstract fun subscriptionDao(): SubscriptionDao
@ -164,6 +167,7 @@ abstract class Database : RoomDatabase() {
.addMigrations(MIGRATION_7_8) .addMigrations(MIGRATION_7_8)
.addMigrations(MIGRATION_8_9) .addMigrations(MIGRATION_8_9)
.addMigrations(MIGRATION_9_10) .addMigrations(MIGRATION_9_10)
.addMigrations(MIGRATION_10_11)
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.build() .build()
this.instance = instance this.instance = instance
@ -245,6 +249,13 @@ abstract class Database : RoomDatabase() {
db.execSQL("ALTER TABLE Notification ADD COLUMN actions TEXT") db.execSQL("ALTER TABLE Notification ADD COLUMN actions TEXT")
} }
} }
private val MIGRATION_10_11 = object : Migration(10, 11) {
override fun migrate(db: SupportSQLiteDatabase) {
db.execSQL("ALTER TABLE Notification ADD COLUMN minPriority INT NOT NULL DEFAULT (0)") // 0 = use global setting
db.execSQL("ALTER TABLE Notification ADD COLUMN autoDelete INT NOT NULL DEFAULT (0)")
}
}
} }
} }
@ -252,7 +263,7 @@ abstract class Database : RoomDatabase() {
interface SubscriptionDao { interface SubscriptionDao {
@Query(""" @Query("""
SELECT SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken, s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount, COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive IFNULL(MAX(n.timestamp),0) AS lastActive
@ -265,7 +276,7 @@ interface SubscriptionDao {
@Query(""" @Query("""
SELECT SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken, s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount, COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive IFNULL(MAX(n.timestamp),0) AS lastActive
@ -278,7 +289,7 @@ interface SubscriptionDao {
@Query(""" @Query("""
SELECT SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken, s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount, COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive IFNULL(MAX(n.timestamp),0) AS lastActive
@ -291,7 +302,7 @@ interface SubscriptionDao {
@Query(""" @Query("""
SELECT SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken, s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount, COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive IFNULL(MAX(n.timestamp),0) AS lastActive
@ -304,7 +315,7 @@ interface SubscriptionDao {
@Query(""" @Query("""
SELECT SELECT
s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.upAppId, s.upConnectorToken, s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken,
COUNT(n.id) totalCount, COUNT(n.id) totalCount,
COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount,
IFNULL(MAX(n.timestamp),0) AS lastActive IFNULL(MAX(n.timestamp),0) AS lastActive
@ -357,14 +368,14 @@ 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") @Query("UPDATE notification SET deleted = 1 WHERE subscriptionId = :subscriptionId AND timestamp < :olderThanTimestamp")
fun markAsDeletedIfOlderThan(olderThanTimestamp: Long) fun markAsDeletedIfOlderThan(subscriptionId: Long, 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") @Query("DELETE FROM notification WHERE subscriptionId = :subscriptionId AND timestamp < :olderThanTimestamp")
fun removeIfOlderThan(olderThanTimestamp: Long) fun removeIfOlderThan(subscriptionId: Long, 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

@ -136,12 +136,12 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
notificationDao.markAllAsDeleted(subscriptionId) notificationDao.markAllAsDeleted(subscriptionId)
} }
fun markAsDeletedIfOlderThan(olderThanTimestamp: Long) { fun markAsDeletedIfOlderThan(subscriptionId: Long, olderThanTimestamp: Long) {
notificationDao.markAsDeletedIfOlderThan(olderThanTimestamp) notificationDao.markAsDeletedIfOlderThan(subscriptionId, olderThanTimestamp)
} }
fun removeNotificationsIfOlderThan(olderThanTimestamp: Long) { fun removeNotificationsIfOlderThan(subscriptionId: Long, olderThanTimestamp: Long) {
notificationDao.removeIfOlderThan(olderThanTimestamp) notificationDao.removeIfOlderThan(subscriptionId, olderThanTimestamp)
} }
fun removeAllNotifications(subscriptionId: Long) { fun removeAllNotifications(subscriptionId: Long) {
@ -203,7 +203,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
} }
fun setMinPriority(minPriority: Int) { fun setMinPriority(minPriority: Int) {
if (minPriority <= 1) { if (minPriority <= MIN_PRIORITY_ANY) {
sharedPrefs.edit() sharedPrefs.edit()
.remove(SHARED_PREFS_MIN_PRIORITY) .remove(SHARED_PREFS_MIN_PRIORITY)
.apply() .apply()
@ -215,7 +215,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
} }
fun getMinPriority(): Int { fun getMinPriority(): Int {
return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, 1) // 1/low means all priorities return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, MIN_PRIORITY_ANY)
} }
fun getAutoDownloadMaxSize(): Long { fun getAutoDownloadMaxSize(): Long {
@ -377,6 +377,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
topic = s.topic, topic = s.topic,
instant = s.instant, instant = s.instant,
mutedUntil = s.mutedUntil, mutedUntil = s.mutedUntil,
minPriority = s.minPriority,
autoDelete = s.autoDelete,
upAppId = s.upAppId, upAppId = s.upAppId,
upConnectorToken = s.upConnectorToken, upConnectorToken = s.upConnectorToken,
totalCount = s.totalCount, totalCount = s.totalCount,
@ -397,6 +399,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
topic = s.topic, topic = s.topic,
instant = s.instant, instant = s.instant,
mutedUntil = s.mutedUntil, mutedUntil = s.mutedUntil,
minPriority = s.minPriority,
autoDelete = s.autoDelete,
upAppId = s.upAppId, upAppId = s.upAppId,
upConnectorToken = s.upConnectorToken, upConnectorToken = s.upConnectorToken,
totalCount = s.totalCount, totalCount = s.totalCount,
@ -449,6 +453,9 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
private const val LAST_TOPICS_COUNT = 3 private const val LAST_TOPICS_COUNT = 3
const val MIN_PRIORITY_USE_GLOBAL = 0
const val MIN_PRIORITY_ANY = 1
const val MUTED_UNTIL_SHOW_ALL = 0L const val MUTED_UNTIL_SHOW_ALL = 0L
const val MUTED_UNTIL_FOREVER = 1L const val MUTED_UNTIL_FOREVER = 1L
const val MUTED_UNTIL_TOMORROW = 2L const val MUTED_UNTIL_TOMORROW = 2L
@ -459,7 +466,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas
const val AUTO_DOWNLOAD_DEFAULT = ONE_MB const val AUTO_DOWNLOAD_DEFAULT = ONE_MB
private const val ONE_DAY_SECONDS = 24 * 60 * 60L private const val ONE_DAY_SECONDS = 24 * 60 * 60L
const val AUTO_DELETE_NEVER = 0L // Values must match values.xml const val AUTO_DELETE_USE_GLOBAL = -1L // Values must match values.xml
const val AUTO_DELETE_NEVER = 0L
const val AUTO_DELETE_ONE_DAY_SECONDS = ONE_DAY_SECONDS 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_THREE_DAYS_SECONDS = 3 * ONE_DAY_SECONDS
const val AUTO_DELETE_ONE_WEEK_SECONDS = 7 * ONE_DAY_SECONDS const val AUTO_DELETE_ONE_WEEK_SECONDS = 7 * ONE_DAY_SECONDS

View file

@ -73,7 +73,8 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
return false return false
} }
val priority = if (notification.priority > 0) notification.priority else 3 val priority = if (notification.priority > 0) notification.priority else 3
if (priority < repository.getMinPriority()) { val minPriority = if (subscription.minPriority > 0) subscription.minPriority else repository.getMinPriority()
if (priority < minPriority) {
return false return false
} }
val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId

View file

@ -112,6 +112,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
topic = topic, topic = topic,
instant = instant, instant = instant,
mutedUntil = 0, mutedUntil = 0,
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
upAppId = null, upAppId = null,
upConnectorToken = null, upConnectorToken = null,
totalCount = 0, totalCount = 0,
@ -348,10 +350,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
onClearClick() onClearClick()
true true
} }
/*R.id.detail_menu_settings -> { R.id.detail_menu_settings -> {
onSettingsClick() onSettingsClick()
true true
}*/ }
R.id.detail_menu_unsubscribe -> { R.id.detail_menu_unsubscribe -> {
onDeleteClick() onDeleteClick()
true true
@ -549,6 +551,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val intent = Intent(this, DetailSettingsActivity::class.java) val intent = Intent(this, DetailSettingsActivity::class.java)
intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscriptionId) intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscriptionId)
intent.putExtra(EXTRA_SUBSCRIPTION_BASE_URL, subscriptionBaseUrl)
intent.putExtra(EXTRA_SUBSCRIPTION_TOPIC, subscriptionTopic)
startActivity(intent) startActivity(intent)
} }
@ -729,5 +733,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
companion object { companion object {
const val TAG = "NtfyDetailActivity" const val TAG = "NtfyDetailActivity"
const val EXTRA_SUBSCRIPTION_ID = "subscriptionId" const val EXTRA_SUBSCRIPTION_ID = "subscriptionId"
const val EXTRA_SUBSCRIPTION_BASE_URL = "baseUrl"
const val EXTRA_SUBSCRIPTION_TOPIC = "topic"
} }
} }

View file

@ -3,19 +3,24 @@ package io.heckel.ntfy.ui
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.preference.ListPreference
import androidx.preference.Preference
import androidx.preference.PreferenceDataStore
import androidx.preference.PreferenceFragmentCompat 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.util.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.formatDateShort
import io.heckel.ntfy.util.toPriorityString
import io.heckel.ntfy.util.topicShortUrl
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.util.*
/** /**
* Subscription settings * Subscription settings
*
* THIS IS CURRENTLY UNUSED.
*/ */
class DetailSettingsActivity : AppCompatActivity() { class DetailSettingsActivity : AppCompatActivity() {
private lateinit var repository: Repository private lateinit var repository: Repository
@ -44,7 +49,10 @@ class DetailSettingsActivity : AppCompatActivity() {
.commit() .commit()
} }
title = getString(R.string.detail_settings_title) // Title
val baseUrl = intent.getStringExtra(DetailActivity.EXTRA_SUBSCRIPTION_BASE_URL) ?: return
val topic = intent.getStringExtra(DetailActivity.EXTRA_SUBSCRIPTION_TOPIC) ?: return
title = topicShortUrl(baseUrl, topic)
// Show 'Back' button // Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
@ -58,6 +66,7 @@ class DetailSettingsActivity : AppCompatActivity() {
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {
private lateinit var repository: Repository private lateinit var repository: Repository
private lateinit var serviceManager: SubscriberServiceManager private lateinit var serviceManager: SubscriberServiceManager
private lateinit var subscription: Subscription
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.detail_preferences, rootKey) setPreferencesFromResource(R.xml.detail_preferences, rootKey)
@ -69,15 +78,130 @@ class DetailSettingsActivity : AppCompatActivity() {
// Load subscription and users // Load subscription and users
val subscriptionId = arguments?.getLong(DetailActivity.EXTRA_SUBSCRIPTION_ID) ?: return val subscriptionId = arguments?.getLong(DetailActivity.EXTRA_SUBSCRIPTION_ID) ?: return
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
val subscription = repository.getSubscription(subscriptionId) ?: return@launch subscription = repository.getSubscription(subscriptionId) ?: return@launch
activity?.runOnUiThread { activity?.runOnUiThread {
loadView(subscription) loadView()
} }
} }
} }
private fun loadView(subscription: Subscription) { private fun loadView() {
// ... // Notifications muted until
val mutedUntilPrefId = context?.getString(R.string.detail_settings_notifications_muted_until_key) ?: return
val mutedUntil: ListPreference? = findPreference(mutedUntilPrefId)
mutedUntil?.value = subscription.mutedUntil.toString()
mutedUntil?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
val mutedUntilValue = value?.toLongOrNull() ?:return
when (mutedUntilValue) {
Repository.MUTED_UNTIL_SHOW_ALL -> save(subscription.copy(mutedUntil = mutedUntilValue))
Repository.MUTED_UNTIL_FOREVER -> save(subscription.copy(mutedUntil = mutedUntilValue))
Repository.MUTED_UNTIL_TOMORROW -> {
val date = Calendar.getInstance()
date.add(Calendar.DAY_OF_MONTH, 1)
date.set(Calendar.HOUR_OF_DAY, 8)
date.set(Calendar.MINUTE, 30)
date.set(Calendar.SECOND, 0)
date.set(Calendar.MILLISECOND, 0)
save(subscription.copy(mutedUntil = date.timeInMillis/1000))
}
else -> {
val mutedUntilTimestamp = System.currentTimeMillis()/1000 + mutedUntilValue * 60
save(subscription.copy(mutedUntil = mutedUntilTimestamp))
}
}
}
override fun getString(key: String?, defValue: String?): String {
return subscription.mutedUntil.toString()
}
}
mutedUntil?.summaryProvider = Preference.SummaryProvider<ListPreference> { _ ->
val mutedUntilValue = subscription.mutedUntil
when (mutedUntilValue) {
Repository.MUTED_UNTIL_SHOW_ALL -> getString(R.string.settings_notifications_muted_until_show_all)
Repository.MUTED_UNTIL_FOREVER -> getString(R.string.settings_notifications_muted_until_forever)
else -> {
val formattedDate = formatDateShort(mutedUntilValue)
getString(R.string.settings_notifications_muted_until_x, formattedDate)
}
}
}
// Minimum priority
val minPriorityPrefId = context?.getString(R.string.detail_settings_notifications_min_priority_key) ?: return
val minPriority: ListPreference? = findPreference(minPriorityPrefId)
minPriority?.value = subscription.minPriority.toString()
minPriority?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
val minPriorityValue = value?.toIntOrNull() ?:return
save(subscription.copy(minPriority = minPriorityValue))
}
override fun getString(key: String?, defValue: String?): String {
return subscription.minPriority.toString()
}
}
minPriority?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref ->
var value = pref.value.toIntOrNull() ?: Repository.MIN_PRIORITY_USE_GLOBAL
val global = value == Repository.MIN_PRIORITY_USE_GLOBAL
if (value == Repository.MIN_PRIORITY_USE_GLOBAL) {
value = repository.getMinPriority()
}
val summary = when (value) {
1 -> getString(R.string.settings_notifications_min_priority_summary_any)
5 -> getString(R.string.settings_notifications_min_priority_summary_max)
else -> {
val minPriorityString = toPriorityString(requireContext(), value)
getString(R.string.settings_notifications_min_priority_summary_x_or_higher, value, minPriorityString)
}
}
maybeAppendGlobal(summary, global)
}
// Auto delete
val autoDeletePrefId = context?.getString(R.string.detail_settings_notifications_auto_delete_key) ?: return
val autoDelete: ListPreference? = findPreference(autoDeletePrefId)
autoDelete?.value = subscription.autoDelete.toString()
autoDelete?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
val seconds = value?.toLongOrNull() ?:return
save(subscription.copy(autoDelete = seconds))
}
override fun getString(key: String?, defValue: String?): String {
return subscription.autoDelete.toString()
}
}
autoDelete?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref ->
var seconds = pref.value.toLongOrNull() ?: Repository.AUTO_DELETE_USE_GLOBAL
val global = seconds == Repository.AUTO_DELETE_USE_GLOBAL
if (seconds == Repository.AUTO_DELETE_USE_GLOBAL) {
seconds = repository.getAutoDeleteSeconds()
}
val summary = 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
}
maybeAppendGlobal(summary, global)
}
}
private fun save(newSubscription: Subscription) {
subscription = newSubscription
lifecycleScope.launch(Dispatchers.IO) {
repository.updateSubscription(newSubscription)
}
}
private fun maybeAppendGlobal(summary: String, global: Boolean): String {
return if (global) {
summary + " (" + getString(R.string.settings_global_setting_suffix) + ")"
} else {
summary
}
} }
} }

View file

@ -425,6 +425,8 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
topic = topic, topic = topic,
instant = instant, instant = instant,
mutedUntil = 0, mutedUntil = 0,
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
upAppId = null, upAppId = null,
upConnectorToken = null, upConnectorToken = null,
totalCount = 0, totalCount = 0,

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.content.Intent 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.Repository
import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.Log
@ -74,6 +75,8 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
topic = topic, topic = topic,
instant = true, // No Firebase, always instant! instant = true, // No Firebase, always instant!
mutedUntil = 0, mutedUntil = 0,
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
upAppId = appId, upAppId = appId,
upConnectorToken = connectorToken, upConnectorToken = connectorToken,
totalCount = 0, totalCount = 0,

View file

@ -9,6 +9,7 @@ import io.heckel.ntfy.db.ATTACHMENT_PROGRESS_DELETED
import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.ui.DetailAdapter import io.heckel.ntfy.ui.DetailAdapter
import io.heckel.ntfy.util.Log import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.topicShortUrl
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -53,29 +54,38 @@ class DeleteWorker(ctx: Context, params: WorkerParameters) : CoroutineWorker(ctx
val newNotification = notification.copy(attachment = newAttachment) val newNotification = notification.copy(attachment = newAttachment)
repository.updateNotification(newNotification) repository.updateNotification(newNotification)
} catch (e: Exception) { } catch (e: Exception) {
Log.w(DetailAdapter.TAG, "Failed to delete attachment for notification: ${e.message}", e) Log.w(TAG, "Failed to delete attachment for notification: ${e.message}", e)
} }
} }
} }
private fun deleteExpiredNotifications() { private suspend fun deleteExpiredNotifications() {
Log.d(TAG, "Deleting expired notifications") Log.d(TAG, "Deleting expired notifications")
val repository = Repository.getInstance(applicationContext) val repository = Repository.getInstance(applicationContext)
val deleteAfterSeconds = repository.getAutoDeleteSeconds() val subscriptions = repository.getSubscriptions()
if (deleteAfterSeconds == Repository.AUTO_DELETE_NEVER) { subscriptions.forEach { subscription ->
Log.d(TAG, "Not deleting any notifications; global setting set to NEVER") val logId = topicShortUrl(subscription.baseUrl, subscription.topic)
return val deleteAfterSeconds = if (subscription.autoDelete == Repository.AUTO_DELETE_USE_GLOBAL) {
repository.getAutoDeleteSeconds()
} else {
subscription.autoDelete
}
if (deleteAfterSeconds == Repository.AUTO_DELETE_NEVER) {
Log.d(TAG, "[$logId] Not deleting any notifications; global setting set to NEVER")
return@forEach
}
// Mark as deleted
val markDeletedOlderThanTimestamp = (System.currentTimeMillis()/1000) - deleteAfterSeconds
Log.d(TAG, "[$logId] Marking notifications older than $markDeletedOlderThanTimestamp as deleted")
repository.markAsDeletedIfOlderThan(subscription.id, markDeletedOlderThanTimestamp)
// Hard delete
val deleteOlderThanTimestamp = (System.currentTimeMillis()/1000) - HARD_DELETE_AFTER_SECONDS
Log.d(TAG, "[$logId] Hard deleting notifications older than $markDeletedOlderThanTimestamp")
repository.removeNotificationsIfOlderThan(subscription.id, deleteOlderThanTimestamp)
} }
// 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)
} }
companion object { companion object {

View file

@ -9,9 +9,9 @@
app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/> app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/>
<item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant" <item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant"
android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/> android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/>
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/> <item android:id="@+id/detail_menu_settings" android:title="@string/detail_menu_settings"/>
<item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/> <item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/>
<item android:id="@+id/detail_menu_clear" android:title="@string/detail_menu_clear"/> <item android:id="@+id/detail_menu_clear" android:title="@string/detail_menu_clear"/>
<!--<item android:id="@+id/detail_menu_settings" android:title="@string/detail_menu_settings"/>--> <item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
<item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/> <item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/>
</menu> </menu>

View file

@ -334,6 +334,8 @@
<string name="settings_about_version_title">Version</string> <string name="settings_about_version_title">Version</string>
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string> <string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
<string name="settings_about_version_copied_to_clipboard_message">Copied to clipboard</string> <string name="settings_about_version_copied_to_clipboard_message">Copied to clipboard</string>
<string name="settings_global_setting_title">Use global setting</string>
<string name="settings_global_setting_suffix">global</string>
<!-- User add/edit dialog --> <!-- User add/edit dialog -->
<string name="user_dialog_title_add">Add user</string> <string name="user_dialog_title_add">Add user</string>

View file

@ -30,6 +30,11 @@
<string name="settings_advanced_connection_protocol_key" translatable="false">ConnectionProtocol</string> <string name="settings_advanced_connection_protocol_key" translatable="false">ConnectionProtocol</string>
<string name="settings_about_version_key" translatable="false">Version</string> <string name="settings_about_version_key" translatable="false">Version</string>
<!-- Detail settings constants -->
<string name="detail_settings_notifications_muted_until_key" translatable="false">SubscriptionMutedUntil</string>
<string name="detail_settings_notifications_min_priority_key" translatable="false">SubscriptionMinPriority</string>
<string name="detail_settings_notifications_auto_delete_key" translatable="false">SubscriptionAutoDelete</string>
<!-- Main settings --> <!-- Main settings -->
<string-array name="settings_notifications_muted_until_entries"> <string-array name="settings_notifications_muted_until_entries">
<item>@string/notification_dialog_show_all</item> <item>@string/notification_dialog_show_all</item>
@ -63,6 +68,22 @@
<item>4</item> <item>4</item>
<item>5</item> <item>5</item>
</string-array> </string-array>
<string-array name="detail_settings_notifications_min_priority_entries">
<item>@string/settings_global_setting_title</item>
<item>@string/settings_notifications_min_priority_min</item>
<item>@string/settings_notifications_min_priority_low</item>
<item>@string/settings_notifications_min_priority_default</item>
<item>@string/settings_notifications_min_priority_high</item>
<item>@string/settings_notifications_min_priority_max</item>
</string-array>
<string-array name="detail_settings_notifications_min_priority_values">
<item>0</item> <!-- Same as Repository.MIN_PRIORITY_USE_GLOBAL -->
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
<string-array name="settings_notifications_auto_download_entries"> <string-array name="settings_notifications_auto_download_entries">
<item>@string/settings_notifications_auto_download_never</item> <item>@string/settings_notifications_auto_download_never</item>
<item>@string/settings_notifications_auto_download_always</item> <item>@string/settings_notifications_auto_download_always</item>
@ -99,6 +120,24 @@
<item>2592000</item> <item>2592000</item>
<item>7776000</item> <item>7776000</item>
</string-array> </string-array>
<string-array name="detail_settings_notifications_auto_delete_entries">
<item>@string/settings_global_setting_title</item>
<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="detail_settings_notifications_auto_delete_values">
<item>-1</item> <!-- Same as Repository.AUTO_DELETE_USE_GLOBAL -->
<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

@ -1,3 +1,23 @@
<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto" <PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto"
app:title="@string/detail_settings_title"> app:title="@string/detail_settings_title">
<PreferenceCategory app:title="@string/settings_notifications_header">
<ListPreference
app:key="@string/detail_settings_notifications_muted_until_key"
app:title="@string/settings_notifications_muted_until_title"
app:entries="@array/settings_notifications_muted_until_entries"
app:entryValues="@array/settings_notifications_muted_until_values"
app:defaultValue="0"/>
<ListPreference
app:key="@string/detail_settings_notifications_min_priority_key"
app:title="@string/settings_notifications_min_priority_title"
app:entries="@array/detail_settings_notifications_min_priority_entries"
app:entryValues="@array/detail_settings_notifications_min_priority_values"
app:defaultValue="0"/> <!-- Same as Repository.MIN_PRIORITY_USE_GLOBAL -->
<ListPreference
app:key="@string/detail_settings_notifications_auto_delete_key"
app:title="@string/settings_notifications_auto_delete_title"
app:entries="@array/detail_settings_notifications_auto_delete_entries"
app:entryValues="@array/detail_settings_notifications_auto_delete_values"
app:defaultValue="-1"/> <!-- Same as Repository.AUTO_DELETE_USE_GLOBAL -->
</PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>