This commit is contained in:
Philipp Heckel 2021-11-26 15:51:05 -05:00
parent 7ed8287abe
commit 3556ffda8f
5 changed files with 90 additions and 33 deletions

View file

@ -39,6 +39,16 @@
<meta-data <meta-data
android:name="android.support.PARENT_ACTIVITY" android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.MainActivity"/> android:value=".ui.MainActivity"/>
<!-- Open https://ntfy.sh links with the app -->
<intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="@string/app_base_scheme"
android:host="@string/app_base_host"
android:pathPattern="/..*" /> <!-- This is awful, but it's the only way in Android -->
</intent-filter>
</activity> </activity>
<!-- Subscriber foreground service for hosts other than ntfy.sh --> <!-- Subscriber foreground service for hosts other than ntfy.sh -->

View file

@ -40,7 +40,7 @@ data class SubscriptionWithMetadata(
@Entity @Entity
data class Notification( data class Notification(
@PrimaryKey val id: String, @PrimaryKey val id: String, // TODO make [id, subscriptionId] the primary key
@ColumnInfo(name = "subscriptionId") val subscriptionId: Long, @ColumnInfo(name = "subscriptionId") val subscriptionId: Long,
@ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp @ColumnInfo(name = "timestamp") val timestamp: Long, // Unix timestamp
@ColumnInfo(name = "message") val message: String, @ColumnInfo(name = "message") val message: String,

View file

@ -5,6 +5,8 @@ import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.Intent.ACTION_VIEW
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.text.Html import android.text.Html
import android.util.Log import android.util.Log
@ -24,12 +26,15 @@ import io.heckel.ntfy.BuildConfig
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.Subscription
import io.heckel.ntfy.data.topicShortUrl import io.heckel.ntfy.data.topicShortUrl
import io.heckel.ntfy.data.topicUrl import io.heckel.ntfy.data.topicUrl
import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.msg.ApiService import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationService import io.heckel.ntfy.msg.NotificationService
import kotlinx.coroutines.* import kotlinx.coroutines.*
import java.util.* import java.util.*
import kotlin.random.Random
class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFragment.NotificationSettingsListener { class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFragment.NotificationSettingsListener {
private val viewModel by viewModels<DetailViewModel> { private val viewModel by viewModels<DetailViewModel> {
@ -37,8 +42,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
} }
private val repository by lazy { (application as Application).repository } private val repository by lazy { (application as Application).repository }
private val api = ApiService() private val api = ApiService()
private val messenger = FirebaseMessenger()
private var subscriberManager: SubscriberManager? = null // Context-dependent private var subscriberManager: SubscriberManager? = null // Context-dependent
private var notifier: NotificationService? = null // Context-dependent private var notifier: NotificationService? = null // Context-dependent
private var appBaseUrl: String? = null // Context-dependent
// Which subscription are we looking at // Which subscription are we looking at
private var subscriptionId: Long = 0L // Set in onCreate() private var subscriptionId: Long = 0L // Set in onCreate()
@ -65,10 +72,70 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
// Dependencies that depend on Context // Dependencies that depend on Context
subscriberManager = SubscriberManager(this) subscriberManager = SubscriberManager(this)
notifier = NotificationService(this) notifier = NotificationService(this)
appBaseUrl = getString(R.string.app_base_url)
// Show 'Back' button // Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
// Handle direct deep links to topic "https://ntfy.sh/..."
val url = intent?.data
if (intent?.action == ACTION_VIEW && url != null) {
val topic = url.pathSegments.first()
title = topicShortUrl(appBaseUrl!!, topic) // We assume the app base URL
maybeSubscribeAndLoadView(topic)
} else {
loadView()
}
}
private fun maybeSubscribeAndLoadView(topic: String) {
lifecycleScope.launch(Dispatchers.IO) {
val baseUrl = appBaseUrl!!
var subscription = repository.getSubscription(baseUrl, topic)
if (subscription == null) {
subscription = Subscription(
id = Random.nextLong(),
baseUrl = baseUrl,
topic = topic,
instant = false,
mutedUntil = 0,
totalCount = 0,
newCount = 0,
lastActive = Date().time/1000
)
repository.addSubscription(subscription)
// Subscribe to Firebase topic if ntfy.sh (even if instant, just to be sure!)
Log.d(MainActivity.TAG, "Subscribing to Firebase")
messenger.subscribe(topic)
// Fetch cached messages
try {
val notifications = api.poll(subscription.id, subscription.baseUrl, subscription.topic)
notifications.forEach { notification -> repository.addNotification(notification) }
} catch (e: Exception) {
Log.e(MainActivity.TAG, "Unable to fetch notifications: ${e.stackTrace}")
}
runOnUiThread {
val message = getString(R.string.detail_deep_link_subscribed_toast_message, topicShortUrl(baseUrl, topic))
Toast.makeText(this@DetailActivity, message, Toast.LENGTH_LONG).show()
}
}
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscription.id)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscription.baseUrl)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
runOnUiThread {
loadView()
}
}
}
private fun loadView() {
// 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 subscriptionBaseUrl = intent.getStringExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL) ?: return
@ -434,17 +501,15 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val dialog = builder val dialog = builder
.setMessage(R.string.detail_delete_dialog_message) .setMessage(R.string.detail_delete_dialog_message)
.setPositiveButton(R.string.detail_delete_dialog_permanently_delete) { _, _ -> .setPositiveButton(R.string.detail_delete_dialog_permanently_delete) { _, _ ->
// Return to main activity Log.d(MainActivity.TAG, "Deleting subscription with subscription ID $subscriptionId (topic: $subscriptionTopic)")
val result = Intent() GlobalScope.launch(Dispatchers.IO) {
.putExtra(MainActivity.EXTRA_SUBSCRIPTION_ID, subscriptionId) repository.removeAllNotifications(subscriptionId)
.putExtra(MainActivity.EXTRA_SUBSCRIPTION_BASE_URL, subscriptionBaseUrl) repository.removeSubscription(subscriptionId)
.putExtra(MainActivity.EXTRA_SUBSCRIPTION_TOPIC, subscriptionTopic) if (subscriptionBaseUrl == appBaseUrl) {
.putExtra(MainActivity.EXTRA_SUBSCRIPTION_INSTANT, subscriptionInstant) messenger.unsubscribe(subscriptionTopic)
.putExtra(MainActivity.EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscriptionMutedUntil) }
setResult(RESULT_OK, result) }
finish() finish()
// The deletion will be done in MainActivity.onResult
} }
.setNegativeButton(R.string.detail_delete_dialog_cancel) { _, _ -> /* Do nothing */ } .setNegativeButton(R.string.detail_delete_dialog_cancel) { _, _ -> /* Do nothing */ }
.create() .create()

View file

@ -345,27 +345,7 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
intent.putExtra(EXTRA_SUBSCRIPTION_TOPIC, subscription.topic) intent.putExtra(EXTRA_SUBSCRIPTION_TOPIC, subscription.topic)
intent.putExtra(EXTRA_SUBSCRIPTION_INSTANT, subscription.instant) intent.putExtra(EXTRA_SUBSCRIPTION_INSTANT, subscription.instant)
intent.putExtra(EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil) intent.putExtra(EXTRA_SUBSCRIPTION_MUTED_UNTIL, subscription.mutedUntil)
startActivityForResult(intent, REQUEST_CODE_DELETE_SUBSCRIPTION) startActivity(intent)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == REQUEST_CODE_DELETE_SUBSCRIPTION && resultCode == RESULT_OK) {
val subscriptionId = data?.getLongExtra(EXTRA_SUBSCRIPTION_ID, 0)
val subscriptionBaseUrl = data?.getStringExtra(EXTRA_SUBSCRIPTION_BASE_URL)
val subscriptionTopic = data?.getStringExtra(EXTRA_SUBSCRIPTION_TOPIC)
Log.d(TAG, "Deleting subscription with subscription ID $subscriptionId (topic: $subscriptionTopic)")
subscriptionId?.let { id -> viewModel.remove(id) }
subscriptionBaseUrl?.let { baseUrl ->
if (baseUrl == appBaseUrl) {
Log.d(TAG, "Unsubscribing from Firebase")
subscriptionTopic?.let { topic -> messenger.unsubscribe(topic) }
}
// Subscriber service changes are triggered in the observe() call above
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
} }
private fun handleActionModeClick(subscription: Subscription) { private fun handleActionModeClick(subscription: Subscription) {
@ -492,7 +472,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
const val EXTRA_SUBSCRIPTION_TOPIC = "subscriptionTopic" const val EXTRA_SUBSCRIPTION_TOPIC = "subscriptionTopic"
const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant" const val EXTRA_SUBSCRIPTION_INSTANT = "subscriptionInstant"
const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil" const val EXTRA_SUBSCRIPTION_MUTED_UNTIL = "subscriptionMutedUntil"
const val REQUEST_CODE_DELETE_SUBSCRIPTION = 1
const val ANIMATION_DURATION = 80L const val ANIMATION_DURATION = 80L
} }
} }

View file

@ -2,6 +2,8 @@
<!-- Main app--> <!-- Main app-->
<string name="app_name">Ntfy</string> <string name="app_name">Ntfy</string>
<string name="app_base_url">https://ntfy.sh</string> <!-- If changed, you must also change google-services.json! --> <string name="app_base_url">https://ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
<string name="app_base_scheme">https</string> <!-- If changed, you must also change google-services.json! -->
<string name="app_base_host">ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
<!-- Notification channels --> <!-- Notification channels -->
<string name="channel_notifications_name">Notifications</string> <string name="channel_notifications_name">Notifications</string>
@ -72,6 +74,7 @@
<string name="add_dialog_button_subscribe">Subscribe</string> <string name="add_dialog_button_subscribe">Subscribe</string>
<!-- Detail activity --> <!-- Detail activity -->
<string name="detail_deep_link_subscribed_toast_message">Subscribed to topic %1$s</string>
<string name="detail_no_notifications_text">You haven\'t received any notifications for this topic yet.</string> <string name="detail_no_notifications_text">You haven\'t received any notifications for this topic yet.</string>
<string name="detail_how_to_intro">To send notifications to this topic, simply PUT or POST to the topic URL.</string> <string name="detail_how_to_intro">To send notifications to this topic, simply PUT or POST to the topic URL.</string>
<string name="detail_how_to_example"><![CDATA[ Example (using curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string> <string name="detail_how_to_example"><![CDATA[ Example (using curl):<br/><tt>$ curl -d \"Hi\" %1$s</tt> ]]></string>