diff --git a/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt b/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt index 6b00538..6fe39c4 100644 --- a/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt @@ -21,8 +21,8 @@ import android.content.Intent import android.os.Bundle import android.widget.Button import androidx.appcompat.app.AppCompatActivity -import io.heckel.ntfy.R import com.google.android.material.textfield.TextInputEditText +import io.heckel.ntfy.R const val TOPIC_URL = "url" diff --git a/app/src/main/java/io/heckel/ntfy/data/NtfyApi.kt b/app/src/main/java/io/heckel/ntfy/data/NtfyApi.kt deleted file mode 100644 index 39f5c95..0000000 --- a/app/src/main/java/io/heckel/ntfy/data/NtfyApi.kt +++ /dev/null @@ -1,18 +0,0 @@ -package io.heckel.ntfy.data - -import android.content.Context -import com.google.gson.GsonBuilder -import com.google.gson.JsonObject -import com.google.gson.JsonSyntaxException -import kotlinx.coroutines.* -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import java.io.IOException -import java.net.HttpURLConnection -import java.net.URL - -data class Event(val name: String = "", val data: JsonObject = JsonObject()) - -class NtfyApi(context: Context) { - -} diff --git a/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt b/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt index cd270a3..4118d65 100644 --- a/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt @@ -36,11 +36,10 @@ import com.google.gson.JsonSyntaxException import io.heckel.ntfy.R import io.heckel.ntfy.add.AddTopicActivity import io.heckel.ntfy.add.TOPIC_URL -import io.heckel.ntfy.data.Event -import io.heckel.ntfy.data.NtfyApi import io.heckel.ntfy.data.Topic import io.heckel.ntfy.detail.TopicDetailActivity import kotlinx.coroutines.* +import java.io.BufferedReader import java.io.IOException import java.net.HttpURLConnection import java.net.URL @@ -49,7 +48,7 @@ import kotlin.random.Random const val TOPIC_ID = "topic id" class TopicsListActivity : AppCompatActivity() { - private val api = NtfyApi(this) + private val gson = GsonBuilder().create() private val jobs = mutableMapOf() private val newTopicActivityRequestCode = 1 private val topicsListViewModel by viewModels { @@ -101,12 +100,60 @@ class TopicsListActivity : AppCompatActivity() { val topicUrl = data.getStringExtra(TOPIC_URL) ?: return val topic = Topic(topicId, topicUrl) - jobs[topicId] = startListening(topicUrl) + jobs[topicId] = subscribeTopic(topicUrl) topicsListViewModel.add(topic) } } } + private fun subscribeTopic(url: String): Job { + return this.lifecycleScope.launch(Dispatchers.IO) { + while (isActive) { + openURL(this, url) + delay(5000) // TODO exponential back-off + } + } + } + + private fun openURL(scope: CoroutineScope, url: String) { + println("Connecting to $url ...") + val conn = (URL(url).openConnection() as HttpURLConnection).also { + it.doInput = true + } + try { + val input = conn.inputStream.bufferedReader() + while (scope.isActive) { + val line = input.readLine() ?: break // Break if null! + try { + displayNotification(gson.fromJson(line, JsonObject::class.java)) + } catch (e: JsonSyntaxException) { + // Ignore invalid JSON + } + } + } catch (e: IOException) { + println("PHIL: " + e.message) + } finally { + conn.disconnect() + } + println("Connection terminated: $url") + } + + private fun displayNotification(json: JsonObject) { + if (json.isJsonNull || !json.has("message")) { + return + } + val channelId = getString(R.string.notification_channel_id) + val notification = NotificationCompat.Builder(this, channelId) + .setSmallIcon(R.drawable.ntfy) + .setContentTitle("ntfy") + .setContentText(json.get("message").asString) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .build() + with(NotificationManagerCompat.from(this)) { + notify(Random.nextInt(), notification) + } + } + private fun createNotificationChannel() { // Create the NotificationChannel, but only on API 26+ because // the NotificationChannel class is new and not in the support library @@ -124,71 +171,4 @@ class TopicsListActivity : AppCompatActivity() { notificationManager.createNotificationChannel(channel) } } - - private val gson = GsonBuilder().create() - - fun startListening(url: String): Job { - return this.lifecycleScope.launch(Dispatchers.IO) { - while (isActive) { - println("connecting ...") - val conn = (URL(url).openConnection() as HttpURLConnection).also { - it.doInput = true - } - val input = conn.inputStream.bufferedReader() - try { - conn.connect() - var event = Event() - while (isActive) { - val line = input.readLine() - println("PHIL: " + line) - when { - line == null -> { - println("line is null") - break - } - line.startsWith("event:") -> { - event = event.copy(name = line.substring(6).trim()) - } - line.startsWith("data:") -> { - val data = line.substring(5).trim() - try { - event = event.copy(data = gson.fromJson(data, JsonObject::class.java)) - } catch (e: JsonSyntaxException) { - // Nothing - } - } - line.isEmpty() -> { - handleEvent(event) - event = Event() - } - } - } - } catch (e: IOException) { - println("PHIL: " + e.message) - } finally { - conn.disconnect() - input.close() - } - println("connection died") - delay(5000) - } - } - } - - private fun handleEvent(event: Event) { - if (event.data.isJsonNull || !event.data.has("message")) { - return - } - println("PHIL EVENT: " + event.data) - val channelId = getString(R.string.notification_channel_id) - val notification = NotificationCompat.Builder(this, channelId) - .setSmallIcon(R.drawable.ntfy) - .setContentTitle("ntfy") - .setContentText(event.data.get("message").asString) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .build() - with(NotificationManagerCompat.from(this)) { - notify(Random.nextInt(), notification) - } - } } diff --git a/app/src/main/java/io/heckel/ntfy/list/TopicsListViewModel.kt b/app/src/main/java/io/heckel/ntfy/list/TopicsListViewModel.kt index d8b13e4..8f291fc 100644 --- a/app/src/main/java/io/heckel/ntfy/list/TopicsListViewModel.kt +++ b/app/src/main/java/io/heckel/ntfy/list/TopicsListViewModel.kt @@ -22,7 +22,6 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.heckel.ntfy.data.DataSource import io.heckel.ntfy.data.Topic -import kotlin.random.Random class TopicsListViewModel(val dataSource: DataSource) : ViewModel() { val topics: LiveData> = dataSource.getTopicList() diff --git a/app/src/main/res/layout/header_item.xml b/app/src/main/res/layout/header_item.xml deleted file mode 100644 index 1dd470e..0000000 --- a/app/src/main/res/layout/header_item.xml +++ /dev/null @@ -1,19 +0,0 @@ - - - - diff --git a/app/src/main/res/layout/topic_detail_activity.xml b/app/src/main/res/layout/topic_detail_activity.xml index 302a31b..29d8eba 100644 --- a/app/src/main/res/layout/topic_detail_activity.xml +++ b/app/src/main/res/layout/topic_detail_activity.xml @@ -13,10 +13,9 @@ --> + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical">