Test notification

This commit is contained in:
Philipp Heckel 2021-11-01 09:57:05 -04:00
parent 7d561a5068
commit fd1cfafdbf
8 changed files with 72 additions and 17 deletions

View file

@ -41,11 +41,14 @@ dependencies {
implementation "androidx.activity:activity-ktx:$rootProject.activityVersion" implementation "androidx.activity:activity-ktx:$rootProject.activityVersion"
implementation 'com.google.code.gson:gson:2.8.8' implementation 'com.google.code.gson:gson:2.8.8'
// Room // Room (SQLite)
def roomVersion = "2.3.0" def roomVersion = "2.3.0"
implementation "androidx.room:room-ktx:$roomVersion" implementation "androidx.room:room-ktx:$roomVersion"
kapt "androidx.room:room-compiler:$roomVersion" kapt "androidx.room:room-compiler:$roomVersion"
// Volley (HTTP library)
implementation 'com.android.volley:volley:1.2.1'
// Firebase, sigh ... // Firebase, sigh ...
implementation 'com.google.firebase:firebase-messaging:22.0.0' implementation 'com.google.firebase:firebase-messaging:22.0.0'

View file

@ -1,6 +1,7 @@
package io.heckel.ntfy.data package io.heckel.ntfy.data
fun topicUrl(baseUrl: String, topic: String) = "${baseUrl}/${topic}"
fun topicShortUrl(baseUrl: String, topic: String) = fun topicShortUrl(baseUrl: String, topic: String) =
"${baseUrl}/${topic}" topicUrl(baseUrl, topic)
.replace("http://", "") .replace("http://", "")
.replace("https://", "") .replace("https://", "")

View file

@ -3,22 +3,36 @@ package io.heckel.ntfy.ui
import android.app.AlertDialog import android.app.AlertDialog
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.Toast
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.android.volley.Request
import com.android.volley.toolbox.StringRequest
import com.android.volley.toolbox.Volley
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.data.Notification import io.heckel.ntfy.data.Notification
import io.heckel.ntfy.data.topicShortUrl import io.heckel.ntfy.data.topicShortUrl
import io.heckel.ntfy.data.topicUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.util.*
class DetailActivity : AppCompatActivity() { class DetailActivity : AppCompatActivity() {
private val viewModel by viewModels<DetailViewModel> { private val viewModel by viewModels<DetailViewModel> {
DetailViewModelFactory((application as Application).repository) DetailViewModelFactory((application as Application).repository)
} }
private var subscriptionId: Long = 0L // Set in onCreate() private var subscriptionId: Long = 0L // Set in onCreate()
private var subscriptionBaseUrl: String = "" // Set in onCreate()
private var subscriptionTopic: String = "" // Set in onCreate() private var subscriptionTopic: String = "" // Set in onCreate()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -28,6 +42,7 @@ class DetailActivity : AppCompatActivity() {
// Get extras required for the return to the main activity // Get extras required for the return to the main activity
subscriptionId = intent.getLongExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, 0) subscriptionId = intent.getLongExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, 0)
subscriptionBaseUrl = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL) ?: return
subscriptionTopic = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC) ?: return subscriptionTopic = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC) ?: return
// Set title // Set title
@ -61,6 +76,10 @@ class DetailActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.detail_menu_test -> {
onTestClick()
true
}
R.id.detail_menu_delete -> { R.id.detail_menu_delete -> {
onDeleteClick() onDeleteClick()
true true
@ -69,7 +88,31 @@ class DetailActivity : AppCompatActivity() {
} }
} }
private fun onTestClick() {
val url = topicUrl(subscriptionBaseUrl, subscriptionTopic)
Log.d(TAG, "Sending test notification to $url")
val queue = Volley.newRequestQueue(this) // This should be a Singleton :-O
val stringRequest = object : StringRequest(
Method.PUT,
url,
{ _ -> /* Do nothing */ },
{ error ->
Toast
.makeText(this, getString(R.string.detail_test_message_error, error.message), Toast.LENGTH_LONG)
.show()
}
) {
override fun getBody(): ByteArray {
return getString(R.string.detail_test_message, Date().toString()).toByteArray()
}
}
queue.add(stringRequest)
}
private fun onDeleteClick() { private fun onDeleteClick() {
Log.d(TAG, "Deleting subscription ${topicShortUrl(subscriptionBaseUrl, subscriptionTopic)}")
val builder = AlertDialog.Builder(this) val builder = AlertDialog.Builder(this)
builder builder
.setMessage(R.string.detail_delete_dialog_message) .setMessage(R.string.detail_delete_dialog_message)
@ -90,6 +133,10 @@ class DetailActivity : AppCompatActivity() {
} }
private fun onNotificationClick(notification: Notification) { private fun onNotificationClick(notification: Notification) {
println("clicked " + notification.id) // TODO Do something
}
companion object {
const val TAG = "NtfyDetailActivity"
} }
} }

View file

@ -3,20 +3,18 @@ package io.heckel.ntfy.ui
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.messaging.FirebaseMessaging import com.google.firebase.messaging.FirebaseMessaging
import io.heckel.ntfy.R import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application import io.heckel.ntfy.app.Application
import io.heckel.ntfy.data.Subscription import io.heckel.ntfy.data.Subscription
import io.heckel.ntfy.data.topicShortUrl import io.heckel.ntfy.data.topicShortUrl
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.* import java.util.*
import kotlin.random.Random import kotlin.random.Random
@ -29,6 +27,8 @@ class MainActivity : AppCompatActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity) setContentView(R.layout.main_activity)
// TODO implement multi-select delete - https://enoent.fr/posts/recyclerview-basics/
// Action bar // Action bar
title = getString(R.string.main_action_bar_title) title = getString(R.string.main_action_bar_title)
@ -65,11 +65,11 @@ class MainActivity : AppCompatActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.menu_action_source -> { R.id.main_menu_source -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_source_url)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_source_url))))
true true
} }
R.id.detail_menu_delete -> { R.id.main_menu_website -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.app_base_url)))) startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.app_base_url))))
true true
} }
@ -83,12 +83,16 @@ class MainActivity : AppCompatActivity() {
} }
private fun onSubscribe(topic: String, baseUrl: String) { private fun onSubscribe(topic: String, baseUrl: String) {
Log.d(TAG, "Adding subscription ${topicShortUrl(baseUrl, topic)}")
val subscription = Subscription(id = Random.nextLong(), baseUrl = baseUrl, topic = topic, notifications = 0, lastActive = Date().time/1000) val subscription = Subscription(id = Random.nextLong(), baseUrl = baseUrl, topic = topic, notifications = 0, lastActive = Date().time/1000)
viewModel.add(subscription) viewModel.add(subscription)
FirebaseMessaging.getInstance().subscribeToTopic(topic) // FIXME ignores baseUrl FirebaseMessaging.getInstance().subscribeToTopic(topic) // FIXME ignores baseUrl
} }
private fun onSubscriptionItemClick(subscription: Subscription) { private fun onSubscriptionItemClick(subscription: Subscription) {
Log.d(TAG, "Entering detail view for subscription $subscription")
val intent = Intent(this, DetailActivity::class.java) val intent = Intent(this, DetailActivity::class.java)
intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscription.id) intent.putExtra(EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl) intent.putExtra(EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
@ -100,6 +104,8 @@ class MainActivity : AppCompatActivity() {
if (requestCode == REQUEST_CODE_DELETE_SUBSCRIPTION && resultCode == RESULT_OK) { if (requestCode == REQUEST_CODE_DELETE_SUBSCRIPTION && resultCode == RESULT_OK) {
val subscriptionId = data?.getLongExtra(EXTRA_SUBSCRIPTION_ID, 0) val subscriptionId = data?.getLongExtra(EXTRA_SUBSCRIPTION_ID, 0)
val subscriptionTopic = data?.getStringExtra(EXTRA_SUBSCRIPTION_TOPIC) val subscriptionTopic = data?.getStringExtra(EXTRA_SUBSCRIPTION_TOPIC)
Log.d(TAG, "Deleting subscription with subscription ID $subscriptionId (topic: $subscriptionTopic)")
subscriptionId?.let { id -> viewModel.remove(id) } subscriptionId?.let { id -> viewModel.remove(id) }
subscriptionTopic?.let { topic -> FirebaseMessaging.getInstance().unsubscribeFromTopic(topic) } // FIXME This only works for ntfy.sh subscriptionTopic?.let { topic -> FirebaseMessaging.getInstance().unsubscribeFromTopic(topic) } // FIXME This only works for ntfy.sh
} else { } else {

View file

@ -1,3 +1,4 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:title="@string/detail_menu_delete" android:id="@+id/detail_menu_delete"/> <item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
<item android:id="@+id/detail_menu_delete" android:title="@string/detail_menu_delete"/>
</menu> </menu>

View file

@ -1,5 +1,4 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/menu_action_source" <item android:id="@+id/main_menu_source" android:title="@string/main_menu_source_title"/>
android:title="@string/main_menu_source_title"/> <item android:id="@+id/main_menu_website" android:title="@string/main_menu_website_title"/>
<item android:title="@string/main_menu_website_title" android:id="@+id/detail_menu_delete"/>
</menu> </menu>

View file

@ -1,4 +0,0 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:id="@+id/main_item_popup_unsubscribe"
android:title="@string/main_item_popup_unsubscribe"/>
</menu>

View file

@ -18,7 +18,6 @@
<string name="main_item_status_reconnecting">reconnecting …</string> <string name="main_item_status_reconnecting">reconnecting …</string>
<string name="main_item_status_text_one">%1$d notification received</string> <string name="main_item_status_text_one">%1$d notification received</string>
<string name="main_item_status_text_not_one">%1$d notifications received</string> <string name="main_item_status_text_not_one">%1$d notifications received</string>
<string name="main_item_popup_unsubscribe">Unsubscribe</string>
<string name="main_add_button_description">Add subscription</string> <string name="main_add_button_description">Add subscription</string>
<string name="main_no_subscriptions_text">It looks like you don\'t have any subscriptions yet.</string> <string name="main_no_subscriptions_text">It looks like you don\'t have any subscriptions yet.</string>
@ -34,7 +33,10 @@
<string name="detail_delete_dialog_message">Do you really want to permanently delete this subscription and all its messages?</string> <string name="detail_delete_dialog_message">Do you really want to permanently delete this subscription and all its messages?</string>
<string name="detail_delete_dialog_permanently_delete">Permanently delete</string> <string name="detail_delete_dialog_permanently_delete">Permanently delete</string>
<string name="detail_delete_dialog_cancel">Cancel</string> <string name="detail_delete_dialog_cancel">Cancel</string>
<string name="detail_test_message">This is a test notification from the Ntfy Android app. It was sent at %1$s.</string>
<string name="detail_test_message_error">Could not send test message: %1$s</string>
<!-- Detail activity: Action bar --> <!-- Detail activity: Action bar -->
<string name="detail_menu_test">Send test notification</string>
<string name="detail_menu_delete">Delete topic</string> <string name="detail_menu_delete">Delete topic</string>
</resources> </resources>