diff --git a/app/schemas/io.heckel.ntfy.db.Database/12.json b/app/schemas/io.heckel.ntfy.db.Database/12.json index e9e36a1..801d9ed 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/12.json +++ b/app/schemas/io.heckel.ntfy.db.Database/12.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 12, - "identityHash": "d230005f4d9824ba9aa34c61003bdcbb", + "identityHash": "40948c056fa4ccc9765735111177cf85", "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, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, PRIMARY KEY(`id`))", + "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, `insistent` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -50,6 +50,12 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "insistent", + "columnName": "insistent", + "affinity": "INTEGER", + "notNull": true + }, { "fieldPath": "lastNotificationId", "columnName": "lastNotificationId", @@ -338,7 +344,7 @@ "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, 'd230005f4d9824ba9aa34c61003bdcbb')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '40948c056fa4ccc9765735111177cf85')" ] } } \ No newline at end of file diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt index 8c87f90..f4e7b4e 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -102,6 +102,7 @@ class Backuper(val context: Context) { mutedUntil = s.mutedUntil, minPriority = s.minPriority ?: Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = s.autoDelete ?: Repository.AUTO_DELETE_USE_GLOBAL, + insistent = s.insistent ?: Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -239,6 +240,7 @@ class Backuper(val context: Context) { mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -356,6 +358,7 @@ data class Subscription( val mutedUntil: Long, val minPriority: Int?, val autoDelete: Long?, + val insistent: Int?, val lastNotificationId: String?, val icon: String?, val upAppId: String?, diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index effdba0..141cf13 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -18,7 +18,7 @@ data class Subscription( @ColumnInfo(name = "mutedUntil") val mutedUntil: Long, @ColumnInfo(name = "minPriority") val minPriority: Int, @ColumnInfo(name = "autoDelete") val autoDelete: Long, // Seconds - //@ColumnInfo(name = "insistent") val insistent: Boolean?, // Seconds + @ColumnInfo(name = "insistent") val insistent: Int, // Ring constantly for max priority notifications (-1 = use global, 0 = off, 1 = on) @ColumnInfo(name = "lastNotificationId") val lastNotificationId: String?, // Used for polling, with since= @ColumnInfo(name = "icon") val icon: String?, // content://-URI (or later other identifier) @ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name @@ -29,8 +29,40 @@ data class Subscription( @Ignore val lastActive: Long = 0, // Unix timestamp @Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE ) { - constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, lastNotificationId: String, icon: String, upAppId: String, upConnectorToken: String, displayName: String?) : - this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, lastNotificationId, icon, upAppId, upConnectorToken, displayName, 0, 0, 0, ConnectionState.NOT_APPLICABLE) + constructor( + id: Long, + baseUrl: String, + topic: String, + instant: Boolean, + mutedUntil: Long, + minPriority: Int, + autoDelete: Long, + insistent: Int, + lastNotificationId: String, + icon: String, + upAppId: String, + upConnectorToken: String, + displayName: String? + ) : + this( + id, + baseUrl, + topic, + instant, + mutedUntil, + minPriority, + autoDelete, + insistent, + lastNotificationId, + icon, + upAppId, + upConnectorToken, + displayName, + 0, + 0, + 0, + ConnectionState.NOT_APPLICABLE + ) } enum class ConnectionState { @@ -45,6 +77,7 @@ data class SubscriptionWithMetadata( val mutedUntil: Long, val autoDelete: Long, val minPriority: Int, + val insistent: Int, val lastNotificationId: String?, val icon: String?, val upAppId: String?, @@ -291,7 +324,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -304,7 +337,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -317,7 +350,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -330,7 +363,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -343,7 +376,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.insistent, s.lastNotificationId, s.icon, s.upAppId, s.upConnectorToken, s.displayName, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt index d92e4a8..e5344f0 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -402,6 +402,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -427,6 +428,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + insistent = s.insistent, lastNotificationId = s.lastNotificationId, icon = s.icon, upAppId = s.upAppId, @@ -505,6 +507,10 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas const val AUTO_DELETE_THREE_MONTHS_SECONDS = 90 * ONE_DAY_SECONDS const val AUTO_DELETE_DEFAULT_SECONDS = AUTO_DELETE_ONE_MONTH_SECONDS + const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml + const val INSISTENT_MAX_PRIORITY_DISABLED = 0 + const val INSISTENT_MAX_PRIORITY_ENABLED = 1 + const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp" const val CONNECTION_PROTOCOL_WS = "ws" diff --git a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt index 2a1ee12..42e225b 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -65,7 +65,8 @@ class NotificationService(val context: Context) { private fun displayInternal(subscription: Subscription, notification: Notification, update: Boolean = false) { val title = formatTitle(subscription, notification) val channelId = toChannelId(notification.priority) - val insistent = notification.priority == 5 && repository.getInsistentMaxPriorityEnabled() + val insistent = notification.priority == 5 && + (repository.getInsistentMaxPriorityEnabled() || subscription.insistent == Repository.INSISTENT_MAX_PRIORITY_ENABLED) val builder = NotificationCompat.Builder(context, channelId) .setSmallIcon(R.drawable.ic_notification) .setColor(ContextCompat.getColor(context, Colors.notificationIcon(context))) diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index e900d70..e29e919 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -113,6 +113,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = null, icon = null, upAppId = null, @@ -241,7 +242,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() { override fun onItemRangeInserted(positionStart: Int, itemCount: Int) { if (positionStart == 0) { - Log.d(TAG, "$itemCount item(s) inserted at $positionStart, scrolling to the top") + Log.d(TAG, "$itemCount item(s) inserted at 0, scrolling to the top") mainList.scrollToPosition(positionStart) } } @@ -626,7 +627,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra handleActionModeClick(notification) } else if (notification.click != "") { try { - startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(notification.click))) + startActivity(Intent(ACTION_VIEW, Uri.parse(notification.click))) } catch (e: Exception) { Log.w(TAG, "Cannot open click URL", e) runOnUiThread { diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt index e5108a3..1be59e2 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -110,7 +110,7 @@ class DetailSettingsActivity : AppCompatActivity() { loadMutedUntilPref() loadMinPriorityPref() loadAutoDeletePref() - //loadInsistentMaxPriority() + loadInsistentMaxPriorityPref() loadIconSetPref() loadIconRemovePref() } else { @@ -238,7 +238,7 @@ class DetailSettingsActivity : AppCompatActivity() { pref?.summaryProvider = Preference.SummaryProvider { preference -> var seconds = preference.value.toLongOrNull() ?: Repository.AUTO_DELETE_USE_GLOBAL val global = seconds == Repository.AUTO_DELETE_USE_GLOBAL - if (seconds == Repository.AUTO_DELETE_USE_GLOBAL) { + if (global) { seconds = repository.getAutoDeleteSeconds() } val summary = when (seconds) { @@ -253,30 +253,34 @@ class DetailSettingsActivity : AppCompatActivity() { maybeAppendGlobal(summary, global) } } -/* - private fun loadInsistentMaxPriority() { - val appBaseUrl = getString(R.string.app_base_url) - val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return - val pref: SwitchPreference? = findPreference(prefId) + + private fun loadInsistentMaxPriorityPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_insistent_max_priority_key) ?: return + val pref: ListPreference? = findPreference(prefId) pref?.isVisible = true - pref?.isChecked = subscription.instant + pref?.value = subscription.insistent.toString() pref?.preferenceDataStore = object : PreferenceDataStore() { - override fun putBoolean(key: String?, value: Boolean) { - save(subscription.copy(instant = value), refresh = true) + override fun putString(key: String?, value: String?) { + val intValue = value?.toIntOrNull() ?:return + save(subscription.copy(insistent = intValue)) } - override fun getBoolean(key: String?, defValue: Boolean): Boolean { - return subscription.instant + override fun getString(key: String?, defValue: String?): String { + return subscription.insistent.toString() } } - pref?.summaryProvider = Preference.SummaryProvider { preference -> - if (preference.isChecked) { - getString(R.string.detail_settings_notifications_instant_summary_on) + pref?.summaryProvider = Preference.SummaryProvider { preference -> + val value = preference.value.toIntOrNull() ?: Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL + val global = value == Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL + val enabled = if (global) repository.getInsistentMaxPriorityEnabled() else value == Repository.INSISTENT_MAX_PRIORITY_ENABLED + val summary = if (enabled) { + getString(R.string.settings_notifications_insistent_max_priority_summary_enabled) } else { - getString(R.string.detail_settings_notifications_instant_summary_off) + getString(R.string.settings_notifications_insistent_max_priority_summary_disabled) } + maybeAppendGlobal(summary, global) } } -*/ + private fun loadIconSetPref() { val prefId = context?.getString(R.string.detail_settings_appearance_icon_set_key) ?: return iconSetPref = findPreference(prefId) ?: return diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt index ad928a4..6b02791 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -455,6 +455,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = null, icon = null, upAppId = null, diff --git a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt index 9119e15..9110cb9 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -75,6 +75,7 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL, lastNotificationId = null, icon = null, upAppId = appId, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82f1d79..33be8df 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -282,7 +282,7 @@ After 3 months Keep alerting for highest priority Max priority notifications continuously alert until dismissed - If enabled, the notification sound will continuously repeat until dismissed + Max priority notifications only alert once General Default server Enter your server\'s root URL to use your own server as a default when subscribing to new topics and/or sharing to topics. @@ -352,6 +352,8 @@ Instant delivery Notifications are delivered instantly. Requires a foreground service and consumes more battery. Notifications are delivered using Firebase. Delivery may be delayed, but consumes less battery. + Keep alerting + Alert only once Appearance Subscription icon Set an icon to be displayed in notifications diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 0b33496..0dc8b2b 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -37,6 +37,7 @@ SubscriptionMutedUntil SubscriptionMinPriority SubscriptionAutoDelete + SubscriptionInsistentMaxPriority SubscriptionAppearance SubscriptionIconSet SubscriptionIconRemove @@ -147,6 +148,16 @@ 2592000 7776000 + + @string/detail_settings_global_setting_title + @string/detail_settings_notifications_insistent_max_priority_list_item_enabled + @string/detail_settings_notifications_insistent_max_priority_list_item_disabled + + + -1 + 1 + 0 + @string/settings_advanced_connection_protocol_entry_jsonhttp @string/settings_advanced_connection_protocol_entry_ws diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index e209f16..0f56d86 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -28,6 +28,13 @@ app:entryValues="@array/detail_settings_notifications_auto_delete_values" app:defaultValue="-1" app:isPreferenceVisible="false"/> +