Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d1034454 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Add min priority and broadcast enabled switch, fix #57

parent 1ce42048
Loading
Loading
Loading
Loading
+28 −1
Original line number Original line Diff line number Diff line
@@ -143,9 +143,34 @@ class Repository(private val sharedPrefs: SharedPreferences, private val subscri
            .apply()
            .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 {
    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) {
    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_POLL_WORKER_VERSION = "PollWorkerVersion"
        const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
        const val SHARED_PREFS_AUTO_RESTART_WORKER_VERSION = "AutoRestartWorkerVersion"
        const val SHARED_PREFS_MUTED_UNTIL_TIMESTAMP = "MutedUntil"
        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_ENABLED = "UnifiedPushEnabled"
        const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL"
        const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL"


+34 −19
Original line number Original line Diff line number Diff line
@@ -13,8 +13,8 @@ import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch


/**
/**
 * The broadcast service is responsible for sending and receiving broadcasted intents
 * The broadcast service is responsible for sending and receiving broadcast intents
 * in order to facilitate taks app integrations.
 * in order to facilitate tasks app integrations.
 */
 */
class BroadcastService(private val ctx: Context) {
class BroadcastService(private val ctx: Context) {
    fun send(subscription: Subscription, notification: Notification, muted: Boolean) {
    fun send(subscription: Subscription, notification: Notification, muted: Boolean) {
@@ -36,6 +36,10 @@ class BroadcastService(private val ctx: Context) {
        ctx.sendBroadcast(intent)
        ctx.sendBroadcast(intent)
    }
    }


    /**
     * This receiver is triggered when the SEND_MESSAGE intent is received.
     * See AndroidManifest.xml for details.
     */
    class BroadcastReceiver : android.content.BroadcastReceiver() {
    class BroadcastReceiver : android.content.BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
        override fun onReceive(context: Context, intent: Intent) {
            Log.d(TAG, "Broadcast received: $intent")
            Log.d(TAG, "Broadcast received: $intent")
@@ -46,13 +50,12 @@ class BroadcastService(private val ctx: Context) {


        private fun send(ctx: Context, intent: Intent) {
        private fun send(ctx: Context, intent: Intent) {
            val api = ApiService()
            val api = ApiService()
            val baseUrl = intent.getStringExtra("base_url") ?: ctx.getString(R.string.app_base_url)
            val baseUrl = getStringExtra(intent, "base_url") ?: ctx.getString(R.string.app_base_url)
            val topic = intent.getStringExtra("topic") ?: return
            val topic = getStringExtra(intent, "topic") ?: return
            val message = intent.getStringExtra("message") ?: return
            val message = getStringExtra(intent, "message") ?: return
            val title = intent.getStringExtra("title") ?: ""
            val title = getStringExtra(intent, "title") ?: ""
            val tags = intent.getStringExtra("tags") ?: ""
            val tags = getStringExtra(intent,"tags") ?: ""
            val priority = if (intent.getStringExtra("priority") != null) {
            val priority = when (getStringExtra(intent, "priority")) {
                when (intent.getStringExtra("priority")) {
                "min", "1" -> 1
                "min", "1" -> 1
                "low", "2" -> 2
                "low", "2" -> 2
                "default", "3" -> 3
                "default", "3" -> 3
@@ -60,10 +63,7 @@ class BroadcastService(private val ctx: Context) {
                "urgent", "max", "5" -> 5
                "urgent", "max", "5" -> 5
                else -> 0
                else -> 0
            }
            }
            } else {
            val delay = getStringExtra(intent,"delay") ?: ""
                intent.getIntExtra("priority", 0)
            }
            val delay = intent.getStringExtra("delay") ?: ""
            GlobalScope.launch(Dispatchers.IO) {
            GlobalScope.launch(Dispatchers.IO) {
                api.publish(
                api.publish(
                    baseUrl = baseUrl,
                    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 {
    companion object {
        private const val TAG = "NtfyBroadcastService"
        private const val TAG = "NtfyBroadcastService"
        private const val MESSAGE_RECEIVED_ACTION = "io.heckel.ntfy.MESSAGE_RECEIVED"
        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 MESSAGE_SEND_ACTION = "io.heckel.ntfy.SEND_MESSAGE" // If changed, change in manifest too!
        private const val DOES_NOT_EXIST = -2586000
    }
    }
}
}
+21 −6
Original line number Original line Diff line number Diff line
@@ -21,10 +21,10 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
    }
    }


    fun dispatch(subscription: Subscription, notification: Notification) {
    fun dispatch(subscription: Subscription, notification: Notification) {
        val muted = checkMuted(subscription)
        val muted = getMuted(subscription)
        val notify = checkNotify(subscription, notification, muted)
        val notify = shouldNotify(subscription, notification, muted)
        val broadcast = subscription.upAppId == null // Never broadcast for UnifiedPush
        val broadcast = shouldBroadcast(subscription)
        val distribute = subscription.upAppId != null // Only distribute for UnifiedPush subscriptions
        val distribute = shouldDistribute(subscription)
        if (notify) {
        if (notify) {
            notifier.send(subscription, notification)
            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) {
        if (subscription.upAppId != null) {
            return false
            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
        val detailsVisible = repository.detailViewSubscriptionId.get() == notification.subscriptionId
        return !detailsVisible && !muted
        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()) {
        if (repository.isGlobalMuted()) {
            return true
            return true
        }
        }
+2 −1
Original line number Original line Diff line number Diff line
@@ -87,7 +87,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
        val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) }
        val onSubscriptionLongClick = { s: Subscription -> onSubscriptionItemLongClick(s) }


        mainList = findViewById(R.id.main_subscriptions_list)
        mainList = findViewById(R.id.main_subscriptions_list)
        adapter = MainAdapter(onSubscriptionClick, onSubscriptionLongClick)
        adapter = MainAdapter(repository, onSubscriptionClick, onSubscriptionLongClick)
        mainList.adapter = adapter
        mainList.adapter = adapter


        viewModel.list().observe(this) {
        viewModel.list().observe(this) {
@@ -261,6 +261,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
        repository.setGlobalMutedUntil(mutedUntilTimestamp)
        repository.setGlobalMutedUntil(mutedUntilTimestamp)
        showHideNotificationMenuItems()
        showHideNotificationMenuItems()
        runOnUiThread {
        runOnUiThread {
            redrawList() // Update the "muted until" icons
            when (mutedUntilTimestamp) {
            when (mutedUntilTimestamp) {
                0L -> Toast.makeText(this@MainActivity, getString(R.string.notification_dialog_enabled_toast_message), Toast.LENGTH_LONG).show()
                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()
                1L -> Toast.makeText(this@MainActivity, getString(R.string.notification_dialog_muted_forever_toast_message), Toast.LENGTH_LONG).show()
+9 −5
Original line number Original line Diff line number Diff line
@@ -10,12 +10,13 @@ import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView
import io.heckel.ntfy.R
import io.heckel.ntfy.R
import io.heckel.ntfy.data.ConnectionState
import io.heckel.ntfy.data.ConnectionState
import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.util.topicShortUrl
import io.heckel.ntfy.util.topicShortUrl
import java.text.DateFormat
import java.text.DateFormat
import java.util.*
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) {
    ListAdapter<Subscription, MainAdapter.SubscriptionViewHolder>(TopicDiffCallback) {
    val selected = mutableSetOf<Long>() // Subscription IDs
    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 {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
        val view = LayoutInflater.from(parent.context)
        val view = LayoutInflater.from(parent.context)
            .inflate(R.layout.fragment_main_item, parent, false)
            .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. */
    /* 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. */
    /* 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) {
        RecyclerView.ViewHolder(itemView) {
        private var subscription: Subscription? = null
        private var subscription: Subscription? = null
        private val context: Context = itemView.context
        private val context: Context = itemView.context
@@ -78,11 +79,14 @@ class MainAdapter(private val onClick: (Subscription) -> Unit, private val onLon
            } else {
            } else {
                dateStr
                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)
            nameView.text = topicShortUrl(subscription.baseUrl, subscription.topic)
            statusView.text = statusMessage
            statusView.text = statusMessage
            dateView.text = dateText
            dateView.text = dateText
            notificationDisabledUntilImageView.visibility = if (subscription.mutedUntil > 1L) View.VISIBLE else View.GONE
            notificationDisabledUntilImageView.visibility = if (showMutedUntilIcon) View.VISIBLE else View.GONE
            notificationDisabledForeverImageView.visibility = if (subscription.mutedUntil == 1L) View.VISIBLE else View.GONE
            notificationDisabledForeverImageView.visibility = if (showMutedForeverIcon) View.VISIBLE else View.GONE
            instantImageView.visibility = if (subscription.instant) View.VISIBLE else View.GONE
            instantImageView.visibility = if (subscription.instant) View.VISIBLE else View.GONE
            if (subscription.upAppId != null || subscription.newCount == 0) {
            if (subscription.upAppId != null || subscription.newCount == 0) {
                newItemsView.visibility = View.GONE
                newItemsView.visibility = View.GONE
Loading