From 74497488a7836842587397bc3d39bfeabb2d5cf0 Mon Sep 17 00:00:00 2001 From: andrekir Date: Tue, 17 Dec 2024 12:42:09 -0300 Subject: [PATCH] refactor: remove `getString()` from `RadioConfigViewModel` --- .../java/com/geeksville/mesh/model/Message.kt | 49 +++++++++---------- .../mesh/model/RadioConfigViewModel.kt | 34 +++++++------ .../java/com/geeksville/mesh/ui/NavGraph.kt | 3 +- .../config/PacketResponseStateDialog.kt | 2 +- .../java/com/geeksville/mesh/util/UiText.kt | 44 +++++++++++++++++ 5 files changed, 90 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/com/geeksville/mesh/util/UiText.kt diff --git a/app/src/main/java/com/geeksville/mesh/model/Message.kt b/app/src/main/java/com/geeksville/mesh/model/Message.kt index 6dc98b8be..0a5105907 100644 --- a/app/src/main/java/com/geeksville/mesh/model/Message.kt +++ b/app/src/main/java/com/geeksville/mesh/model/Message.kt @@ -17,32 +17,34 @@ package com.geeksville.mesh.model +import androidx.annotation.StringRes import com.geeksville.mesh.MeshProtos.Routing import com.geeksville.mesh.MessageStatus import com.geeksville.mesh.R import com.geeksville.mesh.database.entity.NodeEntity import com.geeksville.mesh.database.entity.Reaction -val Routing.Error.stringRes: Int - get() = when (this) { - Routing.Error.NONE -> R.string.routing_error_none - Routing.Error.NO_ROUTE -> R.string.routing_error_no_route - Routing.Error.GOT_NAK -> R.string.routing_error_got_nak - Routing.Error.TIMEOUT -> R.string.routing_error_timeout - Routing.Error.NO_INTERFACE -> R.string.routing_error_no_interface - Routing.Error.MAX_RETRANSMIT -> R.string.routing_error_max_retransmit - Routing.Error.NO_CHANNEL -> R.string.routing_error_no_channel - Routing.Error.TOO_LARGE -> R.string.routing_error_too_large - Routing.Error.NO_RESPONSE -> R.string.routing_error_no_response - Routing.Error.DUTY_CYCLE_LIMIT -> R.string.routing_error_duty_cycle_limit - Routing.Error.BAD_REQUEST -> R.string.routing_error_bad_request - Routing.Error.NOT_AUTHORIZED -> R.string.routing_error_not_authorized - Routing.Error.PKI_FAILED -> R.string.routing_error_pki_failed - Routing.Error.PKI_UNKNOWN_PUBKEY -> R.string.routing_error_pki_unknown_pubkey - Routing.Error.ADMIN_BAD_SESSION_KEY -> R.string.routing_error_admin_bad_session_key - Routing.Error.ADMIN_PUBLIC_KEY_UNAUTHORIZED -> R.string.routing_error_admin_public_key_unauthorized - else -> R.string.unrecognized - } +@Suppress("CyclomaticComplexMethod") +@StringRes +fun getStringResFrom(routingError: Int): Int = when (routingError) { + Routing.Error.NONE_VALUE -> R.string.routing_error_none + Routing.Error.NO_ROUTE_VALUE -> R.string.routing_error_no_route + Routing.Error.GOT_NAK_VALUE -> R.string.routing_error_got_nak + Routing.Error.TIMEOUT_VALUE -> R.string.routing_error_timeout + Routing.Error.NO_INTERFACE_VALUE -> R.string.routing_error_no_interface + Routing.Error.MAX_RETRANSMIT_VALUE -> R.string.routing_error_max_retransmit + Routing.Error.NO_CHANNEL_VALUE -> R.string.routing_error_no_channel + Routing.Error.TOO_LARGE_VALUE -> R.string.routing_error_too_large + Routing.Error.NO_RESPONSE_VALUE -> R.string.routing_error_no_response + Routing.Error.DUTY_CYCLE_LIMIT_VALUE -> R.string.routing_error_duty_cycle_limit + Routing.Error.BAD_REQUEST_VALUE -> R.string.routing_error_bad_request + Routing.Error.NOT_AUTHORIZED_VALUE -> R.string.routing_error_not_authorized + Routing.Error.PKI_FAILED_VALUE -> R.string.routing_error_pki_failed + Routing.Error.PKI_UNKNOWN_PUBKEY_VALUE -> R.string.routing_error_pki_unknown_pubkey + Routing.Error.ADMIN_BAD_SESSION_KEY_VALUE -> R.string.routing_error_admin_bad_session_key + Routing.Error.ADMIN_PUBLIC_KEY_UNAUTHORIZED_VALUE -> R.string.routing_error_admin_public_key_unauthorized + else -> R.string.unrecognized +} data class Message( val uuid: Long, @@ -56,18 +58,13 @@ data class Message( val packetId: Int, val emojis: List, ) { - private fun getStatusStringRes(value: Int): Int { - val error = Routing.Error.forNumber(value) ?: Routing.Error.UNRECOGNIZED - return error.stringRes - } - fun getStatusStringRes(): Pair { val title = if (routingError > 0) R.string.error else R.string.message_delivery_status val text = when (status) { MessageStatus.RECEIVED -> R.string.delivery_confirmed MessageStatus.QUEUED -> R.string.message_status_queued MessageStatus.ENROUTE -> R.string.message_status_enroute - else -> getStatusStringRes(routingError) + else -> getStringResFrom(routingError) } return title to text } diff --git a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt index 9ff528aee..d9258ef35 100644 --- a/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt +++ b/app/src/main/java/com/geeksville/mesh/model/RadioConfigViewModel.kt @@ -20,6 +20,7 @@ package com.geeksville.mesh.model import android.app.Application import android.net.Uri import android.os.RemoteException +import androidx.annotation.StringRes import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope @@ -47,6 +48,7 @@ import com.geeksville.mesh.ui.ConfigRoute import com.geeksville.mesh.ui.ModuleRoute import com.geeksville.mesh.ui.ResponseState import com.geeksville.mesh.ui.Route +import com.geeksville.mesh.util.UiText import com.google.protobuf.MessageLite import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers @@ -118,7 +120,7 @@ class RadioConfigViewModel @Inject constructor( combine(radioConfigRepository.connectionState, radioConfigState) { connState, configState -> _radioConfigState.update { it.copy(connected = connState == ConnectionState.CONNECTED) } if (connState.isDisconnected() && configState.responseState.isWaiting()) { - setResponseStateError(app.getString(R.string.disconnected)) + sendError(R.string.disconnected) } }.launchIn(viewModelScope) @@ -321,7 +323,7 @@ class RadioConfigViewModel @Inject constructor( AdminRoute.REBOOT.name -> requestReboot(destNum) AdminRoute.SHUTDOWN.name -> with(radioConfigState.value) { if (hasMetadata() && !metadata.canShutdown) { - setResponseStateError(app.getString(R.string.cant_shutdown)) + sendError(R.string.cant_shutdown) } else { requestShutdown(destNum) } @@ -364,7 +366,7 @@ class RadioConfigViewModel @Inject constructor( } } catch (ex: Exception) { errormsg("Import DeviceProfile error: ${ex.message}") - setResponseStateError(ex.customMessage) + sendError(ex.customMessage) } } @@ -382,7 +384,7 @@ class RadioConfigViewModel @Inject constructor( setResponseStateSuccess() } catch (ex: Exception) { errormsg("Can't write file error: ${ex.message}") - setResponseStateError(ex.customMessage) + sendError(ex.customMessage) } } @@ -399,11 +401,13 @@ class RadioConfigViewModel @Inject constructor( setOwner(user) } } - if (hasChannelUrl()) try { - setChannels(channelUrl) - } catch (ex: Exception) { - errormsg("DeviceProfile channel import error", ex) - setResponseStateError(ex.customMessage) + if (hasChannelUrl()) { + try { + setChannels(channelUrl) + } catch (ex: Exception) { + errormsg("DeviceProfile channel import error", ex) + sendError(ex.customMessage) + } } if (hasConfig()) { val descriptor = ConfigProtos.Config.getDescriptor() @@ -494,7 +498,9 @@ class RadioConfigViewModel @Inject constructor( } private val Exception.customMessage: String get() = "${javaClass.simpleName}: $message" - private fun setResponseStateError(error: String) { + private fun sendError(error: String) = setResponseStateError(UiText.DynamicString(error)) + private fun sendError(@StringRes id: Int) = setResponseStateError(UiText.StringResource(id)) + private fun setResponseStateError(error: UiText) { _radioConfigState.update { it.copy(responseState = ResponseState.Error(error)) } } @@ -521,7 +527,7 @@ class RadioConfigViewModel @Inject constructor( val parsed = MeshProtos.Routing.parseFrom(data.payload) debug(debugMsg.format(parsed.errorReason.name)) if (parsed.errorReason != MeshProtos.Routing.Error.NONE) { - setResponseStateError(app.getString(parsed.errorReason.stringRes)) + sendError(getStringResFrom(parsed.errorReasonValue)) } else if (packet.from == destNum && route.isEmpty()) { requestIds.update { it.apply { remove(data.requestId) } } if (requestIds.value.isEmpty()) { @@ -535,7 +541,7 @@ class RadioConfigViewModel @Inject constructor( val parsed = AdminProtos.AdminMessage.parseFrom(data.payload) debug(debugMsg.format(parsed.payloadVariantCase.name)) if (destNum != packet.from) { - setResponseStateError("Unexpected sender: ${packet.from.toUInt()} instead of ${destNum.toUInt()}.") + sendError("Unexpected sender: ${packet.from.toUInt()} instead of ${destNum.toUInt()}.") return } when (parsed.payloadVariantCase) { @@ -572,7 +578,7 @@ class RadioConfigViewModel @Inject constructor( AdminProtos.AdminMessage.PayloadVariantCase.GET_CONFIG_RESPONSE -> { val response = parsed.getConfigResponse if (response.payloadVariantCase.number == 0) { // PAYLOADVARIANT_NOT_SET - setResponseStateError(response.payloadVariantCase.name) + sendError(response.payloadVariantCase.name) } _radioConfigState.update { it.copy(radioConfig = response) } incrementCompleted() @@ -581,7 +587,7 @@ class RadioConfigViewModel @Inject constructor( AdminProtos.AdminMessage.PayloadVariantCase.GET_MODULE_CONFIG_RESPONSE -> { val response = parsed.getModuleConfigResponse if (response.payloadVariantCase.number == 0) { // PAYLOADVARIANT_NOT_SET - setResponseStateError(response.payloadVariantCase.name) + sendError(response.payloadVariantCase.name) } _radioConfigState.update { it.copy(moduleConfig = response) } incrementCompleted() diff --git a/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt index f43f08a01..e872ddc07 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/NavGraph.kt @@ -81,6 +81,7 @@ import com.geeksville.mesh.ui.components.NodeMapScreen import com.geeksville.mesh.ui.components.PositionLogScreen import com.geeksville.mesh.ui.components.SignalMetricsScreen import com.geeksville.mesh.ui.components.TracerouteLogScreen +import com.geeksville.mesh.util.UiText import com.geeksville.mesh.ui.components.config.AmbientLightingConfigScreen import com.geeksville.mesh.ui.components.config.AudioConfigScreen import com.geeksville.mesh.ui.components.config.BluetoothConfigScreen @@ -260,7 +261,7 @@ sealed class ResponseState { data object Empty : ResponseState() data class Loading(var total: Int = 1, var completed: Int = 0) : ResponseState() data class Success(val result: T) : ResponseState() - data class Error(val error: String) : ResponseState() + data class Error(val error: UiText) : ResponseState() fun isWaiting() = this !is Empty } diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/config/PacketResponseStateDialog.kt b/app/src/main/java/com/geeksville/mesh/ui/components/config/PacketResponseStateDialog.kt index 62bcf8ad7..f9f0318dc 100644 --- a/app/src/main/java/com/geeksville/mesh/ui/components/config/PacketResponseStateDialog.kt +++ b/app/src/main/java/com/geeksville/mesh/ui/components/config/PacketResponseStateDialog.kt @@ -74,7 +74,7 @@ fun PacketResponseStateDialog( } if (state is ResponseState.Error) { Text(text = stringResource(id = R.string.error), minLines = 2) - Text(text = state.error) + Text(text = state.error.asString()) } } }, diff --git a/app/src/main/java/com/geeksville/mesh/util/UiText.kt b/app/src/main/java/com/geeksville/mesh/util/UiText.kt new file mode 100644 index 000000000..ecf2eb3ad --- /dev/null +++ b/app/src/main/java/com/geeksville/mesh/util/UiText.kt @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2024 Meshtastic LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.geeksville.mesh.util + +import android.content.Context +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource + +@Suppress("SpreadOperator") +sealed class UiText { + data class DynamicString(val value: String) : UiText() + class StringResource(@StringRes val resId: Int, vararg val args: Any) : UiText() + + @Composable + fun asString(): String { + return when (this) { + is DynamicString -> value + is StringResource -> stringResource(resId, *args) + } + } + + fun asString(context: Context): String { + return when (this) { + is DynamicString -> value + is StringResource -> context.getString(resId, *args) + } + } +}