diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 6ca9d3c0..f2df392b 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
+
@@ -131,15 +132,14 @@
android:enabled="true"
android:exported="false"/>
-
+
-
-
+
diff --git a/app/src/main/java/io/heckel/ntfy/app/Application.kt b/app/src/main/java/io/heckel/ntfy/app/Application.kt
index d594d670..bc06f046 100644
--- a/app/src/main/java/io/heckel/ntfy/app/Application.kt
+++ b/app/src/main/java/io/heckel/ntfy/app/Application.kt
@@ -1,8 +1,13 @@
package io.heckel.ntfy.app
import android.app.Application
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import com.google.android.material.color.DynamicColors
import io.heckel.ntfy.db.Repository
+import io.heckel.ntfy.service.SubscriberServiceManager
import io.heckel.ntfy.util.Log
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
@@ -24,5 +29,21 @@ class Application : Application() {
if (repository.getDynamicColorsEnabled()) {
DynamicColors.applyToActivitiesIfAvailable(this)
}
+ registerNetworkCallback()
+ }
+
+ private fun registerNetworkCallback() {
+ val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+ val networkRequest = NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+ connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ SubscriberServiceManager.refresh(this@Application)
+ }
+ override fun onLost(network: Network) {
+ SubscriberServiceManager.refresh(this@Application)
+ }
+ })
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt
index 2dafa839..2bb28909 100644
--- a/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt
+++ b/app/src/main/java/io/heckel/ntfy/backup/Backuper.kt
@@ -90,6 +90,9 @@ class Backuper(val context: Context) {
if (settings.mutedUntil != null) {
repository.setGlobalMutedUntil(settings.mutedUntil)
}
+ if (settings.connectionAlertSeconds != null) {
+ repository.setConnectionAlertSeconds(settings.connectionAlertSeconds)
+ }
if (settings.lastSharedTopics != null) {
settings.lastSharedTopics.forEach { repository.addLastShareTopic(it) }
}
@@ -278,6 +281,7 @@ class Backuper(val context: Context) {
recordLogs = repository.getRecordLogs(),
defaultBaseUrl = repository.getDefaultBaseUrl() ?: "",
mutedUntil = repository.getGlobalMutedUntil(),
+ connectionAlertSeconds = repository.getConnectionAlertSeconds(),
lastSharedTopics = repository.getLastShareTopics()
)
}
@@ -421,6 +425,7 @@ data class Settings(
val recordLogs: Boolean?,
val defaultBaseUrl: String?,
val mutedUntil: Long?,
+ val connectionAlertSeconds: Long?,
val lastSharedTopics: List?,
)
diff --git a/app/src/main/java/io/heckel/ntfy/db/Repository.kt b/app/src/main/java/io/heckel/ntfy/db/Repository.kt
index a6716c74..fc3dd561 100644
--- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt
+++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt
@@ -440,8 +440,18 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database)
}
}
+ fun getConnectionAlertSeconds(): Long {
+ return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, CONNECTION_ALERT_DEFAULT)
+ }
+
+ fun setConnectionAlertSeconds(seconds: Long) {
+ sharedPrefs.edit {
+ putLong(SHARED_PREFS_CONNECTION_ALERT_SECONDS, seconds)
+ }
+ }
+
fun getConnectionAlertSnoozeUntil(): Long {
- return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, CONNECTION_ALERT_SNOOZE_UNTIL_DEFAULT)
+ return sharedPrefs.getLong(SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL, 0L)
}
fun setConnectionAlertSnoozeUntil(timeMillis: Long) {
@@ -608,6 +618,11 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database)
return connectionDetails.toMap()
}
+ fun clearConnectionDetails() {
+ connectionDetails.clear()
+ connectionDetailsLiveData.postValue(emptyMap())
+ }
+
fun getConnectionForceReconnectVersion(baseUrl: String): Long {
return connectionForceReconnectVersions[baseUrl] ?: 0L
}
@@ -639,9 +654,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database)
const val SHARED_PREFS_WEBSOCKET_RECONNECT_REMIND_TIME = "WebSocketReconnectRemindTime"
const val SHARED_PREFS_UNIFIED_PUSH_BASE_URL = "UnifiedPushBaseURL" // Legacy key required for migration to DefaultBaseURL
const val SHARED_PREFS_DEFAULT_BASE_URL = "DefaultBaseURL"
+ const val SHARED_PREFS_CONNECTION_ALERT_SECONDS = "ConnectionAlertSeconds"
const val SHARED_PREFS_CONNECTION_ALERT_SNOOZE_UNTIL = "ConnectionAlertSnoozeUntil"
- const val CONNECTION_ALERT_SNOOZE_UNTIL_DEFAULT = 0L
- const val CONNECTION_ALERT_NEVER_SHOW = Long.MAX_VALUE
const val SHARED_PREFS_LAST_TOPICS = "LastTopics"
private const val LAST_TOPICS_COUNT = 3
@@ -671,6 +685,14 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database)
const val INSISTENT_MAX_PRIORITY_USE_GLOBAL = -1 // Values must match values.xml
const val INSISTENT_MAX_PRIORITY_ENABLED = 1 // 0 = Disabled (but not needed in code)
+ const val CONNECTION_ALERT_NEVER = 0L
+ const val CONNECTION_ALERT_FIVE_MINUTES = 5 * 60L
+ const val CONNECTION_ALERT_FIFTEEN_MINUTES = 15 * 60L
+ const val CONNECTION_ALERT_ONE_HOUR = 60 * 60L
+ const val CONNECTION_ALERT_THREE_HOURS = 3 * 60 * 60L
+ const val CONNECTION_ALERT_TWELVE_HOURS = 12 * 60 * 60L
+ const val CONNECTION_ALERT_DEFAULT = CONNECTION_ALERT_NEVER
+
const val CONNECTION_PROTOCOL_JSONHTTP = "jsonhttp"
const val CONNECTION_PROTOCOL_WS = "ws"
diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
index 56d3233c..189537a0 100644
--- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt
@@ -11,6 +11,8 @@ import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.pm.ServiceInfo
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
import android.os.Build
import android.os.IBinder
import android.os.PowerManager
@@ -24,8 +26,6 @@ import io.heckel.ntfy.db.Repository
import io.heckel.ntfy.db.Subscription
import io.heckel.ntfy.msg.ApiService
import io.heckel.ntfy.msg.NotificationDispatcher
-import io.heckel.ntfy.msg.NotificationService
-import io.heckel.ntfy.util.PRIORITY_HIGH
import io.heckel.ntfy.ui.Colors
import io.heckel.ntfy.ui.MainActivity
import io.heckel.ntfy.util.HttpUtil
@@ -316,25 +316,40 @@ class SubscriberService : Service() {
}
private fun maybeShowConnectionAlert() {
+ val thresholdSeconds = repository.getConnectionAlertSeconds()
+ if (thresholdSeconds <= 0L) return
+
+ // Don't show alert if the device has no network connectivity (e.g. airplane mode)
+ if (!isNetworkAvailable()) return
+
val now = System.currentTimeMillis()
- // Check snooze / never-show-again
+ // Check snooze
val snoozeUntil = repository.getConnectionAlertSnoozeUntil()
- if (snoozeUntil == Repository.CONNECTION_ALERT_NEVER_SHOW) return
if (snoozeUntil > now) return
- // Check if any connection has been in error for 15+ minutes
+ // Check if any connection has been in error for longer than the threshold
+ val thresholdMillis = thresholdSeconds * 1000L
val allDetails = repository.getConnectionDetails()
val disconnectedUrls = allDetails.filter { (_, details) ->
details.hasError() && details.firstErrorTime > 0L &&
- (now - details.firstErrorTime) >= CONNECTION_ALERT_THRESHOLD_MILLIS
+ (now - details.firstErrorTime) >= thresholdMillis
}.keys
if (disconnectedUrls.isNotEmpty()) {
- showConnectionAlertNotification(disconnectedUrls)
+ val thresholdMinutes = (thresholdSeconds / 60).toInt()
+ showConnectionAlertNotification(disconnectedUrls, thresholdMinutes)
}
}
+ private fun isNetworkAvailable(): Boolean {
+ val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+ val network = connectivityManager.activeNetwork ?: return false
+ val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
+ return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ && capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED)
+ }
+
private fun maybeAutoDismissConnectionAlert() {
val allDetails = repository.getConnectionDetails()
val anyStillDisconnected = allDetails.any { (_, details) ->
@@ -345,36 +360,28 @@ class SubscriberService : Service() {
}
}
- private fun showConnectionAlertNotification(disconnectedUrls: Set) {
+ private fun showConnectionAlertNotification(disconnectedUrls: Set, thresholdMinutes: Int) {
val text = if (disconnectedUrls.size == 1) {
- getString(R.string.connection_alert_text_one, disconnectedUrls.first(), CONNECTION_ALERT_THRESHOLD_MINUTES)
+ getString(R.string.connection_alert_text_one, disconnectedUrls.first(), thresholdMinutes)
} else {
- getString(R.string.connection_alert_text_multiple, disconnectedUrls.size, CONNECTION_ALERT_THRESHOLD_MINUTES)
+ getString(R.string.connection_alert_text_multiple, disconnectedUrls.size, thresholdMinutes)
}
- val dismissIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply {
- action = CONNECTION_ALERT_ACTION_DISMISS
- }
- val dismissPendingIntent = PendingIntent.getBroadcast(this, 0, dismissIntent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
-
- val snoozeIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply {
- action = CONNECTION_ALERT_ACTION_SNOOZE
- }
- val snoozePendingIntent = PendingIntent.getBroadcast(this, 1, snoozeIntent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
-
- val neverIntent = Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply {
- action = CONNECTION_ALERT_ACTION_NEVER
- }
- val neverPendingIntent = PendingIntent.getBroadcast(this, 2, neverIntent,
- PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
-
val contentIntent = PendingIntent.getActivity(this, 0,
Intent(this, MainActivity::class.java), PendingIntent.FLAG_IMMUTABLE)
- val channelId = NotificationService(this).toChannelId(NotificationService.DEFAULT_GROUP, PRIORITY_HIGH)
- val notification = NotificationCompat.Builder(this, channelId)
+ val snoozeIntent = PendingIntent.getBroadcast(this, 0,
+ Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+ val disableIntent = PendingIntent.getBroadcast(this, 1,
+ Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_DISABLE },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+
+ val deleteIntent = PendingIntent.getBroadcast(this, 2,
+ Intent(this, ConnectionAlertBroadcastReceiver::class.java).apply { action = CONNECTION_ALERT_ACTION_SNOOZE },
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
+
+ val notification = NotificationCompat.Builder(this, NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notification)
.setColor(Colors.notificationIcon(this))
.setContentTitle(getString(R.string.connection_alert_title))
@@ -383,9 +390,9 @@ class SubscriberService : Service() {
.setContentIntent(contentIntent)
.setAutoCancel(true)
.setOnlyAlertOnce(true)
- .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_dismiss), dismissPendingIntent).build())
- .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze), snoozePendingIntent).build())
- .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_never), neverPendingIntent).build())
+ .setDeleteIntent(deleteIntent)
+ .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_snooze, CONNECTION_ALERT_SNOOZE_HOURS), snoozeIntent).build())
+ .addAction(NotificationCompat.Action.Builder(0, getString(R.string.connection_alert_action_disable), disableIntent).build())
.build()
Log.d(TAG, "Showing connection alert notification")
@@ -440,6 +447,9 @@ class SubscriberService : Service() {
it
}
notificationManager.createNotificationChannel(channel)
+ val connectionAlertChannelName = getString(R.string.channel_connection_alert_name)
+ val connectionAlertChannel = NotificationChannel(NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID, connectionAlertChannelName, NotificationManager.IMPORTANCE_DEFAULT)
+ notificationManager.createNotificationChannel(connectionAlertChannel)
return notificationManager
}
@@ -500,22 +510,16 @@ class SubscriberService : Service() {
Log.d(TAG, "ConnectionAlertBroadcastReceiver: action=${intent.action}")
val repository = Repository.getInstance(context)
val notificationManager = context.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
-
when (intent.action) {
- CONNECTION_ALERT_ACTION_DISMISS -> {
- notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID)
- }
CONNECTION_ALERT_ACTION_SNOOZE -> {
- repository.setConnectionAlertSnoozeUntil(
- System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS
- )
- notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID)
+ repository.setConnectionAlertSnoozeUntil(System.currentTimeMillis() + CONNECTION_ALERT_SNOOZE_DURATION_MILLIS)
}
- CONNECTION_ALERT_ACTION_NEVER -> {
- repository.setConnectionAlertSnoozeUntil(Repository.CONNECTION_ALERT_NEVER_SHOW)
- notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID)
+ CONNECTION_ALERT_ACTION_DISABLE -> {
+ repository.setConnectionAlertSeconds(Repository.CONNECTION_ALERT_NEVER)
}
+ else -> return
}
+ notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID)
}
}
@@ -531,16 +535,15 @@ class SubscriberService : Service() {
private const val WAKE_LOCK_TAG = "SubscriberService:lock"
private const val NOTIFICATION_CHANNEL_ID = "ntfy-subscriber"
+ private const val NOTIFICATION_CONNECTION_ALERT_CHANNEL_ID = "ntfy-connection-alert"
private const val NOTIFICATION_GROUP_ID = "io.heckel.ntfy.NOTIFICATION_GROUP_SERVICE"
private const val NOTIFICATION_SERVICE_ID = 2586
private const val NOTIFICATION_RECEIVED_WAKELOCK_TIMEOUT_MILLIS = 10 * 60 * 1000L /*10 minutes*/
private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587
- private const val CONNECTION_ALERT_THRESHOLD_MINUTES = 15
- private const val CONNECTION_ALERT_THRESHOLD_MILLIS = CONNECTION_ALERT_THRESHOLD_MINUTES * 60 * 1000L
- private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = 60 * 60 * 1000L /*1 hour*/
- private const val CONNECTION_ALERT_ACTION_DISMISS = "io.heckel.ntfy.CONNECTION_ALERT_DISMISS"
+ private const val CONNECTION_ALERT_SNOOZE_HOURS = 8
+ private const val CONNECTION_ALERT_SNOOZE_DURATION_MILLIS = CONNECTION_ALERT_SNOOZE_HOURS * 60 * 60 * 1000L
private const val CONNECTION_ALERT_ACTION_SNOOZE = "io.heckel.ntfy.CONNECTION_ALERT_SNOOZE"
- private const val CONNECTION_ALERT_ACTION_NEVER = "io.heckel.ntfy.CONNECTION_ALERT_NEVER"
+ private const val CONNECTION_ALERT_ACTION_DISABLE = "io.heckel.ntfy.CONNECTION_ALERT_DISABLE"
}
}
diff --git a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
index 71f51b48..8109e66d 100644
--- a/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
+++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberServiceManager.kt
@@ -1,7 +1,10 @@
package io.heckel.ntfy.service
+import android.app.NotificationManager
import android.content.Context
import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.NetworkCapabilities
import androidx.core.content.ContextCompat
import androidx.work.*
import io.heckel.ntfy.app.Application
@@ -40,8 +43,9 @@ class SubscriberServiceManager(private val context: Context) {
withContext(Dispatchers.IO) {
val app = context.applicationContext as Application
val subscriptionIdsWithInstantStatus = app.repository.getSubscriptionIdsWithInstantStatus()
+ val hasNetwork = isNetworkAvailable(context)
val instantSubscriptions = subscriptionIdsWithInstantStatus.toList().filter { (_, instant) -> instant }.size
- if (instantSubscriptions > 0) {
+ if (instantSubscriptions > 0 && hasNetwork) {
// We have instant subscriptions, start the service
Log.d(TAG, "ServiceStartWorker: Starting foreground service (work ID: ${id})")
Intent(context, SubscriberService::class.java).also {
@@ -56,9 +60,14 @@ class SubscriberServiceManager(private val context: Context) {
}
}
} else {
- // No instant subscriptions, stop the service using stopService()
+ // No instant subscriptions (or no network), stop the service using stopService()
// This avoids ForegroundServiceDidNotStartInTimeException, see #1520
Log.d(TAG, "ServiceStartWorker: Stopping service (work ID: ${id})")
+ if (!hasNetwork) {
+ app.repository.clearConnectionDetails()
+ val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
+ notificationManager.cancel(NOTIFICATION_CONNECTION_ALERT_ID)
+ }
Intent(context, SubscriberService::class.java).also {
context.stopService(it)
}
@@ -71,6 +80,14 @@ class SubscriberServiceManager(private val context: Context) {
companion object {
const val TAG = "NtfySubscriberMgr"
const val WORK_NAME_ONCE = "ServiceStartWorkerOnce"
+ private const val NOTIFICATION_CONNECTION_ALERT_ID = 2587 // Same as SubscriberService
+
+ private fun isNetworkAvailable(context: Context): Boolean {
+ val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
+ val network = connectivityManager.activeNetwork ?: return false
+ val capabilities = connectivityManager.getNetworkCapabilities(network) ?: return false
+ return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ }
fun refresh(context: Context) {
val manager = SubscriberServiceManager(context)
diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
index 251a4ba0..2ce74222 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt
@@ -6,8 +6,13 @@ import android.animation.AnimatorListenerAdapter
import android.app.AlarmManager
import android.app.AlertDialog
import android.content.ActivityNotFoundException
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkRequest
import android.os.Build
import android.os.Bundle
import android.provider.Settings
@@ -95,6 +100,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
private lateinit var fab: FloatingActionButton
// Other stuff
+ private var networkCallback: ConnectivityManager.NetworkCallback? = null
private var workManager: WorkManager? = null // Context-dependent
private var dispatcher: NotificationDispatcher? = null // Context-dependent
private var appBaseUrl: String? = null // Context-dependent
@@ -342,6 +348,22 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
}
}
+ // Network state banner
+ showHideNoNetworkBanner()
+ val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+ networkCallback = object : ConnectivityManager.NetworkCallback() {
+ override fun onAvailable(network: Network) {
+ runOnUiThread { showHideNoNetworkBanner() }
+ }
+ override fun onLost(network: Network) {
+ runOnUiThread { showHideNoNetworkBanner() }
+ }
+ }
+ val networkRequest = NetworkRequest.Builder()
+ .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+ .build()
+ connectivityManager.registerNetworkCallback(networkRequest, networkCallback!!)
+
// Hide links that lead to payments, see https://github.com/binwiederhier/ntfy/issues/1463
val howToLink = findViewById(R.id.main_how_to_link)
howToLink.isVisible = BuildConfig.PAYMENT_LINKS_AVAILABLE
@@ -377,6 +399,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
super.onResume()
showHideNotificationMenuItems()
showHideConnectionErrorMenuItem(repository.getConnectionDetails())
+ showHideNoNetworkBanner()
redrawList()
}
@@ -425,6 +448,30 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific
}
}
+ private fun showHideNoNetworkBanner() {
+ val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+ val network = connectivityManager.activeNetwork
+ val hasNetwork = if (network != null) {
+ val capabilities = connectivityManager.getNetworkCapabilities(network)
+ capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) == true
+ } else {
+ false
+ }
+ val banner = findViewById(R.id.main_banner_no_network)
+ banner.visibility = if (hasNetwork) View.GONE else View.VISIBLE
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ try {
+ val connectivityManager = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
+ networkCallback?.let { connectivityManager.unregisterNetworkCallback(it) }
+ } catch (e: Exception) {
+ Log.d(TAG, "Failed to unregister network callback: ${e.message}")
+ }
+ networkCallback = null
+ }
+
private fun schedulePeriodicPollWorker() {
val workerVersion = repository.getPollWorkerVersion()
val workPolicy = if (workerVersion == PollWorker.VERSION) {
diff --git a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt
index 8ebf2f87..ba8c9dc1 100644
--- a/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt
+++ b/app/src/main/java/io/heckel/ntfy/ui/SettingsActivity.kt
@@ -327,6 +327,32 @@ class SettingsActivity : AppCompatActivity(), PreferenceFragmentCompat.OnPrefere
}
}
+ // Connection alert
+ val connectionAlertPrefId = context?.getString(R.string.settings_notifications_connection_alert_key) ?: return
+ val connectionAlert: ListPreference? = findPreference(connectionAlertPrefId)
+ connectionAlert?.value = repository.getConnectionAlertSeconds().toString()
+ connectionAlert?.preferenceDataStore = object : PreferenceDataStore() {
+ override fun putString(key: String?, value: String?) {
+ val seconds = value?.toLongOrNull() ?:return
+ repository.setConnectionAlertSeconds(seconds)
+ }
+ override fun getString(key: String?, defValue: String?): String {
+ return repository.getConnectionAlertSeconds().toString()
+ }
+ }
+ connectionAlert?.summaryProvider = Preference.SummaryProvider { pref ->
+ when (pref.value.toLongOrNull() ?: repository.getConnectionAlertSeconds()) {
+ Repository.CONNECTION_ALERT_NEVER -> getString(R.string.settings_notifications_connection_alert_summary_never)
+ 30L -> "Alert after 30 seconds (testing)"
+ Repository.CONNECTION_ALERT_FIVE_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_five_minutes)
+ Repository.CONNECTION_ALERT_FIFTEEN_MINUTES -> getString(R.string.settings_notifications_connection_alert_summary_fifteen_minutes)
+ Repository.CONNECTION_ALERT_ONE_HOUR -> getString(R.string.settings_notifications_connection_alert_summary_one_hour)
+ Repository.CONNECTION_ALERT_THREE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_three_hours)
+ Repository.CONNECTION_ALERT_TWELVE_HOURS -> getString(R.string.settings_notifications_connection_alert_summary_twelve_hours)
+ else -> getString(R.string.settings_notifications_connection_alert_summary_never) // Must match default const
+ }
+ }
+
// Dark mode
val darkModePrefId = context?.getString(R.string.settings_general_dark_mode_key) ?: return
val darkMode: ListPreference? = findPreference(darkModePrefId)
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 1bf20371..282e2c7a 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -232,6 +232,22 @@
+
+
+ app:layout_constraintTop_toBottomOf="@id/main_banner_no_network">
Връзката е загубена
От най-малко %2$d минути няма връзка с %1$s
От най-малко %2$d минути няма връзка със сървърите на %1$d
- Отхвърляне
- Отлагане с 1ч
- Да не се показва повече
+
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 41d8ddb6..dddbc759 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -474,7 +474,5 @@
Verbindung verloren
Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$s hergestellt werden
Es konnte länger als %2$d Minuten lang keine Verbindung zu %1$d Servern hergestellt werden
- Verwerfen
- 1 Stunde schlummern
- Niemals zeigen
+
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 2151e87c..129c9a79 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -413,9 +413,7 @@
Conexión perdida
No se pudo conectar a %1$s por más de %2$d minutos
No se pudo conectar a %1$d servidores por más de %2$d minutos
- Ignorar
- Suspender 1h
- Nunca mostrar
+
Su búsqueda no tuvo ningún resultado
Buscar notificaciones
Buscar en notificaciones
diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml
index 0d9b6459..c18876c5 100644
--- a/app/src/main/res/values-et/strings.xml
+++ b/app/src/main/res/values-et/strings.xml
@@ -464,7 +464,5 @@
Ühendus on katkenud
Ühendus %1$s teenusega toimib vaid %2$d minuti(t)
Ühendus %1$d serveriga toimib vaid %2$d minuti(t)
- Loobu
- Tukasta 1t
- Ära näita iialgi
+
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index c4b04a7e..22a1fda1 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -474,7 +474,5 @@
Connexion perdue
Impossible de se connecter à %1$s depuis plus de %2$d minutes
Impossible de se connecter à %1$d serveurs depuis plus de %2$d minutes
- Ignorer
- Sourdine 1h
- Ne jamais montrer
+
diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
index aea03527..d0ecf643 100644
--- a/app/src/main/res/values-ja/strings.xml
+++ b/app/src/main/res/values-ja/strings.xml
@@ -474,7 +474,4 @@
接続が切断されました
%2$d 分以上 %1$s に接続できませんでした
%1$d 個のサーバーに %2$d 分以上接続できませんでした
- 了解
- 1時間後に再通知
- 再表示しない
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index a566e76d..2ee259f6 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -378,9 +378,7 @@
Verbinding verbroken
Kan geen verbinding maken met %1$s voor meer dan %2$d minuten
Kan geen verbinding maken met %1$d servers voor meer dan %2$d minuten
- Afwijzen
- Sluimer voor 1 uur
- Nooit laten zien
+
Publieer naar %1$s
Uploaden: %1$s (%2$s / %3$s)
Upload geannuleerd
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index bf4ef4a6..00489ea8 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -474,7 +474,5 @@
连接丢失
已无法连接到 %1$s 超过 %2$d 分钟
已无法连接到 %1$d 台服务器超过 %2$d 分钟
- 关闭
- 延后 1 小时
- 永不显示
+
diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
index 77191ab5..920d6956 100644
--- a/app/src/main/res/values-zh-rTW/strings.xml
+++ b/app/src/main/res/values-zh-rTW/strings.xml
@@ -474,7 +474,5 @@
連線已中斷
超過 %2$d 分鐘無法連線至 %1$s
超過 %2$d 分鐘無法連線至 %1$d 個伺服器
- 關閉
- 延後 1 小時
- 永不顯示
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 986a33e4..f2eb6bce 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -29,6 +29,7 @@
Default
Subscription Service
+ Connection alerts
Listening for incoming notifications
Subscribed to instant delivery topics
Subscribed to one instant delivery topic
@@ -49,11 +50,10 @@
Connection lost
- Unable to connect to %1$s for more than %2$d minutes
- Unable to connect to %1$d servers for more than %2$d minutes
- Dismiss
- Snooze 1h
- Never show
+ Unable to connect to %1$s for more than %2$d minutes. Check your network connection.
+ Unable to connect to %1$d servers for more than %2$d minutes. Check your network connection.
+ Snooze %1$dh
+ Disable alerts
%1$d notification(s) received
@@ -115,6 +115,7 @@
Ask later
Dismiss
Grant now
+ No network
Subscribe to topic
@@ -370,6 +371,19 @@
After one week
After one month
After 3 months
+ Alert when connection is lost
+ Never alert when connection is lost
+ Alert after 5 minutes without connection
+ Alert after 15 minutes without connection
+ Alert after 1 hour without connection
+ Alert after 3 hours without connection
+ Alert after 12 hours without connection
+ Never
+ After 5 minutes
+ After 15 minutes
+ After 1 hour
+ After 3 hours
+ After 12 hours
Keep alerting for highest priority
Max priority notifications continuously alert until dismissed
Max priority notifications only alert once
diff --git a/app/src/main/res/values/values.xml b/app/src/main/res/values/values.xml
index 0b45baf5..bc1e156e 100644
--- a/app/src/main/res/values/values.xml
+++ b/app/src/main/res/values/values.xml
@@ -47,6 +47,7 @@
SubscriptionMinPriority
SubscriptionAutoDelete
SubscriptionInsistentMaxPriority
+ ConnectionAlert
SubscriptionAppearance
SubscriptionIconSet
SubscriptionIconRemove
@@ -167,6 +168,24 @@
- 1
- 0
+
+ - @string/settings_notifications_connection_alert_never
+ - After 30 seconds (testing)
+ - @string/settings_notifications_connection_alert_five_minutes
+ - @string/settings_notifications_connection_alert_fifteen_minutes
+ - @string/settings_notifications_connection_alert_one_hour
+ - @string/settings_notifications_connection_alert_three_hours
+ - @string/settings_notifications_connection_alert_twelve_hours
+
+
+ - 0
+ - 30
+ - 300
+ - 900
+ - 3600
+ - 10800
+ - 43200
+
- @string/settings_advanced_connection_protocol_entry_jsonhttp
- @string/settings_advanced_connection_protocol_entry_ws
diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml
index 1ef47d3e..dd0dd085 100644
--- a/app/src/main/res/xml/main_preferences.xml
+++ b/app/src/main/res/xml/main_preferences.xml
@@ -29,6 +29,12 @@
app:key="@string/settings_notifications_insistent_max_priority_key"
app:title="@string/settings_notifications_insistent_max_priority_title"
app:defaultValue="false"/>
+