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 c3212a6a..23677fd8 100644 --- a/app/src/main/java/io/heckel/ntfy/db/Database.kt +++ b/app/src/main/java/io/heckel/ntfy/db/Database.kt @@ -1,13 +1,28 @@ package io.heckel.ntfy.db import android.content.Context -import androidx.room.* +import androidx.room.ColumnInfo +import androidx.room.Dao +import androidx.room.Embedded +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.Index +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.PrimaryKey +import androidx.room.Query +import androidx.room.Room +import androidx.room.RoomDatabase +import androidx.room.TypeConverter +import androidx.room.TypeConverters +import androidx.room.Update import androidx.room.migration.Migration import androidx.sqlite.db.SupportSQLiteDatabase import com.google.gson.Gson import com.google.gson.reflect.TypeToken import kotlinx.coroutines.flow.Flow import java.lang.reflect.Type +import java.net.ConnectException @Entity(indices = [Index(value = ["baseUrl", "topic"], unique = true), Index(value = ["upConnectorToken"], unique = true)]) data class Subscription( @@ -90,7 +105,7 @@ data class ConnectionDetails( } fun isConnectionRefused(): Boolean { - return hasCauseOfType() + return hasCauseOfType() } private inline fun hasCauseOfType(): Boolean { 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 03b5a0b6..598f13f4 100644 --- a/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt +++ b/app/src/main/java/io/heckel/ntfy/ui/ConnectionErrorFragment.kt @@ -76,7 +76,6 @@ class ConnectionErrorFragment : DialogFragment() { repository.incrementReconnectVersion(baseUrl) } SubscriberServiceManager.refresh(requireContext()) - dismiss() true } R.id.connection_error_dialog_action_copy -> { @@ -120,13 +119,32 @@ class ConnectionErrorFragment : DialogFragment() { selectedBaseUrl = baseUrls.firstOrNull() updateErrorDisplay() - // Observe connection details to update countdown when it changes + // Observe connection details to update when errors change repository.getConnectionDetailsLiveData().observe(this) { details -> connectionDetails = if (filterBaseUrl != null) { details.filterKeys { it == filterBaseUrl }.filterValues { it.hasError() } } else { details.filterValues { it.hasError() } } + + // Close dialog if no more errors + if (connectionDetails.isEmpty()) { + dismiss() + return@observe + } + + // Update dropdown if the list of errored URLs changed + val baseUrls = connectionDetails.keys.toList() + updateServerDropdown(baseUrls) + + // If selected URL no longer has an error, switch to first available + if (selectedBaseUrl == null || !connectionDetails.containsKey(selectedBaseUrl)) { + selectedBaseUrl = baseUrls.firstOrNull() + if (baseUrls.size > 1) { + serverDropdown.setText(selectedBaseUrl ?: "", false) + } + } + updateErrorDisplay() } @@ -155,6 +173,20 @@ class ConnectionErrorFragment : DialogFragment() { handler.removeCallbacks(countdownRunnable) } + private fun updateServerDropdown(baseUrls: List) { + if (baseUrls.size > 1) { + serverLayout.visibility = View.VISIBLE + val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_dropdown_item_1line, baseUrls) + serverDropdown.setAdapter(adapter) + serverDropdown.setOnItemClickListener { _, _, position, _ -> + selectedBaseUrl = baseUrls[position] + updateErrorDisplay() + } + } else { + serverLayout.visibility = View.GONE + } + } + private fun updateErrorDisplay() { val baseUrl = selectedBaseUrl ?: return descriptionTextView.text = getString(R.string.connection_error_dialog_message) @@ -163,7 +195,7 @@ class ConnectionErrorFragment : DialogFragment() { if (details != null && details.hasError()) { errorTextView.text = when { details.isConnectionRefused() -> getString(R.string.connection_error_dialog_connection_refused) - else -> details.error?.message ?: getString(R.string.connection_error_dialog_no_error) + else -> getErrorDisplayText(details.error) } val stackTrace = details.getStackTraceString() if (stackTrace.isNotEmpty()) { @@ -179,6 +211,18 @@ class ConnectionErrorFragment : DialogFragment() { updateCountdown() } + private fun getErrorDisplayText(error: Throwable?): String { + if (error == null) { + return getString(R.string.connection_error_dialog_no_error) + } + val message = error.message + if (!message.isNullOrBlank()) { + return message + } + // If no message, return the simple class name (e.g., "IOException") + return error.javaClass.simpleName + } + private fun updateCountdown() { val details = selectedBaseUrl?.let { connectionDetails[it] } if (details != null && details.nextRetryTime > 0) { @@ -201,7 +245,7 @@ class ConnectionErrorFragment : DialogFragment() { val details = connectionDetails[baseUrl] ?: return val text = buildString { appendLine("Server: $baseUrl") - appendLine("Error: ${details.error?.message ?: "Unknown error"}") + appendLine("Error: ${getErrorDisplayText(details.error)}") appendLine() appendLine("Stack trace:") append(details.getStackTraceString().ifEmpty { "No stack trace available" })