feat(logging): Replace Timber with Kermit for multiplatform logging (#4083)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2025-12-28 08:30:15 -06:00 committed by GitHub
parent a927481e4d
commit 0776e029f3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
92 changed files with 727 additions and 957 deletions

View file

@ -64,7 +64,7 @@ dependencies {
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.timber)
implementation(libs.kermit)
implementation(libs.nordic)
implementation(libs.nordic.dfu)

View file

@ -19,6 +19,7 @@ package org.meshtastic.feature.firmware
import android.content.Context
import android.net.Uri
import co.touchlab.kermit.Logger
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
@ -27,7 +28,6 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request
import org.meshtastic.core.model.DeviceHardware
import timber.log.Timber
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
@ -57,7 +57,7 @@ constructor(
}
tempDir.mkdirs()
}
.onFailure { e -> Timber.w(e, "Failed to cleanup temp directory") }
.onFailure { e -> Logger.w(e) { "Failed to cleanup temp directory" } }
}
suspend fun checkUrlExists(url: String): Boolean = withContext(Dispatchers.IO) {
@ -65,7 +65,7 @@ constructor(
try {
client.newCall(request).execute().use { response -> response.isSuccessful }
} catch (e: IOException) {
Timber.w(e, "Failed to check URL existence: $url")
Logger.w(e) { "Failed to check URL existence: $url" }
false
}
}
@ -77,12 +77,12 @@ constructor(
try {
client.newCall(request).execute()
} catch (e: IOException) {
Timber.w(e, "Download failed for $url")
Logger.w(e) { "Download failed for $url" }
return@withContext null
}
if (!response.isSuccessful) {
Timber.w("Download failed: ${response.code} for $url")
Logger.w { "Download failed: ${response.code} for $url" }
return@withContext null
}
@ -167,7 +167,7 @@ constructor(
}
}
} catch (e: IOException) {
Timber.w(e, "Failed to extract firmware from URI")
Logger.w(e) { "Failed to extract firmware from URI" }
return@withContext null
}
matchingEntries.minByOrNull { it.first.name.length }?.second

View file

@ -26,6 +26,7 @@ import android.net.Uri
import android.os.Build
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
@ -72,7 +73,6 @@ import org.meshtastic.core.strings.firmware_update_starting_service
import org.meshtastic.core.strings.firmware_update_unknown_hardware
import org.meshtastic.core.strings.firmware_update_updating
import org.meshtastic.core.strings.unknown
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ -169,7 +169,7 @@ constructor(
}
.onFailure { e ->
if (e is CancellationException) throw e
Timber.e(e)
Logger.e(e) { "Error checking for updates" }
_state.value = FirmwareUpdateState.Error(e.message ?: "Unknown error")
}
}
@ -224,13 +224,13 @@ constructor(
_state.value = FirmwareUpdateState.Processing(getString(Res.string.firmware_update_flashing))
withTimeoutOrNull(DEVICE_DETACH_TIMEOUT) { waitForDeviceDetach(context).first() }
?: Timber.w("Timed out waiting for device to detach, assuming success")
?: Logger.w { "Timed out waiting for device to detach, assuming success" }
_state.value = FirmwareUpdateState.Success
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e)
Logger.e(e) { "Error saving DFU file" }
_state.value = FirmwareUpdateState.Error(e.message ?: getString(Res.string.firmware_update_failed))
} finally {
cleanupTemporaryFiles(fileHandler, tempFirmwareFile)
@ -279,7 +279,7 @@ constructor(
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e)
Logger.e(e) { "Error starting update from file" }
_state.value = FirmwareUpdateState.Error(e.message ?: "Local update failed")
}
}
@ -345,7 +345,7 @@ private fun cleanupTemporaryFiles(fileHandler: FirmwareFileHandler, tempFirmware
tempFirmwareFile?.takeIf { it.exists() }?.delete()
fileHandler.cleanupAllTemporaryFiles()
}
.onFailure { e -> Timber.w(e, "Failed to cleanup temp files") }
.onFailure { e -> Logger.w(e) { "Failed to cleanup temp files" } }
return null
}

View file

@ -19,6 +19,7 @@ package org.meshtastic.feature.firmware
import android.content.Context
import android.net.Uri
import co.touchlab.kermit.Logger
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.delay
@ -26,7 +27,6 @@ import no.nordicsemi.android.dfu.DfuServiceInitiator
import org.meshtastic.core.database.entity.FirmwareRelease
import org.meshtastic.core.model.DeviceHardware
import org.meshtastic.core.service.ServiceRepository
import timber.log.Timber
import java.io.File
import javax.inject.Inject
@ -81,7 +81,7 @@ class FirmwareRetriever @Inject constructor(private val fileHandler: FirmwareFil
return it
}
} catch (e: Exception) {
Timber.w(e, "Direct download for $filename failed, falling back to release zip")
Logger.w(e) { "Direct download for $filename failed, falling back to release zip" }
}
}
@ -141,7 +141,7 @@ constructor(
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e)
Logger.e(e) { "OTA Update failed" }
updateState(FirmwareUpdateState.Error(e.message ?: "OTA Update failed"))
null
}
@ -214,7 +214,7 @@ constructor(
} catch (e: CancellationException) {
throw e
} catch (e: Exception) {
Timber.e(e)
Logger.e(e) { "USB Update failed" }
updateState(FirmwareUpdateState.Error(e.message ?: "USB Update failed"))
null
}

View file

@ -1,8 +1,5 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComposableParamOrder:PermissionScreenLayout.kt$PermissionScreenLayout</ID>
<ID>ParameterNaming:WelcomeScreen.kt$onGetStarted</ID>
</CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View file

@ -67,7 +67,7 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.common)
implementation(libs.material)
implementation(libs.timber)
implementation(libs.kermit)
fdroidImplementation(libs.osmbonuspack)
fdroidImplementation(libs.osmdroid.android)

View file

@ -76,6 +76,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
import com.google.android.material.dialog.MaterialAlertDialogBuilder
@ -156,7 +157,6 @@ import org.osmdroid.views.overlay.Polygon
import org.osmdroid.views.overlay.Polyline
import org.osmdroid.views.overlay.infowindow.InfoWindow
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
import timber.log.Timber
import java.io.File
import java.text.DateFormat
import kotlin.math.abs
@ -170,7 +170,7 @@ private fun MapView.updateMarkers(
waypointMarkers: List<MarkerWithLabel>,
nodeClusterer: RadiusMarkerClusterer,
) {
Timber.d("Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints")
Logger.d { "Showing on map: ${nodeMarkers.size} nodes ${waypointMarkers.size} waypoints" }
overlays.removeAll { it is MarkerWithLabel }
// overlays.addAll(nodeMarkers + waypointMarkers)
overlays.addAll(waypointMarkers)
@ -271,7 +271,7 @@ fun MapView(
fun loadOnlineTileSourceBase(): ITileSource {
val id = mapViewModel.mapStyleId
Timber.d("mapStyleId from prefs: $id")
Logger.d { "mapStyleId from prefs: $id" }
return CustomTileSource.getTileSource(id).also {
zoomLevelMax = it.maximumZoomLevel.toDouble()
showDownloadButton = if (it is OnlineTileSourceBase) it.tileSourcePolicy.acceptsBulkDownload() else false
@ -295,11 +295,11 @@ fun MapView(
fun MapView.toggleMyLocation() {
if (context.gpsDisabled()) {
Timber.d("Telling user we need location turned on for MyLocationNewOverlay")
Logger.d { "Telling user we need location turned on for MyLocationNewOverlay" }
scope.launch { context.showToast(Res.string.location_disabled) }
return
}
Timber.d("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
Logger.d { "user clicked MyLocationNewOverlay ${myLocationOverlay == null}" }
if (myLocationOverlay == null) {
myLocationOverlay =
MyLocationNewOverlay(this).apply {
@ -454,15 +454,15 @@ fun MapView(
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(com.meshtastic.core.strings.getString(Res.string.waypoint_delete))
builder.setNeutralButton(com.meshtastic.core.strings.getString(Res.string.cancel)) { _, _ ->
Timber.d("User canceled marker delete dialog")
Logger.d { "User canceled marker delete dialog" }
}
builder.setNegativeButton(com.meshtastic.core.strings.getString(Res.string.delete_for_me)) { _, _ ->
Timber.d("User deleted waypoint ${waypoint.id} for me")
Logger.d { "User deleted waypoint ${waypoint.id} for me" }
mapViewModel.deleteWaypoint(waypoint.id)
}
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
builder.setPositiveButton(com.meshtastic.core.strings.getString(Res.string.delete_for_everyone)) { _, _ ->
Timber.d("User deleted waypoint ${waypoint.id} for everyone")
Logger.d { "User deleted waypoint ${waypoint.id} for everyone" }
mapViewModel.sendWaypoint(waypoint.copy { expire = 1 })
mapViewModel.deleteWaypoint(waypoint.id)
}
@ -485,7 +485,7 @@ fun MapView(
fun showMarkerLongPressDialog(id: Int) {
performHapticFeedback()
Timber.d("marker long pressed id=$id")
Logger.d { "marker long pressed id=$id" }
val waypoint = waypoints[id]?.data?.waypoint ?: return
// edit only when unlocked or lockedTo myNodeNum
if (waypoint.lockedTo in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
@ -691,9 +691,9 @@ fun MapView(
),
)
} catch (ex: TileSourcePolicyException) {
Timber.d("Tile source does not allow archiving: ${ex.message}")
Logger.d { "Tile source does not allow archiving: ${ex.message}" }
} catch (ex: Exception) {
Timber.d("Tile source exception: ${ex.message}")
Logger.d { "Tile source exception: ${ex.message}" }
}
}
@ -897,7 +897,7 @@ fun MapView(
EditWaypointDialog(
waypoint = showEditWaypointDialog ?: return, // Safe call
onSendClicked = { waypoint ->
Timber.d("User clicked send waypoint ${waypoint.id}")
Logger.d { "User clicked send waypoint ${waypoint.id}" }
showEditWaypointDialog = null
mapViewModel.sendWaypoint(
waypoint.copy {
@ -910,12 +910,12 @@ fun MapView(
)
},
onDeleteClicked = { waypoint ->
Timber.d("User clicked delete waypoint ${waypoint.id}")
Logger.d { "User clicked delete waypoint ${waypoint.id}" }
showEditWaypointDialog = null
showDeleteMarkerDialog(waypoint)
},
onDismissRequest = {
Timber.d("User clicked cancel marker edit dialog")
Logger.d { "User clicked cancel marker edit dialog" }
showEditWaypointDialog = null
},
)

View file

@ -33,6 +33,7 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner
import co.touchlab.kermit.Logger
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
@ -40,7 +41,6 @@ import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.CustomZoomButtonsController
import org.osmdroid.views.MapView
import timber.log.Timber
@SuppressLint("WakelockTimeout")
private fun PowerManager.WakeLock.safeAcquire() {
@ -48,9 +48,9 @@ private fun PowerManager.WakeLock.safeAcquire() {
try {
acquire()
} catch (e: SecurityException) {
Timber.e("WakeLock permission exception: ${e.message}")
Logger.e { "WakeLock permission exception: ${e.message}" }
} catch (e: IllegalStateException) {
Timber.e("WakeLock acquire() exception: ${e.message}")
Logger.e { "WakeLock acquire() exception: ${e.message}" }
}
}
}
@ -60,7 +60,7 @@ private fun PowerManager.WakeLock.safeRelease() {
try {
release()
} catch (e: IllegalStateException) {
Timber.e("WakeLock release() exception: ${e.message}")
Logger.e { "WakeLock release() exception: ${e.message}" }
}
}
}

View file

@ -32,12 +32,12 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.core.content.ContextCompat
import co.touchlab.kermit.Logger
import com.google.android.gms.common.api.ResolvableApiException
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationServices
import com.google.android.gms.location.LocationSettingsRequest
import com.google.android.gms.location.Priority
import timber.log.Timber
private const val INTERVAL_MILLIS = 10000L
@ -66,11 +66,11 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) {
val locationSettingsLauncher =
rememberLauncherForActivityResult(contract = ActivityResultContracts.StartIntentSenderForResult()) { result ->
if (result.resultCode == Activity.RESULT_OK) {
Timber.d("Location settings changed by user.")
Logger.d { "Location settings changed by user." }
// User has enabled location services or improved accuracy.
onPermissionResult(true) // Settings are now adequate, and permission was already granted.
} else {
Timber.d("Location settings change cancelled by user.")
Logger.d { "Location settings change cancelled by user." }
// User chose not to change settings. The permission itself is still granted,
// but the experience might be degraded. For the purpose of enabling map features,
// we consider this as success if the core permission is there.
@ -111,7 +111,7 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) {
val task = client.checkLocationSettings(builder.build())
task.addOnSuccessListener {
Timber.d("Location settings are satisfied.")
Logger.d { "Location settings are satisfied." }
onPermissionResult(true) // Permission granted and settings are good
}
@ -122,11 +122,11 @@ fun LocationPermissionsHandler(onPermissionResult: (Boolean) -> Unit) {
locationSettingsLauncher.launch(intentSenderRequest)
// Result of this launch will be handled by locationSettingsLauncher's callback
} catch (sendEx: ActivityNotFoundException) {
Timber.d("Error launching location settings resolution ${sendEx.message}.")
Logger.d { "Error launching location settings resolution ${sendEx.message}." }
onPermissionResult(true) // Permission is granted, but settings dialog failed. Proceed.
}
} else {
Timber.d("Location settings are not satisfiable.${exception.message}")
Logger.d { "Location settings are not satisfiable.${exception.message}" }
onPermissionResult(true) // Permission is granted, but settings not ideal. Proceed.
}
}

View file

@ -62,6 +62,7 @@ import androidx.compose.ui.unit.dp
import androidx.core.graphics.createBitmap
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest
import com.google.android.gms.location.LocationResult
@ -124,7 +125,6 @@ import org.meshtastic.proto.MeshProtos.Position
import org.meshtastic.proto.MeshProtos.Waypoint
import org.meshtastic.proto.copy
import org.meshtastic.proto.waypoint
import timber.log.Timber
import java.text.DateFormat
import kotlin.math.abs
import kotlin.math.max
@ -219,7 +219,7 @@ fun MapView(
try {
cameraPositionState.animate(cameraUpdate)
} catch (e: IllegalStateException) {
Timber.d("Error animating camera to location: ${e.message}")
Logger.d { "Error animating camera to location: ${e.message}" }
}
}
}
@ -239,14 +239,14 @@ fun MapView(
try {
@Suppress("MissingPermission")
fusedLocationClient.requestLocationUpdates(locationRequest, locationCallback, null)
Timber.d("Started location tracking")
Logger.d { "Started location tracking" }
} catch (e: SecurityException) {
Timber.d("Location permission not available: ${e.message}")
Logger.d { "Location permission not available: ${e.message}" }
isLocationTrackingEnabled = false
}
} else {
fusedLocationClient.removeLocationUpdates(locationCallback)
Timber.d("Stopped location tracking")
Logger.d { "Stopped location tracking" }
}
}
@ -412,7 +412,7 @@ fun MapView(
cameraPositionState.animate(cameraUpdate)
hasCenteredTraceroute = true
} catch (e: IllegalStateException) {
Timber.d("Error centering traceroute overlay: ${e.message}")
Logger.d { "Error centering traceroute overlay: ${e.message}" }
}
}
}
@ -548,7 +548,7 @@ fun MapView(
CameraUpdateFactory.newLatLngBounds(bounds.build(), 100),
)
}
Timber.d("Cluster clicked! $cluster")
Logger.d { "Cluster clicked! $cluster" }
}
true
},
@ -679,9 +679,9 @@ fun MapView(
val currentPosition = cameraPositionState.position
val newCameraPosition = CameraPosition.Builder(currentPosition).bearing(0f).build()
cameraPositionState.animate(CameraUpdateFactory.newCameraPosition(newCameraPosition))
Timber.d("Oriented map to north")
Logger.d { "Oriented map to north" }
} catch (e: IllegalStateException) {
Timber.d("Error orienting map to north: ${e.message}")
Logger.d { "Error orienting map to north: ${e.message}" }
}
}
}
@ -715,7 +715,7 @@ fun MapView(
internal fun convertIntToEmoji(unicodeCodePoint: Int): String = try {
String(Character.toChars(unicodeCodePoint))
} catch (e: IllegalArgumentException) {
Timber.w(e, "Invalid unicode code point: $unicodeCodePoint")
Logger.w(e) { "Invalid unicode code point: $unicodeCodePoint" }
"\uD83D\uDCCD"
}

View file

@ -21,6 +21,7 @@ import android.app.Application
import android.net.Uri
import androidx.core.net.toFile
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.LatLng
@ -56,7 +57,6 @@ import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.ConfigProtos
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
@ -200,7 +200,7 @@ constructor(
fun selectCustomTileProvider(config: CustomTileProviderConfig?) {
if (config != null) {
if (!isValidTileUrlTemplate(config.urlTemplate)) {
Timber.tag("MapViewModel").w("Attempted to select invalid URL template: ${config.urlTemplate}")
Logger.withTag("MapViewModel").w("Attempted to select invalid URL template: ${config.urlTemplate}")
_selectedCustomTileProviderUrl.value = null
googleMapsPrefs.selectedCustomTileUrl = null
return
@ -224,7 +224,8 @@ constructor(
fun createUrlTileProvider(urlString: String): TileProvider? {
if (!isValidTileUrlTemplate(urlString)) {
Timber.tag("MapViewModel").e("Tile URL does not contain valid {x}, {y}, and {z} placeholders: $urlString")
Logger.withTag("MapViewModel")
.e("Tile URL does not contain valid {x}, {y}, and {z} placeholders: $urlString")
return null
}
return object : UrlTileProvider(TILE_SIZE, TILE_SIZE) {
@ -237,7 +238,7 @@ constructor(
return try {
URL(formattedUrl)
} catch (e: MalformedURLException) {
Timber.tag("MapViewModel").e(e, "Malformed URL: $formattedUrl")
Logger.withTag("MapViewModel").e(e) { "Malformed URL: $formattedUrl" }
null
}
}
@ -290,7 +291,7 @@ constructor(
try {
_selectedGoogleMapType.value = MapType.valueOf(savedGoogleMapTypeName ?: MapType.NORMAL.name)
} catch (e: IllegalArgumentException) {
Timber.e(e, "Invalid saved Google Map type: $savedGoogleMapTypeName")
Logger.e(e) { "Invalid saved Google Map type: $savedGoogleMapTypeName" }
_selectedGoogleMapType.value = MapType.NORMAL // Fallback in case of invalid stored name
googleMapsPrefs.selectedGoogleMapType = null
}
@ -335,14 +336,14 @@ constructor(
}
_mapLayers.value = loadedItems
if (loadedItems.isNotEmpty()) {
Timber.tag("MapViewModel").i("Loaded ${loadedItems.size} persisted map layers.")
Logger.withTag("MapViewModel").i("Loaded ${loadedItems.size} persisted map layers.")
}
}
} else {
Timber.tag("MapViewModel").i("Map layers directory does not exist. No layers loaded.")
Logger.withTag("MapViewModel").i("Map layers directory does not exist. No layers loaded.")
}
} catch (e: Exception) {
Timber.tag("MapViewModel").e(e, "Error loading persisted map layers")
Logger.withTag("MapViewModel").e(e) { "Error loading persisted map layers" }
_mapLayers.value = emptyList()
}
}
@ -367,7 +368,7 @@ constructor(
}
if (layerType == null) {
Timber.tag("MapViewModel").e("Unsupported map layer file type: $extension")
Logger.withTag("MapViewModel").e("Unsupported map layer file type: $extension")
return@launch
}
@ -384,7 +385,7 @@ constructor(
val newItem = MapLayerItem(name = layerName, uri = localFileUri, layerType = layerType)
_mapLayers.value = _mapLayers.value + newItem
} else {
Timber.tag("MapViewModel").e("Failed to copy file to internal storage.")
Logger.withTag("MapViewModel").e("Failed to copy file to internal storage.")
}
}
}
@ -402,7 +403,7 @@ constructor(
inputStream?.use { input -> outputStream.use { output -> input.copyTo(output) } }
Uri.fromFile(outputFile)
} catch (e: IOException) {
Timber.tag("MapViewModel").e(e, "Error copying file to internal storage")
Logger.withTag("MapViewModel").e(e) { "Error copying file to internal storage" }
null
}
}
@ -453,7 +454,7 @@ constructor(
file.delete()
}
} catch (e: Exception) {
Timber.tag("MapViewModel").e(e, "Error deleting file from internal storage")
Logger.withTag("MapViewModel").e(e) { "Error deleting file from internal storage" }
}
}
}
@ -465,7 +466,7 @@ constructor(
try {
application.contentResolver.openInputStream(uriToLoad)
} catch (_: Exception) {
Timber.d("MapViewModel: Error opening InputStream from URI: $uriToLoad")
Logger.d { "MapViewModel: Error opening InputStream from URI: $uriToLoad" }
null
}
}
@ -480,7 +481,7 @@ constructor(
LayerType.GEOJSON -> loadGeoJsonLayerIfNeeded(layerItem, map)
}
} catch (e: Exception) {
Timber.tag("MapViewModel").e(e, "Error loading map layer for ${layerItem.uri}")
Logger.withTag("MapViewModel").e(e) { "Error loading map layer for ${layerItem.uri}" }
}
}

View file

@ -20,6 +20,7 @@ package org.meshtastic.feature.map
import android.os.RemoteException
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
@ -44,7 +45,6 @@ import org.meshtastic.core.strings.two_days
import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.feature.map.model.TracerouteOverlay
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
import java.util.concurrent.TimeUnit
@Suppress("MagicNumber")
@ -150,7 +150,7 @@ abstract class BaseMapViewModel(
return try {
serviceRepository.meshService?.packetId
} catch (ex: RemoteException) {
Timber.e("RemoteException: ${ex.message}")
Logger.e { "RemoteException: ${ex.message}" }
return null
}
}
@ -170,7 +170,7 @@ abstract class BaseMapViewModel(
try {
serviceRepository.meshService?.send(p)
} catch (ex: RemoteException) {
Timber.e("Send DataPacket error: ${ex.message}")
Logger.e { "Send DataPacket error: ${ex.message}" }
}
}

View file

@ -58,7 +58,7 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.paging.compose)
implementation(libs.timber)
implementation(libs.kermit)
debugImplementation(libs.androidx.compose.ui.test.manifest)

View file

@ -1,26 +1,5 @@
<?xml version="1.0" ?>
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComposableParamOrder:Message.kt$MessageScreen</ID>
<ID>ComposableParamOrder:Message.kt$QuickChatRow</ID>
<ID>ComposableParamOrder:MessageActions.kt$MessageActions</ID>
<ID>ComposableParamOrder:MessageActions.kt$MessageStatusButton</ID>
<ID>ComposableParamOrder:MessageItem.kt$MessageItem</ID>
<ID>ComposableParamOrder:MessageList.kt$DeliveryInfo</ID>
<ID>ComposableParamOrder:MessageList.kt$MessageList</ID>
<ID>ComposableParamOrder:QuickChat.kt$OutlinedTextFieldWithCounter</ID>
<ID>LambdaParameterEventTrailing:Message.kt$onClick</ID>
<ID>LambdaParameterEventTrailing:Message.kt$onSendMessage</ID>
<ID>LambdaParameterEventTrailing:MessageList.kt$onReply</ID>
<ID>LambdaParameterEventTrailing:QuickChat.kt$onNavigateUp</ID>
<ID>LambdaParameterInRestartableEffect:MessageList.kt$onUnreadChanged</ID>
<ID>LongParameterList:MessageViewModel.kt$MessageViewModel$( private val nodeRepository: NodeRepository, radioConfigRepository: RadioConfigRepository, quickChatActionRepository: QuickChatActionRepository, private val serviceRepository: ServiceRepository, private val packetRepository: PacketRepository, private val uiPrefs: UiPrefs, private val meshServiceNotifications: MeshServiceNotifications, )</ID>
<ID>ModifierMissing:Message.kt$MessageScreen</ID>
<ID>ModifierNotUsedAtRoot:QuickChat.kt$modifier = modifier.fillMaxSize().padding(innerPadding)</ID>
<ID>MutableStateParam:MessageList.kt$selectedIds</ID>
<ID>ParameterNaming:MessageList.kt$onUnreadChanged</ID>
<ID>TooManyFunctions:MessageViewModel.kt$MessageViewModel : ViewModel</ID>
<ID>Wrapping:Message.kt${ event -&gt; when (event) { is MessageScreenEvent.SendMessage -&gt; { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -&gt; viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -&gt; { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -&gt; viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -&gt; navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -&gt; viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -&gt; navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -&gt; navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -&gt; onNavigateBack() is MessageScreenEvent.CopyToClipboard -&gt; { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>
</CurrentIssues>
<CurrentIssues/>
</SmellBaseline>

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.paging.PagingData
import androidx.paging.cachedIn
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
@ -50,7 +51,6 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.ConfigProtos.Config.DeviceConfig.Role
import org.meshtastic.proto.channelSet
import org.meshtastic.proto.sharedContact
import timber.log.Timber
import javax.inject.Inject
private const val VERIFIED_CONTACT_FIRMWARE_CUTOFF = "2.7.12"
@ -198,7 +198,7 @@ constructor(
try {
serviceRepository.onServiceAction(ServiceAction.Favorite(node))
} catch (ex: RemoteException) {
Timber.e(ex, "Favorite node error")
Logger.e(ex) { "Favorite node error" }
}
}
@ -211,7 +211,7 @@ constructor(
}
serviceRepository.onServiceAction(ServiceAction.SendContact(contact = contact))
} catch (ex: RemoteException) {
Timber.e(ex, "Send shared contact error")
Logger.e(ex) { "Send shared contact error" }
}
}
@ -219,7 +219,7 @@ constructor(
try {
serviceRepository.meshService?.send(p)
} catch (ex: RemoteException) {
Timber.e("Send DataPacket error: ${ex.message}")
Logger.e { "Send DataPacket error: ${ex.message}" }
}
}
}

View file

@ -62,7 +62,7 @@ dependencies {
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.common)
implementation(libs.timber)
implementation(libs.kermit)
implementation(libs.coil)
implementation(libs.markdown.renderer.android)
implementation(libs.markdown.renderer.m3)

View file

@ -3,86 +3,9 @@
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>CommentWrapping:SignalMetrics.kt$Metric.SNR$/* Selected 12 as the max to get 4 equal vertical sections. */</ID>
<ID>ComposableParamOrder:DeviceMetrics.kt$DeviceMetricsChart</ID>
<ID>ComposableParamOrder:ElevationInfo.kt$ElevationInfo</ID>
<ID>ComposableParamOrder:EnvironmentCharts.kt$ChartContent</ID>
<ID>ComposableParamOrder:EnvironmentCharts.kt$EnvironmentMetricsChart</ID>
<ID>ComposableParamOrder:EnvironmentCharts.kt$MetricPlottingCanvas</ID>
<ID>ComposableParamOrder:HostMetricsLog.kt$HostMetricsItem</ID>
<ID>ComposableParamOrder:HostMetricsLog.kt$LogLine</ID>
<ID>ComposableParamOrder:LastHeardInfo.kt$LastHeardInfo</ID>
<ID>ComposableParamOrder:NodeFilterTextField.kt$NodeFilterTextField</ID>
<ID>ComposableParamOrder:NodeItem.kt$NodeItem</ID>
<ID>ComposableParamOrder:PaxMetrics.kt$PaxMetricsChart</ID>
<ID>ComposableParamOrder:PowerMetrics.kt$PowerMetricsChart</ID>
<ID>ComposableParamOrder:SatelliteCountInfo.kt$SatelliteCountInfo</ID>
<ID>ComposableParamOrder:SignalMetrics.kt$SignalMetricsChart</ID>
<ID>ComposableParamOrder:TracerouteButton.kt$TracerouteButton</ID>
<ID>LambdaParameterEventTrailing:TracerouteLog.kt$onNavigateUp</ID>
<ID>LongMethod:EnvironmentMetrics.kt$@Composable fun EnvironmentMetricsScreen(viewModel: MetricsViewModel = hiltViewModel(), onNavigateUp: () -&gt; Unit)</ID>
<ID>LongMethod:NodeDetailsSection.kt$@Composable private fun MainNodeDetails(node: Node)</ID>
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1000L</ID>
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-5</ID>
<ID>MagicNumber:MetricsViewModel.kt$MetricsViewModel$1e-7</ID>
<ID>ModifierMissing:CommonCharts.kt$ChartHeader</ID>
<ID>ModifierMissing:CommonCharts.kt$Legend</ID>
<ID>ModifierMissing:CommonCharts.kt$TimeLabels</ID>
<ID>ModifierMissing:DeviceMetrics.kt$DeviceMetricsScreen</ID>
<ID>ModifierMissing:EnvironmentMetrics.kt$EnvironmentMetricsScreen</ID>
<ID>ModifierMissing:HostMetricsLog.kt$HostMetricsLogScreen</ID>
<ID>ModifierMissing:NodeListScreen.kt$NodeListScreen</ID>
<ID>ModifierMissing:NodeStatusIcons.kt$NodeStatusIcons</ID>
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsItem</ID>
<ID>ModifierMissing:PaxMetrics.kt$PaxMetricsScreen</ID>
<ID>ModifierMissing:PositionLog.kt$PositionItem</ID>
<ID>ModifierMissing:PositionLog.kt$PositionLogScreen</ID>
<ID>ModifierMissing:PowerMetrics.kt$PowerMetricsScreen</ID>
<ID>ModifierMissing:SignalMetrics.kt$SignalMetricsScreen</ID>
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier = modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:DeviceMetrics.kt$modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier = modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:EnvironmentCharts.kt$modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:PaxMetrics.kt$modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier = modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:PowerMetrics.kt$modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.weight(weight = Y_AXIS_WEIGHT)</ID>
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier = modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:SignalMetrics.kt$modifier.width(dp)</ID>
<ID>ModifierNotUsedAtRoot:TracerouteLog.kt$modifier = modifier.fillMaxSize().padding(innerPadding)</ID>
<ID>ModifierReused:DeviceMetrics.kt$Canvas(modifier = modifier.width(dp)) { val height = size.height val width = size.width for (i in telemetries.indices) { val telemetry = telemetries[i] /* x-value time */ val xRatio = (telemetry.time - oldest.time).toFloat() / timeDiff val x = xRatio * width /* Channel Utilization */ plotPoint( drawContext = drawContext, color = Device.CH_UTIL.color, x = x, value = telemetry.deviceMetrics.channelUtilization, divisor = MAX_PERCENT_VALUE, ) /* Air Utilization Transmit */ plotPoint( drawContext = drawContext, color = Device.AIR_UTIL.color, x = x, value = telemetry.deviceMetrics.airUtilTx, divisor = MAX_PERCENT_VALUE, ) } /* Battery Line */ var index = 0 while (index &lt; telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -&gt; val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = telemetry.deviceMetrics.batteryLevel / MAX_PERCENT_VALUE val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = Device.BATTERY.color, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } }</ID>
<ID>ModifierReused:DeviceMetrics.kt$HorizontalLinesOverlay( modifier.width(dp), lineColors = listOf(graphColor, Color.Yellow, Color.Red, graphColor, graphColor), )</ID>
<ID>ModifierReused:DeviceMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval())</ID>
<ID>ModifierReused:EnvironmentCharts.kt$Box( contentAlignment = Alignment.TopStart, modifier = modifier.horizontalScroll(state = scrollState, reverseScrolling = true), ) { HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor }) TimeAxisOverlay(modifier = modifier.width(dp), oldest = oldest, newest = newest, selectedTime.lineInterval()) MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, ) }</ID>
<ID>ModifierReused:EnvironmentCharts.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor })</ID>
<ID>ModifierReused:EnvironmentCharts.kt$MetricPlottingCanvas( modifier = modifier.width(dp), telemetries = telemetries, graphData = graphData, selectedTime = selectedTime, oldest = oldest, timeDiff = timeDiff, rightMin = rightMin, rightMax = rightMax, )</ID>
<ID>ModifierReused:EnvironmentCharts.kt$TimeAxisOverlay(modifier = modifier.width(dp), oldest = oldest, newest = newest, selectedTime.lineInterval())</ID>
<ID>ModifierReused:PaxMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { Color.LightGray })</ID>
<ID>ModifierReused:PaxMetrics.kt$Row(modifier = modifier.fillMaxWidth().fillMaxHeight(fraction = 0.33f)) { YAxisLabels( modifier = Modifier.weight(Y_AXIS_WEIGHT).fillMaxHeight().padding(start = 8.dp), labelColor = MaterialTheme.colorScheme.onSurface, minValue = minValue, maxValue = maxValue, ) Box( contentAlignment = Alignment.TopStart, modifier = Modifier.horizontalScroll(state = scrollState, reverseScrolling = true).weight(CHART_WEIGHT), ) { HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { Color.LightGray }) TimeAxisOverlay(modifier.width(dp), oldest = minTime, newest = maxTime, timeFrame.lineInterval()) Canvas(modifier = Modifier.width(dp).fillMaxHeight()) { val width = size.width val height = size.height fun xForTime(t: Int): Float = if (maxTime == minTime) width / 2 else (t - minTime).toFloat() / (maxTime - minTime) * width fun yForValue(v: Int): Float = height - (v - minValue) / (maxValue - minValue) * height fun drawLine(series: List&lt;Pair&lt;Int, Int&gt;&gt;, color: Color) { for (i in 1 until series.size) { drawLine( color = color, start = Offset(xForTime(series[i - 1].first), yForValue(series[i - 1].second)), end = Offset(xForTime(series[i].first), yForValue(series[i].second)), strokeWidth = 2.dp.toPx(), ) } } drawLine(bleSeries, PaxSeries.BLE.color) drawLine(wifiSeries, PaxSeries.WIFI.color) drawLine(totalSeries, PaxSeries.PAX.color) } } YAxisLabels( modifier = Modifier.weight(Y_AXIS_WEIGHT).fillMaxHeight().padding(end = 8.dp), labelColor = MaterialTheme.colorScheme.onSurface, minValue = minValue, maxValue = maxValue, ) }</ID>
<ID>ModifierReused:PaxMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = minTime, newest = maxTime, timeFrame.lineInterval())</ID>
<ID>ModifierReused:PowerMetrics.kt$Canvas(modifier = modifier.width(dp)) { val width = size.width val height = size.height /* Voltage */ var index = 0 while (index &lt; telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -&gt; val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = (retrieveVoltage(selectedChannel, telemetry) - voltageMin) / voltageDiff val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = VOLTAGE_COLOR, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } /* Current */ index = 0 while (index &lt; telemetries.size) { val path = Path() index = createPath( telemetries = telemetries, index = index, path = path, oldestTime = oldest.time, timeRange = timeDiff, width = width, timeThreshold = selectedTime.timeThreshold(), ) { i -&gt; val telemetry = telemetries.getOrNull(i) ?: telemetries.last() val ratio = (retrieveCurrent(selectedChannel, telemetry) - Power.CURRENT.min) / currentDiff val y = height - (ratio * height) return@createPath y } drawPath( path = path, color = Power.CURRENT.color, style = Stroke(width = GraphUtil.RADIUS, cap = StrokeCap.Round), ) } }</ID>
<ID>ModifierReused:PowerMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor })</ID>
<ID>ModifierReused:PowerMetrics.kt$TimeAxisOverlay(modifier.width(dp), oldest = oldest.time, newest = newest.time, selectedTime.lineInterval())</ID>
<ID>ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Power.CURRENT.color, minValue = Power.CURRENT.min, maxValue = Power.CURRENT.max, )</ID>
<ID>ModifierReused:PowerMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), VOLTAGE_COLOR, minValue = voltageMin, maxValue = voltageMax, )</ID>
<ID>ModifierReused:SignalMetrics.kt$Canvas(modifier = modifier.width(dp)) { val width = size.width /* Plot */ for (packet in meshPackets) { val xRatio = (packet.rxTime - oldest.rxTime).toFloat() / timeDiff val x = xRatio * width /* SNR */ plotPoint( drawContext = drawContext, color = Metric.SNR.color, x = x, value = packet.rxSnr - Metric.SNR.min, divisor = snrDiff, ) /* RSSI */ plotPoint( drawContext = drawContext, color = Metric.RSSI.color, x = x, value = packet.rxRssi - Metric.RSSI.min, divisor = rssiDiff, ) } }</ID>
<ID>ModifierReused:SignalMetrics.kt$HorizontalLinesOverlay(modifier.width(dp), lineColors = List(size = 5) { graphColor })</ID>
<ID>ModifierReused:SignalMetrics.kt$TimeAxisOverlay( modifier.width(dp), oldest = oldest.rxTime, newest = newest.rxTime, selectedTime.lineInterval(), )</ID>
<ID>ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.RSSI.color, minValue = Metric.RSSI.min, maxValue = Metric.RSSI.max, )</ID>
<ID>ModifierReused:SignalMetrics.kt$YAxisLabels( modifier = modifier.weight(weight = Y_AXIS_WEIGHT), Metric.SNR.color, minValue = Metric.SNR.min, maxValue = Metric.SNR.max, )</ID>
<ID>ModifierWithoutDefault:CommonCharts.kt$modifier</ID>
<ID>ModifierWithoutDefault:EnvironmentCharts.kt$modifier</ID>
<ID>MultipleEmitters:CommonCharts.kt$LegendLabel</ID>
<ID>MultipleEmitters:DeviceMetrics.kt$DeviceMetricsChart</ID>
<ID>MultipleEmitters:EnvironmentCharts.kt$EnvironmentMetricsChart</ID>
<ID>MultipleEmitters:NodeDetailsSection.kt$MainNodeDetails</ID>
<ID>MultipleEmitters:PaxMetrics.kt$PaxMetricsChart</ID>
<ID>MultipleEmitters:PowerMetrics.kt$PowerMetricsChart</ID>
<ID>MultipleEmitters:RemoteDeviceActions.kt$RemoteDeviceActions</ID>
<ID>MultipleEmitters:SignalMetrics.kt$SignalMetricsChart</ID>
<ID>ParameterNaming:NodeFilterTextField.kt$onToggleShowIgnored</ID>
<ID>PreviewPublic:NodeItem.kt$NodeInfoPreview</ID>
<ID>PreviewPublic:NodeItem.kt$NodeInfoSimplePreview</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -41,6 +41,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import co.touchlab.kermit.Logger
import com.mikepenz.markdown.m3.Markdown
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
@ -50,7 +51,6 @@ import org.meshtastic.core.strings.download
import org.meshtastic.core.strings.error_no_app_to_handle_link
import org.meshtastic.core.strings.view_release
import org.meshtastic.core.ui.util.showToast
import timber.log.Timber
@Composable
fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modifier = Modifier) {
@ -72,7 +72,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) }
Timber.e(e)
Logger.e(e) { "Failed to handle release page URL" }
}
},
modifier = Modifier.weight(1f),
@ -88,7 +88,7 @@ fun FirmwareReleaseSheetContent(firmwareRelease: FirmwareRelease, modifier: Modi
context.startActivity(intent)
} catch (e: ActivityNotFoundException) {
scope.launch { context.showToast(Res.string.error_no_app_to_handle_link) }
Timber.e(e)
Logger.e(e) { "Failed to handle release zip URL" }
}
},
modifier = Modifier.weight(1f),

View file

@ -32,6 +32,7 @@ import androidx.compose.ui.platform.LocalClipboard
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.PreviewLightDark
import androidx.core.net.toUri
import co.touchlab.kermit.Logger
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.database.model.Node
@ -47,7 +48,6 @@ import org.meshtastic.core.ui.component.icon
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.proto.ConfigProtos.Config.DisplayConfig.DisplayUnits
import timber.log.Timber
import java.net.URLEncoder
@OptIn(ExperimentalFoundationApi::class)
@ -82,7 +82,7 @@ fun LinkedCoordinatesItem(node: Node, displayUnits: DisplayUnits = DisplayUnits.
coroutineScope.launch { context.showToast("No application available to open this location!") }
}
} catch (ex: ActivityNotFoundException) {
Timber.d("Failed to open geo intent: $ex")
Logger.d { "Failed to open geo intent: $ex" }
}
},
onLongClick = {

View file

@ -20,6 +20,7 @@ package org.meshtastic.feature.node.detail
import android.os.RemoteException
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@ -33,7 +34,6 @@ import org.meshtastic.core.model.TelemetryType
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.feature.node.component.NodeMenuAction
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
@ -77,20 +77,20 @@ constructor(
try {
nodeRepository.setNodeNotes(nodeNum, notes)
} catch (ex: java.io.IOException) {
Timber.e("Set node notes IO error: ${ex.message}")
Logger.e { "Set node notes IO error: ${ex.message}" }
} catch (ex: java.sql.SQLException) {
Timber.e("Set node notes SQL error: ${ex.message}")
Logger.e { "Set node notes SQL error: ${ex.message}" }
}
}
private fun removeNode(nodeNum: Int) = viewModelScope.launch(Dispatchers.IO) {
Timber.i("Removing node '$nodeNum'")
Logger.i { "Removing node '$nodeNum'" }
try {
val packetId = serviceRepository.meshService?.packetId ?: return@launch
serviceRepository.meshService?.removeByNodenum(packetId, nodeNum)
nodeRepository.deleteNode(nodeNum)
} catch (ex: RemoteException) {
Timber.e("Remove node error: ${ex.message}")
Logger.e { "Remove node error: ${ex.message}" }
}
}
@ -98,7 +98,7 @@ constructor(
try {
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
} catch (ex: RemoteException) {
Timber.e(ex, "Ignore node error")
Logger.e(ex) { "Ignore node error" }
}
}
@ -106,55 +106,55 @@ constructor(
try {
serviceRepository.onServiceAction(ServiceAction.Favorite(node))
} catch (ex: RemoteException) {
Timber.e(ex, "Favorite node error")
Logger.e(ex) { "Favorite node error" }
}
}
private fun requestUserInfo(destNum: Int) {
Timber.i("Requesting UserInfo for '$destNum'")
Logger.i { "Requesting UserInfo for '$destNum'" }
try {
serviceRepository.meshService?.requestUserInfo(destNum)
} catch (ex: RemoteException) {
Timber.e("Request NodeInfo error: ${ex.message}")
Logger.e { "Request NodeInfo error: ${ex.message}" }
}
}
private fun requestNeighborInfo(destNum: Int) {
Timber.i("Requesting NeighborInfo for '$destNum'")
Logger.i { "Requesting NeighborInfo for '$destNum'" }
try {
val packetId = serviceRepository.meshService?.packetId ?: return
serviceRepository.meshService?.requestNeighborInfo(packetId, destNum)
} catch (ex: RemoteException) {
Timber.e("Request NeighborInfo error: ${ex.message}")
Logger.e { "Request NeighborInfo error: ${ex.message}" }
}
}
private fun requestPosition(destNum: Int, position: Position = Position(0.0, 0.0, 0)) {
Timber.i("Requesting position for '$destNum'")
Logger.i { "Requesting position for '$destNum'" }
try {
serviceRepository.meshService?.requestPosition(destNum, position)
} catch (ex: RemoteException) {
Timber.e("Request position error: ${ex.message}")
Logger.e { "Request position error: ${ex.message}" }
}
}
private fun requestTelemetry(destNum: Int, type: TelemetryType) {
Timber.i("Requesting telemetry for '$destNum'")
Logger.i { "Requesting telemetry for '$destNum'" }
try {
val packetId = serviceRepository.meshService?.packetId ?: return
serviceRepository.meshService?.requestTelemetry(packetId, destNum, type.ordinal)
} catch (ex: RemoteException) {
Timber.e("Request telemetry error: ${ex.message}")
Logger.e { "Request telemetry error: ${ex.message}" }
}
}
private fun requestTraceroute(destNum: Int) {
Timber.i("Requesting traceroute for '$destNum'")
Logger.i { "Requesting traceroute for '$destNum'" }
try {
val packetId = serviceRepository.meshService?.packetId ?: return
serviceRepository.meshService?.requestTraceroute(packetId, destNum)
} catch (ex: RemoteException) {
Timber.e("Request traceroute error: ${ex.message}")
Logger.e { "Request traceroute error: ${ex.message}" }
}
}
}

View file

@ -18,13 +18,13 @@
package org.meshtastic.feature.node.list
import android.os.RemoteException
import co.touchlab.kermit.Logger
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.service.ServiceAction
import org.meshtastic.core.service.ServiceRepository
import timber.log.Timber
import javax.inject.Inject
class NodeActions
@ -37,7 +37,7 @@ constructor(
try {
serviceRepository.onServiceAction(ServiceAction.Favorite(node))
} catch (ex: RemoteException) {
Timber.e(ex, "Favorite node error")
Logger.e(ex) { "Favorite node error" }
}
}
@ -45,18 +45,18 @@ constructor(
try {
serviceRepository.onServiceAction(ServiceAction.Ignore(node))
} catch (ex: RemoteException) {
Timber.e(ex, "Ignore node error")
Logger.e(ex) { "Ignore node error" }
}
}
suspend fun removeNode(nodeNum: Int) = withContext(Dispatchers.IO) {
Timber.i("Removing node '$nodeNum'")
Logger.i { "Removing node '$nodeNum'" }
try {
val packetId = serviceRepository.meshService?.packetId ?: return@withContext
serviceRepository.meshService?.removeByNodenum(packetId, nodeNum)
nodeRepository.deleteNode(nodeNum)
} catch (ex: RemoteException) {
Timber.e("Remove node error: ${ex.message}")
Logger.e { "Remove node error: ${ex.message}" }
}
}
}

View file

@ -17,8 +17,8 @@
package org.meshtastic.feature.node.metrics
import co.touchlab.kermit.Logger
import org.meshtastic.proto.MeshProtos
import timber.log.Timber
/**
* Safely extracts the hardware model number from a HardwareModel enum.
@ -34,6 +34,6 @@ import timber.log.Timber
fun MeshProtos.HardwareModel.safeNumber(fallbackValue: Int = -1): Int = try {
this.number
} catch (e: IllegalArgumentException) {
Timber.w("Unknown hardware model enum value: $this, using fallback value: $fallbackValue")
Logger.w { "Unknown hardware model enum value: $this, using fallback value: $fallbackValue" }
fallbackValue
}

View file

@ -23,6 +23,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.MutableStateFlow
@ -63,7 +64,6 @@ import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.MeshProtos.MeshPacket
import org.meshtastic.proto.Portnums
import org.meshtastic.proto.Portnums.PortNum
import timber.log.Timber
import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter
@ -343,16 +343,16 @@ constructor(
}
}
Timber.d("MetricsViewModel created")
Logger.d { "MetricsViewModel created" }
} else {
Timber.d("MetricsViewModel: destNum is null, skipping metrics flows initialization.")
Logger.d { "MetricsViewModel: destNum is null, skipping metrics flows initialization." }
}
}
}
override fun onCleared() {
super.onCleared()
Timber.d("MetricsViewModel cleared")
Logger.d { "MetricsViewModel cleared" }
}
fun setTimeFrame(timeFrame: TimeFrame) {
@ -395,7 +395,7 @@ constructor(
}
}
} catch (ex: FileNotFoundException) {
Timber.e(ex, "Can't write file error")
Logger.e(ex) { "Can't write file error" }
}
}
}

View file

@ -65,7 +65,7 @@ dependencies {
implementation(libs.androidx.hilt.lifecycle.viewmodel.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.kotlinx.collections.immutable)
implementation(libs.timber)
implementation(libs.kermit)
implementation(libs.zxing.android.embedded)
androidTestImplementation(libs.androidx.compose.ui.test.junit4)

View file

@ -2,28 +2,18 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>ComposableParamOrder:ChannelConfigScreen.kt$ChannelConfigScreen</ID>
<ID>ComposableParamOrder:Debug.kt$DecodedPayloadBlock</ID>
<ID>ComposableParamOrder:DebugSearch.kt$DebugSearchState</ID>
<ID>ComposableParamOrder:DebugSearch.kt$DebugSearchStateviewModelDefaults</ID>
<ID>ComposableParamOrder:MapReportingPreference.kt$MapReportingPreference</ID>
<ID>ComposableParamOrder:NodeActionButton.kt$NodeActionButton</ID>
<ID>ComposableParamOrder:WarningDialog.kt$WarningDialog</ID>
<ID>CyclomaticComplexMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>CyclomaticComplexMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>CyclomaticComplexMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>LambdaParameterEventTrailing:NodeActionButton.kt$onClick</ID>
<ID>LongMethod:AudioConfigItemList.kt$@Composable fun AudioConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:CannedMessageConfigItemList.kt$@Composable fun CannedMessageConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DetectionSensorConfigItemList.kt$@Composable fun DetectionSensorConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DeviceConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun DeviceConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:DisplayConfigItemList.kt$@Composable fun DisplayConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:ExternalNotificationConfigItemList.kt$@Composable fun ExternalNotificationConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:LoRaConfigItemList.kt$@Composable fun LoRaConfigScreen(viewModel: RadioConfigViewModel, onBack: () -&gt; Unit)</ID>
<ID>LongMethod:NetworkConfigItemList.kt$@Composable fun NetworkConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:PositionConfigItemList.kt$@OptIn(ExperimentalPermissionsApi::class) @Composable fun PositionConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:PowerConfigItemList.kt$@Composable fun PowerConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:RadioConfigScreenList.kt$@Composable fun &lt;T : MessageLite&gt; RadioConfigScreenList( title: String, onBack: () -&gt; Unit, responseState: ResponseState&lt;Any&gt;, onDismissPacketResponse: () -&gt; Unit, configState: ConfigState&lt;T&gt;, enabled: Boolean, onSave: (T) -&gt; Unit, content: LazyListScope.() -&gt; Unit, )</ID>
<ID>LongMethod:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>LongMethod:SecurityConfigItemList.kt$@OptIn(ExperimentalMaterial3ExpressiveApi::class) @Composable fun SecurityConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
<ID>LongMethod:SerialConfigItemList.kt$@Composable fun SerialConfigScreen(viewModel: RadioConfigViewModel = hiltViewModel(), onBack: () -&gt; Unit)</ID>
@ -34,35 +24,10 @@
<ID>MagicNumber:EditChannelDialog.kt$16</ID>
<ID>MagicNumber:EditChannelDialog.kt$32</ID>
<ID>MagicNumber:PacketResponseStateDialog.kt$100</ID>
<ID>ModifierMissing:CleanNodeDatabaseScreen.kt$CleanNodeDatabaseScreen</ID>
<ID>ModifierMissing:Debug.kt$DebugScreen</ID>
<ID>ModifierMissing:DeviceConfigItemList.kt$DeviceConfigScreen</ID>
<ID>ModifierMissing:MapReportingPreference.kt$MapReportingPreference</ID>
<ID>ModifierMissing:NetworkConfigItemList.kt$NetworkConfigScreen</ID>
<ID>ModifierMissing:PositionConfigItemList.kt$PositionConfigScreen</ID>
<ID>ModifierMissing:RadioConfig.kt$RadioConfigItemList</ID>
<ID>ModifierMissing:RadioConfigScreenList.kt$RadioConfigScreenList</ID>
<ID>ModifierMissing:SecurityConfigItemList.kt$SecurityConfigScreen</ID>
<ID>ModifierMissing:SettingsScreen.kt$SettingsScreen</ID>
<ID>ModifierNotUsedAtRoot:EditChannelDialog.kt$modifier = modifier.weight(1f)</ID>
<ID>ModifierNotUsedAtRoot:EditDeviceProfileDialog.kt$modifier = modifier.weight(1f)</ID>
<ID>MultipleEmitters:CleanNodeDatabaseScreen.kt$NodesDeletionPreview</ID>
<ID>MultipleEmitters:RadioConfig.kt$RadioConfigItemList</ID>
<ID>NestedBlockDepth:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>ParameterNaming:ChannelConfigScreen.kt$onPositiveClicked</ID>
<ID>ParameterNaming:CleanNodeDatabaseScreen.kt$onCheckedChanged</ID>
<ID>ParameterNaming:CleanNodeDatabaseScreen.kt$onDaysChanged</ID>
<ID>ParameterNaming:MapReportingPreference.kt$onMapReportingEnabledChanged</ID>
<ID>ParameterNaming:MapReportingPreference.kt$onPositionPrecisionChanged</ID>
<ID>ParameterNaming:MapReportingPreference.kt$onPublishIntervalSecsChanged</ID>
<ID>ParameterNaming:MapReportingPreference.kt$onShouldReportLocationChanged</ID>
<ID>PreviewPublic:MapReportingPreference.kt$MapReportingPreview</ID>
<ID>ReturnCount:RadioConfigViewModel.kt$RadioConfigViewModel$private fun processPacketResponse(packet: MeshProtos.MeshPacket)</ID>
<ID>TooGenericExceptionCaught:LanguageUtils.kt$LanguageUtils$e: Exception</ID>
<ID>TooGenericExceptionCaught:RadioConfigViewModel.kt$RadioConfigViewModel$ex: Exception</ID>
<ID>TooManyFunctions:RadioConfigViewModel.kt$RadioConfigViewModel : ViewModel</ID>
<ID>UnusedParameter:ChannelConfigScreen.kt$onBack: () -&gt; Unit</ID>
<ID>UnusedParameter:ChannelConfigScreen.kt$title: String</ID>
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -21,6 +21,7 @@ import android.app.Application
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
@ -57,7 +58,6 @@ import org.meshtastic.core.ui.viewmodel.stateInWhileSubscribed
import org.meshtastic.proto.LocalOnlyProtos.LocalConfig
import org.meshtastic.proto.MeshProtos
import org.meshtastic.proto.Portnums
import timber.log.Timber
import java.io.BufferedWriter
import java.io.FileNotFoundException
import java.io.FileWriter
@ -287,7 +287,7 @@ constructor(
}
}
} catch (ex: FileNotFoundException) {
Timber.e("Can't write file error: ${ex.message}")
Logger.e { "Can't write file error: ${ex.message}" }
}
}
}

View file

@ -75,6 +75,7 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -96,7 +97,6 @@ import org.meshtastic.core.ui.theme.AnnotationColor
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.core.ui.util.showToast
import org.meshtastic.feature.settings.debugging.DebugViewModel.UiMeshLog
import timber.log.Timber
import java.io.IOException
import java.io.OutputStreamWriter
import java.nio.charset.StandardCharsets
@ -408,7 +408,7 @@ private suspend fun exportAllLogsToUri(context: Context, targetUri: Uri, logs: L
withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_success, logs.size) }
} catch (e: IOException) {
withContext(Dispatchers.Main) { context.showToast(Res.string.debug_export_failed, e.message ?: "") }
Timber.w(e, "Error:IOException ")
Logger.w(e) { "Error:IOException" }
}
}

View file

@ -20,6 +20,7 @@ package org.meshtastic.feature.settings.debugging
import androidx.compose.runtime.Immutable
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import co.touchlab.kermit.Logger
import com.google.protobuf.InvalidProtocolBufferException
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
@ -44,7 +45,6 @@ import org.meshtastic.proto.PaxcountProtos
import org.meshtastic.proto.Portnums.PortNum
import org.meshtastic.proto.StoreAndForwardProtos
import org.meshtastic.proto.TelemetryProtos
import timber.log.Timber
import java.text.DateFormat
import java.util.Date
import java.util.Locale
@ -237,7 +237,7 @@ constructor(
}
init {
Timber.d("DebugViewModel created")
Logger.d { "DebugViewModel created" }
viewModelScope.launch {
combine(searchManager.searchText, filterManager.filteredLogs) { searchText, logs ->
searchManager.findSearchMatches(searchText, logs)
@ -250,7 +250,7 @@ constructor(
override fun onCleared() {
super.onCleared()
Timber.d("DebugViewModel cleared")
Logger.d { "DebugViewModel cleared" }
}
private fun toUiState(databaseLogs: List<MeshLog>) = databaseLogs

View file

@ -31,6 +31,7 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.navigation.toRoute
import co.touchlab.kermit.Logger
import com.google.protobuf.MessageLite
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -80,7 +81,6 @@ import org.meshtastic.proto.Portnums
import org.meshtastic.proto.config
import org.meshtastic.proto.deviceProfile
import org.meshtastic.proto.moduleConfig
import timber.log.Timber
import java.io.FileOutputStream
import javax.inject.Inject
@ -179,7 +179,7 @@ constructor(
}
.launchIn(viewModelScope)
Timber.d("RadioConfigViewModel created")
Logger.d { "RadioConfigViewModel created" }
}
private val myNodeInfo: StateFlow<MyNodeEntity?>
@ -205,7 +205,7 @@ constructor(
override fun onCleared() {
super.onCleared()
Timber.d("RadioConfigViewModel cleared")
Logger.d { "RadioConfigViewModel cleared" }
}
private fun request(destNum: Int, requestAction: suspend (IMeshService, Int, Int) -> Unit, errorMessage: String) =
@ -227,7 +227,7 @@ constructor(
}
}
} catch (ex: RemoteException) {
Timber.e("$errorMessage: ${ex.message}")
Logger.e { "$errorMessage: ${ex.message}" }
}
}
}
@ -422,7 +422,7 @@ constructor(
try {
meshService?.setFixedPosition(destNum, position)
} catch (ex: RemoteException) {
Timber.e("Set fixed position error: ${ex.message}")
Logger.e { "Set fixed position error: ${ex.message}" }
}
}
@ -436,7 +436,7 @@ constructor(
onResult(protobuf)
}
} catch (ex: Exception) {
Timber.e("Import DeviceProfile error: ${ex.message}")
Logger.e { "Import DeviceProfile error: ${ex.message}" }
sendError(ex.customMessage)
}
}
@ -452,7 +452,7 @@ constructor(
}
setResponseStateSuccess()
} catch (ex: Exception) {
Timber.e("Can't write file error: ${ex.message}")
Logger.e { "Can't write file error: ${ex.message}" }
sendError(ex.customMessage)
}
}
@ -491,7 +491,7 @@ constructor(
setResponseStateSuccess()
} catch (ex: Exception) {
val errorMessage = "Can't write security keys JSON error: ${ex.message}"
Timber.e(errorMessage)
Logger.e { errorMessage }
sendError(ex.customMessage)
}
}
@ -514,7 +514,7 @@ constructor(
try {
setChannels(channelUrl)
} catch (ex: Exception) {
Timber.e(ex, "DeviceProfile channel import error")
Logger.e(ex) { "DeviceProfile channel import error" }
sendError(ex.customMessage)
}
}
@ -656,7 +656,7 @@ constructor(
if (data?.portnumValue == Portnums.PortNum.ROUTING_APP_VALUE) {
val parsed = MeshProtos.Routing.parseFrom(data.payload)
Timber.d(debugMsg.format(parsed.errorReason.name))
Logger.d { debugMsg.format(parsed.errorReason.name) }
if (parsed.errorReason != MeshProtos.Routing.Error.NONE) {
sendError(getStringResFrom(parsed.errorReasonValue))
} else if (packet.from == destNum && route.isEmpty()) {
@ -670,7 +670,7 @@ constructor(
}
if (data?.portnumValue == Portnums.PortNum.ADMIN_APP_VALUE) {
val parsed = AdminProtos.AdminMessage.parseFrom(data.payload)
Timber.d(debugMsg.format(parsed.payloadVariantCase.name))
Logger.d { debugMsg.format(parsed.payloadVariantCase.name) }
if (destNum != packet.from) {
sendError("Unexpected sender: ${packet.from.toUInt()} instead of ${destNum.toUInt()}.")
return
@ -744,7 +744,7 @@ constructor(
incrementCompleted()
}
else -> Timber.d("No custom processing needed for ${parsed.payloadVariantCase}")
else -> Logger.d { "No custom processing needed for ${parsed.payloadVariantCase}" }
}
if (AdminRoute.entries.any { it.name == route }) {

View file

@ -44,6 +44,7 @@ import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import co.touchlab.kermit.Logger
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.advanced
@ -79,7 +80,6 @@ import org.meshtastic.feature.settings.util.gpioPins
import org.meshtastic.feature.settings.util.toDisplayString
import org.meshtastic.proto.copy
import org.meshtastic.proto.moduleConfig
import timber.log.Timber
import java.io.File
private const val MAX_RINGTONE_SIZE = 230
@ -116,7 +116,7 @@ fun ExternalNotificationConfigScreen(
}
}
} catch (e: Exception) {
Timber.e(e, "Error importing ringtone")
Logger.e(e) { "Error importing ringtone" }
Toast.makeText(context, "Error importing: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
@ -308,7 +308,7 @@ fun ExternalNotificationConfigScreen(
tempFile.delete()
}
} catch (e: Exception) {
Timber.e(e, "Failed to play ringtone")
Logger.e(e) { "Failed to play ringtone" }
}
},
enabled = state.connected,

View file

@ -22,6 +22,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalResources
import androidx.core.os.LocaleListCompat
import co.touchlab.kermit.Logger
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.fr_HT
@ -30,7 +31,6 @@ import org.meshtastic.core.strings.pt_BR
import org.meshtastic.core.strings.zh_CN
import org.meshtastic.core.strings.zh_TW
import org.xmlpull.v1.XmlPullParser
import timber.log.Timber
import java.util.Locale
object LanguageUtils {
@ -69,7 +69,7 @@ object LanguageUtils {
}
}
} catch (e: Exception) {
Timber.e("Error parsing locale_config.xml: ${e.message}")
Logger.e { "Error parsing locale_config.xml: ${e.message}" }
}
}
}