Special case for connection refused
This commit is contained in:
parent
ed9ce05282
commit
1dcd77287c
7 changed files with 81 additions and 83 deletions
|
|
@ -88,6 +88,19 @@ data class ConnectionDetails(
|
|||
fun hasError(): Boolean {
|
||||
return error != null
|
||||
}
|
||||
|
||||
fun isConnectionRefused(): Boolean {
|
||||
return hasCauseOfType<java.net.ConnectException>()
|
||||
}
|
||||
|
||||
private inline fun <reified T : Throwable> hasCauseOfType(): Boolean {
|
||||
var current: Throwable? = error
|
||||
while (current != null) {
|
||||
if (current is T) return true
|
||||
current = current.cause
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
data class SubscriptionWithMetadata(
|
||||
|
|
|
|||
|
|
@ -202,7 +202,7 @@ class SubscriberService : Service() {
|
|||
* It is guaranteed that only one of function is run at a time (see mutex above).
|
||||
*/
|
||||
private suspend fun reallyRefreshConnections(scope: CoroutineScope) {
|
||||
// Group INSTANT subscriptions by base URL, there is only one connection per base URL
|
||||
// Group instant subscriptions by base URL, there is only one connection per base URL
|
||||
val instantSubscriptions = repository.getSubscriptions().filter { s -> s.instant }
|
||||
val activeConnectionIds = connections.keys().toList().toSet()
|
||||
val connectionProtocol = repository.getConnectionProtocol()
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import android.widget.HorizontalScrollView
|
|||
import android.widget.TextView
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import com.google.android.material.appbar.MaterialToolbar
|
||||
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
|
||||
|
|
@ -31,10 +30,9 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
private lateinit var toolbar: MaterialToolbar
|
||||
private lateinit var serverLayout: TextInputLayout
|
||||
private lateinit var serverDropdown: AutoCompleteTextView
|
||||
private lateinit var descriptionTextView: TextView
|
||||
private lateinit var errorTextView: TextView
|
||||
private lateinit var countdownTextView: TextView
|
||||
private lateinit var retryChip: Chip
|
||||
private lateinit var detailsChip: Chip
|
||||
private lateinit var detailsScrollView: HorizontalScrollView
|
||||
private lateinit var stackTraceTextView: TextView
|
||||
|
||||
|
|
@ -73,6 +71,11 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
toolbar.setNavigationOnClickListener { dismiss() }
|
||||
toolbar.setOnMenuItemClickListener { menuItem ->
|
||||
when (menuItem.itemId) {
|
||||
R.id.connection_error_dialog_action_retry -> {
|
||||
SubscriberServiceManager.refresh(requireContext())
|
||||
dismiss()
|
||||
true
|
||||
}
|
||||
R.id.connection_error_dialog_action_copy -> {
|
||||
copyErrorToClipboard()
|
||||
true
|
||||
|
|
@ -83,16 +86,15 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
|
||||
// Tint menu icons to match toolbar text color
|
||||
val iconColor = MaterialColors.getColor(requireContext(), R.attr.colorOnSurface, Color.BLACK)
|
||||
val copyMenuItem = toolbar.menu.findItem(R.id.connection_error_dialog_action_copy)
|
||||
copyMenuItem?.icon?.setTint(iconColor)
|
||||
toolbar.menu.findItem(R.id.connection_error_dialog_action_retry)?.icon?.setTint(iconColor)
|
||||
toolbar.menu.findItem(R.id.connection_error_dialog_action_copy)?.icon?.setTint(iconColor)
|
||||
|
||||
// Get view references
|
||||
serverLayout = view.findViewById(R.id.connection_error_dialog_server_layout)
|
||||
serverDropdown = view.findViewById(R.id.connection_error_dialog_server_dropdown)
|
||||
descriptionTextView = view.findViewById(R.id.connection_error_dialog_description)
|
||||
errorTextView = view.findViewById(R.id.connection_error_dialog_error_text)
|
||||
countdownTextView = view.findViewById(R.id.connection_error_dialog_countdown)
|
||||
retryChip = view.findViewById(R.id.connection_error_dialog_retry_chip)
|
||||
detailsChip = view.findViewById(R.id.connection_error_dialog_details_chip)
|
||||
detailsScrollView = view.findViewById(R.id.connection_error_dialog_details_scroll)
|
||||
stackTraceTextView = view.findViewById(R.id.connection_error_dialog_stack_trace)
|
||||
|
||||
|
|
@ -115,17 +117,6 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
selectedBaseUrl = baseUrls.firstOrNull()
|
||||
updateErrorDisplay()
|
||||
|
||||
// Toggle details visibility using chip checked state
|
||||
detailsChip.setOnCheckedChangeListener { _, isChecked ->
|
||||
updateDetailsVisibility(isChecked)
|
||||
}
|
||||
|
||||
// Retry now button
|
||||
retryChip.setOnClickListener {
|
||||
SubscriberServiceManager.refresh(requireContext())
|
||||
dismiss()
|
||||
}
|
||||
|
||||
// Observe connection details to update countdown when it changes
|
||||
repository.getConnectionDetailsLiveData().observe(this) { details ->
|
||||
connectionDetails = if (filterBaseUrl != null) {
|
||||
|
|
@ -162,17 +153,26 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
}
|
||||
|
||||
private fun updateErrorDisplay() {
|
||||
val details = selectedBaseUrl?.let { connectionDetails[it] }
|
||||
val baseUrl = selectedBaseUrl ?: return
|
||||
descriptionTextView.text = getString(R.string.connection_error_dialog_message)
|
||||
|
||||
val details = connectionDetails[baseUrl]
|
||||
if (details != null && details.hasError()) {
|
||||
errorTextView.text = details.error?.message ?: getString(R.string.connection_error_dialog_no_error)
|
||||
stackTraceTextView.text = details.getStackTraceString().ifEmpty {
|
||||
getString(R.string.connection_error_dialog_no_stack_trace)
|
||||
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)
|
||||
}
|
||||
val stackTrace = details.getStackTraceString()
|
||||
if (stackTrace.isNotEmpty()) {
|
||||
stackTraceTextView.text = stackTrace
|
||||
detailsScrollView.visibility = View.VISIBLE
|
||||
} else {
|
||||
detailsScrollView.visibility = View.GONE
|
||||
}
|
||||
} else {
|
||||
errorTextView.text = getString(R.string.connection_error_dialog_no_error)
|
||||
stackTraceTextView.text = ""
|
||||
detailsScrollView.visibility = View.GONE
|
||||
}
|
||||
updateDetailsVisibility(detailsChip.isChecked)
|
||||
updateCountdown()
|
||||
}
|
||||
|
||||
|
|
@ -193,10 +193,6 @@ class ConnectionErrorFragment : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun updateDetailsVisibility(visible: Boolean) {
|
||||
detailsScrollView.visibility = if (visible) View.VISIBLE else View.GONE
|
||||
}
|
||||
|
||||
private fun copyErrorToClipboard() {
|
||||
val baseUrl = selectedBaseUrl ?: return
|
||||
val details = connectionDetails[baseUrl] ?: return
|
||||
|
|
|
|||
11
app/src/main/res/drawable/ic_refresh_white_24dp.xml
Normal file
11
app/src/main/res/drawable/ic_refresh_white_24dp.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z"/>
|
||||
</vector>
|
||||
|
|
@ -41,19 +41,6 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<!-- Description text (left aligned like UserFragment) -->
|
||||
<TextView
|
||||
android:id="@+id/connection_error_dialog_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:paddingTop="16dp"
|
||||
android:text="@string/connection_error_dialog_message"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<!-- Server dropdown (Material 3 style, only visible when multiple servers have errors) -->
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/connection_error_dialog_server_layout"
|
||||
|
|
@ -65,7 +52,7 @@
|
|||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_description">
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:id="@+id/connection_error_dialog_server_dropdown"
|
||||
|
|
@ -76,27 +63,16 @@
|
|||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<!-- Error icon -->
|
||||
<ImageView
|
||||
android:id="@+id/connection_error_dialog_error_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginTop="16dp"
|
||||
app:srcCompat="@drawable/ic_error_red_24dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_server_layout" />
|
||||
|
||||
<!-- Error message text (red, like AddFragment) -->
|
||||
<!-- Description text (left aligned like UserFragment) -->
|
||||
<TextView
|
||||
android:id="@+id/connection_error_dialog_error_text"
|
||||
android:layout_width="0dp"
|
||||
android:id="@+id/connection_error_dialog_description"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/DangerText"
|
||||
android:paddingTop="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/connection_error_dialog_error_icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_server_layout" />
|
||||
|
||||
<!-- Countdown text -->
|
||||
|
|
@ -104,40 +80,36 @@
|
|||
android:id="@+id/connection_error_dialog_countdown"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="?android:attr/textAppearanceSmall"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_error_text" />
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_description" />
|
||||
|
||||
<!-- Retry now chip (left aligned) -->
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/connection_error_dialog_retry_chip"
|
||||
style="@style/Widget.Material3.Chip.Assist"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/connection_error_dialog_retry_now"
|
||||
app:chipBackgroundColor="@color/chip_background_state"
|
||||
app:chipStrokeWidth="0dp"
|
||||
<!-- Error icon -->
|
||||
<ImageView
|
||||
android:id="@+id/connection_error_dialog_error_icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginTop="8dp"
|
||||
app:srcCompat="@drawable/ic_error_red_24dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_countdown" />
|
||||
|
||||
<!-- Details chip (right aligned) -->
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/connection_error_dialog_details_chip"
|
||||
style="@style/Widget.Material3.Chip.Filter"
|
||||
android:layout_width="wrap_content"
|
||||
<!-- Error message text (red, like AddFragment) -->
|
||||
<TextView
|
||||
android:id="@+id/connection_error_dialog_error_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:text="@string/connection_error_dialog_details"
|
||||
app:chipBackgroundColor="@color/chip_background_state"
|
||||
app:chipStrokeWidth="0dp"
|
||||
app:checkedIconVisible="false"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingStart="4dp"
|
||||
android:paddingEnd="4dp"
|
||||
android:textAppearance="@style/DangerText"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/connection_error_dialog_error_icon"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_countdown" />
|
||||
|
||||
<!-- Stack trace (scrollable horizontally, no word wrap) -->
|
||||
|
|
@ -145,12 +117,12 @@
|
|||
android:id="@+id/connection_error_dialog_details_scroll"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:fillViewport="true"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_retry_chip">
|
||||
app:layout_constraintTop_toBottomOf="@id/connection_error_dialog_error_text">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/connection_error_dialog_action_retry"
|
||||
android:icon="@drawable/ic_refresh_white_24dp"
|
||||
android:title="@string/connection_error_dialog_retry_now"
|
||||
app:showAsAction="always" />
|
||||
<item
|
||||
android:id="@+id/connection_error_dialog_action_copy"
|
||||
android:icon="@drawable/ic_content_copy_white_24dp"
|
||||
|
|
|
|||
|
|
@ -297,6 +297,7 @@
|
|||
<string name="connection_error_dialog_details">Details</string>
|
||||
<string name="connection_error_dialog_no_stack_trace">No additional details available</string>
|
||||
<string name="connection_error_dialog_no_error">No error</string>
|
||||
<string name="connection_error_dialog_connection_refused">Connection refused. The server may be down or the address may be incorrect.</string>
|
||||
<string name="connection_error_dialog_copy">Copy</string>
|
||||
<string name="connection_error_dialog_retry_now">Retry now</string>
|
||||
<string name="connection_error_dialog_retry_countdown">Retrying in %1$ds…</string>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue