From 00a16f454badf00943949301eb5b9ac4bf2f53c1 Mon Sep 17 00:00:00 2001 From: Philipp Heckel Date: Sun, 11 Jan 2026 17:17:48 -0500 Subject: [PATCH] Combine ConnectionState and error details --- .../main/java/io/heckel/ntfy/db/Database.kt | 19 ++--- .../main/java/io/heckel/ntfy/db/Repository.kt | 72 ++++++------------- .../io/heckel/ntfy/service/JsonConnection.kt | 15 ++-- .../heckel/ntfy/service/SubscriberService.kt | 26 +++---- .../io/heckel/ntfy/service/WsConnection.kt | 8 +-- .../heckel/ntfy/ui/ConnectionErrorFragment.kt | 49 ++++++------- .../java/io/heckel/ntfy/ui/DetailActivity.kt | 15 ++-- .../java/io/heckel/ntfy/ui/MainActivity.kt | 15 ++-- .../java/io/heckel/ntfy/ui/MainAdapter.kt | 4 +- .../main/res/menu/menu_detail_action_bar.xml | 12 ++-- .../main/res/menu/menu_main_action_bar.xml | 12 ++-- 11 files changed, 102 insertions(+), 145 deletions(-) diff --git a/app/src/main/java/io/heckel/ntfy/db/Database.kt b/app/src/main/java/io/heckel/ntfy/db/Database.kt index 27bd14cc..278f2482 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -28,7 +28,7 @@ data class Subscription( @Ignore val totalCount: Int = 0, // Total notifications @Ignore val newCount: Int = 0, // New notifications @Ignore val lastActive: Long = 0, // Unix timestamp - @Ignore val state: ConnectionState = ConnectionState.NOT_APPLICABLE + @Ignore val connectionDetails: ConnectionDetails = ConnectionDetails() ) { constructor( id: Long, @@ -64,7 +64,7 @@ data class Subscription( totalCount = 0, newCount = 0, lastActive = 0, - state = ConnectionState.NOT_APPLICABLE + connectionDetails = ConnectionDetails() ) } @@ -73,19 +73,22 @@ enum class ConnectionState { } /** - * Represents a connection error for a specific baseUrl. + * Represents connection details for a specific baseUrl, including state and error info. * This is not persisted to the database, but kept in memory. */ -data class ConnectionError( - val baseUrl: String, - val message: String, - val throwable: Throwable?, - val timestamp: Long = System.currentTimeMillis(), +data class ConnectionDetails( + val state: ConnectionState = ConnectionState.NOT_APPLICABLE, + val error: String? = null, + val throwable: Throwable? = null, val nextRetryTime: Long = 0L ) { fun getStackTraceString(): String { return throwable?.stackTraceToString() ?: "" } + + fun hasError(): Boolean { + return error != null + } } data class SubscriptionWithMetadata( 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 d9d168e9..ba631cae 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Repository.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Repository.kt @@ -25,11 +25,8 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) private val clientCertificateDao = database.clientCertificateDao() private val customHeaderDao = database.customHeaderDao() - private val connectionStates = ConcurrentHashMap() - private val connectionStatesLiveData = MutableLiveData(connectionStates) - - private val connectionErrors = ConcurrentHashMap() - private val connectionErrorsLiveData = MutableLiveData>(emptyMap()) + private val connectionDetails = ConcurrentHashMap() + private val connectionDetailsLiveData = MutableLiveData>(connectionDetails) // TODO Move these into an ApplicationState singleton val detailViewSubscriptionId = AtomicLong(0L) // Omg, what a hack ... @@ -43,7 +40,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) return subscriptionDao .listFlow() .asLiveData() - .combineWith(connectionStatesLiveData) { subscriptionsWithMetadata, _ -> + .combineWith(connectionDetailsLiveData) { subscriptionsWithMetadata, _ -> toSubscriptionList(subscriptionsWithMetadata.orEmpty()) } } @@ -500,7 +497,6 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) private fun toSubscriptionList(list: List): List { return list.map { s -> - val connectionState = connectionStates.getOrElse(s.id) { ConnectionState.NOT_APPLICABLE } Subscription( id = s.id, baseUrl = s.baseUrl, @@ -519,7 +515,7 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, - state = connectionState + connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails() ) } } @@ -546,60 +542,34 @@ class Repository(private val sharedPrefs: SharedPreferences, database: Database) totalCount = s.totalCount, newCount = s.newCount, lastActive = s.lastActive, - state = getState(s.id) + connectionDetails = connectionDetails[s.baseUrl] ?: ConnectionDetails() ) } - fun updateState(subscriptionIds: Collection, newState: ConnectionState) { - var changed = false - subscriptionIds.forEach { subscriptionId -> - val state = connectionStates.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE } - if (state !== newState) { - changed = true - if (newState == ConnectionState.NOT_APPLICABLE) { - connectionStates.remove(subscriptionId) - } else { - connectionStates[subscriptionId] = newState - } + fun updateConnectionDetails(baseUrl: String, state: ConnectionState, error: String? = null, throwable: Throwable? = null, nextRetryTime: Long = 0L) { + val details = ConnectionDetails(state, error, throwable, nextRetryTime) + val current = connectionDetails[baseUrl] + if (current != details) { + if (state == ConnectionState.NOT_APPLICABLE && error == null) { + connectionDetails.remove(baseUrl) + } else { + connectionDetails[baseUrl] = details } - } - if (changed) { - connectionStatesLiveData.postValue(connectionStates) + connectionDetailsLiveData.postValue(connectionDetails.toMap()) + Log.d(TAG, "Connection details updated for $baseUrl: state=$state, error=$error, nextRetry=$nextRetryTime") } } - private fun getState(subscriptionId: Long): ConnectionState { - return connectionStatesLiveData.value!!.getOrElse(subscriptionId) { ConnectionState.NOT_APPLICABLE } + fun getConnectionDetailsLiveData(): LiveData> { + return connectionDetailsLiveData } - fun getConnectionErrorsLiveData(): LiveData> { - return connectionErrorsLiveData + fun getConnectionDetails(): Map { + return connectionDetails.toMap() } - fun getConnectionErrors(): Map { - return connectionErrors.toMap() - } - - fun updateConnectionError(baseUrl: String, message: String, throwable: Throwable?, nextRetryTime: Long = 0L) { - val error = ConnectionError(baseUrl, message, throwable, System.currentTimeMillis(), nextRetryTime) - connectionErrors[baseUrl] = error - connectionErrorsLiveData.postValue(connectionErrors.toMap()) - Log.d(TAG, "Connection error updated for $baseUrl: $message (next retry at $nextRetryTime)") - } - - fun clearConnectionError(baseUrl: String) { - if (connectionErrors.remove(baseUrl) != null) { - connectionErrorsLiveData.postValue(connectionErrors.toMap()) - Log.d(TAG, "Connection error cleared for $baseUrl") - } - } - - fun clearAllConnectionErrors() { - if (connectionErrors.isNotEmpty()) { - connectionErrors.clear() - connectionErrorsLiveData.postValue(emptyMap()) - Log.d(TAG, "All connection errors cleared") - } + fun getConnectionDetailsForBaseUrl(baseUrl: String): ConnectionDetails? { + return connectionDetails[baseUrl] } companion object { diff --git a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt index 2fb1fb93..264d86fb 100644 --- a/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt +++ b/app/src/main/java/io/heckel/ntfy/service/JsonConnection.kt @@ -15,9 +15,8 @@ class JsonConnection( private val api: ApiService, private val user: User?, private val sinceId: String?, - private val stateChangeListener: (Collection, ConnectionState) -> Unit, + private val connectionDetailsListener: (Collection, ConnectionState, Throwable?, Long) -> Unit, private val notificationListener: (Subscription, Notification) -> Unit, - private val errorListener: (String, Throwable, Long) -> Unit, private val serviceActive: () -> Boolean ) : Connection { private val baseUrl = connectionId.baseUrl @@ -51,9 +50,6 @@ class JsonConnection( val fail = { e: Exception -> failed.set(true) lastError = e - if (isActive && serviceActive()) { // Avoid UI update races if we're restarting a connection - stateChangeListener(subscriptionIds, ConnectionState.CONNECTING) - } } // Call /json subscribe endpoint and loop until the call fails, is canceled, @@ -61,23 +57,20 @@ class JsonConnection( try { call = api.subscribe(baseUrl, topicsStr, since, user, notify, fail) while (!failed.get() && !call.isCanceled() && isActive && serviceActive()) { - stateChangeListener(subscriptionIds, ConnectionState.CONNECTED) + connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTED, null, 0L) Log.d(TAG,"[$url] Connection is active (failed=$failed, callCanceled=${call.isCanceled()}, jobActive=$isActive, serviceStarted=${serviceActive()}") delay(CONNECTION_LOOP_DELAY_MILLIS) // Resumes immediately if job is cancelled } } catch (e: Exception) { Log.e(TAG, "[$url] Connection failed: ${e.message}", e) lastError = e - if (isActive && serviceActive()) { // Avoid UI update races if we're restarting a connection - stateChangeListener(subscriptionIds, ConnectionState.CONNECTING) - } } // If we're not cancelled yet, wait little before retrying (incremental back-off) - if (isActive && serviceActive()) { + if (isActive && serviceActive() && lastError != null) { retryMillis = nextRetryMillis(retryMillis, startTime) val nextRetryTime = System.currentTimeMillis() + retryMillis - lastError?.let { errorListener(baseUrl, it, nextRetryTime) } + connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTING, lastError, nextRetryTime) Log.d(TAG, "[$url] Connection failed, retrying connection in ${retryMillis / 1000}s ...") delay(retryMillis) } 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 223691f7..3c31d7b5 100644 --- a/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt +++ b/app/src/main/java/io/heckel/ntfy/service/SubscriberService.kt @@ -262,9 +262,9 @@ class SubscriberService : Service() { val connection = if (connectionId.connectionProtocol == Repository.CONNECTION_PROTOCOL_WS) { val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager val httpClient = HttpUtil.wsClient(this, connectionId.baseUrl) - WsConnection(connectionId, repository, httpClient, user, customHeaders, since, ::onStateChanged, ::onNotificationReceived, ::onConnectionError, alarmManager) + WsConnection(connectionId, repository, httpClient, user, customHeaders, since, ::onConnectionDetailsChanged, ::onNotificationReceived, alarmManager) } else { - JsonConnection(connectionId, scope, repository, api, user, since, ::onStateChanged, ::onNotificationReceived, ::onConnectionError, serviceActive) + JsonConnection(connectionId, scope, repository, api, user, since, ::onConnectionDetailsChanged, ::onNotificationReceived, serviceActive) } connections[connectionId] = connection connection.start() @@ -305,22 +305,12 @@ class SubscriberService : Service() { } } - private fun onStateChanged(subscriptionIds: Collection, state: ConnectionState) { - repository.updateState(subscriptionIds, state) - // Clear connection error when successfully connected - if (state == ConnectionState.CONNECTED) { - subscriptionIds.firstOrNull()?.let { subscriptionId -> - val subscription = repository.getSubscription(subscriptionId) - if (subscription != null) { - repository.clearConnectionError(subscription.baseUrl) - } - } - } - } - - private fun onConnectionError(baseUrl: String, throwable: Throwable, nextRetryTime: Long) { - val message = throwable.message ?: "Unknown error" - repository.updateConnectionError(baseUrl, message, throwable, nextRetryTime) + private fun onConnectionDetailsChanged(subscriptionIds: Collection, state: ConnectionState, throwable: Throwable?, nextRetryTime: Long) { + val subscriptionId = subscriptionIds.firstOrNull() ?: return + val subscription = repository.getSubscription(subscriptionId) ?: return + val baseUrl = subscription.baseUrl + val errorMessage = throwable?.message + repository.updateConnectionDetails(baseUrl, state, errorMessage, throwable, nextRetryTime) } private fun onNotificationReceived(subscription: Subscription, notification: io.heckel.ntfy.db.Notification) { diff --git a/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt b/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt index d0ad9658..8b49b660 100644 --- a/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt +++ b/app/src/main/java/io/heckel/ntfy/service/WsConnection.kt @@ -40,9 +40,8 @@ class WsConnection( private val user: User?, private val customHeaders: List, private val sinceId: String?, - private val stateChangeListener: (Collection, ConnectionState) -> Unit, + private val connectionDetailsListener: (Collection, ConnectionState, Throwable?, Long) -> Unit, private val notificationListener: (Subscription, Notification) -> Unit, - private val errorListener: (String, Throwable, Long) -> Unit, private val alarmManager: AlarmManager ) : Connection { private val parser = NotificationParser() @@ -143,7 +142,7 @@ class WsConnection( if (errorCount > 0) { errorCount = 0 } - stateChangeListener(subscriptionIds, ConnectionState.CONNECTED) + connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTED, null, 0L) } } @@ -183,12 +182,11 @@ class WsConnection( Log.d(TAG, "$shortUrl (gid=$globalId, lid=$id): Connection marked as closed. Not retrying.") return@synchronize } - stateChangeListener(subscriptionIds, ConnectionState.CONNECTING) state = State.Disconnected errorCount++ val retrySeconds = RETRY_SECONDS.getOrNull(errorCount) ?: RETRY_SECONDS.last() val nextRetryTime = System.currentTimeMillis() + (retrySeconds * 1000L) - errorListener(baseUrl, t, nextRetryTime) + connectionDetailsListener(subscriptionIds, ConnectionState.CONNECTING, t, nextRetryTime) scheduleReconnect(retrySeconds) } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt b/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt index b888b7fc..6c23e6fd 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt @@ -17,14 +17,14 @@ import com.google.android.material.chip.Chip import com.google.android.material.color.MaterialColors import com.google.android.material.textfield.TextInputLayout import io.heckel.ntfy.R -import io.heckel.ntfy.db.ConnectionError +import io.heckel.ntfy.db.ConnectionDetails import io.heckel.ntfy.db.Repository import io.heckel.ntfy.service.SubscriberServiceManager import io.heckel.ntfy.util.copyToClipboard class ConnectionErrorFragment : DialogFragment() { private lateinit var repository: Repository - private var connectionErrors: Map = emptyMap() + private var connectionDetails: Map = emptyMap() private var selectedBaseUrl: String? = null private var filterBaseUrl: String? = null @@ -57,12 +57,12 @@ class ConnectionErrorFragment : DialogFragment() { // Dependencies repository = Repository.getInstance(requireContext()) - // Get connection errors, optionally filtered by baseUrl - val allErrors = repository.getConnectionErrors() - connectionErrors = if (filterBaseUrl != null) { - allErrors.filterKeys { it == filterBaseUrl } + // Get connection details with errors, optionally filtered by baseUrl + val allDetails = repository.getConnectionDetails() + connectionDetails = if (filterBaseUrl != null) { + allDetails.filterKeys { it == filterBaseUrl }.filterValues { it.hasError() } } else { - allErrors + allDetails.filterValues { it.hasError() } } // Build root view @@ -97,7 +97,7 @@ class ConnectionErrorFragment : DialogFragment() { stackTraceTextView = view.findViewById(R.id.connection_error_dialog_stack_trace) // Setup server dropdown if multiple errors - val baseUrls = connectionErrors.keys.toList() + val baseUrls = connectionDetails.keys.toList() if (baseUrls.size > 1) { serverLayout.visibility = View.VISIBLE val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, baseUrls) @@ -126,12 +126,12 @@ class ConnectionErrorFragment : DialogFragment() { dismiss() } - // Observe connection errors to update countdown when it changes - repository.getConnectionErrorsLiveData().observe(this) { errors -> - connectionErrors = if (filterBaseUrl != null) { - errors.filterKeys { it == filterBaseUrl } + // Observe connection details to update countdown when it changes + repository.getConnectionDetailsLiveData().observe(this) { details -> + connectionDetails = if (filterBaseUrl != null) { + details.filterKeys { it == filterBaseUrl }.filterValues { it.hasError() } } else { - errors + details.filterValues { it.hasError() } } updateErrorDisplay() } @@ -162,10 +162,10 @@ class ConnectionErrorFragment : DialogFragment() { } private fun updateErrorDisplay() { - val error = selectedBaseUrl?.let { connectionErrors[it] } - if (error != null) { - errorTextView.text = error.message - stackTraceTextView.text = error.getStackTraceString().ifEmpty { + val details = selectedBaseUrl?.let { connectionDetails[it] } + if (details != null && details.hasError()) { + errorTextView.text = details.error + stackTraceTextView.text = details.getStackTraceString().ifEmpty { getString(R.string.connection_error_dialog_no_stack_trace) } } else { @@ -177,9 +177,9 @@ class ConnectionErrorFragment : DialogFragment() { } private fun updateCountdown() { - val error = selectedBaseUrl?.let { connectionErrors[it] } - if (error != null && error.nextRetryTime > 0) { - val remainingMillis = error.nextRetryTime - System.currentTimeMillis() + val details = selectedBaseUrl?.let { connectionDetails[it] } + if (details != null && details.nextRetryTime > 0) { + val remainingMillis = details.nextRetryTime - System.currentTimeMillis() if (remainingMillis > 0) { val remainingSeconds = (remainingMillis / 1000).toInt() countdownTextView.text = getString(R.string.connection_error_dialog_retry_countdown, remainingSeconds) @@ -198,13 +198,14 @@ class ConnectionErrorFragment : DialogFragment() { } private fun copyErrorToClipboard() { - val error = selectedBaseUrl?.let { connectionErrors[it] } ?: return + val baseUrl = selectedBaseUrl ?: return + val details = connectionDetails[baseUrl] ?: return val text = buildString { - appendLine("Server: ${error.baseUrl}") - appendLine("Error: ${error.message}") + appendLine("Server: $baseUrl") + appendLine("Error: ${details.error}") appendLine() appendLine("Stack trace:") - append(error.getStackTraceString().ifEmpty { "No stack trace available" }) + append(details.getStackTraceString().ifEmpty { "No stack trace available" }) } copyToClipboard(requireContext(), "connection error", text) } diff --git a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt index 02c1afd7..d0a19521 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/DetailActivity.kt @@ -361,9 +361,9 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet SubscriberServiceManager.refresh(this) } - // Observe connection errors and update menu item visibility - repository.getConnectionErrorsLiveData().observe(this) { errors -> - showHideConnectionErrorMenuItem(errors) + // Observe connection details and update menu item visibility + repository.getConnectionDetailsLiveData().observe(this) { details -> + showHideConnectionErrorMenuItem(details) } // Mark this subscription as "open" so we don't receive notifications for it @@ -513,7 +513,7 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet showHideInstantMenuItems(subscriptionInstant) showHideMutedUntilMenuItems(subscriptionMutedUntil) showHideCopyMenuItems(subscription.baseUrl) - showHideConnectionErrorMenuItem(repository.getConnectionErrors()) + showHideConnectionErrorMenuItem(repository.getConnectionDetails()) updateTitle(subscriptionDisplayName) } } @@ -556,7 +556,7 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet showHideInstantMenuItems(subscriptionInstant) showHideMutedUntilMenuItems(subscriptionMutedUntil) showHideCopyMenuItems(subscriptionBaseUrl) - showHideConnectionErrorMenuItem(repository.getConnectionErrors()) + showHideConnectionErrorMenuItem(repository.getConnectionDetails()) // Regularly check if "notification muted" time has passed // NOTE: This is done here, because then we know that we've initialized the menu items. @@ -817,14 +817,15 @@ class DetailActivity : AppCompatActivity(), NotificationFragment.NotificationSet } } - private fun showHideConnectionErrorMenuItem(errors: Map) { + private fun showHideConnectionErrorMenuItem(details: Map) { if (!this::menu.isInitialized) { return } runOnUiThread { val connectionErrorItem = menu.findItem(R.id.detail_menu_connection_error) // Only show if there's an error for this subscription's base URL - connectionErrorItem?.isVisible = errors.containsKey(subscriptionBaseUrl) + val hasError = details[subscriptionBaseUrl]?.hasError() == true + connectionErrorItem?.isVisible = hasError } } 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 312289de..b99d46b6 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainActivity.kt @@ -253,9 +253,9 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific SubscriberServiceManager.refresh(this) } - // Observe connection errors and update menu item visibility - repository.getConnectionErrorsLiveData().observe(this) { errors -> - showHideConnectionErrorMenuItem(errors) + // Observe connection details and update menu item visibility + repository.getConnectionDetailsLiveData().observe(this) { details -> + showHideConnectionErrorMenuItem(details) } // Battery banner @@ -375,7 +375,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific override fun onResume() { super.onResume() showHideNotificationMenuItems() - showHideConnectionErrorMenuItem(repository.getConnectionErrors()) + showHideConnectionErrorMenuItem(repository.getConnectionDetails()) redrawList() } @@ -493,7 +493,7 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific } showHideNotificationMenuItems() - showHideConnectionErrorMenuItem(repository.getConnectionErrors()) + showHideConnectionErrorMenuItem(repository.getConnectionDetails()) checkSubscriptionsMuted() // This is done here, because then we know that we've initialized the menu return true } @@ -557,13 +557,14 @@ class MainActivity : AppCompatActivity(), AddFragment.SubscribeListener, Notific } } - private fun showHideConnectionErrorMenuItem(errors: Map) { + private fun showHideConnectionErrorMenuItem(details: Map) { if (!this::menu.isInitialized) { return } runOnUiThread { val connectionErrorItem = menu.findItem(R.id.main_menu_connection_error) - connectionErrorItem?.isVisible = errors.isNotEmpty() + val hasErrors = details.values.any { it.hasError() } + connectionErrorItem?.isVisible = hasErrors } } diff --git a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt index eb90779c..4791260c 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/MainAdapter.kt @@ -92,7 +92,7 @@ class MainAdapter( } else { context.getString(R.string.main_item_status_text_not_one, subscription.totalCount) } - if (subscription.instant && subscription.state == ConnectionState.CONNECTING) { + if (subscription.instant && subscription.connectionDetails.state == ConnectionState.CONNECTING) { statusMessage += ", " + context.getString(R.string.main_item_status_reconnecting) } val date = Date(subscription.lastActive * 1000) @@ -120,7 +120,7 @@ class MainAdapter( statusView.text = statusMessage dateView.text = dateText dateView.visibility = if (isUnifiedPush) View.GONE else View.VISIBLE - val showConnectionError = subscription.instant && subscription.state == ConnectionState.CONNECTING + val showConnectionError = subscription.instant && subscription.connectionDetails.hasError() connectionErrorImageView.visibility = if (showConnectionError) View.VISIBLE else View.GONE notificationDisabledUntilImageView.visibility = if (showMutedUntilIcon) View.VISIBLE else View.GONE notificationDisabledForeverImageView.visibility = if (showMutedForeverIcon) View.VISIBLE else View.GONE diff --git a/app/src/main/res/menu/menu_detail_action_bar.xml b/app/src/main/res/menu/menu_detail_action_bar.xml index 7018dfa2..e236b33d 100644 --- a/app/src/main/res/menu/menu_detail_action_bar.xml +++ b/app/src/main/res/menu/menu_detail_action_bar.xml @@ -1,6 +1,12 @@ + - diff --git a/app/src/main/res/menu/menu_main_action_bar.xml b/app/src/main/res/menu/menu_main_action_bar.xml index 68957d60..5b0fc3c0 100644 --- a/app/src/main/res/menu/menu_main_action_bar.xml +++ b/app/src/main/res/menu/menu_main_action_bar.xml @@ -1,6 +1,12 @@ + -