diff --git a/app/schemas/io.heckel.ntfy.db.Database/11.json b/app/schemas/io.heckel.ntfy.db.Database/11.json index b46d05c..b54336c 100644 --- a/app/schemas/io.heckel.ntfy.db.Database/11.json +++ b/app/schemas/io.heckel.ntfy.db.Database/11.json @@ -2,11 +2,11 @@ "formatVersion": 1, "database": { "version": 11, - "identityHash": "9a26b356f0d51f2c63fd3e4570b7e645", + "identityHash": "31f8e6a2032d1d404fad4307abf23e1b", "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`))", + "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, `icon` TEXT, `upAppId` TEXT, `upConnectorToken` TEXT, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -50,6 +50,12 @@ "affinity": "INTEGER", "notNull": true }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "upAppId", "columnName": "upAppId", @@ -308,7 +314,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, '9a26b356f0d51f2c63fd3e4570b7e645')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '31f8e6a2032d1d404fad4307abf23e1b')" ] } } \ 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 a0c5377..d49e972 100644 --- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt +++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt @@ -97,6 +97,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, + icon = s.icon, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken )) @@ -219,6 +220,7 @@ class Backuper(val context: Context) { mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + icon = s.icon, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken ) @@ -324,6 +326,7 @@ data class Subscription( val mutedUntil: Long, val minPriority: Int?, val autoDelete: Long?, + val icon: String?, val upAppId: String?, val upConnectorToken: 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 0eb7f87..7d64166 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -18,6 +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 = "icon") val icon: String?, // content://-URI (or later other identifier) @ColumnInfo(name = "upAppId") val upAppId: String?, // UnifiedPush application package name @ColumnInfo(name = "upConnectorToken") val upConnectorToken: String?, // UnifiedPush connector token @Ignore val totalCount: Int = 0, // Total notifications @@ -25,8 +26,8 @@ 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, upAppId: String, upConnectorToken: String) : - this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE) + constructor(id: Long, baseUrl: String, topic: String, instant: Boolean, mutedUntil: Long, minPriority: Int, autoDelete: Long, icon: String, upAppId: String, upConnectorToken: String) : + this(id, baseUrl, topic, instant, mutedUntil, minPriority, autoDelete, icon, upAppId, upConnectorToken, 0, 0, 0, ConnectionState.NOT_APPLICABLE) } enum class ConnectionState { @@ -41,6 +42,7 @@ data class SubscriptionWithMetadata( val mutedUntil: Long, val autoDelete: Long, val minPriority: Int, + val icon: String?, val upAppId: String?, val upConnectorToken: String?, val totalCount: Int, @@ -254,6 +256,7 @@ abstract class Database : RoomDatabase() { override fun migrate(db: SupportSQLiteDatabase) { db.execSQL("ALTER TABLE Subscription ADD COLUMN minPriority INT NOT NULL DEFAULT (0)") // = Repository.MIN_PRIORITY_USE_GLOBAL db.execSQL("ALTER TABLE Subscription ADD COLUMN autoDelete INT NOT NULL DEFAULT (-1)") // = Repository.AUTO_DELETE_USE_GLOBAL + db.execSQL("ALTER TABLE Subscription ADD COLUMN icon TEXT") } } } @@ -263,7 +266,7 @@ abstract class Database : RoomDatabase() { interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -276,7 +279,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -289,7 +292,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -302,7 +305,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken, COUNT(n.id) totalCount, COUNT(CASE n.notificationId WHEN 0 THEN NULL ELSE n.id END) newCount, IFNULL(MAX(n.timestamp),0) AS lastActive @@ -315,7 +318,7 @@ interface SubscriptionDao { @Query(""" SELECT - s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.upAppId, s.upConnectorToken, + s.id, s.baseUrl, s.topic, s.instant, s.mutedUntil, s.minPriority, s.autoDelete, s.icon, s.upAppId, s.upConnectorToken, 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 9f8b257..5786699 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -379,6 +379,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + icon = s.icon, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, totalCount = s.totalCount, @@ -401,6 +402,7 @@ class Repository(private val sharedPrefs: SharedPreferences, private val databas mutedUntil = s.mutedUntil, minPriority = s.minPriority, autoDelete = s.autoDelete, + icon = s.icon, upAppId = s.upAppId, upConnectorToken = s.upConnectorToken, totalCount = s.totalCount, 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 868c3a7..f614b1d 100644 --- a/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt +++ b/app/src/main/java/io/heckel/ntfy/msg/NotificationService.kt @@ -72,6 +72,7 @@ class NotificationService(val context: Context) { .setAutoCancel(true) // Cancel when notification is clicked setStyleAndText(builder, notification) // Preview picture or big text style setClickAction(builder, subscription, notification) + maybeSetIcon(builder, subscription) maybeSetSound(builder, update) maybeSetProgress(builder, notification) maybeAddOpenAction(builder, notification) @@ -84,6 +85,18 @@ class NotificationService(val context: Context) { notificationManager.notify(notification.notificationId, builder.build()) } + private fun maybeSetIcon(builder: NotificationCompat.Builder, subscription: Subscription) { + val icon = subscription.icon ?: return + try { + val resolver = context.applicationContext.contentResolver + val bitmapStream = resolver.openInputStream(Uri.parse(icon)) + val bitmap = BitmapFactory.decodeStream(bitmapStream) + builder.setLargeIcon(bitmap) + } catch (e: Exception) { + Log.w(TAG, "Cannot load subscription icon", e) + } + } + private fun maybeSetSound(builder: NotificationCompat.Builder, update: Boolean) { if (!update) { val defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_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 7b3f606..214d614 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -112,6 +112,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + icon = null, upAppId = null, upConnectorToken = null, totalCount = 0, 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 7499f1c..c3e0134 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailSettingsActivity.kt @@ -1,16 +1,25 @@ package io.heckel.ntfy.ui +import android.net.Uri import android.os.Bundle +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity +import androidx.core.content.FileProvider import androidx.lifecycle.lifecycleScope import androidx.preference.* import io.heckel.ntfy.BuildConfig import io.heckel.ntfy.R +import io.heckel.ntfy.db.Notification import io.heckel.ntfy.db.Repository import io.heckel.ntfy.db.Subscription +import io.heckel.ntfy.msg.DownloadWorker import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.* import kotlinx.coroutines.* +import okio.source +import java.io.File +import java.io.IOException import java.util.* /** @@ -61,6 +70,7 @@ class DetailSettingsActivity : AppCompatActivity() { private lateinit var repository: Repository private lateinit var serviceManager: SubscriberServiceManager private lateinit var subscription: Subscription + private lateinit var pickIconLauncher: ActivityResultLauncher override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.detail_preferences, rootKey) @@ -69,6 +79,9 @@ class DetailSettingsActivity : AppCompatActivity() { repository = Repository.getInstance(requireActivity()) serviceManager = SubscriberServiceManager(requireActivity()) + // Create result launcher for custom icon (must be created in onCreatePreferences() directly) + pickIconLauncher = createCustomIconPickLauncher() + // Load subscription and users val subscriptionId = arguments?.getLong(DetailActivity.EXTRA_SUBSCRIPTION_ID) ?: return runBlocking { @@ -82,13 +95,20 @@ class DetailSettingsActivity : AppCompatActivity() { } private fun loadView() { - // Instant delivery + loadInstantPref() + loadMutedUntilPref() + loadMinPriorityPref() + loadAutoDeletePref() + loadCustomIconsPref() + } + + private fun loadInstantPref() { val appBaseUrl = getString(R.string.app_base_url) - val instantEnabledPrefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return - val instantEnabled: SwitchPreference? = findPreference(instantEnabledPrefId) - instantEnabled?.isVisible = BuildConfig.FIREBASE_AVAILABLE && subscription.baseUrl == appBaseUrl - instantEnabled?.isChecked = subscription.instant - instantEnabled?.preferenceDataStore = object : PreferenceDataStore() { + val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return + val pref: SwitchPreference? = findPreference(prefId) + pref?.isVisible = BuildConfig.FIREBASE_AVAILABLE && subscription.baseUrl == appBaseUrl + pref?.isChecked = subscription.instant + pref?.preferenceDataStore = object : PreferenceDataStore() { override fun putBoolean(key: String?, value: Boolean) { save(subscription.copy(instant = value), refresh = true) } @@ -96,20 +116,21 @@ class DetailSettingsActivity : AppCompatActivity() { return subscription.instant } } - instantEnabled?.summaryProvider = Preference.SummaryProvider { pref -> - if (pref.isChecked) { + pref?.summaryProvider = Preference.SummaryProvider { preference -> + if (preference.isChecked) { getString(R.string.detail_settings_notifications_instant_summary_on) } else { getString(R.string.detail_settings_notifications_instant_summary_off) } } + } - // Notifications muted until - val mutedUntilPrefId = context?.getString(R.string.detail_settings_notifications_muted_until_key) ?: return - val mutedUntil: ListPreference? = findPreference(mutedUntilPrefId) - mutedUntil?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously - mutedUntil?.value = subscription.mutedUntil.toString() - mutedUntil?.preferenceDataStore = object : PreferenceDataStore() { + private fun loadMutedUntilPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_muted_until_key) ?: return + val pref: ListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.value = subscription.mutedUntil.toString() + pref?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { val mutedUntilValue = value?.toLongOrNull() ?:return when (mutedUntilValue) { @@ -134,7 +155,7 @@ class DetailSettingsActivity : AppCompatActivity() { return subscription.mutedUntil.toString() } } - mutedUntil?.summaryProvider = Preference.SummaryProvider { _ -> + pref?.summaryProvider = Preference.SummaryProvider { _ -> val mutedUntilValue = subscription.mutedUntil when (mutedUntilValue) { Repository.MUTED_UNTIL_SHOW_ALL -> getString(R.string.settings_notifications_muted_until_show_all) @@ -145,13 +166,14 @@ class DetailSettingsActivity : AppCompatActivity() { } } } + } - // Minimum priority - val minPriorityPrefId = context?.getString(R.string.detail_settings_notifications_min_priority_key) ?: return - val minPriority: ListPreference? = findPreference(minPriorityPrefId) - minPriority?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously - minPriority?.value = subscription.minPriority.toString() - minPriority?.preferenceDataStore = object : PreferenceDataStore() { + private fun loadMinPriorityPref() { + val prefId = context?.getString(R.string.detail_settings_notifications_min_priority_key) ?: return + val pref: ListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.value = subscription.minPriority.toString() + pref?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { val minPriorityValue = value?.toIntOrNull() ?:return save(subscription.copy(minPriority = minPriorityValue)) @@ -160,8 +182,8 @@ class DetailSettingsActivity : AppCompatActivity() { return subscription.minPriority.toString() } } - minPriority?.summaryProvider = Preference.SummaryProvider { pref -> - var value = pref.value.toIntOrNull() ?: Repository.MIN_PRIORITY_USE_GLOBAL + pref?.summaryProvider = Preference.SummaryProvider { preference -> + var value = preference.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() @@ -176,13 +198,14 @@ class DetailSettingsActivity : AppCompatActivity() { } maybeAppendGlobal(summary, global) } + } - // Auto delete - val autoDeletePrefId = context?.getString(R.string.detail_settings_notifications_auto_delete_key) ?: return - val autoDelete: ListPreference? = findPreference(autoDeletePrefId) - autoDelete?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously - autoDelete?.value = subscription.autoDelete.toString() - autoDelete?.preferenceDataStore = object : PreferenceDataStore() { + private fun loadAutoDeletePref() { + val prefId = context?.getString(R.string.detail_settings_notifications_auto_delete_key) ?: return + val pref: ListPreference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.value = subscription.autoDelete.toString() + pref?.preferenceDataStore = object : PreferenceDataStore() { override fun putString(key: String?, value: String?) { val seconds = value?.toLongOrNull() ?:return save(subscription.copy(autoDelete = seconds)) @@ -191,8 +214,8 @@ class DetailSettingsActivity : AppCompatActivity() { return subscription.autoDelete.toString() } } - autoDelete?.summaryProvider = Preference.SummaryProvider { pref -> - var seconds = pref.value.toLongOrNull() ?: Repository.AUTO_DELETE_USE_GLOBAL + 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) { seconds = repository.getAutoDeleteSeconds() @@ -210,6 +233,50 @@ class DetailSettingsActivity : AppCompatActivity() { } } + private fun loadCustomIconsPref() { + val prefId = context?.getString(R.string.detail_settings_general_icon_key) ?: return + val pref: Preference? = findPreference(prefId) + pref?.isVisible = true // Hack: Show all settings at once, because subscription is loaded asynchronously + pref?.preferenceDataStore = object : PreferenceDataStore() { } // Dummy store to protect from accidentally overwriting + pref?.onPreferenceClickListener = Preference.OnPreferenceClickListener { _ -> + pickIconLauncher.launch("image/*") + false + } + } + + private fun createCustomIconPickLauncher(): ActivityResultLauncher { + return registerForActivityResult(ActivityResultContracts.GetContent()) { inputUri -> + if (inputUri == null) { + return@registerForActivityResult + } + lifecycleScope.launch(Dispatchers.IO) { + try { + val resolver = requireContext().applicationContext.contentResolver + val inputStream = resolver.openInputStream(inputUri) ?: throw IOException("Couldn't open content URI for reading") + val outputUri = createUri() + val outputStream = resolver.openOutputStream(outputUri) ?: throw IOException("Couldn't open content URI for writing") + inputStream.copyTo(outputStream) + save(subscription.copy(icon = outputUri.toString())) + } catch (e: Exception) { + Log.w(TAG, "Saving icon failed", e) + requireActivity().runOnUiThread { + // FIXME + } + } + } + } + } + + + private fun createUri(): Uri { + val dir = File(requireContext().cacheDir, SUBSCRIPTION_ICONS) + if (!dir.exists() && !dir.mkdirs()) { + throw Exception("Cannot create cache directory for attachments: $dir") + } + val file = File(dir, subscription.id.toString()) + return FileProvider.getUriForFile(requireContext(), DownloadWorker.FILE_PROVIDER_AUTHORITY, file) + } + private fun save(newSubscription: Subscription, refresh: Boolean = false) { subscription = newSubscription lifecycleScope.launch(Dispatchers.IO) { @@ -231,5 +298,6 @@ class DetailSettingsActivity : AppCompatActivity() { companion object { private const val TAG = "NtfyDetailSettingsActiv" + private const val SUBSCRIPTION_ICONS = "subscriptionIcons" } } 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 05fdb29..cd77ed6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -427,6 +427,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + icon = null, upAppId = null, upConnectorToken = null, totalCount = 0, 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 2271d48..9cc3255 100644 --- a/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt +++ b/app/src/main/java/io/heckel/ntfy/up/BroadcastReceiver.kt @@ -78,6 +78,7 @@ class BroadcastReceiver : android.content.BroadcastReceiver() { mutedUntil = 0, minPriority = Repository.MIN_PRIORITY_USE_GLOBAL, autoDelete = Repository.AUTO_DELETE_USE_GLOBAL, + icon = null, upAppId = appId, upConnectorToken = connectorToken, totalCount = 0, diff --git a/app/src/main/java/io/heckel/ntfy/util/Util.kt b/app/src/main/java/io/heckel/ntfy/util/Util.kt index 9ab0ac0..5f39c0e 100644 --- a/app/src/main/java/io/heckel/ntfy/util/Util.kt +++ b/app/src/main/java/io/heckel/ntfy/util/Util.kt @@ -35,9 +35,7 @@ import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.RequestBody import okio.BufferedSink import okio.source -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException +import java.io.* import java.security.SecureRandom import java.text.DateFormat import java.text.StringCharacterIterator diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d608aa1..898db0b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -339,6 +339,7 @@ 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. + Custom icon Use global setting global diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml index 05cb39b..37663ed 100644 --- a/app/src/main/res/values/values.xml +++ b/app/src/main/res/values/values.xml @@ -35,6 +35,7 @@ SubscriptionMutedUntil SubscriptionMinPriority SubscriptionAutoDelete + SubscriptionIcon diff --git a/app/src/main/res/xml/detail_preferences.xml b/app/src/main/res/xml/detail_preferences.xml index e63185f..2b29b97 100644 --- a/app/src/main/res/xml/detail_preferences.xml +++ b/app/src/main/res/xml/detail_preferences.xml @@ -27,4 +27,10 @@ app:defaultValue="-1" app:isPreferenceVisible="false"/> + + +