Open ntfy.sh links with the app, closes https://github.com/binwiederhier/ntfy/issues/20
This commit is contained in:
parent
7ed8287abe
commit
3556ffda8f
5 changed files with 90 additions and 33 deletions
|
@ -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 -->
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue