From 60357d271ad904d94d5d5c71dd6b24aa6aa9bc0a Mon Sep 17 00:00:00 2001 From: Avently <7953703+avently@users.noreply.github.com> Date: Sat, 6 Jan 2024 03:43:56 +0700 Subject: [PATCH] android, desktop: show alerts on critical and internal errors --- .../chat/simplex/common/model/ChatModel.kt | 3 ++ .../chat/simplex/common/model/SimpleXAPI.kt | 12 +++++ .../common/views/database/DatabaseView.kt | 15 ++++-- .../common/views/helpers/ProcessedErrors.kt | 47 +++++++++++++++++++ .../views/usersettings/DeveloperView.kt | 4 +- .../commonMain/resources/MR/base/strings.xml | 8 ++++ .../resources/MR/images/ic_report.svg | 1 + 7 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt create mode 100644 apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index 708bbb907..5f1cf5783 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -122,6 +122,9 @@ object ChatModel { val remoteHostPairing = mutableStateOf?>(null) val remoteCtrlSession = mutableStateOf(null) + val processedCriticalError: ProcessedErrors = ProcessedErrors(60_000) + val processedInternalError: ProcessedErrors = ProcessedErrors(20_000) + fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) { currentUser.value } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index 4af3e3f2e..c75ff3fe3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -108,6 +108,7 @@ class AppPreferences { val chatArchiveTime = mkDatePreference(SHARED_PREFS_CHAT_ARCHIVE_TIME, null) val chatLastStart = mkDatePreference(SHARED_PREFS_CHAT_LAST_START, null) val developerTools = mkBoolPreference(SHARED_PREFS_DEVELOPER_TOOLS, false) + val showInternalErrors = mkBoolPreference(SHARED_PREFS_SHOW_INTERNAL_ERRORS, false) val terminalAlwaysVisible = mkBoolPreference(SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE, false) val networkUseSocksProxy = mkBoolPreference(SHARED_PREFS_NETWORK_USE_SOCKS_PROXY, false) val networkProxyHostPort = mkStrPreference(SHARED_PREFS_NETWORK_PROXY_HOST_PORT, "localhost:9050") @@ -276,6 +277,7 @@ class AppPreferences { private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage" private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart" private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools" + private const val SHARED_PREFS_SHOW_INTERNAL_ERRORS = "ShowInternalErrors" private const val SHARED_PREFS_TERMINAL_ALWAYS_VISIBLE = "TerminalAlwaysVisible" private const val SHARED_PREFS_NETWORK_USE_SOCKS_PROXY = "NetworkUseSocksProxy" private const val SHARED_PREFS_NETWORK_PROXY_HOST_PORT = "NetworkProxyHostPort" @@ -1920,6 +1922,14 @@ object ChatController { } } } + is CR.ChatCmdError -> when { + r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> { + chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart) + } + r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.INTERNAL && appPrefs.showInternalErrors.get() -> { + chatModel.processedInternalError.newError(r.chatError.agentError, false) + } + } else -> Log.d(TAG , "unsupported event: ${r.responseType}") } @@ -4710,6 +4720,7 @@ sealed class AgentErrorType { is AGENT -> "AGENT ${agentErr.string}" is INTERNAL -> "INTERNAL $internalErr" is INACTIVE -> "INACTIVE" + is CRITICAL -> "CRITICAL $offerRestart $criticalErr" } @Serializable @SerialName("CMD") class CMD(val cmdErr: CommandErrorType): AgentErrorType() @Serializable @SerialName("CONN") class CONN(val connErr: ConnectionErrorType): AgentErrorType() @@ -4721,6 +4732,7 @@ sealed class AgentErrorType { @Serializable @SerialName("AGENT") class AGENT(val agentErr: SMPAgentError): AgentErrorType() @Serializable @SerialName("INTERNAL") class INTERNAL(val internalErr: String): AgentErrorType() @Serializable @SerialName("INACTIVE") object INACTIVE: AgentErrorType() + @Serializable @SerialName("CRITICAL") data class CRITICAL(val offerRestart: Boolean, val criticalErr: String): AgentErrorType() } @Serializable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 224317f94..a07eac4c9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -407,6 +407,12 @@ private fun stopChatAlert(m: ChatModel) { ) } +fun restartChatAlert() { + authStopChat(chatModel) { + startChat(chatModel, mutableStateOf(Instant.DISTANT_PAST), chatModel.chatDbChanged) + } +} + private fun exportProhibitedAlert() { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.set_password_to_export), @@ -414,7 +420,7 @@ private fun exportProhibitedAlert() { ) } -private fun authStopChat(m: ChatModel) { +private fun authStopChat(m: ChatModel, onStop: (() -> Unit)? = null) { if (m.controller.appPrefs.performLA.get()) { authenticate( generalGetString(MR.strings.auth_stop_chat), @@ -422,7 +428,7 @@ private fun authStopChat(m: ChatModel) { completed = { laResult -> when (laResult) { LAResult.Success, is LAResult.Unavailable -> { - stopChat(m) + stopChat(m, onStop) } is LAResult.Error -> { m.chatRunning.value = true @@ -434,15 +440,16 @@ private fun authStopChat(m: ChatModel) { } ) } else { - stopChat(m) + stopChat(m, onStop) } } -private fun stopChat(m: ChatModel) { +private fun stopChat(m: ChatModel, onStop: (() -> Unit)? = null) { withApi { try { stopChatAsync(m) platform.androidChatStopped() + onStop?.invoke() } catch (e: Error) { m.chatRunning.value = true AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_stopping_chat), e.toString()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt new file mode 100644 index 000000000..502efaea5 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ProcessedErrors.kt @@ -0,0 +1,47 @@ +package chat.simplex.common.views.helpers + +import chat.simplex.common.model.AgentErrorType +import chat.simplex.common.views.database.restartChatAlert +import chat.simplex.res.MR +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlin.math.max + +class ProcessedErrors (val interval: Long) { + private var lastShownTimestamp: Long = System.currentTimeMillis() - interval + private var lastShownOfferRestart: Boolean = false + private var timer: Job = Job() + + fun newError(error: T, offerRestart: Boolean) { + timer.cancel() + val job = withBGApi { + if (lastShownOfferRestart || !offerRestart) { + delay(max((lastShownTimestamp + interval) - System.currentTimeMillis(), 0)) + } + lastShownTimestamp = System.currentTimeMillis() + lastShownOfferRestart = offerRestart + AlertManager.shared.hideAllAlerts() + when (error) { + is AgentErrorType.CRITICAL -> { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.agent_critical_error_title), + text = generalGetString(MR.strings.agent_critical_error_desc).format(error.criticalErr), + confirmText = if (offerRestart) generalGetString(MR.strings.restart_chat_button) else generalGetString(MR.strings.ok), + onConfirm = { + if (offerRestart) { + withApi { restartChatAlert() } + } + } + ) + } + is AgentErrorType.INTERNAL -> { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.agent_internal_error_title), + text = generalGetString(MR.strings.agent_internal_error_desc).format(error.internalErr), + ) + } + } + } + timer = job + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index a6ac8c14e..969a6d9d9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -10,10 +10,11 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler +import chat.simplex.common.model.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.appPlatform +import chat.simplex.common.platform.appPreferences import chat.simplex.common.views.TerminalView import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -44,6 +45,7 @@ fun DeveloperView( m.controller.appPrefs.terminalAlwaysVisible.set(false) } } + SettingsPreferenceItem(painterResource(MR.images.ic_report), stringResource(MR.strings.show_internal_errors), appPreferences.showInternalErrors) } } SectionTextFooter( diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 4ad40d7a6..7042ea1ef 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -662,6 +662,7 @@ Hide: Show developer options Database IDs and Transport isolation option. + Show internal errors Shutdown? Notifications will stop working until you re-launch the app @@ -1724,4 +1725,11 @@ You are already joining the group via this link. You are already in group %1$s. Connect via link? + + + Critical error + Critical problem occurred with message delivery. Report the issue to the app developers: \n%s\n\nYou can restart the chat to repeat the failed process. + Internal error + Internal problem occurred with message delivery. Report the issue to the app developers: \n%s + Restart chat \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg new file mode 100644 index 000000000..8695857b9 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_report.svg @@ -0,0 +1 @@ + \ No newline at end of file