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

Commit 38c82679 authored by Philipp Heckel's avatar Philipp Heckel
Browse files

Status bar, WIP

parent 49b38989
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -14,7 +14,10 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.recyclerview.widget.RecyclerView
import io.heckel.ntfy.add.AddTopicActivity
import io.heckel.ntfy.data.Status
import io.heckel.ntfy.data.Topic
import io.heckel.ntfy.data.topicShortUrl
import io.heckel.ntfy.data.topicUrl
import io.heckel.ntfy.detail.DetailActivity
import kotlin.random.Random

@@ -74,7 +77,7 @@ class MainActivity : AppCompatActivity() {
            intentData?.let { data ->
                val name = data.getStringExtra(TOPIC_NAME) ?: return
                val baseUrl = data.getStringExtra(TOPIC_BASE_URL) ?: return
                val topic = Topic(Random.nextLong(), name, baseUrl)
                val topic = Topic(Random.nextLong(), name, baseUrl, Status.CONNECTING, 0)

                topicsViewModel.add(topic)
            }
@@ -85,7 +88,7 @@ class MainActivity : AppCompatActivity() {
        val channelId = getString(R.string.notification_channel_id)
        val notification = NotificationCompat.Builder(this, channelId)
            .setSmallIcon(R.drawable.ntfy)
            .setContentTitle(n.topic)
            .setContentTitle(topicShortUrl(n.topic))
            .setContentText(n.message)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
            .build()
+22 −10
Original line number Diff line number Diff line
package io.heckel.ntfy

import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -7,7 +8,9 @@ import android.widget.TextView
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import io.heckel.ntfy.data.Status
import io.heckel.ntfy.data.Topic
import io.heckel.ntfy.data.topicUrl

class TopicsAdapter(private val onClick: (Topic) -> Unit) :
    ListAdapter<Topic, TopicsAdapter.TopicViewHolder>(TopicDiffCallback) {
@@ -15,22 +18,32 @@ class TopicsAdapter(private val onClick: (Topic) -> Unit) :
    /* ViewHolder for Topic, takes in the inflated view and the onClick behavior. */
    class TopicViewHolder(itemView: View, val onClick: (Topic) -> Unit) :
        RecyclerView.ViewHolder(itemView) {
        private val topicTextView: TextView = itemView.findViewById(R.id.topic_text)
        private var currentTopic: Topic? = null
        private var topic: Topic? = null
        private val context: Context = itemView.context
        private val nameView: TextView = itemView.findViewById(R.id.topic_text)
        private val statusView: TextView = itemView.findViewById(R.id.topic_status)

        init {
            itemView.setOnClickListener {
                currentTopic?.let {
                topic?.let {
                    onClick(it)
                }
            }
        }

        fun bind(topic: Topic) {
            currentTopic = topic
            val shortBaseUrl = topic.baseUrl.replace("https://", "") // Leave http:// untouched
            val shortName = itemView.context.getString(R.string.topic_short_name_format, shortBaseUrl, topic.name)
            topicTextView.text = shortName
            this.topic = topic
            val statusText = when (topic.status) {
                Status.CONNECTING -> context.getString(R.string.status_connecting)
                else -> context.getString(R.string.status_subscribed)
            }
            val statusMessage = if (topic.messages == 1) {
                context.getString(R.string.status_text_one, statusText, topic.messages)
            } else {
                context.getString(R.string.status_text_not_one, statusText, topic.messages)
            }
            nameView.text = topicUrl(topic)
            statusView.text = statusMessage
        }
    }

@@ -45,16 +58,15 @@ class TopicsAdapter(private val onClick: (Topic) -> Unit) :
    override fun onBindViewHolder(holder: TopicViewHolder, position: Int) {
        val topic = getItem(position)
        holder.bind(topic)

    }
}

object TopicDiffCallback : DiffUtil.ItemCallback<Topic>() {
    override fun areItemsTheSame(oldItem: Topic, newItem: Topic): Boolean {
        return oldItem == newItem
        return oldItem.id == newItem.id
    }

    override fun areContentsTheSame(oldItem: Topic, newItem: Topic): Boolean {
        return oldItem.name == newItem.name
        return oldItem == newItem
    }
}
+1 −1
Original line number Diff line number Diff line
@@ -8,7 +8,7 @@ import io.heckel.ntfy.data.Repository
import io.heckel.ntfy.data.Topic
import kotlin.collections.List

data class Notification(val topic: String, val message: String)
data class Notification(val topic: Topic, val message: String)
typealias NotificationListener = (notification: Notification) -> Unit

class TopicsViewModel(private val repository: Repository) : ViewModel() {
+30 −6
Original line number Diff line number Diff line
@@ -12,14 +12,14 @@ import java.io.IOException
import java.net.HttpURLConnection
import java.net.URL

const val READ_TIMEOUT = 60_000 // Keep alive every 30s assumed

class Repository {
    private val READ_TIMEOUT = 60_000 // Keep alive every 30s assumed
    private val topics: MutableLiveData<List<Topic>> = MutableLiveData(mutableListOf())
    private val jobs = mutableMapOf<Long, Job>()
    private val gson = GsonBuilder().create()
    private var notificationListener: NotificationListener? = null;

    /* Adds topic to liveData and posts value. */
    fun add(topic: Topic, scope: CoroutineScope) {
        val currentList = topics.value
        if (currentList == null) {
@@ -32,7 +32,24 @@ class Repository {
        jobs[topic.id] = subscribeTopic(topic, scope)
    }

    /* Removes topic from liveData and posts value. */
    fun update(topic: Topic) {
        val currentList = topics.value
        if (currentList == null) {
            topics.postValue(listOf(topic))
        } else {
            val index = currentList.indexOfFirst { it.id == topic.id } // Find index by Topic ID
            if (index == -1) {
                return // TODO race?
            } else {
                val updatedList = currentList.toMutableList()
                updatedList[index] = topic
                println("PHIL updated list:")
                println(updatedList)
                topics.postValue(updatedList)
            }
        }
    }

    fun remove(topic: Topic) {
        val currentList = topics.value
        if (currentList != null) {
@@ -40,10 +57,9 @@ class Repository {
            updatedList.remove(topic)
            topics.postValue(updatedList)
        }
        jobs.remove(topic.id)?.cancel() // Cancel and remove
        jobs.remove(topic.id)?.cancel() // Cancel coroutine and remove
    }

    /* Returns topic given an ID. */
    fun get(id: Long): Topic? {
        topics.value?.let { topics ->
            return topics.firstOrNull{ it.id == id}
@@ -75,6 +91,7 @@ class Repository {
            it.doInput = true
            it.readTimeout = READ_TIMEOUT
        }
        update(topic.copy(status = Status.SUBSCRIBED))
        try {
            val input = conn.inputStream.bufferedReader()
            while (scope.isActive) {
@@ -86,7 +103,13 @@ class Repository {
                    val json = gson.fromJson(line, JsonObject::class.java) ?: break // Break on unexpected line
                    if (!json.isJsonNull && json.has("message")) {
                        val message = json.get("message").asString
                        notificationListener?.let { it(Notification(topic.name, message)) }
                        notificationListener?.let { it(Notification(topic, message)) }

                        // TODO ugly
                        val currentTopic = get(topic.id)
                        if (currentTopic != null) {
                            update(currentTopic.copy(messages = currentTopic.messages+1))
                        }
                    }
                } catch (e: JsonSyntaxException) {
                    break // Break on unexpected line
@@ -97,6 +120,7 @@ class Repository {
        } finally {
            conn.disconnect()
        }
        update(topic.copy(status = Status.CONNECTING))
        println("Connection terminated: $url")
    }

+10 −1
Original line number Diff line number Diff line
package io.heckel.ntfy.data

enum class Status {
    SUBSCRIBED, CONNECTING
}

data class Topic(
    val id: Long, // Internal to Repository only
    val id: Long, // Internal ID, only used in Repository and activities
    val name: String,
    val baseUrl: String,
    val status: Status,
    val messages: Int
)

fun topicUrl(t: Topic) = "${t.baseUrl}/${t.name}"
fun topicShortUrl(t: Topic) = topicUrl(t).replace("http://", "").replace("https://", "")
Loading