diff --git a/app/build.gradle b/app/build.gradle index c11c7b6..accde94 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -22,7 +22,7 @@ android { compileSdkVersion 30 defaultConfig { - applicationId "com.example.recyclersample" + applicationId "io.heckel.ntfy" minSdkVersion 21 targetSdkVersion 30 versionCode 1 @@ -45,7 +45,6 @@ android { kotlinOptions { jvmTarget = JavaVersion.VERSION_1_8.toString() } - } dependencies { @@ -54,6 +53,7 @@ dependencies { implementation "androidx.core:core-ktx:$rootProject.coreKtxVersion" implementation "androidx.constraintlayout:constraintlayout:$rootProject.constraintLayoutVersion" implementation "androidx.activity:activity-ktx:$rootProject.activityVersion" + implementation 'com.google.code.gson:gson:2.8.8' // RecyclerView implementation "androidx.recyclerview:recyclerview:$rootProject.recyclerViewVersion" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9a0a2b2..d837ed9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ --> + package="io.heckel.ntfy"> 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 cd47e8e..6b00538 100644 --- a/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/add/AddTopicActivity.kt @@ -21,7 +21,7 @@ import android.content.Intent import android.os.Bundle import android.widget.Button import androidx.appcompat.app.AppCompatActivity -import com.heckel.ntfy.R +import io.heckel.ntfy.R import com.google.android.material.textfield.TextInputEditText 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 new file mode 100644 index 0000000..a0f9108 --- /dev/null +++ b/app/src/main/java/io/heckel/ntfy/data/NtfyApi.kt @@ -0,0 +1,64 @@ +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 + +class NtfyApi(context: Context) { + private val gson = GsonBuilder().create() + + private suspend fun getStreamConnection(url: String): HttpURLConnection = + withContext(Dispatchers.IO) { + return@withContext (URL(url).openConnection() as HttpURLConnection).also { + it.setRequestProperty("Accept", "text/event-stream") + it.doInput = true + } + } + + data class Event(val name: String = "", val data: JsonObject = JsonObject()) + + fun getEventsFlow(): Flow = flow { + coroutineScope { + val conn = getStreamConnection("https://ntfy.sh/_phil") + val input = conn.inputStream.bufferedReader() + try { + conn.connect() + var event = Event() + while (isActive) { + val line = input.readLine() + println("PHIL: " + line) + when { + 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() -> { + emit(event) + event = Event() + } + } + } + } catch (e: IOException) { + println("PHIL: " + e.message) + this.cancel(CancellationException("Network Problem", e)) + } finally { + conn.disconnect() + input.close() + } + } + } +} diff --git a/app/src/main/java/io/heckel/ntfy/detail/TopicDetailActivity.kt b/app/src/main/java/io/heckel/ntfy/detail/TopicDetailActivity.kt index 31f6a01..122fff3 100644 --- a/app/src/main/java/io/heckel/ntfy/detail/TopicDetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/detail/TopicDetailActivity.kt @@ -21,7 +21,7 @@ import android.widget.Button import android.widget.TextView import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity -import com.heckel.ntfy.R +import io.heckel.ntfy.R import io.heckel.ntfy.list.TOPIC_ID class TopicDetailActivity : AppCompatActivity() { diff --git a/app/src/main/java/io/heckel/ntfy/list/TopicsAdapter.kt b/app/src/main/java/io/heckel/ntfy/list/TopicsAdapter.kt index 1a3d304..ab0658a 100644 --- a/app/src/main/java/io/heckel/ntfy/list/TopicsAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/list/TopicsAdapter.kt @@ -19,12 +19,11 @@ package io.heckel.ntfy.list import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView import android.widget.TextView import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView -import com.heckel.ntfy.R +import io.heckel.ntfy.R import io.heckel.ntfy.data.Topic class TopicsAdapter(private val onClick: (Topic) -> Unit) : 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 d3390bd..3234bfe 100644 --- a/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/list/TopicsListActivity.kt @@ -22,16 +22,24 @@ import android.os.Bundle import android.view.View import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.lifecycle.asLiveData +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.RecyclerView -import com.heckel.ntfy.R +import io.heckel.ntfy.R import io.heckel.ntfy.add.AddTopicActivity import io.heckel.ntfy.add.TOPIC_URL +import io.heckel.ntfy.data.NtfyApi import io.heckel.ntfy.data.Topic import io.heckel.ntfy.detail.TopicDetailActivity +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext const val TOPIC_ID = "topic id" class TopicsListActivity : AppCompatActivity() { + private val api = NtfyApi(this) private val newTopicActivityRequestCode = 1 private val topicsListViewModel by viewModels { TopicsListViewModelFactory(this) @@ -55,6 +63,19 @@ class TopicsListActivity : AppCompatActivity() { fab.setOnClickListener { fabOnClick() } + + val self = this + api.getEventsFlow().asLiveData(Dispatchers.IO).observe(this, Observer { event -> + // Get the Activity's lifecycleScope and launch + this.lifecycleScope.launch(Dispatchers.Main) { + // run the code again in IO context + withContext(Dispatchers.IO) { + println(event.data) + //Toast.makeText(self, event.data, Toast.LENGTH_SHORT) + } + } + } + ) } /* Opens TopicDetailActivity when RecyclerView item is clicked. */