Add min priority and broadcast enabled switch, fix #57

This commit is contained in:
Philipp Heckel 2022-01-01 16:56:18 +01:00
parent 1ce42048b5
commit d10344549f
10 changed files with 210 additions and 51 deletions

View file

@ -143,9 +143,34 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
.apply()
}
fun setMinPriority(minPriority: Int) {
if (minPriority <= 1) {
sharedPrefs.edit()
.remove(SHARED_PREFS_MIN_PRIORITY)
.apply()
} else {
sharedPrefs.edit()
.putInt(SHARED_PREFS_MIN_PRIORITY, minPriority)
.apply()
}
}
fun getMinPriority(): Int {
return sharedPrefs.getInt(SHARED_PREFS_MIN_PRIORITY, 1) // 1/low means all priorities
}
fun getBroadcastEnabled(): Boolean {
return sharedPrefs.getBoolean(SHARED_PREFS_BROADCAST_ENABLED, true) // Enabled by default
}
fun setBroadcastEnabled(enabled: Boolean) {
sharedPrefs.edit()
.putBoolean(SHARED_PREFS_BROADCAST_ENABLED, enabled)
.apply()
}
fun getUnifiedPushEnabled(): Boolean {
return sharedPrefs.getBoolean(SHARED_PREFS_UNIFIED_PUSH_ENABLED, true) // Enabled by default!
return sharedPrefs.getBoolean(SHARED_PREFS_UNIFIED_PUSH_ENABLED, true) // Enabled by default
}
fun setUnifiedPushEnabled(enabled: Boolean) {
@ -263,6 +288,8 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
const val SHARED_PREFS_POLL_WORKER_VERSION = "PollWorkerVersion"
const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil"
const val SHARED_PREFS_MIN_PRIORITY = "MinPriority"
const val SHARED_PREFS_BROADCAST_ENABLED = "BroadcastEnabled"
const val SHARED_PREFS_UNIFIED_PUSH_ENABLED = "UnifiedPushEnabled"
const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL"

View file

@ -13,8 +13,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
/**
* The broadcast service is responsible for sending and receiving broadcasted intents
* in order to facilitate taks app integrations.
* The broadcast service is responsible for sending and receiving broadcast intents
* in order to facilitate tasks app integrations.
*/
class BroadcastService(private val ctx: Context) {
fun send(subscription: Subscription, notification: Notification, muted: Boolean) {
@ -36,6 +36,10 @@ class BroadcastService(private val ctx: Context) {
ctx.sendBroadcast(intent)
}
/**
* This receiver is triggered when the SEND_MESSAGE intent is received.
* See AndroidManifest.xml for details.
*/
class BroadcastReceiver : android.content.BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
Log.d(TAG, "Broadcast received: $intent")
@ -46,24 +50,20 @@ class BroadcastService(private val ctx: Context) {
private fun send(ctx: Context, intent: Intent) {
val api = ApiService()
val baseUrl = intent.getStringExtra("base_url") ?: ctx.getString(R.string.app_base_url)
val topic = intent.getStringExtra("topic") ?: return
val message = intent.getStringExtra("message") ?: return
val title = intent.getStringExtra("title") ?: ""
val tags = intent.getStringExtra("tags") ?: ""
val priority = if (intent.getStringExtra("priority") != null) {
when (intent.getStringExtra("priority")) {
"min", "1" -> 1
"low", "2" -> 2
"default", "3" -> 3
"high", "4" -> 4
"urgent", "max", "5" -> 5
else -> 0
}
} else {
intent.getIntExtra("priority", 0)
val baseUrl = getStringExtra(intent, "base_url") ?: ctx.getString(R.string.app_base_url)
val topic = getStringExtra(intent, "topic") ?: return
val message = getStringExtra(intent, "message") ?: return
val title = getStringExtra(intent, "title") ?: ""
val tags = getStringExtra(intent,"tags") ?: ""
val priority = when (getStringExtra(intent, "priority")) {
"min", "1" -> 1
"low", "2" -> 2
"default", "3" -> 3
"high", "4" -> 4
"urgent", "max", "5" -> 5
else -> 0
}
val delay = intent.getStringExtra("delay") ?: ""
val delay = getStringExtra(intent,"delay") ?: ""
GlobalScope.launch(Dispatchers.IO) {
api.publish(
baseUrl = baseUrl,
@ -76,11 +76,26 @@ class BroadcastService(private val ctx: Context) {
)
}
}
/**
* Gets an extra as a String value, even if the extra may be an int or a long.
*/
private fun getStringExtra(intent: Intent, name: String): String? {
if (intent.getStringExtra(name) != null) {
return intent.getStringExtra(name)
} else if (intent.getIntExtra(name, DOES_NOT_EXIST) != DOES_NOT_EXIST) {
return intent.getIntExtra(name, DOES_NOT_EXIST).toString()
} else if (intent.getLongExtra(name, DOES_NOT_EXIST.toLong()) != DOES_NOT_EXIST.toLong()) {
return intent.getLongExtra(name, DOES_NOT_EXIST.toLong()).toString()
}
return null
}
}
companion object {
private const val TAG = "NtfyBroadcastService"
private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
private const val MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" // If changed, change in manifest too!
private const val DOES_NOT_EXIST = -2586000
}
}

View file

@ -21,10 +21,10 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
}
fun dispatch(subscription: Subscription, notification: Notification) {
val muted = checkMuted(subscription)
val notify = checkNotify(subscription, notification, muted)
val broadcast = subscription.upAppId == null // Never broadcast for UnifiedPush
val distribute = subscription.upAppId != null // Only distribute for UnifiedPush subscriptions
val muted = getMuted(subscription)
val notify = shouldNotify(subscription, notification, muted)
val broadcast = shouldBroadcast(subscription)
val distribute = shouldDistribute(subscription)
if (notify) {
notifier.send(subscription, notification)
}
@ -38,15 +38,30 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
}
}
private fun checkNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {
private fun shouldNotify(subscription: Subscription, notification: Notification, muted: Boolean): Boolean {
if (subscription.upAppId != null) {
return false
}
val priority = if (notification.priority > 0) notification.priority else 3
if (priority < repository.getMinPriority()) {
return false
}
val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId
return !detailsVisible && !muted
}
private fun checkMuted(subscription: Subscription): Boolean {
private fun shouldBroadcast(subscription: Subscription): Boolean {
if (subscription.upAppId != null) { // Never broadcast for UnifiedPush subscriptions
return false
}
return repository.getBroadcastEnabled()
}
private fun shouldDistribute(subscription: Subscription): Boolean {
return subscription.upAppId != null // Only distribute for UnifiedPush subscriptions
}
private fun getMuted(subscription: Subscription): Boolean {
if (repository.isGlobalMuted()) {
return true
}

View file

@ -87,7 +87,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) }
mainList = findViewById(R.id.main_subscriptions_list)
adapter = MainAdapter(onSubscriptionClick, onSubscriptionLongClick)
adapter = MainAdapter(repository, onSubscriptionClick, onSubscriptionLongClick)
mainList.adapter = adapter
viewModel.list().observe(this) {
@ -261,6 +261,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
repository.setGlobalMutedUntil(mutedUntilTimestamp)
showHideNotificationMenuItems()
runOnUiThread {
redrawList() // Update the "muted until" icons
when (mutedUntilTimestamp) {
0L -> Toast.makeText(this@MainActivity, getString(R.string.notification_dialog_enabled_toast_message), Toast.LENGTH_LONG).show()
1L -> Toast.makeText(this@MainActivity, getString(R.string.notification_dialog_muted_forever_toast_message), Toast.LENGTH_LONG).show()

View file

@ -10,12 +10,13 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.heckel.ntfy.R
import io.heckel.ntfy.data.ConnectionState
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.util.topicShortUrl
import java.text.DateFormat
import java.util.*
class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
class MainAdapter(private val repository: Repository, private val onClick: (Subscription) -> Unit, private val onLongClick: (Subscription) -> Unit) :
ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
val selected = mutableSetOf<Long>() // Subscription IDs
@ -23,7 +24,7 @@ class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLon
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_main_item, parent, false)
return SubscriptionViewHolder(view, selected, onClick, onLongClick)
return SubscriptionViewHolder(view, repository, selected, onClick, onLongClick)
}
/* Gets current topic and uses it to bind view. */
@ -41,7 +42,7 @@ class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLon
}
/* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
class SubscriptionViewHolder(itemView: View, private val selected: Set<Long>, val onClick: (Subscription) -> Unit, val onLongClick: (Subscription) -> Unit) :
class SubscriptionViewHolder(itemView: View, private val repository: Repository, private val selected: Set<Long>, val onClick: (Subscription) -> Unit, val onLongClick: (Subscription) -> Unit) :
RecyclerView.ViewHolder(itemView) {
private var subscription: Subscription? = null
private val context: Context = itemView.context
@ -78,11 +79,14 @@ class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLon
} else {
dateStr
}
val globalMutedUntil = repository.getGlobalMutedUntil()
val showMutedForeverIcon = (subscription.mutedUntil == 1L || globalMutedUntil == 1L) && subscription.upAppId == null
val showMutedUntilIcon = !showMutedForeverIcon && (subscription.mutedUntil > 1L || globalMutedUntil > 1L) && subscription.upAppId == null
nameView.text = topicShortUrl(subscription.baseUrl, subscription.topic)
statusView.text = statusMessage
dateView.text = dateText
notificationDisabledUntilImageView.visibility = if (subscription.mutedUntil > 1L) View.VISIBLE else View.GONE
notificationDisabledForeverImageView.visibility = if (subscription.mutedUntil == 1L) View.VISIBLE else View.GONE
notificationDisabledUntilImageView.visibility = if (showMutedUntilIcon) View.VISIBLE else View.GONE
notificationDisabledForeverImageView.visibility = if (showMutedForeverIcon) View.VISIBLE else View.GONE
instantImageView.visibility = if (subscription.instant) View.VISIBLE else View.GONE
if (subscription.upAppId != null || subscription.newCount == 0) {
newItemsView.visibility = View.GONE

View file

@ -16,6 +16,7 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.util.formatDateShort
import io.heckel.ntfy.util.toPriorityString
class SettingsActivity : AppCompatActivity() {
private val repository by lazy { (application as Application).repository }
@ -49,7 +50,7 @@ class SettingsActivity : AppCompatActivity() {
// everybody has access to the repository.
// Notifications muted until (global)
val mutedUntilPrefId = context?.getString(R.string.pref_notifications_muted_until) ?: return
val mutedUntilPrefId = context?.getString(R.string.settings_notifications_muted_until_key) ?: return
val mutedUntilSummary = { s: Long ->
when (s) {
0L -> getString(R.string.settings_notifications_muted_until_enabled)
@ -80,8 +81,53 @@ class SettingsActivity : AppCompatActivity() {
true
}
// UnifiedPush Enabled
val upEnabledPrefId = context?.getString(R.string.pref_unified_push_enabled) ?: return
// Minimum priority
val minPriorityPrefId = context?.getString(R.string.settings_notifications_min_priority_key) ?: return
val minPriority: ListPreference? = findPreference(minPriorityPrefId)
minPriority?.value = repository.getMinPriority().toString()
minPriority?.preferenceDataStore = object : PreferenceDataStore() {
override fun putString(key: String?, value: String?) {
val minPriorityValue = value?.toIntOrNull() ?:return
repository.setMinPriority(minPriorityValue)
}
override fun getString(key: String?, defValue: String?): String {
return repository.getMinPriority().toString()
}
}
minPriority?.summaryProvider = Preference.SummaryProvider<ListPreference> { pref ->
val minPriorityValue = pref.value.toIntOrNull() ?: 1 // 1/low means all priorities
when (minPriorityValue) {
1 -> getString(R.string.settings_notifications_min_priority_summary_any)
5 -> getString(R.string.settings_notifications_min_priority_summary_max)
else -> {
val minPriorityString = toPriorityString(minPriorityValue)
getString(R.string.settings_notifications_min_priority_summary_x_or_higher, minPriorityValue, minPriorityString)
}
}
}
// Broadcast enabled
val broadcastEnabledPrefId = context?.getString(R.string.settings_advanced_broadcast_key) ?: return
val broadcastEnabled: SwitchPreference? = findPreference(broadcastEnabledPrefId)
broadcastEnabled?.isChecked = repository.getBroadcastEnabled()
broadcastEnabled?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
repository.setBroadcastEnabled(value)
}
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
return repository.getBroadcastEnabled()
}
}
broadcastEnabled?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { pref ->
if (pref.isChecked) {
getString(R.string.settings_advanced_broadcast_summary_enabled)
} else {
getString(R.string.settings_advanced_broadcast_summary_disabled)
}
}
// UnifiedPush enabled
val upEnabledPrefId = context?.getString(R.string.settings_unified_push_enabled_key) ?: return
val upEnabled: SwitchPreference? = findPreference(upEnabledPrefId)
upEnabled?.isChecked = repository.getUnifiedPushEnabled()
upEnabled?.preferenceDataStore = object : PreferenceDataStore() {
@ -102,7 +148,7 @@ class SettingsActivity : AppCompatActivity() {
// UnifiedPush Base URL
val appBaseUrl = context?.getString(R.string.app_base_url) ?: return
val upBaseUrlPrefId = context?.getString(R.string.pref_unified_push_base_url) ?: return
val upBaseUrlPrefId = context?.getString(R.string.settings_unified_push_base_url_key) ?: return
val upBaseUrl: EditTextPreference? = findPreference(upBaseUrlPrefId)
upBaseUrl?.text = repository.getUnifiedPushBaseUrl() ?: ""
upBaseUrl?.preferenceDataStore = object : PreferenceDataStore() {
@ -123,7 +169,7 @@ class SettingsActivity : AppCompatActivity() {
}
// Version
val versionPrefId = context?.getString(R.string.pref_version) ?: return
val versionPrefId = context?.getString(R.string.settings_about_version_key) ?: return
val versionPref: Preference? = findPreference(versionPrefId)
val version = getString(R.string.settings_about_version_format, BuildConfig.VERSION_NAME, BuildConfig.FLAVOR)
versionPref?.summary = version

View file

@ -28,6 +28,17 @@ fun toPriority(priority: Int?): Int {
else return 3
}
fun toPriorityString(priority: Int): String {
return when (priority) {
1 -> "min"
2 -> "low"
3 -> "default"
4 -> "high"
5 -> "max"
else -> "default"
}
}
fun joinTags(tags: List<String>?): String {
return tags?.joinToString(",") ?: ""
}

View file

@ -32,7 +32,7 @@
<string name="main_menu_notifications_disabled_until">Notifications disabled until %1$s</string>
<string name="main_menu_settings_title">Settings</string>
<string name="main_menu_source_title">Report a bug</string>
<string name="main_menu_source_url">https://heckel.io/ntfy-android</string>
<string name="main_menu_source_url">https://github.com/binwiederhier/ntfy/issues</string>
<string name="main_menu_website_title">Visit ntfy.sh</string>
<!-- Main activity: Action mode -->
@ -147,26 +147,38 @@
<!-- Settings -->
<string name="settings_title">Settings</string>
<string name="settings_notifications_header">Notifications</string>
<string name="settings_notifications_header_summary">General settings for all subscribed topics</string>
<string name="settings_notifications_muted_until_key">MutedUntil</string>
<string name="settings_notifications_muted_until_title">Pause notifications</string>
<string name="settings_notifications_muted_until_enabled">All notifications will be displayed</string>
<string name="settings_notifications_muted_until_disabled_forever">Notifications muted until re-enabled</string>
<string name="settings_notifications_muted_until_disabled_until">Notifications muted until %1$s</string>
<string name="settings_notifications_min_priority_key">MinPriority</string>
<string name="settings_notifications_min_priority_title">Minimum priority</string>
<string name="settings_notifications_min_priority_summary_any">Notifications of all priorities are shown</string>
<string name="settings_notifications_min_priority_summary_x_or_higher">Show notifications if priority is %1$d (%2$s) or higher</string>
<string name="settings_notifications_min_priority_summary_max">Show notifications if priority is 5 (max)</string>
<string name="settings_notifications_min_priority_min">Any priority</string>
<string name="settings_notifications_min_priority_low">Low priority and higher</string>
<string name="settings_notifications_min_priority_default">Default priority and higher</string>
<string name="settings_notifications_min_priority_high">High priority and higher</string>
<string name="settings_notifications_min_priority_max">Only max priority</string>
<string name="settings_unified_push_header">UnifiedPush</string>
<string name="settings_unified_push_header_summary">Allows other apps to use ntfy as a message distributor. Find out more at unifiedpush.org.</string>
<string name="settings_unified_push_enabled_key">UnifiedPushEnabled</string>
<string name="settings_unified_push_enabled_title">Enable distributor</string>
<string name="settings_unified_push_enabled_summary_on">Apps can use ntfy as distributor</string>
<string name="settings_unified_push_enabled_summary_off">Apps cannot use ntfy as distributor</string>
<string name="settings_unified_push_base_url_key">UnifiedPushBaseURL</string>
<string name="settings_unified_push_base_url_title">Server URL</string>
<string name="settings_unified_push_base_url_default_summary">%1$s (default)</string>
<string name="settings_advanced_header">Advanced</string>
<string name="settings_advanced_broadcast_key">BroadcastEnabled</string>
<string name="settings_advanced_broadcast_title">Broadcast messages</string>
<string name="settings_advanced_broadcast_summary_enabled">Apps can receive incoming notifications as broadcasts</string>
<string name="settings_advanced_broadcast_summary_disabled">Apps cannot receive notifications as broadcasts</string>
<string name="settings_about_header">About</string>
<string name="settings_about_version_key">Version</string>
<string name="settings_about_version_title">Version</string>
<string name="settings_about_version_format">ntfy %1$s (%2$s)</string>
<string name="settings_about_version_copied_to_clipboard_message">Copied to clipboard</string>
<!-- Preferences IDs -->
<string name="pref_notifications_muted_until">MutedUntil</string>
<string name="pref_unified_push_enabled">UnifiedPushEnabled</string>
<string name="pref_unified_push_base_url">UnifiedPushBaseURL</string>
<string name="pref_version">Version</string>
</resources>

View file

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="settings_notifications_min_priority_entries">
<item>@string/settings_notifications_min_priority_min</item>
<item>@string/settings_notifications_min_priority_low</item>
<item>@string/settings_notifications_min_priority_default</item>
<item>@string/settings_notifications_min_priority_high</item>
<item>@string/settings_notifications_min_priority_max</item>
</string-array>
<string-array name="settings_notifications_min_priority_values">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
</string-array>
</resources>

View file

@ -3,28 +3,39 @@
app:title="@string/settings_title">
<PreferenceCategory
app:title="@string/settings_notifications_header"
app:summary="@string/settings_notifications_header_summary"
app:layout="@layout/preference_category_material_edited">
<Preference
app:key="@string/pref_notifications_muted_until"
app:key="@string/settings_notifications_muted_until_key"
app:title="@string/settings_notifications_muted_until_title"/>
<ListPreference
app:key="@string/settings_notifications_min_priority_key"
app:title="@string/settings_notifications_min_priority_title"
app:entries="@array/settings_notifications_min_priority_entries"
app:entryValues="@array/settings_notifications_min_priority_values"
app:defaultValue="1"/>
</PreferenceCategory>
<PreferenceCategory
app:title="@string/settings_unified_push_header"
app:summary="@string/settings_unified_push_header_summary"
app:layout="@layout/preference_category_material_edited">
<SwitchPreference
app:key="@string/pref_unified_push_enabled"
app:key="@string/settings_unified_push_enabled_key"
app:title="@string/settings_unified_push_enabled_title"
app:enabled="true"/>
<EditTextPreference
app:key="@string/pref_unified_push_base_url"
app:key="@string/settings_unified_push_base_url_key"
app:title="@string/settings_unified_push_base_url_title"
app:dependency="@string/pref_unified_push_enabled"/>
app:dependency="@string/settings_unified_push_enabled_key"/>
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_advanced_header">
<SwitchPreference
app:key="@string/settings_advanced_broadcast_key"
app:title="@string/settings_advanced_broadcast_title"
app:enabled="true"/>
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_about_header">
<Preference
app:key="@string/pref_version"
app:key="@string/settings_about_version_key"
app:title="@string/settings_about_version_title"/>
</PreferenceCategory>
</PreferenceScreen>