From d783455ed8b9fa1dfceb80a6f59b752feba03088 Mon Sep 17 00:00:00 2001 From: Harish Vishwakarma Date: Thu, 29 Jan 2026 17:10:13 +0530 Subject: [PATCH] Prevent crash and better discovery page --- app/build.gradle.kts | 4 +- .../java/chat/zekochat/PeptideApplication.kt | 54 +++++++++++++++++++ app/src/main/java/chat/zekochat/api/AppAPI.kt | 4 ++ .../chat/discover/DiscoverServersList.kt | 42 +++++++-------- 4 files changed, 81 insertions(+), 23 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 28091054..fa052e42 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -72,8 +72,8 @@ android { applicationId = "com.zekochat" minSdk = 24 targetSdk = 36 - versionCode = Integer.parseInt("001_003_107".replace("_", ""), 10) - versionName = "1.3.6b" + versionCode = Integer.parseInt("001_003_109".replace("_", ""), 10) + versionName = "1.3.6d" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { diff --git a/app/src/main/java/chat/zekochat/PeptideApplication.kt b/app/src/main/java/chat/zekochat/PeptideApplication.kt index 37c2e322..e1d75317 100644 --- a/app/src/main/java/chat/zekochat/PeptideApplication.kt +++ b/app/src/main/java/chat/zekochat/PeptideApplication.kt @@ -1,12 +1,18 @@ package chat.zekochat import android.app.Application +import android.os.Looper +import android.util.Log import chat.zekochat.api.PeptideHttp +import chat.zekochat.api.realtime.DisconnectionState +import chat.zekochat.api.realtime.RealtimeSocket import chat.zekochat.persistence.KVStorage import com.google.android.material.color.DynamicColors import dagger.hilt.android.HiltAndroidApp import logcat.AndroidLogcatLogger import logcat.LogPriority +import java.io.IOException +import java.net.SocketException @HiltAndroidApp class PeptideApplication : Application() { @@ -17,9 +23,57 @@ class PeptideApplication : Application() { override fun onCreate() { super.onCreate() AndroidLogcatLogger.installOnDebuggableApp(this, minPriority = LogPriority.VERBOSE) + installNetworkUncaughtExceptionHandler() PeptideHttp // Trigger initialization } + /** + * Prevents app crashes when OkHttp's WebSocket reader thread throws uncaught + * network exceptions (e.g. SocketException: Software caused connection abort) + * that are not propagated to our coroutine try/catch in connectWS(). + */ + private fun installNetworkUncaughtExceptionHandler() { + val previousHandler = Thread.getDefaultUncaughtExceptionHandler() + Thread.setDefaultUncaughtExceptionHandler { thread, throwable -> + if (isNetworkRelatedFromBackgroundThread(thread, throwable)) { + Log.w( + "PeptideApplication", + "Suppressing uncaught network exception from background thread (e.g. WebSocket connection abort): ${throwable.message}", + throwable + ) + try { + if (isWebSocketRelated(throwable)) { + RealtimeSocket.updateDisconnectionState(DisconnectionState.Disconnected) + } + } catch (_: Throwable) { + // Avoid double-fault when updating state + } + return@setDefaultUncaughtExceptionHandler + } + previousHandler?.uncaughtException(thread, throwable) + } + } + + private fun isNetworkRelatedFromBackgroundThread(thread: Thread, throwable: Throwable): Boolean { + if (thread == Looper.getMainLooper().thread) return false + var t: Throwable? = throwable + while (t != null) { + when (t) { + is SocketException -> return true + is IOException -> return true + else -> t = t.cause + } + } + return false + } + + private fun isWebSocketRelated(throwable: Throwable): Boolean { + val stack = throwable.stackTraceToString() + return stack.contains("okhttp3.internal.ws") || + stack.contains("WebSocketReader") || + stack.contains("RealWebSocket") + } + init { instance = this DynamicColors.applyToActivitiesIfAvailable(this) diff --git a/app/src/main/java/chat/zekochat/api/AppAPI.kt b/app/src/main/java/chat/zekochat/api/AppAPI.kt index 40335921..32460b04 100644 --- a/app/src/main/java/chat/zekochat/api/AppAPI.kt +++ b/app/src/main/java/chat/zekochat/api/AppAPI.kt @@ -50,6 +50,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.cbor.Cbor import kotlinx.serialization.json.Json +import java.io.IOException import java.net.SocketException import chat.zekochat.api.schemas.Channel as ChannelSchema @@ -314,6 +315,9 @@ object PeptideAPI { } catch (e: SocketException) { Log.d("PeptideAPI", "Socket closed, probably no big deal /// " + e.message) RealtimeSocket.updateDisconnectionState(DisconnectionState.Disconnected) + } catch (e: IOException) { + Log.d("PeptideAPI", "WebSocket connection abort/reset (network): " + e.message) + RealtimeSocket.updateDisconnectionState(DisconnectionState.Disconnected) } catch (e: Exception) { Log.e("PeptideAPI", "WebSocket error", e) RealtimeSocket.updateDisconnectionState(DisconnectionState.Disconnected) diff --git a/app/src/main/java/chat/zekochat/composables/screens/chat/discover/DiscoverServersList.kt b/app/src/main/java/chat/zekochat/composables/screens/chat/discover/DiscoverServersList.kt index f66f8d63..06ea9c9a 100644 --- a/app/src/main/java/chat/zekochat/composables/screens/chat/discover/DiscoverServersList.kt +++ b/app/src/main/java/chat/zekochat/composables/screens/chat/discover/DiscoverServersList.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -71,31 +72,30 @@ fun DiscoverServersList( Column( modifier = Modifier - .padding(top = 24.dp, start = 16.dp, end = 16.dp), - horizontalAlignment = Alignment.CenterHorizontally, + .padding(top = 12.dp, start = 16.dp, end = 16.dp), ) { - Image( - modifier = Modifier - .padding(8.dp), - painter = painterResource(R.drawable.discover_character_image), - contentDescription = null, - ) - - Text( - text = stringResource(R.string.discover_servers), - style = MaterialTheme.typography.headlineMedium - ) + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(12.dp), + ) { + Image( + modifier = Modifier + .size(72.dp) + .padding(4.dp), + painter = painterResource(R.drawable.discover_character_image), + contentDescription = null, + contentScale = ContentScale.Fit, + ) + Text( + text = stringResource(R.string.discover_servers_description), + style = MaterialTheme.typography.bodySmall, + modifier = Modifier.weight(1f), + ) + } Spacer(modifier = Modifier.height(8.dp)) - Text( - text = stringResource(R.string.discover_servers_description), - textAlign = TextAlign.Center, - style = MaterialTheme.typography.bodyMedium - ) - - Spacer(modifier = Modifier.height(16.dp)) - when { uiState.isLoading -> { Box(