WIP: Icon
This commit is contained in:
parent
3d5b723072
commit
2909d877f7
13 changed files with 148 additions and 44 deletions
|
@ -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')"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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?
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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<String>
|
||||
|
||||
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<SwitchPreference> { pref ->
|
||||
if (pref.isChecked) {
|
||||
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { 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<ListPreference> { _ ->
|
||||
pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { _ ->
|
||||
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<ListPreference> { pref ->
|
||||
var value = pref.value.toIntOrNull() ?: Repository.MIN_PRIORITY_USE_GLOBAL
|
||||
pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { 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<ListPreference> { pref ->
|
||||
var seconds = pref.value.toLongOrNull() ?: Repository.AUTO_DELETE_USE_GLOBAL
|
||||
pref?.summaryProvider = Preference.SummaryProvider<ListPreference> { 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<String> {
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -339,6 +339,7 @@
|
|||
<string name="detail_settings_notifications_instant_title">Instant delivery</string>
|
||||
<string name="detail_settings_notifications_instant_summary_on">Notifications are delivered instantly. Requires a foreground service and consumes more battery.</string>
|
||||
<string name="detail_settings_notifications_instant_summary_off">Notifications are delivered using Firebase. Delivery may be delayed, but consumes less battery.</string>
|
||||
<string name="detail_settings_general_icon_title">Custom icon</string>
|
||||
<string name="detail_settings_global_setting_title">Use global setting</string>
|
||||
<string name="detail_settings_global_setting_suffix">global</string>
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@
|
|||
<string name="detail_settings_notifications_muted_until_key" translatable="false">SubscriptionMutedUntil</string>
|
||||
<string name="detail_settings_notifications_min_priority_key" translatable="false">SubscriptionMinPriority</string>
|
||||
<string name="detail_settings_notifications_auto_delete_key" translatable="false">SubscriptionAutoDelete</string>
|
||||
<string name="detail_settings_general_icon_key" translatable="false">SubscriptionIcon</string>
|
||||
|
||||
<!-- Main settings -->
|
||||
<string-array name="settings_notifications_muted_until_entries">
|
||||
|
|
|
@ -27,4 +27,10 @@
|
|||
app:defaultValue="-1"
|
||||
app:isPreferenceVisible="false"/> <!-- Same as Repository.AUTO_DELETE_USE_GLOBAL -->
|
||||
</PreferenceCategory>
|
||||
<PreferenceCategory app:title="@string/settings_general_header">
|
||||
<Preference
|
||||
app:key="@string/detail_settings_general_icon_key"
|
||||
app:title="@string/detail_settings_general_icon_title"
|
||||
app:isPreferenceVisible="false"/>
|
||||
</PreferenceCategory>
|
||||
</PreferenceScreen>
|
||||
|
|
Loading…
Reference in a new issue