Compare commits

..

25 commits

Author SHA1 Message Date
Sayantan Roychowdhury
2318e28f11 Merge branch '11-get_title' into 'develop'
add title in notificationDispatcher

See merge request e/os/ntfy-android!11
2024-07-04 11:53:07 +00:00
Sayantan Roychowdhury
bb6adde03a add title in notificationDispatcher 2024-07-04 16:57:17 +05:30
Jonathan Klee
6f9d4bff5a Merge branch '0000-t-block-user-on-murena-channel' into 'develop'
Block user on the murena channel

See merge request e/os/ntfy-android!9
2024-07-04 05:46:45 +00:00
althafvly
bcec43769b mute notifications for ntfy 2024-07-04 10:20:32 +05:30
althafvly
2def9da084 disable back press on details page 2024-07-04 10:14:22 +05:30
Jonathan Klee
aea2d6b289 Remove default instructions to use ntfy cli 2024-07-04 10:14:22 +05:30
Jonathan Klee
0571096d44 Remove ActionBar 2024-07-04 10:14:22 +05:30
Jonathan Klee
a9fa4fa0bf Block user on the murena channel 2024-07-04 10:14:22 +05:30
Mohammed Althaf Thayyil
3e28ee2722 Merge branch '10-fix_registration' into 'develop'
Fix registration on unified poc app

See merge request e/os/ntfy-android!10
2024-07-04 04:43:01 +00:00
Sayantan Roychowdhury
a6cd89247c Fix registration on unified poc app 2024-07-04 04:43:00 +00:00
Sayantan Roychowdhury
125fe48297 Merge branch '9-develop-broadcast' into 'develop'
add unified push feature for poc

See merge request e/os/ntfy-android!8
2024-07-03 20:52:31 +00:00
althafvly
2cf668e685 add unified push feature for poc 2024-07-03 18:29:07 +05:30
Mohammed Althaf Thayyil
a44f572fb4 Merge branch '8-develop-app_topic' into 'develop'
Use our topic for any app register

See merge request e/os/ntfy-android!6
2024-07-03 12:53:57 +00:00
Jonathan Klee
409086e32a Merge branch '0000-t-add-murena-icon' into 'develop'
Add Murena icon, rename the app, change color to Murena orange kind.

See merge request e/os/ntfy-android!7
2024-07-03 12:35:11 +00:00
Jonathan Klee
a434a1d722 Change theme to Murena one 2024-07-03 14:26:30 +02:00
althafvly
37be787eb8 Use our topic for any app register 2024-07-03 17:30:29 +05:30
Jonathan Klee
5f780c27fb Change app name 2024-07-03 13:26:54 +02:00
Jonathan Klee
67a80d738c Add Murena icon 2024-07-03 13:25:06 +02:00
Jonathan Klee
484c66b0b5 Merge branch '0000-t-remove-useless-ui-elements' into 'develop'
Remove useless UI elements for Murena purposes

Closes #1

See merge request e/os/ntfy-android!5
2024-07-03 09:47:11 +00:00
Jonathan Klee
e0e2edf8a0 Remove useless UI elements for Murena purposes 2024-07-03 08:53:40 +02:00
Jonathan Klee
8af874a499 Merge branch '6-develop-topic' into 'develop'
subscribe to a topic by default

See merge request e/os/ntfy-android!4
2024-07-03 06:48:00 +00:00
althafvly
182819670b subscribe to a topic by default 2024-07-03 11:41:47 +05:30
althafvly
085f0d51e8 nfty: Hide unused prefs 2024-07-02 16:34:18 +05:30
althafvly
5b39062c4a nfty: Remove unused menu entries 2024-07-02 16:30:32 +05:30
althafvly
3486333c2a nfty: Remove firebase support and rating 2024-07-02 16:28:30 +05:30
60 changed files with 230 additions and 811 deletions

View file

@ -1,21 +0,0 @@
image: "registry.gitlab.e.foundation/e/os/docker-android-apps-cicd:latest"
stages:
- build
before_script:
- export GRADLE_USER_HOME=$(pwd)/.gradle
- chmod +x ./gradlew
cache:
key: ${CI_PROJECT_ID}
paths:
- .gradle/
buildRelease:
stage: build
script:
- ./gradlew assembleFdroid
artifacts:
paths:
- app/build/outputs/apk/fdroid/release

View file

@ -10,8 +10,8 @@ android {
compileSdkVersion 33
defaultConfig {
applicationId "foundation.e.ntfy"
minSdkVersion 23
applicationId "io.heckel.ntfy"
minSdkVersion 21
targetSdkVersion 33
versionCode 33
@ -27,10 +27,6 @@ android {
}
}
buildFeatures {
viewBinding = true
}
buildTypes {
release {
minifyEnabled true
@ -45,14 +41,7 @@ android {
flavorDimensions "store"
productFlavors {
play {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'true'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'true'
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'false'
}
fdroid {
buildConfigField 'boolean', 'FIREBASE_AVAILABLE', 'false'
buildConfigField 'boolean', 'RATE_APP_AVAILABLE', 'false'
buildConfigField 'boolean', 'INSTALL_PACKAGES_AVAILABLE', 'true'
}
}
@ -68,7 +57,6 @@ android {
'-Xjvm-default=all-compatibility' // https://stackoverflow.com/a/71234042/1440785
]
}
namespace "io.heckel.ntfy"
}
// Disables GoogleServices tasks for F-Droid variant
@ -115,9 +103,6 @@ dependencies {
// OkHttp (HTTP library)
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
// Firebase, sigh ... (only Google Play)
playImplementation 'com.google.firebase:firebase-messaging:23.1.2'
// RecyclerView
implementation "androidx.recyclerview:recyclerview:1.3.0"
@ -133,6 +118,4 @@ dependencies {
// Image viewer
implementation 'com.github.stfalcon-studio:StfalconImageViewer:v1.0.1'
implementation 'foundation.e:elib:0.0.1-alpha11'
}

View file

@ -1,12 +0,0 @@
package io.heckel.ntfy.firebase
@Suppress("UNUSED_PARAMETER")
class FirebaseMessenger {
fun subscribe(topic: String) {
// Dummy to keep F-Droid flavor happy
}
fun unsubscribe(topic: String) {
// Dummy to keep F-Droid flavor happy
}
}

View file

@ -1,12 +0,0 @@
package io.heckel.ntfy.firebase
import android.app.Service
import android.content.Intent
import android.os.IBinder
// Dummy to keep F-Droid flavor happy
class FirebaseService : Service() {
override fun onBind(intent: Intent?): IBinder? {
return null
}
}

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
package="io.heckel.ntfy">
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET"/>
@ -23,10 +23,9 @@
<application
android:name=".app.Application"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:icon="@mipmap/ic_murena"
android:label="@string/app_name"
android:persistent="true"
android:roundIcon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_murena_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:networkSecurityConfig="@xml/network_security_config"
@ -36,10 +35,10 @@
<activity
android:name=".ui.MainActivity"
android:label="@string/app_name"
android:excludeFromRecents="true"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
@ -133,6 +132,7 @@
<action android:name="org.unifiedpush.android.distributor.REGISTER"/>
<action android:name="org.unifiedpush.android.distributor.UNREGISTER"/>
<action android:name="org.unifiedpush.android.distributor.feature.BYTES_MESSAGE"/>
<action android:name="org.unifiedpush.android.distributor.feature.MURENA"/>
</intent-filter>
</receiver>
@ -176,25 +176,5 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
<activity
android:name=".ui.MainSettingsActivity"
android:theme="@style/PreferenceTheme"/>
<activity-alias
android:name=".ui.SettingsActivityLink"
android:exported="true"
android:label="@string/eos_settings_title"
android:targetActivity=".ui.MainSettingsActivity">
<intent-filter>
<action android:name="com.android.settings.action.EXTRA_SETTINGS" />
</intent-filter>
<meta-data
android:name="com.android.settings.category"
android:value="com.android.settings.category.device" />
<meta-data
android:name="com.android.settings.icon"
android:resource="@drawable/ic_notification" />
</activity-alias>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View file

@ -8,7 +8,6 @@ import com.google.gson.stream.JsonReader
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.msg.NotificationService
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.util.topicUrl
@ -18,7 +17,6 @@ class Backuper(val context: Context) {
private val gson = Gson()
private val resolver = context.applicationContext.contentResolver
private val repository = (context.applicationContext as Application).repository
private val messenger = FirebaseMessenger()
private val notifier = NotificationService(context)
suspend fun backup(uri: Uri, withSettings: Boolean = true, withSubscriptions: Boolean = true, withUsers: Boolean = true) {
@ -114,11 +112,6 @@ class Backuper(val context: Context) {
)
repository.addSubscription(subscription)
// Subscribe to Firebase topics
if (s.baseUrl == appBaseUrl) {
messenger.subscribe(s.topic)
}
// Create dedicated channels
if (s.dedicatedChannels) {
notifier.createSubscriptionNotificationChannels(subscription)

View file

@ -7,6 +7,7 @@ import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.up.Distributor
import io.heckel.ntfy.util.decodeBytesMessage
import io.heckel.ntfy.util.decodeBytesTitle
import io.heckel.ntfy.util.safeLet
/**
@ -39,7 +40,12 @@ class NotificationDispatcher(val context: Context, val repository: Repository) {
}
if (distribute) {
safeLet(subscription.upAppId, subscription.upConnectorToken) { appId, connectorToken ->
distributor.sendMessage(appId, connectorToken, decodeBytesMessage(notification))
distributor.sendMessage(
appId,
connectorToken,
decodeBytesMessage(notification),
decodeBytesTitle(notification),
)
}
}
if (downloadAttachment && downloadIcon) {

View file

@ -10,7 +10,6 @@ import android.os.PowerManager
import android.os.SystemClock
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.preference.PreferenceManager
import io.heckel.ntfy.BuildConfig
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
@ -89,16 +88,19 @@ class SubscriberService : Service() {
Log.init(this) // Init logs in all entry points
Log.d(TAG, "Subscriber service has been created")
val title = getString(R.string.channel_subscriber_notification_title)
val text = getString(R.string.channel_subscriber_notification_noinstant_text)
notificationManager = createNotificationChannel()
serviceNotification = createNotification(title, text)
startForeground(NOTIFICATION_SERVICE_ID, serviceNotification)
}
override fun onDestroy() {
Log.d(TAG, "Subscriber service has been destroyed")
stopService()
val preferenceKey = getString(R.string.eos_preference_key_is_enabled)
if (PreferenceManager.getDefaultSharedPreferences(this).getBoolean(preferenceKey, false)) {
sendBroadcast(Intent(this, AutoRestartReceiver::class.java))
}
sendBroadcast(Intent(this, AutoRestartReceiver::class.java)) // Restart it if necessary!
super.onDestroy()
}
@ -132,6 +134,7 @@ class SubscriberService : Service() {
}
}
wakeLock = null
stopForeground(true)
stopSelf()
} catch (e: Exception) {
Log.d(TAG, "Service stopped without being started: ${e.message}")
@ -214,18 +217,7 @@ class SubscriberService : Service() {
// Update foreground service notification popup
if (connections.size > 0) {
val title = getString(R.string.channel_subscriber_notification_title)
val text = if (BuildConfig.FIREBASE_AVAILABLE) {
when (instantSubscriptions.size) {
1 -> getString(R.string.channel_subscriber_notification_instant_text_one)
2 -> getString(R.string.channel_subscriber_notification_instant_text_two)
3 -> getString(R.string.channel_subscriber_notification_instant_text_three)
4 -> getString(R.string.channel_subscriber_notification_instant_text_four)
5 -> getString(R.string.channel_subscriber_notification_instant_text_five)
6 -> getString(R.string.channel_subscriber_notification_instant_text_six)
else -> getString(R.string.channel_subscriber_notification_instant_text_more, instantSubscriptions.size)
}
} else {
when (instantSubscriptions.size) {
val text = when (instantSubscriptions.size) {
1 -> getString(R.string.channel_subscriber_notification_noinstant_text_one)
2 -> getString(R.string.channel_subscriber_notification_noinstant_text_two)
3 -> getString(R.string.channel_subscriber_notification_noinstant_text_three)
@ -234,7 +226,6 @@ class SubscriberService : Service() {
6 -> getString(R.string.channel_subscriber_notification_noinstant_text_six)
else -> getString(R.string.channel_subscriber_notification_noinstant_text_more, instantSubscriptions.size)
}
}
serviceNotification = createNotification(title, text)
notificationManager?.notify(NOTIFICATION_SERVICE_ID, serviceNotification)
}

View file

@ -2,11 +2,10 @@ package io.heckel.ntfy.service
import android.content.Context
import android.content.Intent
import androidx.preference.PreferenceManager
import androidx.core.content.ContextCompat
import androidx.work.*
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.R
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@ -44,17 +43,11 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Failed, no application found (work ID: ${id})")
return Result.failure()
}
withContext(Dispatchers.IO) {
val app = context.applicationContext as Application
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app)
val preferenceKey = context.getString(R.string.eos_preference_key_is_enabled)
val action = if (sharedPreferences.getBoolean(preferenceKey, false)) {
SubscriberService.Action.START
} else {
SubscriberService.Action.STOP
}
val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus()
val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
val action = if (instantSubscriptions > 0) SubscriberService.Action.START else SubscriberService.Action.STOP
val serviceState = SubscriberService.readServiceState(context)
if (serviceState == SubscriberService.ServiceState.STOPPED && action == SubscriberService.Action.STOP) {
return@withContext Result.success()
@ -62,7 +55,7 @@ class SubscriberServiceManager(private val context: Context) {
Log.d(TAG, "ServiceStartWorker: Starting foreground service with action $action (work ID: ${id})")
Intent(context, SubscriberService::class.java).also {
it.action = action.name
context.startService(it)
ContextCompat.startForegroundService(context, it)
}
}
return Result.success()

View file

@ -114,10 +114,7 @@ class AddFragment : DialogFragment() {
// Set foreground description text
subscribeForegroundDescription.text = getString(R.string.add_dialog_foreground_description, shortUrl(appBaseUrl))
// Show/hide based on flavor (faster shortcut for validateInputSubscribeView, which can only run onShow)
if (!BuildConfig.FIREBASE_AVAILABLE) {
subscribeInstantDeliveryBox.visibility = View.GONE
}
// Add baseUrl auto-complete behavior
lifecycleScope.launch(Dispatchers.IO) {
@ -291,16 +288,6 @@ class AddFragment : DialogFragment() {
private fun validateInputSubscribeView() {
if (!this::positiveButton.isInitialized) return // As per crash seen in Google Play
// Show/hide things: This logic is intentionally kept simple. Do not simplify "just because it's pretty".
val instantToggleAllowed = if (!BuildConfig.FIREBASE_AVAILABLE) {
false
} else if (subscribeUseAnotherServerCheckbox.isChecked && subscribeBaseUrlText.text.toString() == appBaseUrl) {
true
} else if (!subscribeUseAnotherServerCheckbox.isChecked && defaultBaseUrl == null) {
true
} else {
false
}
if (subscribeUseAnotherServerCheckbox.isChecked) {
subscribeUseAnotherServerDescription.visibility = View.VISIBLE
subscribeBaseUrlLayout.visibility = View.VISIBLE
@ -308,15 +295,9 @@ class AddFragment : DialogFragment() {
subscribeUseAnotherServerDescription.visibility = View.GONE
subscribeBaseUrlLayout.visibility = View.GONE
}
if (instantToggleAllowed) {
subscribeInstantDeliveryBox.visibility = View.VISIBLE
subscribeInstantDeliveryDescription.visibility = if (subscribeInstantDeliveryCheckbox.isChecked) View.VISIBLE else View.GONE
subscribeForegroundDescription.visibility = View.GONE
} else {
subscribeInstantDeliveryBox.visibility = View.GONE
subscribeInstantDeliveryDescription.visibility = View.GONE
subscribeForegroundDescription.visibility = if (BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE
}
subscribeForegroundDescription.visibility = View.GONE
// Enable/disable "Subscribe" button
lifecycleScope.launch(Dispatchers.IO) {
@ -356,7 +337,7 @@ class AddFragment : DialogFragment() {
activity.runOnUiThread {
val topic = subscribeTopicText.text.toString()
val baseUrl = getBaseUrl()
val instant = !BuildConfig.FIREBASE_AVAILABLE || baseUrl != appBaseUrl || subscribeInstantDeliveryCheckbox.isChecked
val instant = baseUrl != appBaseUrl || subscribeInstantDeliveryCheckbox.isChecked
subscribeListener.onSubscribe(topic, baseUrl, instant)
dialog?.dismiss()
}

View file

@ -30,7 +30,6 @@ import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationService
@ -47,7 +46,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
}
private val repository by lazy { (application as Application).repository }
private val api = ApiService()
private val messenger = FirebaseMessenger()
private var notifier: NotificationService? = null // Context-dependent
private var appBaseUrl: String? = null // Context-dependent
@ -78,8 +76,7 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
notifier = NotificationService(this)
appBaseUrl = getString(R.string.app_base_url)
// Show 'Back' button
supportActionBar?.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(false)
// Handle direct deep links to topic "ntfy://..."
val url = intent?.data
@ -90,6 +87,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
}
}
override fun onBackPressed() {
finishAffinity()
}
private fun maybeSubscribeAndLoadView(url: Uri) {
if (url.pathSegments.size != 1) {
Log.w(TAG, "Invalid link $url. Aborting.")
@ -127,12 +128,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
)
repository.addSubscription(subscription)
// Subscribe to Firebase topic if ntfy.sh (even if instant, just to be sure!)
if (baseUrl == appBaseUrl) {
Log.d(TAG, "Subscribing to Firebase topic $topic")
messenger.subscribe(topic)
}
// Fetch cached messages
try {
val user = repository.getUser(subscription.baseUrl) // May be null
@ -176,24 +171,12 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val topicUrl = topicShortUrl(subscriptionBaseUrl, subscriptionTopic)
title = subscriptionDisplayName
// Set "how to instructions"
val howToExample: TextView = findViewById(R.id.detail_how_to_example)
howToExample.linksClickable = true
val howToText = getString(R.string.detail_how_to_example, topicUrl)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
howToExample.text = Html.fromHtml(howToText, Html.FROM_HTML_MODE_LEGACY)
} else {
howToExample.text = Html.fromHtml(howToText)
}
// Swipe to refresh
mainListContainer = findViewById(R.id.detail_notification_list_container)
mainListContainer.setOnRefreshListener { refresh() }
mainListContainer.setColorSchemeResources(Colors.refreshProgressIndicator)
// Update main list based on viewModel (& its datasource/livedata)
val noEntriesText: View = findViewById(R.id.detail_no_notifications)
val onNotificationClick = { n: Notification -> onNotificationClick(n) }
val onNotificationLongClick = { n: Notification -> onNotificationLongClick(n) }
@ -207,10 +190,8 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
adapter.submitList(it as MutableList<Notification>)
if (it.isEmpty()) {
mainListContainer.visibility = View.GONE
noEntriesText.visibility = View.VISIBLE
} else {
mainListContainer.visibility = View.VISIBLE
noEntriesText.visibility = View.GONE
}
// Cancel notifications that still have popups
@ -345,10 +326,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
R.id.detail_menu_test -> {
onTestClick()
true
}
R.id.detail_menu_notifications_enabled -> {
onMutedUntilClick(enable = false)
true
@ -369,22 +346,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
onInstantEnableClick(enable = false)
true
}
R.id.detail_menu_copy_url -> {
onCopyUrlClick()
true
}
R.id.detail_menu_clear -> {
onClearClick()
true
}
R.id.detail_menu_settings -> {
onSettingsClick()
true
}
R.id.detail_menu_unsubscribe -> {
onDeleteClick()
true
}
else -> super.onOptionsItemSelected(item)
}
}
@ -529,16 +490,10 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
val appBaseUrl = getString(R.string.app_base_url)
val enableInstantItem = menu.findItem(R.id.detail_menu_enable_instant)
val disableInstantItem = menu.findItem(R.id.detail_menu_disable_instant)
val allowToggleInstant = BuildConfig.FIREBASE_AVAILABLE && subscriptionBaseUrl == appBaseUrl
if (allowToggleInstant) {
enableInstantItem?.isVisible = !subscriptionInstant
disableInstantItem?.isVisible = subscriptionInstant
} else {
enableInstantItem?.isVisible = false
disableInstantItem?.isVisible = false
}
}
}
private fun showHideMutedUntilMenuItems(mutedUntilTimestamp: Long) {
if (!this::menu.isInitialized) {
@ -608,9 +563,6 @@ class DetailActivity : AppCompatActivity(), ActionMode.Callback, NotificationFra
GlobalScope.launch(Dispatchers.IO) {
repository.removeAllNotifications(subscriptionId)
repository.removeSubscription(subscriptionId)
if (subscriptionBaseUrl == appBaseUrl) {
messenger.unsubscribe(subscriptionTopic)
}
}
finish()
}

View file

@ -114,7 +114,6 @@ class DetailSettingsActivity : AppCompatActivity() {
private fun loadView() {
if (subscription.upAppId == null) {
loadInstantPref()
loadMutedUntilPref()
loadMinPriorityPref()
loadAutoDeletePref()
@ -134,29 +133,6 @@ class DetailSettingsActivity : AppCompatActivity() {
loadTopicUrlPref()
}
private fun loadInstantPref() {
val appBaseUrl = getString(R.string.app_base_url)
val prefId = context?.getString(R.string.detail_settings_notifications_instant_key) ?: return
val pref: SwitchPreference? = findPreference(prefId)
pref?.isVisible = BuildConfig.FIREBASE_AVAILABLE && subscription.baseUrl == appBaseUrl
pref?.isChecked = subscription.instant
pref?.preferenceDataStore = object : PreferenceDataStore() {
override fun putBoolean(key: String?, value: Boolean) {
save(subscription.copy(instant = value), refresh = true)
}
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
return subscription.instant
}
}
pref?.summaryProvider = Preference.SummaryProvider<SwitchPreference> { preference ->
if (preference.isChecked) {
getString(R.string.detail_settings_notifications_instant_summary_on)
} else {
getString(R.string.detail_settings_notifications_instant_summary_off)
}
}
}
private fun loadDedicatedChannelsPrefs() {
val prefId = context?.getString(R.string.detail_settings_notifications_dedicated_channels_key) ?: return
val pref: SwitchPreference? = findPreference(prefId)

View file

@ -34,13 +34,13 @@ import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.firebase.FirebaseMessenger
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.DownloadManager
import io.heckel.ntfy.msg.DownloadType
import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.up.TOPIC_MURENA
import io.heckel.ntfy.util.*
import io.heckel.ntfy.work.DeleteWorker
import io.heckel.ntfy.work.PollWorker
@ -57,14 +57,12 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
private val repository by lazy { (application as Application).repository }
private val api = ApiService()
private val messenger = FirebaseMessenger()
// UI elements
private lateinit var menu: Menu
private lateinit var mainList: RecyclerView
private lateinit var mainListContainer: SwipeRefreshLayout
private lateinit var adapter: MainAdapter
private lateinit var fab: FloatingActionButton
// Other stuff
private var actionMode: ActionMode? = null
@ -87,12 +85,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Action bar
title = getString(R.string.main_action_bar_title)
// Floating action button ("+")
fab = findViewById(R.id.fab)
fab.setOnClickListener {
onSubscribeButtonClick()
}
// Swipe to refresh
mainListContainer = findViewById(R.id.main_subscriptions_list_container)
mainListContainer.setOnRefreshListener { refreshAllSubscriptions() }
@ -199,9 +191,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Create notification channels right away, so we can configure them immediately after installing the app
dispatcher?.init()
// Subscribe to control Firebase channel (so we can re-start the foreground service if it dies)
messenger.subscribe(ApiService.CONTROL_TOPIC)
// Darrkkkk mode
AppCompatDelegate.setDefaultNightMode(repository.getDarkMode())
@ -212,6 +201,42 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
// Permissions
maybeRequestNotificationPermission()
lifecycleScope.launch(Dispatchers.IO) {
val subscriptions = repository.getSubscriptions()
val defaultTopic = TOPIC_MURENA
val hasTestTopic = subscriptions.any { it.topic == defaultTopic }
if (hasTestTopic) {
showMurenaSub()
return@launch
}
val subscription = Subscription(
id = randomSubscriptionId(),
baseUrl = appBaseUrl!!,
topic = defaultTopic,
instant = true,
dedicatedChannels = false,
mutedUntil = Repository.MUTED_UNTIL_FOREVER,
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL,
lastNotificationId = null,
icon = null,
upAppId = null,
upConnectorToken = null,
displayName = null,
totalCount = 0,
newCount = 0,
lastActive = Date().time / 1000
)
viewModel.add(subscription)
showMurenaSub()
}
}
private suspend fun showMurenaSub() {
startDetailView(repository.getSubscriptions().find { it.topic == "murena_notification" }!!)
}
private fun maybeRequestNotificationPermission() {
@ -349,10 +374,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
}
val mutedUntilSeconds = repository.getGlobalMutedUntil()
runOnUiThread {
// Show/hide in-app rate widget
val rateAppItem = menu.findItem(R.id.main_menu_rate)
rateAppItem.isVisible = BuildConfig.RATE_APP_AVAILABLE
// Pause notification icons
val notificationsEnabledItem = menu.findItem(R.id.main_menu_notifications_enabled)
val notificationsDisabledUntilItem = menu.findItem(R.id.main_menu_notifications_disabled_until)
@ -381,30 +402,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
onNotificationSettingsClick(enable = true)
true
}
R.id.main_menu_settings -> {
startActivity(Intent(this, SettingsActivity::class.java))
true
}
R.id.main_menu_report_bug -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_report_bug_url))))
true
}
R.id.main_menu_rate -> {
try {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=$packageName")))
} catch (e: ActivityNotFoundException) {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse("https://play.google.com/store/apps/details?id=$packageName")))
}
true
}
R.id.main_menu_donate -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_donate_url))))
true
}
R.id.main_menu_docs -> {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.main_menu_docs_url))))
true
}
else -> super.onOptionsItemSelected(item)
}
}
@ -466,12 +463,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
)
viewModel.add(subscription)
// Subscribe to Firebase topic if ntfy.sh (even if instant, just to be sure!)
if (baseUrl == appBaseUrl) {
Log.d(TAG, "Subscribing to Firebase topic $topic")
messenger.subscribe(topic)
}
// Fetch cached messages
lifecycleScope.launch(Dispatchers.IO) {
try {
@ -635,18 +626,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
actionMode = startActionMode(this)
adapter.toggleSelection(subscription.id)
// Fade out FAB
fab.alpha = 1f
fab
.animate()
.alpha(0f)
.setDuration(ANIMATION_DURATION)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
fab.visibility = View.GONE
}
})
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
@ -663,19 +642,6 @@ class MainActivity : AppCompatActivity(), ActionMode.Callback, AddFragment.Subsc
adapter.selected.clear()
redrawList()
// Fade in FAB
fab.alpha = 0f
fab.visibility = View.VISIBLE
fab
.animate()
.alpha(1f)
.setDuration(ANIMATION_DURATION)
.setListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
fab.visibility = View.VISIBLE // Required to replace the old listener
}
})
// Fade status bar color
val fromColor = ContextCompat.getColor(this, Colors.statusBarActionMode(this))
val toColor = ContextCompat.getColor(this, Colors.statusBarNormal(this))

View file

@ -105,7 +105,7 @@ class MainAdapter(private val repository: Repository, private val onClick: (Subs
dateView.visibility = if (isUnifiedPush) View.GONE else View.VISIBLE
notificationDisabledUntilImageView.visibility = if (showMutedUntilIcon) View.VISIBLE else View.GONE
notificationDisabledForeverImageView.visibility = if (showMutedForeverIcon) View.VISIBLE else View.GONE
instantImageView.visibility = if (subscription.instant && BuildConfig.FIREBASE_AVAILABLE) View.VISIBLE else View.GONE
instantImageView.visibility = View.GONE
if (isUnifiedPush || subscription.newCount == 0) {
newItemsView.visibility = View.GONE
} else {

View file

@ -1,71 +0,0 @@
package io.heckel.ntfy.ui
import android.content.res.Configuration
import android.os.Build
import android.os.Bundle
import android.view.WindowInsetsController
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import io.heckel.ntfy.R
import io.heckel.ntfy.databinding.MainSettingsActivityBinding
class MainSettingsActivity : AppCompatActivity() {
private lateinit var mBinding: MainSettingsActivityBinding
@RequiresApi(Build.VERSION_CODES.R)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding = MainSettingsActivityBinding.inflate(layoutInflater)
setContentView(mBinding.root)
setupToolbar()
setSystemBarsAppearance()
showPreferencesFragment()
}
private fun setupToolbar() {
mBinding.toolbar.setNavigationOnClickListener {
onBackPressedDispatcher.onBackPressed()
}
}
@RequiresApi(Build.VERSION_CODES.R)
private fun setSystemBarsAppearance() {
val insetsController = window.insetsController ?: return
val isLightMode = isSystemInLightMode()
if (isLightMode) {
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
} else {
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
)
insetsController.setSystemBarsAppearance(
0,
WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
)
}
}
private fun isSystemInLightMode(): Boolean {
val nightModeFlags = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
return nightModeFlags != Configuration.UI_MODE_NIGHT_YES
}
private fun showPreferencesFragment() {
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, PreferencesFragment())
.commit()
}
}

View file

@ -1,37 +0,0 @@
package io.heckel.ntfy.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toolbar
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
import io.heckel.ntfy.R
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.util.Log
class PreferencesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.settings_preferences, rootKey)
val preference: SwitchPreferenceCompat? =
findPreference(getString(R.string.eos_preference_key_is_enabled))
preference?.setOnPreferenceChangeListener { _, newValue ->
val isChecked = newValue as Boolean
val intent = Intent(context, SubscriberService::class.java)
intent.action = if (isChecked) {
SubscriberService.Action.START.name
} else {
SubscriberService.Action.STOP.name
}
requireContext().startService(intent)
true
}
}
}

View file

@ -68,15 +68,23 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
// Add subscription
val baseUrl = repository.getDefaultBaseUrl() ?: context.getString(R.string.app_base_url)
val topic = UP_PREFIX + randomString(TOPIC_RANDOM_ID_LENGTH)
var topic = UP_PREFIX + randomString(TOPIC_RANDOM_ID_LENGTH)
if (appId == PACKAGE_MURENA_UNIFIED_PUSH) {
topic = TOPIC_MURENA
}
val endpoint = topicUrlUp(baseUrl, topic)
val subscriptionId =
appId.takeIf { it == PACKAGE_MURENA_UNIFIED_PUSH }?.run {
repository.getSubscriptions()
.find { it.baseUrl == baseUrl && it.topic == topic }?.id
} ?: randomSubscriptionId()
val subscription = Subscription(
id = randomSubscriptionId(),
id = subscriptionId,
baseUrl = baseUrl,
topic = topic,
instant = true, // No Firebase, always instant!
dedicatedChannels = false,
mutedUntil = 0,
mutedUntil = Repository.MUTED_UNTIL_FOREVER,
minPriority = Repository.MIN_PRIORITY_USE_GLOBAL,
autoDelete = Repository.AUTO_DELETE_USE_GLOBAL,
insistent = Repository.INSISTENT_MAX_PRIORITY_USE_GLOBAL,
@ -89,10 +97,15 @@ class BroadcastReceiver : android.content.BroadcastReceiver() {
newCount = 0,
lastActive = Date().time/1000
)
Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription")
try {
// Note, this may fail due to a SQL constraint exception, see https://github.com/binwiederhier/ntfy/issues/185
if (appId == PACKAGE_MURENA_UNIFIED_PUSH) {
Log.d(TAG, "Updating subscription with for app $appId (connectorToken $connectorToken): $subscription")
repository.updateSubscription(subscription)
} else {
Log.d(TAG, "Adding subscription with for app $appId (connectorToken $connectorToken): $subscription")
repository.addSubscription(subscription)
}
distributor.sendEndpoint(appId, connectorToken, endpoint)
// Refresh (and maybe start) foreground service

View file

@ -19,4 +19,9 @@ const val EXTRA_APPLICATION = "application"
const val EXTRA_TOKEN = "token"
const val EXTRA_ENDPOINT = "endpoint"
const val EXTRA_MESSAGE = "message"
const val EXTRA_TITLE = "title"
const val EXTRA_BYTES_MESSAGE = "bytesMessage"
const val EXTRA_BYTES_TITLE = "bytesTitle"
const val PACKAGE_MURENA_UNIFIED_PUSH = "foundation.e.unifiedpoc"
const val TOPIC_MURENA = "murena_notification"

View file

@ -9,14 +9,16 @@ import io.heckel.ntfy.util.Log
* See https://unifiedpush.org/spec/android/ for details.
*/
class Distributor(val context: Context) {
fun sendMessage(app: String, connectorToken: String, message: ByteArray) {
fun sendMessage(app: String, connectorToken: String, message: ByteArray, title: ByteArray) {
Log.d(TAG, "Sending MESSAGE to $app (token=$connectorToken): ${message.size} bytes")
val broadcastIntent = Intent()
broadcastIntent.`package` = app
broadcastIntent.action = ACTION_MESSAGE
broadcastIntent.putExtra(EXTRA_TOKEN, connectorToken)
broadcastIntent.putExtra(EXTRA_MESSAGE, String(message)) // UTF-8
broadcastIntent.putExtra(EXTRA_TITLE, String(title)) // UTF-8
broadcastIntent.putExtra(EXTRA_BYTES_MESSAGE, message)
broadcastIntent.putExtra(EXTRA_BYTES_TITLE, title)
context.sendBroadcast(broadcastIntent)
}

View file

@ -170,18 +170,26 @@ fun decodeMessage(notification: Notification): String {
}
}
fun decodeBytesMessage(notification: Notification): ByteArray {
fun decodeBytes(notification: Notification, string: String): ByteArray {
return try {
if (notification.encoding == MESSAGE_ENCODING_BASE64) {
Base64.decode(notification.message, Base64.DEFAULT)
Base64.decode(string, Base64.DEFAULT)
} else {
notification.message.toByteArray()
string.toByteArray()
}
} catch (e: IllegalArgumentException) {
notification.message.toByteArray()
string.toByteArray()
}
}
fun decodeBytesMessage(notification: Notification): ByteArray {
return decodeBytes(notification, notification.message)
}
fun decodeBytesTitle(notification: Notification): ByteArray {
return decodeBytes(notification, notification.title)
}
/**
* See above; prepend emojis to title if the title is non-empty.
* Otherwise, they are prepended to the message.

View file

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<vector
android:height="108dp"
android:width="108dp"
android:viewportHeight="108"
android:viewportWidth="108"
xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#3DDC84"
android:pathData="M0,0h108v108h-108z"/>
<path android:fillColor="#00000000" android:pathData="M9,0L9,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,0L19,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,0L29,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,0L39,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,0L49,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,0L59,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,0L69,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,0L79,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M89,0L89,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M99,0L99,108"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,9L108,9"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,19L108,19"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,29L108,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,39L108,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,49L108,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,59L108,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,69L108,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,79L108,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,89L108,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M0,99L108,99"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,29L89,29"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,39L89,39"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,49L89,49"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,59L89,59"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,69L89,69"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M19,79L89,79"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M29,19L29,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M39,19L39,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M49,19L49,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M59,19L59,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M69,19L69,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
<path android:fillColor="#00000000" android:pathData="M79,19L79,89"
android:strokeColor="#33FFFFFF" android:strokeWidth="0.8"/>
</vector>

View file

@ -1,31 +1,31 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:width="50dp"
android:height="50dp"
android:viewportWidth="50"
android:viewportHeight="50">
<path
android:pathData="m7.8399,6.35c-3.58,0 -6.6469,2.817 -6.6469,6.3983v0.003l0.0351,27.8668 -0.8991,6.6347 12.2261,-3.248L42.9487,44.0049c3.58,0 6.6469,-2.8208 6.6469,-6.4022v-24.8545c0,-3.5803 -3.0652,-6.3967 -6.6438,-6.3983h-0.0031zM7.8399,10.8662h35.1088,0.0031c1.2579,0.0013 2.1277,0.9164 2.1277,1.8821v24.8544c0,0.9666 -0.8714,1.8821 -2.1307,1.8821L11.8924,39.4849l-6.2114,1.8768 0.0633,-0.366 -0.0343,-28.2473c0,-0.9665 0.8706,-1.8821 2.13,-1.8821z"
android:strokeWidth="0.754022"
android:fillColor="?android:attr/colorControlNormal"
android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/>
<path
android:pathData="m11.5278,32.0849l0,-3.346l7.0363,-3.721q0.3397,-0.1732 0.6551,-0.2596 0.3397,-0.1153 0.6066,-0.1732 0.2912,-0.0288 0.5823,-0.0576l0,-0.2308q-0.2912,-0.0288 -0.5823,-0.1153 -0.2669,-0.0576 -0.6066,-0.1443 -0.3154,-0.1153 -0.6551,-0.2884l-7.0363,-3.721l0,-3.3749l10.8699,5.9132l0,3.6056z"
android:strokeWidth="0.525121"
android:fillColor="?android:attr/colorControlNormal"
android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/>
<path
android:pathData="m10.9661,15.6112l0,4.8516l7.3742,3.9002c0.0157,0.0077 0.0305,0.0128 0.0461,0.0204 -0.0157,0.0077 -0.0305,0.0128 -0.0461,0.0204l-7.3742,3.9002l0,4.8267l0.7961,-0.4333 11.1995,-6.0969l0,-4.463zM12.0931,17.6933 L21.8346,22.9981l0,2.7446l-9.7414,5.2999l0,-1.8679l6.6912,-3.5416 0.0084,-0.0051c0.1961,-0.0992 0.3826,-0.1724 0.5531,-0.2191l0.0127,0l0.0167,-0.0051c0.2034,-0.0691 0.3777,-0.1209 0.5279,-0.1545l1.0684,-0.1046l0,-1.4644l-0.5154,-0.0497c-0.1632,-0.0153 -0.3288,-0.0505 -0.4944,-0.0997l-0.0167,-0.0051 -0.0167,-0.0051c-0.1632,-0.0352 -0.3552,-0.0811 -0.5656,-0.1344 -0.1802,-0.0668 -0.3706,-0.1479 -0.5698,-0.2492l-0.0084,-0.0051 -6.6912,-3.5416z"
android:strokeWidth="0.525121"
android:fillColor="?android:attr/colorControlNormal"
android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.7503,30.9206l11.6118,0l0,3.1388L26.7503,34.0594Z"
android:strokeWidth="0.525121"
android:fillColor="?android:attr/colorControlNormal"
android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/>
<path
android:pathData="m26.1875,30.2775l0,0.6427 0,3.7845l12.7371,0l0,-4.4272zM27.3113,31.563l10.4896,0l0,1.8515l-10.4896,0z"
android:strokeWidth="0.525121"
android:fillColor="?android:attr/colorControlNormal"
android:fillColor="#FFFFFFFF"
android:strokeColor="#00000000"/>
</vector>

View file

@ -26,51 +26,4 @@
app:layoutManager="LinearLayoutManager"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_no_notifications" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content" app:srcCompat="@drawable/ic_sms_gray_48dp"
android:id="@+id/detail_no_notifications_image"/>
<TextView
android:id="@+id/detail_no_notifications_text"
android:text="@string/detail_no_notifications_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:padding="10dp" android:gravity="center_horizontal"
android:paddingStart="50dp" android:paddingEnd="50dp"/>
<TextView
android:text="@string/detail_how_to_intro"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_how_to_intro"
android:layout_marginTop="20dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"/>
<TextView
android:text="@string/detail_how_to_example"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_how_to_example"
android:layout_marginTop="7dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"/>
<TextView
android:text="@string/detail_how_to_link"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/detail_how_to_link"
android:layout_marginTop="7dp"
android:layout_marginStart="50dp"
android:layout_marginEnd="50dp"
android:linksClickable="true"
android:autoLink="web"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -174,7 +174,8 @@
android:layout_width="match_parent"
android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@+id/fab" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:id="@+id/main_no_subscriptions" android:visibility="gone">
<ImageView
android:layout_width="match_parent"
@ -208,16 +209,4 @@
android:autoLink="web"/>
</LinearLayout>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:contentDescription="@string/main_add_button_description"
android:src="@drawable/ic_add_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/FloatingActionButton"
/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:fitsSystemWindows="true"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapsing_toolbar"
style="?attr/collapsingToolbarLayoutLargeStyle"
android:background="@color/e_background"
android:layout_width="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize">
<com.google.android.material.appbar.MaterialToolbar
app:navigationIcon="@drawable/e_ic_back"
app:title="@string/eos_settings_title"
android:id="@+id/toolbar"
android:background="@color/e_background"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<FrameLayout
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.core.widget.NestedScrollView>
</LinearLayout>

View file

@ -9,9 +9,4 @@
app:showAsAction="ifRoom" android:icon="@drawable/ic_bolt_outline_white_24dp"/>
<item android:id="@+id/detail_menu_disable_instant" android:title="@string/detail_menu_disable_instant"
android:icon="@drawable/ic_bolt_white_24dp" app:showAsAction="ifRoom"/>
<item android:id="@+id/detail_menu_settings" android:title="@string/detail_menu_settings"/>
<item android:id="@+id/detail_menu_copy_url" android:title="@string/detail_menu_copy_url"/>
<item android:id="@+id/detail_menu_clear" android:title="@string/detail_menu_clear"/>
<item android:id="@+id/detail_menu_test" android:title="@string/detail_menu_test"/>
<item android:id="@+id/detail_menu_unsubscribe" android:title="@string/detail_menu_unsubscribe"/>
</menu>

View file

@ -5,9 +5,4 @@
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_time_white_outline_24dp"/>
<item android:id="@+id/main_menu_notifications_disabled_forever" android:title="@string/detail_menu_notifications_disabled_forever"
app:showAsAction="ifRoom" android:icon="@drawable/ic_notifications_off_white_outline_24dp"/>
<item android:id="@+id/main_menu_settings" android:title="@string/main_menu_settings_title"/>
<item android:id="@+id/main_menu_docs" android:title="@string/main_menu_docs_title"/>
<item android:id="@+id/main_menu_rate" android:title="@string/main_menu_rate_title"/>
<item android:id="@+id/main_menu_donate" android:title="@string/main_menu_donate_title"/>
<item android:id="@+id/main_menu_report_bug" android:title="@string/main_menu_report_bug_title"/>
</menu>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_murena_background"/>
<foreground android:drawable="@mipmap/ic_murena_foreground"/>
</adaptive-icon>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_murena_background"/>
<foreground android:drawable="@mipmap/ic_murena_foreground"/>
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

View file

@ -343,6 +343,4 @@
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy arbeitet nicht als UnifiedPush-Distributor</string>
<string name="settings_advanced_unifiedpush_title">UnifiedPush aktivieren</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy arbeitet als UnifiedPush-Distributor</string>
<string name="eos_settings_enable_title">Den Verteiler aktivieren</string>
<string name="eos_settings_enable_description">Es ermöglicht Drittanbieteranwendungen, UnifiedPush-Benachrichtigungen zu empfangen</string>
</resources>

View file

@ -343,6 +343,4 @@
<string name="settings_advanced_unifiedpush_title">Activar UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy actuará como distribuidor UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy no actuará como distribuidor UnifiedPush</string>
<string name="eos_settings_enable_title">Habilitar el distribuidor</string>
<string name="eos_settings_enable_description">Permite a las aplicaciones de terceros recibir notificaciones de UnifiedPush</string>
</resources>

View file

@ -343,6 +343,4 @@
<string name="settings_advanced_unifiedpush_summary_enabled">ntfy agira comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_summary_disabled">ntfy n\'agira pas comme un distributeur UnifiedPush</string>
<string name="settings_advanced_unifiedpush_title">Activer le \"UnifiedPush\"</string>
<string name="eos_settings_enable_title">Activer le distributeur</string>
<string name="eos_settings_enable_description">Cela permet aux applications tierces de recevoir des notifications UnifiedPush</string>
</resources>

View file

@ -330,6 +330,4 @@
<string name="channel_notifications_group_default_name">Default</string>
<string name="main_menu_donate_title">Dona 💸</string>
<string name="detail_item_cannot_open_apk">Le app non possono più essere installate: devono essere scaricate via browser. Vedi l\'issue #531 per dettagli.</string>
<string name="eos_settings_enable_title">Abilitare il distributore</string>
<string name="eos_settings_enable_description">Consente alle applicazioni di terze parti di ricevere notifiche UnifiedPush</string>
</resources>

View file

@ -10,12 +10,12 @@
- https://developer.android.com/guide/topics/ui/look-and-feel/themes
-->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/teal_light</item>
<item name="colorAccent">@color/teal_light</item> <!-- checkboxes, text fields -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/e_accent_dark</item>
<item name="colorAccent">@color/e_accent_dark</item> <!-- checkboxes, text fields -->
<item name="android:colorBackground">@color/black_900</item> <!-- background -->
<item name="android:statusBarColor">@color/black_900</item>
<item name="actionModeBackground">@color/black_900</item>
<item name="android:statusBarColor">@color/e_accent_dark</item>
<item name="actionModeBackground">@color/e_accent_dark</item>
<!-- Action bar background & text color -->
<item name="colorSurface">@color/black_800b</item>

View file

@ -13,5 +13,7 @@
<color name="teal_dark">#2a6e60</color> <!-- Action bar background in action mode (light mode) -->
<color name="red_light">#fe4d2e</color> <!-- Danger text (dark mode) -->
<color name="red_dark">#c30000</color> <!-- Danger text (light mode) -->
<color name="e_accent_light">#0086FF</color>
<color name="e_accent_dark">#5DB2FF</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_murena_background">#FFFFFF</color>
</resources>

View file

@ -391,10 +391,4 @@
<string name="user_dialog_button_cancel">Cancel</string>
<string name="user_dialog_button_delete">Delete user</string>
<string name="user_dialog_button_save">Save</string>
<!-- /e/OS integration preferences -->
<string name="eos_preference_key_is_enabled" translatable="false">isEnabled</string>
<string name="eos_settings_title" translatable="false">UnifiedPush</string>
<string name="eos_settings_enable_title" translatable="true">Enable the distributor</string>
<string name="eos_settings_enable_description" translatable="true">It allows 3rd party applications to receive UnifiedPush notifications</string>
</resources>

View file

@ -1,11 +1,11 @@
<resources>
<!-- Main app theme; dark theme styles see values-night/styles.xml -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<item name="colorPrimary">@color/teal</item>
<item name="colorAccent">@color/teal</item> <!-- checkboxes, text fields -->
<style name="AppTheme" parent="Theme.MaterialComponents.DayNight.NoActionBar">
<item name="colorPrimary">@color/e_accent_light</item>
<item name="colorAccent">@color/e_accent_light</item> <!-- checkboxes, text fields -->
<item name="android:colorBackground">@color/white</item> <!-- background -->
<item name="android:statusBarColor">@color/teal</item>
<item name="actionModeBackground">@color/teal_dark</item>
<item name="android:statusBarColor">@color/e_accent_light</item>
<item name="actionModeBackground">@color/e_accent_light</item>
</style>
<style name="DangerText" parent="@android:style/TextAppearance">
@ -30,26 +30,4 @@
<item name="cornerFamily">rounded</item>
<item name="cornerSize">5dp</item>
</style>
<style name="PreferenceTheme" parent="PreferenceTheme.Base"/>
<style name="PreferenceTheme.Base" parent="Theme.Material3.DayNight.NoActionBar">
<item name="colorPrimary">@color/e_action_bar</item>
<item name="colorPrimaryDark">@color/e_action_bar</item>
<item name="colorAccent">@color/e_accent</item>
<item name="android:textColorPrimary">@color/e_primary_text_color</item>
<item name="android:textColorSecondary">@color/e_secondary_text_color</item>
<item name="android:textColorPrimaryInverse">@color/e_background</item>
<item name="android:windowBackground">@color/e_background</item>
<item name="colorControlActivated">@color/e_accent</item>
<item name="colorButtonNormal">@color/e_icon_color</item>
<item name="colorControlHighlight">@color/e_icon_color</item>
<item name="homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:homeAsUpIndicator">@drawable/e_ic_back</item>
<item name="android:popupBackground">@color/e_floating_background</item>
<item name="android:divider">@color/e_divider_color</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
<item name="android:statusBarColor">@color/e_background</item>
<item name="switchStyle">@style/ETheme.Switch</item>
</style>
</resources>

View file

@ -4,7 +4,7 @@
The translatable="false" attribute is just an additional safety. -->
<!-- Main app constants -->
<string name="app_name" translatable="false">ntfy</string>
<string name="app_name" translatable="false">Murena Box</string>
<string name="app_base_url" translatable="false">https://ntfy.sh</string> <!-- If changed, you must also change google-services.json! -->
<!-- Main activity -->

View file

@ -52,7 +52,7 @@
app:entryValues="@array/settings_general_dark_mode_values"
app:defaultValue="-1"/>
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_backup_restore_header">
<PreferenceCategory app:title="@string/settings_backup_restore_header" app:isPreferenceVisible="false">
<ListPreference
app:key="@string/settings_backup_restore_backup_key"
app:title="@string/settings_backup_restore_backup_title"
@ -96,7 +96,7 @@
app:title="@string/settings_advanced_clear_logs_title"
app:summary="@string/settings_advanced_clear_logs_summary"/>
</PreferenceCategory>
<PreferenceCategory app:title="@string/settings_about_header">
<PreferenceCategory app:title="@string/settings_about_header" app:isPreferenceVisible="false">
<Preference
app:key="@string/settings_about_version_key"
app:title="@string/settings_about_version_title"/>

View file

@ -1,11 +0,0 @@
<PreferenceScreen
xmlns:app="http://schemas.android.com/apk/res-auto">
<SwitchPreferenceCompat
app:icon="@drawable/ic_notification"
app:key="@string/eos_preference_key_is_enabled"
app:defaultValue="false"
app:title="@string/eos_settings_enable_title"
app:summary="@string/eos_settings_enable_description" />
</PreferenceScreen>

View file

@ -1,36 +0,0 @@
package io.heckel.ntfy.firebase
import com.google.firebase.messaging.FirebaseMessaging
import io.heckel.ntfy.util.Log
class FirebaseMessenger {
fun subscribe(topic: String) {
val firebase = maybeInstance() ?: return
firebase
.subscribeToTopic(topic)
.addOnCompleteListener {
Log.d(TAG, "Subscribing to topic $topic complete: result=${it.result}, exception=${it.exception}, successful=${it.isSuccessful}")
}
.addOnFailureListener { e ->
Log.e(TAG, "Subscribing to topic $topic failed: ${e.message}", e)
}
}
fun unsubscribe(topic: String) {
val firebase = maybeInstance() ?: return
firebase.unsubscribeFromTopic(topic)
}
private fun maybeInstance(): FirebaseMessaging? {
return try {
FirebaseMessaging.getInstance()
} catch (e: Exception) {
Log.e(TAG, "Firebase instance unavailable: ${e.message}", e)
null
}
}
companion object {
private const val TAG = "NtfyFirebase"
}
}

View file

@ -1,166 +0,0 @@
package io.heckel.ntfy.firebase
import android.content.Intent
import androidx.work.*
import com.google.firebase.messaging.FirebaseMessagingService
import com.google.firebase.messaging.RemoteMessage
import io.heckel.ntfy.R
import io.heckel.ntfy.app.Application
import io.heckel.ntfy.db.Attachment
import io.heckel.ntfy.db.Icon
import io.heckel.ntfy.db.Notification
import io.heckel.ntfy.util.Log
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationDispatcher
import io.heckel.ntfy.msg.NotificationParser
import io.heckel.ntfy.service.SubscriberService
import io.heckel.ntfy.util.nullIfZero
import io.heckel.ntfy.util.toPriority
import io.heckel.ntfy.util.topicShortUrl
import io.heckel.ntfy.work.PollWorker
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlin.random.Random
class FirebaseService : FirebaseMessagingService() {
private val repository by lazy { (application as Application).repository }
private val dispatcher by lazy { NotificationDispatcher(this, repository) }
private val job = SupervisorJob()
private val messenger = FirebaseMessenger()
private val parser = NotificationParser()
override fun onMessageReceived(remoteMessage: RemoteMessage) {
// Init log (this is done in all entrypoints)
Log.init(this)
// We only process data messages
if (remoteMessage.data.isEmpty()) {
Log.d(TAG, "Discarding unexpected message (1): from=${remoteMessage.from}")
return
}
// Dispatch event
val data = remoteMessage.data
when (data["event"]) {
ApiService.EVENT_MESSAGE -> handleMessage(remoteMessage)
ApiService.EVENT_KEEPALIVE -> handleKeepalive(remoteMessage)
ApiService.EVENT_POLL_REQUEST -> handlePollRequest(remoteMessage)
else -> Log.d(TAG, "Discarding unexpected message (2): from=${remoteMessage.from}, data=${data}")
}
}
private fun handleKeepalive(remoteMessage: RemoteMessage) {
Log.d(TAG, "Keepalive received, sending auto restart broadcast for foregrounds service")
sendBroadcast(Intent(this, SubscriberService.AutoRestartReceiver::class.java)) // Restart it if necessary!
val topic = remoteMessage.data["topic"]
if (topic != ApiService.CONTROL_TOPIC) {
Log.d(TAG, "Keepalive on non-control topic $topic received, subscribing to control topic ${ApiService.CONTROL_TOPIC}")
messenger.subscribe(ApiService.CONTROL_TOPIC)
}
}
private fun handlePollRequest(remoteMessage: RemoteMessage) {
val baseUrl = getString(R.string.app_base_url) // Everything from Firebase comes from main service URL!
val topic = remoteMessage.data["topic"] ?: return
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workName = "${PollWorker.WORK_NAME_ONCE_SINGE_PREFIX}_${baseUrl}_${topic}"
val workManager = WorkManager.getInstance(this)
val workRequest = OneTimeWorkRequest.Builder(PollWorker::class.java)
.setInputData(workDataOf(
PollWorker.INPUT_DATA_BASE_URL to baseUrl,
PollWorker.INPUT_DATA_TOPIC to topic
))
.setConstraints(constraints)
.build()
Log.d(TAG, "Poll request for ${topicShortUrl(baseUrl, topic)} received, scheduling unique poll worker with name $workName")
workManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest)
}
private fun handleMessage(remoteMessage: RemoteMessage) {
val data = remoteMessage.data
val id = data["id"]
val timestamp = data["time"]?.toLongOrNull()
val topic = data["topic"]
val title = data["title"]
val message = data["message"]
val priority = data["priority"]?.toIntOrNull()
val tags = data["tags"]
val click = data["click"]
val iconUrl = data["icon"]
val actions = data["actions"] // JSON array as string, sigh ...
val encoding = data["encoding"]
val attachmentName = data["attachment_name"] ?: "attachment.bin"
val attachmentType = data["attachment_type"]
val attachmentSize = data["attachment_size"]?.toLongOrNull()?.nullIfZero()
val attachmentExpires = data["attachment_expires"]?.toLongOrNull()?.nullIfZero()
val attachmentUrl = data["attachment_url"]
val truncated = (data["truncated"] ?: "") == "1"
if (id == null || topic == null || message == null || timestamp == null) {
Log.d(TAG, "Discarding unexpected message: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}")
return
}
Log.d(TAG, "Received message: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}")
CoroutineScope(job).launch {
val baseUrl = getString(R.string.app_base_url) // Everything from Firebase comes from main service URL!
// Check if notification was truncated and discard if it will (or likely already did) arrive via instant delivery
val subscription = repository.getSubscription(baseUrl, topic) ?: return@launch
if (truncated && subscription.instant) {
Log.d(TAG, "Discarding truncated message that did/will arrive via instant delivery: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}")
return@launch
}
// Add notification
val attachment = if (attachmentUrl != null) {
Attachment(
name = attachmentName,
type = attachmentType,
size = attachmentSize,
expires = attachmentExpires,
url = attachmentUrl,
)
} else null
val icon: Icon? = if (iconUrl != null && iconUrl != "") Icon(url = iconUrl) else null
val notification = Notification(
id = id,
subscriptionId = subscription.id,
timestamp = timestamp,
title = title ?: "",
message = message,
encoding = encoding ?: "",
priority = toPriority(priority),
tags = tags ?: "",
click = click ?: "",
icon = icon,
actions = parser.parseActions(actions),
attachment = attachment,
notificationId = Random.nextInt(),
deleted = false
)
if (repository.addNotification(notification)) {
Log.d(TAG, "Dispatching notification: from=${remoteMessage.from}, fcmprio=${remoteMessage.priority}, fcmprio_orig=${remoteMessage.originalPriority}, data=${data}")
dispatcher.dispatch(subscription, notification)
}
}
}
override fun onNewToken(token: String) {
// Called if the FCM registration token is updated
// We don't actually use or care about the token, since we're using topics
Log.d(TAG, "Registration token was updated: $token")
}
override fun onDestroy() {
super.onDestroy()
job.cancel()
}
companion object {
private const val TAG = "NtfyFirebase"
}
}

View file

@ -18,7 +18,6 @@ allprojects {
repositories {
google()
mavenCentral()
maven { url 'https://gitlab.e.foundation/api/v4/groups/9/-/packages/maven'}
maven { url "https://jitpack.io" } // For StfalconImageViewer
}
}