Switch to /json endpoint

This commit is contained in:
Philipp Heckel 2021-10-26 13:46:49 -04:00
parent 0c26703c78
commit 3751366c19
7 changed files with 55 additions and 115 deletions

View file

@ -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"

View file

@ -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) {
}

View file

@ -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<Long, Job>()
private val newTopicActivityRequestCode = 1
private val topicsListViewModel by viewModels<TopicsListViewModel> {
@ -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)
}
}
}

View file

@ -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<List<Topic>> = dataSource.getTopicList()

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2020 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>

View file

@ -13,10 +13,9 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"

View file

@ -12,7 +12,6 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="80dp"
android:orientation="vertical">