From 88686aed50f5babcee17a6fd259acfe663de7b64 Mon Sep 17 00:00:00 2001 From: Scrounger Date: Thu, 19 Feb 2026 12:54:33 +0100 Subject: [PATCH 1/8] gradle update --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 5920c2950..4e84f735e 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,7 @@ buildscript { ext.kotlin_version = '2.2.10' dependencies { - classpath 'com.android.tools.build:gradle:9.0.0' + classpath 'com.android.tools.build:gradle:9.0.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'com.google.gms:google-services:4.4.4' // This is removed in the "fdroid" flavor From 075393fc962c5de0c2b45d9d5f23d045487a50b1 Mon Sep 17 00:00:00 2001 From: Scrounger Date: Thu, 19 Feb 2026 14:01:08 +0100 Subject: [PATCH 2/8] db migration --- .../io.heckel.ntfy.db.Database/19.json | 441 ++++++++++++++++++ .../java/io/heckel/ntfy/backup/Backuper.kt | 10 +- .../main/java/io/heckel/ntfy/db/Database.kt | 31 +- .../main/java/io/heckel/ntfy/db/Repository.kt | 12 +- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 4 +- .../java/io/heckel/ntfy/ui/MainActivity.kt | 4 +- .../io/heckel/ntfy/up/BroadcastReceiver.kt | 4 +- 7 files changed, 492 insertions(+), 14 deletions(-) create mode 100644 app/schemas/io.heckel.ntfy.db.Database/19.json diff --git a/app/schemas/io.heckel.ntfy.db.Database/19.json b/app/schemas/io.heckel.ntfy.db.Database/19.json new file mode 100644 index 000000000..22d1527e5 --- /dev/null +++ b/app/schemas/io.heckel.ntfy.db.Database/19.json @@ -0,0 +1,441 @@ +{ + "formatVersion": 1, + "database": { + "version": 19, + "identityHash": "e720fb35a4903e41c8452a73c21732e3", + "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, `insistent` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, `imageLayout` INTEGER NOT NULL, `linkHandler` TEXT NOT NULL, 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": "insistent", + "columnName": "insistent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastNotificationId", + "columnName": "lastNotificationId", + "affinity": "TEXT" + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT" + }, + { + "fieldPath": "upAppId", + "columnName": "upAppId", + "affinity": "TEXT" + }, + { + "fieldPath": "upConnectorToken", + "columnName": "upConnectorToken", + "affinity": "TEXT" + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT" + }, + { + "fieldPath": "dedicatedChannels", + "columnName": "dedicatedChannels", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "imageLayout", + "columnName": "imageLayout", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "linkHandler", + "columnName": "linkHandler", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "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`)" + } + ] + }, + { + "tableName": "Notification", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `subscriptionId` INTEGER NOT NULL, `timestamp` INTEGER NOT NULL, `sequenceId` TEXT NOT NULL, `title` TEXT NOT NULL, `message` TEXT NOT NULL, `contentType` 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, `icon_url` TEXT, `icon_contentUri` TEXT, `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": "sequenceId", + "columnName": "sequenceId", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "message", + "columnName": "message", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "contentType", + "columnName": "contentType", + "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" + }, + { + "fieldPath": "deleted", + "columnName": "deleted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "icon.url", + "columnName": "icon_url", + "affinity": "TEXT" + }, + { + "fieldPath": "icon.contentUri", + "columnName": "icon_contentUri", + "affinity": "TEXT" + }, + { + "fieldPath": "attachment.name", + "columnName": "attachment_name", + "affinity": "TEXT" + }, + { + "fieldPath": "attachment.type", + "columnName": "attachment_type", + "affinity": "TEXT" + }, + { + "fieldPath": "attachment.size", + "columnName": "attachment_size", + "affinity": "INTEGER" + }, + { + "fieldPath": "attachment.expires", + "columnName": "attachment_expires", + "affinity": "INTEGER" + }, + { + "fieldPath": "attachment.url", + "columnName": "attachment_url", + "affinity": "TEXT" + }, + { + "fieldPath": "attachment.contentUri", + "columnName": "attachment_contentUri", + "affinity": "TEXT" + }, + { + "fieldPath": "attachment.progress", + "columnName": "attachment_progress", + "affinity": "INTEGER" + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id", + "subscriptionId" + ] + } + }, + { + "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": { + "autoGenerate": false, + "columnNames": [ + "baseUrl" + ] + } + }, + { + "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" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "CustomHeader", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `name` TEXT NOT NULL, `value` TEXT NOT NULL, PRIMARY KEY(`baseUrl`, `name`))", + "fields": [ + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "baseUrl", + "name" + ] + } + }, + { + "tableName": "TrustedCertificate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `pem` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))", + "fields": [ + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "pem", + "columnName": "pem", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "baseUrl" + ] + } + }, + { + "tableName": "ClientCertificate", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`baseUrl` TEXT NOT NULL, `p12Base64` TEXT NOT NULL, `password` TEXT NOT NULL, PRIMARY KEY(`baseUrl`))", + "fields": [ + { + "fieldPath": "baseUrl", + "columnName": "baseUrl", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "p12Base64", + "columnName": "p12Base64", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "password", + "columnName": "password", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "baseUrl" + ] + } + } + ], + "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, 'e720fb35a4903e41c8452a73c21732e3')" + ] + } +} \ 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 2dafa8397..de10445d6 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -118,6 +118,8 @@ class Backuper(val context: Context) { upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, displayName = s.displayName, + imageLayout = s.imageLayout ?: Repository.IMAGE_LAYOUT_DEFAULT, + linkHandler = s.linkHandler ?: Repository.LINK_HANDLER_DEFAULT, ) repository.addSubscription(subscription) @@ -298,7 +300,9 @@ class Backuper(val context: Context) { icon = s.icon, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, - displayName = s.displayName + displayName = s.displayName, + imageLayout = s.imageLayout, + linkHandler = s.linkHandler ) } } @@ -438,7 +442,9 @@ data class Subscription( val icon: String?, val upAppId: String?, val upConnectorToken: String?, - val displayName: String? + val displayName: String?, + val imageLayout: Int?, + val linkHandler: String?, ) data class Notification( 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 16ab22318..3dec4b552 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -44,6 +44,8 @@ data class Subscription( @ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token @ColumnInfo(name = "displayName") val displayName: String?, @ColumnInfo(name = "dedicatedChannels") val dedicatedChannels: Boolean, + @ColumnInfo(name = "imageLayout") val imageLayout: Int, + @ColumnInfo(name = "linkHandler") val linkHandler: String, @Ignore val totalCount: Int = 0, // Total notifications @Ignore val newCount: Int = 0, // New notifications @Ignore val lastActive: Long = 0, // Unix timestamp @@ -63,7 +65,9 @@ data class Subscription( upAppId: String, upConnectorToken: String, displayName: String?, - dedicatedChannels: Boolean + dedicatedChannels: Boolean, + imageLayout: Int, + linkHandler: String, ) : this( id, @@ -80,6 +84,8 @@ data class Subscription( upConnectorToken, displayName, dedicatedChannels, + imageLayout, + linkHandler, totalCount = 0, newCount = 0, lastActive = 0, @@ -136,6 +142,8 @@ data class SubscriptionWithMetadata( val upConnectorToken: String?, val displayName: String?, val dedicatedChannels: Boolean, + val imageLayout: Int, + val linkHandler: String, val totalCount: Int, val newCount: Int, val lastActive: Long @@ -298,7 +306,7 @@ data class LogEntry( } @androidx.room.Database( - version = 18, + version = 19, entities = [ Subscription::class, Notification::class, @@ -344,6 +352,7 @@ abstract class Database : RoomDatabase() { .addMigrations(MIGRATION_15_16) .addMigrations(MIGRATION_16_17) .addMigrations(MIGRATION_17_18) + .addMigrations(MIGRATION_18_19) .fallbackToDestructiveMigration(true) .build() this.instance = instance @@ -484,6 +493,14 @@ abstract class Database : RoomDatabase() { db.execSQL("UPDATE Notification SET sequenceId = id WHERE sequenceId = ''") } } + + private val MIGRATION_18_19 = object : Migration(18, 19) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("ALTER TABLE Subscription ADD COLUMN imageLayout INT NOT NULL DEFAULT (0)") + db.execSQL("ALTER TABLE Subscription ADD COLUMN linkHandler TEXT NOT NULL DEFAULT ''") + } + } + } } @@ -491,7 +508,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, + 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -504,7 +521,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, + 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -517,7 +534,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, + 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -530,7 +547,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, + 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -543,7 +560,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, + 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, 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 43c8c8581..e2319b37c 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -539,7 +539,9 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, - connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails() + connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), + imageLayout = s.imageLayout, + linkHandler = s.linkHandler ) } } @@ -566,7 +568,9 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, - connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails() + connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), + imageLayout = s.imageLayout, + linkHandler = s.linkHandler ) } @@ -664,6 +668,10 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val WEBSOCKET_RECONNECT_REMIND_TIME_ALWAYS = 1L const val WEBSOCKET_RECONNECT_REMIND_TIME_NEVER = Long.MAX_VALUE + const val IMAGE_LAYOUT_DEFAULT = 0 + + const val LINK_HANDLER_DEFAULT = "web" + private const val TAG = "NtfyRepository" private var instance: Repository? = null 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 b2554a875..0fcc453c1 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -234,7 +234,9 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet displayName = displayName, totalCount = 0, newCount = 0, - lastActive = Date().time/1000 + lastActive = Date().time/1000, + imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + linkHandler = Repository.LINK_HANDLER_DEFAULT ) repository.addSubscription(subscription) 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 251a4ba0e..c9a522360 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -678,7 +678,9 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific displayName = null, totalCount = 0, newCount = 0, - lastActive = Date().time/1000 + lastActive = Date().time/1000, + imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + linkHandler = Repository.LINK_HANDLER_DEFAULT ) viewModel.add(subscription) 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 b5a44a463..fbca20f89 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -92,7 +92,9 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { displayName = null, totalCount = 0, newCount = 0, - lastActive = Date().time/1000 + lastActive = Date().time/1000, + imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + linkHandler = Repository.LINK_HANDLER_DEFAULT ) Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription") try { From 4b7905ab58799c044b42a6dea55ba90b5d45f852 Mon Sep 17 00:00:00 2001 From: Scrounger Date: Thu, 19 Feb 2026 17:39:46 +0100 Subject: [PATCH 3/8] setttings added --- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 78 ++++++- .../layout/fragment_detail_item_layout_1.xml | 218 ++++++++++++++++++ app/src/main/res/values-de/strings.xml | 1 + app/src/main/res/values/strings.xml | 5 + app/src/main/res/values/values.xml | 23 ++ app/src/main/res/xml/detail_preferences.xml | 19 ++ 6 files changed, 339 insertions(+), 5 deletions(-) create mode 100644 app/src/main/res/layout/fragment_detail_item_layout_1.xml 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 f0293c79c..212504073 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -13,10 +13,17 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.core.content.FileProvider import androidx.core.graphics.drawable.toDrawable +import androidx.core.net.toUri import androidx.core.view.WindowInsetsControllerCompat import androidx.lifecycle.lifecycleScope -import androidx.preference.* +import androidx.preference.EditTextPreference +import androidx.preference.ListPreference +import androidx.preference.MultiSelectListPreference +import androidx.preference.Preference import androidx.preference.Preference.OnPreferenceClickListener +import androidx.preference.PreferenceCategory +import androidx.preference.PreferenceDataStore +import androidx.preference.SwitchPreferenceCompat import com.google.android.material.appbar.AppBarLayout import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R @@ -25,12 +32,25 @@ import io.heckel.ntfy.db.Subscription import io.heckel.ntfy.msg.DownloadAttachmentWorker import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.service.SubscriberServiceManager -import io.heckel.ntfy.util.* -import kotlinx.coroutines.* +import io.heckel.ntfy.util.Log +import io.heckel.ntfy.util.PRIORITY_MAX +import io.heckel.ntfy.util.PRIORITY_MIN +import io.heckel.ntfy.util.copyToClipboard +import io.heckel.ntfy.util.displayName +import io.heckel.ntfy.util.fileStat +import io.heckel.ntfy.util.formatDateShort +import io.heckel.ntfy.util.isDarkThemeOn +import io.heckel.ntfy.util.readBitmapFromUri +import io.heckel.ntfy.util.supportedImage +import io.heckel.ntfy.util.toPriorityString +import io.heckel.ntfy.util.topicShortUrl +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.withContext import java.io.File import java.io.IOException -import java.util.* -import androidx.core.net.toUri +import java.util.Calendar /** * Subscription settings @@ -144,6 +164,8 @@ class DetailSettingsActivity : AppCompatActivity() { loadIconRemovePref() loadDedicatedChannelsPrefs() loadOpenChannelsPrefs() + loadImageLayoutPref() + loadLinkHandlerPref() } else { val notificationsHeaderId = context?.getString(R.string.detail_settings_notifications_header_key) ?: return val notificationsHeader: PreferenceCategory? = findPreference(notificationsHeaderId) @@ -437,6 +459,52 @@ class DetailSettingsActivity : AppCompatActivity() { } } + private fun loadImageLayoutPref() { + val prefId = context?.getString(R.string.detail_settings_appearance_image_layout_key) ?: return + val pref: ListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.value = subscription.imageLayout.toString() + + pref?.preferenceDataStore = object : PreferenceDataStore() { + override fun putString(key: String?, value: String?) { + val imageLayoutValue = value?.toIntOrNull() ?:return + save(subscription.copy(imageLayout = imageLayoutValue)) + } + override fun getString(key: String?, defValue: String?): String { + return subscription.imageLayout.toString() + } + } + pref?.summaryProvider = Preference.SummaryProvider { + val layoutEntries = resources.getStringArray(R.array.detail_settings_appearance_image_layout_entries) + layoutEntries[subscription.imageLayout] + } + } + + private fun loadLinkHandlerPref() { + val prefId = context?.getString(R.string.detail_settings_appearance_link_handler_key) ?: return + val pref: MultiSelectListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.values = subscription.linkHandler.split("|").toSet() + + pref?.preferenceDataStore = object : PreferenceDataStore() { + override fun putStringSet(key: String?, values: Set?) { + val valueString = values?.joinToString("|") ?: return + val cleanedValueString = if (valueString.startsWith("|")) valueString.substring(1) else valueString + + save(subscription.copy(linkHandler = cleanedValueString)) + } + override fun getStringSet(key: String?, defValues: Set?): Set { + val savedValue = subscription.linkHandler + val cleanedValue = if (savedValue.startsWith("|")) savedValue.substring(1) else savedValue + + return cleanedValue.split("|").toSet() + } + } + pref?.summaryProvider = Preference.SummaryProvider { + subscription.linkHandler + } + } + private fun createIconPickLauncher(): ActivityResultLauncher { return registerForActivityResult(ActivityResultContracts.GetContent()) { inputUri -> if (inputUri == null) { diff --git a/app/src/main/res/layout/fragment_detail_item_layout_1.xml b/app/src/main/res/layout/fragment_detail_item_layout_1.xml new file mode 100644 index 000000000..e3602b806 --- /dev/null +++ b/app/src/main/res/layout/fragment_detail_item_layout_1.xml @@ -0,0 +1,218 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 087d54174..c0bd0cd3e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -473,4 +473,5 @@ In Benachrichtigungen suchen Eine gültige Service-URL eingeben, z. B. https://ntfy.example.com Benachrichtigungen suchen + Layout des Bildes diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2ac24ce1c..c4c7ff3cb 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,6 +485,11 @@ Display name Set a custom display name for this subscription. Leave blank for default (%1$s). %1$s (default) + Image layout + Choose between different image layouts + Link interpreter + Choose how the links contained in the text should be interpreted + Use global setting using global setting About diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 0b45baf59..b9b286cb8 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -53,6 +53,29 @@ SubscriptionDisplayName SubscriptionAbout SubscriptionTopicURL + SubscriptionImageLayout + + Default + Layout 1 + + + 0 + 1 + + SubscriptionLinkHandler + + web + phone + mail + + + web + phone + mail + + + web + diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index 4cb105b12..7cf93cfce 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -64,6 +64,25 @@ app:dialogLayout="@layout/preference_dialog_edittext_edited" app:dialogMessage="@string/detail_settings_appearance_display_name_message" app:isPreferenceVisible="false"/> + + + Date: Fri, 20 Feb 2026 07:55:25 +0100 Subject: [PATCH 4/8] renaming imageLayout to layout --- .../io.heckel.ntfy.db.Database/19.json | 10 +++++----- .../java/io/heckel/ntfy/backup/Backuper.kt | 6 +++--- .../main/java/io/heckel/ntfy/db/Database.kt | 20 +++++++++---------- .../main/java/io/heckel/ntfy/db/Repository.kt | 6 +++--- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 2 +- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 18 ++++++++--------- .../java/io/heckel/ntfy/ui/MainActivity.kt | 2 +- .../io/heckel/ntfy/up/BroadcastReceiver.kt | 2 +- app/src/main/res/values-de/strings.xml | 1 - app/src/main/res/values/strings.xml | 4 ++-- app/src/main/res/values/values.xml | 10 +++++----- app/src/main/res/xml/detail_preferences.xml | 10 +++++----- 12 files changed, 45 insertions(+), 46 deletions(-) diff --git a/app/schemas/io.heckel.ntfy.db.Database/19.json b/app/schemas/io.heckel.ntfy.db.Database/19.json index 22d1527e5..22c25d2ad 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/19.json +++ b/app/schemas/io.heckel.ntfy.db.Database/19.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 19, - "identityHash": "e720fb35a4903e41c8452a73c21732e3", + "identityHash": "a4402a11bb9b5c4f1c51d43d6db246ba", "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, `insistent` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, `imageLayout` INTEGER NOT NULL, `linkHandler` TEXT NOT NULL, 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, `dedicatedChannels` INTEGER NOT NULL, `layout` INTEGER NOT NULL, `linkHandler` TEXT NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -88,8 +88,8 @@ "notNull": true }, { - "fieldPath": "imageLayout", - "columnName": "imageLayout", + "fieldPath": "layout", + "columnName": "layout", "affinity": "INTEGER", "notNull": true }, @@ -435,7 +435,7 @@ ], "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, 'e720fb35a4903e41c8452a73c21732e3')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a4402a11bb9b5c4f1c51d43d6db246ba')" ] } } \ 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 de10445d6..4eaa4f43a 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -118,7 +118,7 @@ class Backuper(val context: Context) { upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, displayName = s.displayName, - imageLayout = s.imageLayout ?: Repository.IMAGE_LAYOUT_DEFAULT, + layout = s.layout ?: Repository.LAYOUT_DEFAULT, linkHandler = s.linkHandler ?: Repository.LINK_HANDLER_DEFAULT, ) repository.addSubscription(subscription) @@ -301,7 +301,7 @@ class Backuper(val context: Context) { upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, displayName = s.displayName, - imageLayout = s.imageLayout, + layout = s.layout, linkHandler = s.linkHandler ) } @@ -443,7 +443,7 @@ data class Subscription( val upAppId: String?, val upConnectorToken: String?, val displayName: String?, - val imageLayout: Int?, + val layout: Int?, val linkHandler: 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 3dec4b552..864f83dfb 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -44,7 +44,7 @@ data class Subscription( @ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token @ColumnInfo(name = "displayName") val displayName: String?, @ColumnInfo(name = "dedicatedChannels") val dedicatedChannels: Boolean, - @ColumnInfo(name = "imageLayout") val imageLayout: Int, + @ColumnInfo(name = "layout") val layout: Int, @ColumnInfo(name = "linkHandler") val linkHandler: String, @Ignore val totalCount: Int = 0, // Total notifications @Ignore val newCount: Int = 0, // New notifications @@ -66,7 +66,7 @@ data class Subscription( upConnectorToken: String, displayName: String?, dedicatedChannels: Boolean, - imageLayout: Int, + layout: Int, linkHandler: String, ) : this( @@ -84,7 +84,7 @@ data class Subscription( upConnectorToken, displayName, dedicatedChannels, - imageLayout, + layout, linkHandler, totalCount = 0, newCount = 0, @@ -142,7 +142,7 @@ data class SubscriptionWithMetadata( val upConnectorToken: String?, val displayName: String?, val dedicatedChannels: Boolean, - val imageLayout: Int, + val layout: Int, val linkHandler: String, val totalCount: Int, val newCount: Int, @@ -496,7 +496,7 @@ abstract class Database : RoomDatabase() { private val MIGRATION_18_19 = object : Migration(18, 19) { override fun migrate(db: SupportSQLiteDatabase) { - db.execSQL("ALTER TABLE Subscription ADD COLUMN imageLayout INT NOT NULL DEFAULT (0)") + db.execSQL("ALTER TABLE Subscription ADD COLUMN layout INT NOT NULL DEFAULT (0)") db.execSQL("ALTER TABLE Subscription ADD COLUMN linkHandler TEXT NOT NULL DEFAULT ''") } } @@ -508,7 +508,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -521,7 +521,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -534,7 +534,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -547,7 +547,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -560,7 +560,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.imageLayout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, 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 e2319b37c..c394ac6c8 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -540,7 +540,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) newCount = s.newCount, lastActive = s.lastActive, connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), - imageLayout = s.imageLayout, + layout = s.layout, linkHandler = s.linkHandler ) } @@ -569,7 +569,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) newCount = s.newCount, lastActive = s.lastActive, connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), - imageLayout = s.imageLayout, + layout = s.layout, linkHandler = s.linkHandler ) } @@ -668,7 +668,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val WEBSOCKET_RECONNECT_REMIND_TIME_ALWAYS = 1L const val WEBSOCKET_RECONNECT_REMIND_TIME_NEVER = Long.MAX_VALUE - const val IMAGE_LAYOUT_DEFAULT = 0 + const val LAYOUT_DEFAULT = 0 const val LINK_HANDLER_DEFAULT = "web" 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 0fcc453c1..35c522a44 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -235,7 +235,7 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet totalCount = 0, newCount = 0, lastActive = Date().time/1000, - imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + layout = Repository.LAYOUT_DEFAULT, linkHandler = Repository.LINK_HANDLER_DEFAULT ) repository.addSubscription(subscription) 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 212504073..0a7acdd9b 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -164,7 +164,7 @@ class DetailSettingsActivity : AppCompatActivity() { loadIconRemovePref() loadDedicatedChannelsPrefs() loadOpenChannelsPrefs() - loadImageLayoutPref() + loadLayoutPref() loadLinkHandlerPref() } else { val notificationsHeaderId = context?.getString(R.string.detail_settings_notifications_header_key) ?: return @@ -459,24 +459,24 @@ class DetailSettingsActivity : AppCompatActivity() { } } - private fun loadImageLayoutPref() { - val prefId = context?.getString(R.string.detail_settings_appearance_image_layout_key) ?: return + private fun loadLayoutPref() { + val prefId = context?.getString(R.string.detail_settings_appearance_layout_key) ?: return val pref: ListPreference? = findPreference(prefId) pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously - pref?.value = subscription.imageLayout.toString() + pref?.value = subscription.layout.toString() pref?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { - val imageLayoutValue = value?.toIntOrNull() ?:return - save(subscription.copy(imageLayout = imageLayoutValue)) + val layoutValue = value?.toIntOrNull() ?:return + save(subscription.copy(layout = layoutValue)) } override fun getString(key: String?, defValue: String?): String { - return subscription.imageLayout.toString() + return subscription.layout.toString() } } pref?.summaryProvider = Preference.SummaryProvider { - val layoutEntries = resources.getStringArray(R.array.detail_settings_appearance_image_layout_entries) - layoutEntries[subscription.imageLayout] + val layoutEntries = resources.getStringArray(R.array.detail_settings_appearance_layout_entries) + layoutEntries[subscription.layout] } } 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 c9a522360..b56af7b23 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -679,7 +679,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific totalCount = 0, newCount = 0, lastActive = Date().time/1000, - imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + layout = Repository.LAYOUT_DEFAULT, linkHandler = Repository.LINK_HANDLER_DEFAULT ) viewModel.add(subscription) 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 fbca20f89..f04b6b396 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -93,7 +93,7 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { totalCount = 0, newCount = 0, lastActive = Date().time/1000, - imageLayout = Repository.IMAGE_LAYOUT_DEFAULT, + layout = Repository.LAYOUT_DEFAULT, linkHandler = Repository.LINK_HANDLER_DEFAULT ) Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription") diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c0bd0cd3e..087d54174 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -473,5 +473,4 @@ In Benachrichtigungen suchen Eine gültige Service-URL eingeben, z. B. https://ntfy.example.com Benachrichtigungen suchen - Layout des Bildes diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c4c7ff3cb..637dd0479 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,8 +485,8 @@ Display name Set a custom display name for this subscription. Leave blank for default (%1$s). %1$s (default) - Image layout - Choose between different image layouts + Layout template + Choose between different layouts Link interpreter Choose how the links contained in the text should be interpreted diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index b9b286cb8..b3268c695 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -53,18 +53,18 @@ SubscriptionDisplayName SubscriptionAbout SubscriptionTopicURL - SubscriptionImageLayout - + SubscriptionLayout + Default Layout 1 - - 0 + + 0 1 SubscriptionLinkHandler - web + web phone mail diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index 7cf93cfce..978d7351f 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -65,12 +65,12 @@ app:dialogMessage="@string/detail_settings_appearance_display_name_message" app:isPreferenceVisible="false"/> Date: Fri, 20 Feb 2026 08:03:38 +0100 Subject: [PATCH 5/8] bug fix --- app/src/main/java/io/heckel/ntfy/db/Database.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 864f83dfb..0edd31e49 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -497,7 +497,7 @@ abstract class Database : RoomDatabase() { private val MIGRATION_18_19 = object : Migration(18, 19) { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE Subscription ADD COLUMN layout INT NOT NULL DEFAULT (0)") - db.execSQL("ALTER TABLE Subscription ADD COLUMN linkHandler TEXT NOT NULL DEFAULT ''") + db.execSQL("ALTER TABLE Subscription ADD COLUMN linkHandler TEXT NOT NULL DEFAULT 'web'") } } From b4d8d2b7a46be7f00ebde5792095a2920c9ab0bc Mon Sep 17 00:00:00 2001 From: Scrounger Date: Fri, 20 Feb 2026 12:39:59 +0100 Subject: [PATCH 6/8] activity & fragment implementation --- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 15 +++++++++++++ .../java/io/heckel/ntfy/ui/DetailAdapter.kt | 22 ++++++++++++++++--- .../java/io/heckel/ntfy/ui/MainActivity.kt | 4 ++++ 3 files changed, 38 insertions(+), 3 deletions(-) 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 35c522a44..f4fb0785d 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -83,6 +83,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet private var subscriptionTopic: String = "" // Set in onCreate() private var subscriptionDisplayName: String = "" // Set in onCreate() & updated by options menu! private var subscriptionMutedUntil: Long = 0L // Set in onCreate() & updated by options menu! + private var subscriptionLayout: Int = Repository.LAYOUT_DEFAULT + private var subscriptionLinkHandler: String = Repository.LINK_HANDLER_DEFAULT // UI elements private lateinit var adapter: DetailAdapter @@ -266,6 +268,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription)) intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant) intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil) + intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_LAYOUT, subscription.layout) + intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_LINK_HANDLER, subscription.linkHandler) runOnUiThread { loadView() @@ -287,6 +291,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet subscriptionTopic = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC) ?: return subscriptionDisplayName = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_DISPLAY_NAME) ?: return subscriptionMutedUntil = intent.getLongExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, 0L) + subscriptionLayout = intent.getIntExtra(MainActivity.EXTRA_SUBSCRIPTION_LAYOUT, Repository.LAYOUT_DEFAULT) + subscriptionLinkHandler = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_LINK_HANDLER) ?: return // Set title val subscriptionBaseUrl = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL) ?: return @@ -542,6 +548,15 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet showHideMenuItems() updateTitle(subscriptionDisplayName) + + if(subscriptionLayout != subscription.layout || subscriptionLinkHandler != subscription.linkHandler){ + intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_LAYOUT, subscription.layout) + intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_LINK_HANDLER, subscription.linkHandler) + + runOnUiThread { + loadView() + } + } } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt index dd2187f87..d2cec0394 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailAdapter.kt @@ -50,8 +50,15 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: /* Creates and inflates view and return TopicViewHolder. */ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DetailViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.fragment_detail_item, parent, false) + val layout = activity.intent.getIntExtra(MainActivity.EXTRA_SUBSCRIPTION_LAYOUT, 0) + + val layoutMap = mapOf( + 1 to R.layout.fragment_detail_item_layout_1, + ) + + val layoutId = layoutMap[layout] ?: R.layout.fragment_detail_item + + val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false) return DetailViewHolder(activity, lifecycleScope, repository, markwon, view, selected, onClick, onLongClick) } @@ -120,7 +127,16 @@ class DetailAdapter(private val activity: Activity, private val lifecycleScope: messageView.autoLinkMask = 0 markwon.setMarkdown(messageView, message.toString()) } else { - messageView.autoLinkMask = Linkify.WEB_URLS or Linkify.EMAIL_ADDRESSES or Linkify.PHONE_NUMBERS + val linkHandler = activity.intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_LINK_HANDLER) + + val autoLinkMask = listOf( + Linkify.WEB_URLS to "web", + Linkify.EMAIL_ADDRESSES to "email", + Linkify.PHONE_NUMBERS to "phone" + ).filter { linkHandler?.contains(it.second) == true } + .fold(0) { acc, pair -> acc or pair.first } + + messageView.autoLinkMask = autoLinkMask messageView.text = message } messageView.movementMethod = BetterLinkMovementMethod.getInstance() 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 b56af7b23..fdf50e8b2 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -769,6 +769,8 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific intent.putExtra(EXTRA_SUBSCRIPTION_DISPLAY_NAME, displayName(appBaseUrl, subscription)) intent.putExtra(EXTRA_SUBSCRIPTION_INSTANT, subscription.instant) intent.putExtra(EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil) + intent.putExtra(EXTRA_SUBSCRIPTION_LAYOUT, subscription.layout) + intent.putExtra(EXTRA_SUBSCRIPTION_LINK_HANDLER, subscription.linkHandler) startActivity(intent) } @@ -869,6 +871,8 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific const val EXTRA_SUBSCRIPTION_DISPLAY_NAME = "subscriptionDisplayName" const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant" const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil" + const val EXTRA_SUBSCRIPTION_LAYOUT = "subscriptionLayout" + const val EXTRA_SUBSCRIPTION_LINK_HANDLER = "subscriptionLinkHandler" const val ANIMATION_DURATION = 80L const val ONE_DAY_MILLIS = 86400000L From 1166d11416db9a10254989e16e9f3718cb57af65 Mon Sep 17 00:00:00 2001 From: Scrounger Date: Fri, 20 Feb 2026 13:58:19 +0100 Subject: [PATCH 7/8] german translation --- .../io/heckel/ntfy/ui/DetailSettingsActivity.kt | 11 +++++++++-- .../main/java/io/heckel/ntfy/ui/SettingsActivity.kt | 3 --- app/src/main/res/values-de/strings.xml | 13 +++++++++++++ app/src/main/res/values/strings.xml | 11 ++++++++++- app/src/main/res/values/values.xml | 9 --------- 5 files changed, 32 insertions(+), 15 deletions(-) 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 0a7acdd9b..e163fade0 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -500,8 +500,15 @@ class DetailSettingsActivity : AppCompatActivity() { return cleanedValue.split("|").toSet() } } - pref?.summaryProvider = Preference.SummaryProvider { - subscription.linkHandler + pref?.summaryProvider = Preference.SummaryProvider { preference -> + // Holen der Werte aus den Arrays + val entries = preference.context.resources.getStringArray(R.array.detail_settings_appearance_link_handler_entries) + val values = preference.context.resources.getStringArray(R.array.detail_settings_appearance_link_handler_values) + + // linkHandler-Werte aufsplitten und mit den entsprechenden Einträgen verbinden + subscription.linkHandler.split("|").mapNotNull { value -> + values.indexOf(value).takeIf { it != -1 }?.let { entries[it] } + }.joinToString(", ") ?: "" } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt index 8ebf2f874..6a553ec21 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt @@ -10,9 +10,7 @@ import android.os.Build import android.os.Bundle import android.provider.Settings import android.provider.Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM -import android.text.TextUtils import android.view.View -import android.widget.Button import android.widget.Toast import androidx.activity.enableEdgeToEdge import androidx.activity.result.contract.ActivityResultContracts @@ -42,7 +40,6 @@ import kotlinx.coroutines.launch import okhttp3.RequestBody.Companion.toRequestBody import java.text.SimpleDateFormat import java.util.* -import java.util.concurrent.TimeUnit /** * Main settings diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 087d54174..5223a818e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -309,6 +309,7 @@ Abo-Icon (entfernen durch Antippen) Kann Icon nicht speichern: %1$s Globale Einstellung verwenden + Layout-Vorlage verwendet globale Einstellung Sechs Sofortnachrichten-Themen abonniert Sofortnachrichten @@ -473,4 +474,16 @@ In Benachrichtigungen suchen Eine gültige Service-URL eingeben, z. B. https://ntfy.example.com Benachrichtigungen suchen + Wähle zwischen verschiedenen Layouts + Link-Interpreter + Wähle aus, wie die im Text enthaltenen Links interpretiert werden sollen. + + Links + Telefonnummern + E-Mail Adressen + + + Standard + Layout 1 + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 637dd0479..c2e07939a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -485,10 +485,19 @@ Display name Set a custom display name for this subscription. Leave blank for default (%1$s). %1$s (default) - Layout template + Layout template Choose between different layouts Link interpreter Choose how the links contained in the text should be interpreted + + link + phone number + e-mail address + + + Default + Layout 1 + Use global setting using global setting diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index b3268c695..39682fc41 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -54,20 +54,11 @@ SubscriptionAbout SubscriptionTopicURL SubscriptionLayout - - Default - Layout 1 - 0 1 SubscriptionLinkHandler - - web - phone - mail - web phone From b3335f7913bb03c191394f0550c094b0da3c1b35 Mon Sep 17 00:00:00 2001 From: Scrounger Date: Fri, 20 Feb 2026 17:37:58 +0100 Subject: [PATCH 8/8] option to hide / show notification open / browse button --- .../io.heckel.ntfy.db.Database/19.json | 12 +++++-- .../java/io/heckel/ntfy/backup/Backuper.kt | 5 ++- .../main/java/io/heckel/ntfy/db/Database.kt | 15 ++++++--- .../main/java/io/heckel/ntfy/db/Repository.kt | 8 +++-- .../io/heckel/ntfy/msg/NotificationService.kt | 10 ++++-- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 3 +- .../heckel/ntfy/ui/DetailSettingsActivity.kt | 33 +++++++++++++++++++ .../java/io/heckel/ntfy/ui/MainActivity.kt | 3 +- .../io/heckel/ntfy/up/BroadcastReceiver.kt | 3 +- app/src/main/res/values-de/strings.xml | 7 ++++ app/src/main/res/values/strings.xml | 7 ++++ app/src/main/res/values/values.xml | 14 ++++++-- app/src/main/res/xml/detail_preferences.xml | 15 +++++++++ 13 files changed, 117 insertions(+), 18 deletions(-) diff --git a/app/schemas/io.heckel.ntfy.db.Database/19.json b/app/schemas/io.heckel.ntfy.db.Database/19.json index 22c25d2ad..5e0b8a9f2 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/19.json +++ b/app/schemas/io.heckel.ntfy.db.Database/19.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 19, - "identityHash": "a4402a11bb9b5c4f1c51d43d6db246ba", + "identityHash": "7428d57caf586048d40e1af61c5d65e6", "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, `insistent` INTEGER NOT NULL, `lastNotificationId` TEXT, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, `displayName` TEXT, `dedicatedChannels` INTEGER NOT NULL, `layout` INTEGER NOT NULL, `linkHandler` TEXT NOT NULL, 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, `dedicatedChannels` INTEGER NOT NULL, `layout` INTEGER NOT NULL, `linkHandler` TEXT NOT NULL, `notificationButtons` TEXT NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -98,6 +98,12 @@ "columnName": "linkHandler", "affinity": "TEXT", "notNull": true + }, + { + "fieldPath": "notificationButtons", + "columnName": "notificationButtons", + "affinity": "TEXT", + "notNull": true } ], "primaryKey": { @@ -435,7 +441,7 @@ ], "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, 'a4402a11bb9b5c4f1c51d43d6db246ba')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7428d57caf586048d40e1af61c5d65e6')" ] } } \ 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 4eaa4f43a..ed937d45c 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -120,6 +120,7 @@ class Backuper(val context: Context) { displayName = s.displayName, layout = s.layout ?: Repository.LAYOUT_DEFAULT, linkHandler = s.linkHandler ?: Repository.LINK_HANDLER_DEFAULT, + notificationButtons = s.notificationButtons ?: Repository.NOTIFICATION_BUTTONS_DEFAULT ) repository.addSubscription(subscription) @@ -302,7 +303,8 @@ class Backuper(val context: Context) { upConnectorToken = s.upConnectorToken, displayName = s.displayName, layout = s.layout, - linkHandler = s.linkHandler + linkHandler = s.linkHandler, + notificationButtons = s.notificationButtons ) } } @@ -445,6 +447,7 @@ data class Subscription( val displayName: String?, val layout: Int?, val linkHandler: String?, + val notificationButtons: String?, ) data class Notification( 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 0edd31e49..fbab7d989 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -46,6 +46,7 @@ data class Subscription( @ColumnInfo(name = "dedicatedChannels") val dedicatedChannels: Boolean, @ColumnInfo(name = "layout") val layout: Int, @ColumnInfo(name = "linkHandler") val linkHandler: String, + @ColumnInfo(name = "notificationButtons") val notificationButtons: String, @Ignore val totalCount: Int = 0, // Total notifications @Ignore val newCount: Int = 0, // New notifications @Ignore val lastActive: Long = 0, // Unix timestamp @@ -68,6 +69,7 @@ data class Subscription( dedicatedChannels: Boolean, layout: Int, linkHandler: String, + notificationButtons: String, ) : this( id, @@ -86,6 +88,7 @@ data class Subscription( dedicatedChannels, layout, linkHandler, + notificationButtons, totalCount = 0, newCount = 0, lastActive = 0, @@ -144,6 +147,7 @@ data class SubscriptionWithMetadata( val dedicatedChannels: Boolean, val layout: Int, val linkHandler: String, + val notificationButtons: String, val totalCount: Int, val newCount: Int, val lastActive: Long @@ -498,6 +502,7 @@ abstract class Database : RoomDatabase() { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE Subscription ADD COLUMN layout INT NOT NULL DEFAULT (0)") db.execSQL("ALTER TABLE Subscription ADD COLUMN linkHandler TEXT NOT NULL DEFAULT 'web'") + db.execSQL("ALTER TABLE Subscription ADD COLUMN notificationButtons TEXT NOT NULL DEFAULT 'open|browse'") } } @@ -508,7 +513,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.layout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, s.notificationButtons, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -521,7 +526,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.layout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, s.notificationButtons, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -534,7 +539,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.layout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, s.notificationButtons, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -547,7 +552,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.layout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, s.notificationButtons, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -560,7 +565,7 @@ interface SubscriptionDao { @Query(""" SELECT - 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, s.dedicatedChannels, s.layout, s.linkHandler, + 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, s.dedicatedChannels, s.layout, s.linkHandler, s.notificationButtons, 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 c394ac6c8..62f81a33e 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -541,7 +541,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) lastActive = s.lastActive, connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), layout = s.layout, - linkHandler = s.linkHandler + linkHandler = s.linkHandler, + notificationButtons = s.notificationButtons ) } } @@ -570,7 +571,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) lastActive = s.lastActive, connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails(), layout = s.layout, - linkHandler = s.linkHandler + linkHandler = s.linkHandler, + notificationButtons = s.notificationButtons ) } @@ -672,6 +674,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) const val LINK_HANDLER_DEFAULT = "web" + const val NOTIFICATION_BUTTONS_DEFAULT = "open|browse" + private const val TAG = "NtfyRepository" private var instance: Repository? = null 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 ad489dde5..ebe81dd2a 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -100,8 +100,14 @@ class NotificationService(val context: Context) { maybeSetDeleteIntent(builder, insistent) maybeSetSound(builder, insistent, update) maybeSetProgress(builder, notification) - maybeAddOpenAction(builder, notification) - maybeAddBrowseAction(builder, notification) + + if(subscription.notificationButtons.contains("open")){ + maybeAddOpenAction(builder, notification) + } + if(subscription.notificationButtons.contains("browse")){ + maybeAddBrowseAction(builder, notification) + } + maybeAddDownloadAction(builder, notification) maybeAddCancelAction(builder, notification) maybeAddUserActions(builder, notification) 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 f4fb0785d..78021a0f3 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -238,7 +238,8 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet newCount = 0, lastActive = Date().time/1000, layout = Repository.LAYOUT_DEFAULT, - linkHandler = Repository.LINK_HANDLER_DEFAULT + linkHandler = Repository.LINK_HANDLER_DEFAULT, + notificationButtons = Repository.NOTIFICATION_BUTTONS_DEFAULT ) repository.addSubscription(subscription) 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 e163fade0..c970e5b75 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -166,6 +166,7 @@ class DetailSettingsActivity : AppCompatActivity() { loadOpenChannelsPrefs() loadLayoutPref() loadLinkHandlerPref() + loadNotificationButtonsPref() } else { val notificationsHeaderId = context?.getString(R.string.detail_settings_notifications_header_key) ?: return val notificationsHeader: PreferenceCategory? = findPreference(notificationsHeaderId) @@ -512,6 +513,38 @@ class DetailSettingsActivity : AppCompatActivity() { } } + private fun loadNotificationButtonsPref() { + val prefId = context?.getString(R.string.detail_settings_appearance_notification_buttons_key) ?: return + val pref: MultiSelectListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.values = subscription.notificationButtons.split("|").toSet() + + pref?.preferenceDataStore = object : PreferenceDataStore() { + override fun putStringSet(key: String?, values: Set?) { + val valueString = values?.joinToString("|") ?: return + val cleanedValueString = if (valueString.startsWith("|")) valueString.substring(1) else valueString + + save(subscription.copy(notificationButtons = cleanedValueString)) + } + override fun getStringSet(key: String?, defValues: Set?): Set { + val savedValue = subscription.notificationButtons + val cleanedValue = if (savedValue.startsWith("|")) savedValue.substring(1) else savedValue + + return cleanedValue.split("|").toSet() + } + } + pref?.summaryProvider = Preference.SummaryProvider { preference -> + // Holen der Werte aus den Arrays + val entries = preference.context.resources.getStringArray(R.array.detail_settings_appearance_notification_buttons_entries) + val values = preference.context.resources.getStringArray(R.array.detail_settings_appearance_notification_buttons_values) + + // linkHandler-Werte aufsplitten und mit den entsprechenden Einträgen verbinden + subscription.notificationButtons.split("|").mapNotNull { value -> + values.indexOf(value).takeIf { it != -1 }?.let { entries[it] } + }.joinToString(", ") ?: "" + } + } + private fun createIconPickLauncher(): ActivityResultLauncher { return registerForActivityResult(ActivityResultContracts.GetContent()) { inputUri -> if (inputUri == null) { 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 fdf50e8b2..390068004 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -680,7 +680,8 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific newCount = 0, lastActive = Date().time/1000, layout = Repository.LAYOUT_DEFAULT, - linkHandler = Repository.LINK_HANDLER_DEFAULT + linkHandler = Repository.LINK_HANDLER_DEFAULT, + notificationButtons = Repository.NOTIFICATION_BUTTONS_DEFAULT ) viewModel.add(subscription) 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 f04b6b396..7d7184732 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -94,7 +94,8 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { newCount = 0, lastActive = Date().time/1000, layout = Repository.LAYOUT_DEFAULT, - linkHandler = Repository.LINK_HANDLER_DEFAULT + linkHandler = Repository.LINK_HANDLER_DEFAULT, + notificationButtons = Repository.NOTIFICATION_BUTTONS_DEFAULT ) Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription") try { diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 5223a818e..1baaff12d 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -304,6 +304,7 @@ Sechs Themen abonniert Benachrichtigungen werden über Firebase zugestellt. Die Zustellung kann verzögert werden, aber der Akkuverbrauch ist niedriger. Darstellung + Darstellung Benachrichtigungen Abo-Icon Ein Icon zur Darstellung in Benachrichtigungen auswählen Abo-Icon (entfernen durch Antippen) @@ -486,4 +487,10 @@ Standard Layout 1 + Schaltflächen + Wähle aus, welche Schaltflächen in der Benachrichtigung angezeigt werden sollen. + + Öffnen + Durchsuchen + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c2e07939a..81f0052f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,6 +477,7 @@ Keep alerting Alert only once Appearance + Appearance Notification Subscription icon Set an icon to be displayed in notifications Subscription icon (tap to remove) @@ -498,6 +499,12 @@ Default Layout 1 + Buttons + Choose which buttons should be shown in the notification + + Open + Browse + Use global setting using global setting diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 39682fc41..6be557651 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -60,12 +60,22 @@ SubscriptionLinkHandler - web + web phone mail - web + web + + SubscriptionAppearanceNotification + SubscriptionNotificationButtons + + open + browse + + + open + browse diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index 978d7351f..ddc148d38 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -84,6 +84,21 @@ app:singleLine="false" app:isPreferenceVisible="false"/> + + + +