refactor(ble): Centralize BLE logic into a core module (#4550)

Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
This commit is contained in:
James Rich 2026-02-20 06:41:52 -06:00 committed by GitHub
parent 7a68802bc2
commit 6bfa5b5f70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
214 changed files with 3471 additions and 2405 deletions

View file

@ -49,7 +49,6 @@ import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
@ -83,14 +82,13 @@ import com.google.accompanist.permissions.rememberMultiplePermissionsState
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
import org.jetbrains.compose.resources.getString
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.common.gpsDisabled
import org.meshtastic.core.common.hasGps
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.database.entity.Packet
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.nowMillis
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.calculating
import org.meshtastic.core.strings.cancel
@ -99,6 +97,7 @@ import org.meshtastic.core.strings.close
import org.meshtastic.core.strings.delete_for_everyone
import org.meshtastic.core.strings.delete_for_me
import org.meshtastic.core.strings.expires
import org.meshtastic.core.strings.getString
import org.meshtastic.core.strings.location_disabled
import org.meshtastic.core.strings.map_cache_info
import org.meshtastic.core.strings.map_cache_manager
@ -415,7 +414,7 @@ fun MapView(
id = u.id
title = u.long_name
snippet =
com.meshtastic.core.strings.getString(
getString(
Res.string.map_node_popup_details,
node.gpsString(),
formatAgo(node.lastHeard),
@ -423,12 +422,7 @@ fun MapView(
if (node.batteryStr != "") node.batteryStr else "?",
)
ourNode?.distanceStr(node, displayUnits)?.let { dist ->
subDescription =
com.meshtastic.core.strings.getString(
Res.string.map_subDescription,
ourNode.bearing(node).toString(),
dist,
)
subDescription = getString(Res.string.map_subDescription, ourNode.bearing(node).toString(), dist)
}
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
position = nodePosition
@ -449,16 +443,16 @@ fun MapView(
fun showDeleteMarkerDialog(waypoint: Waypoint) {
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)) { _, _ ->
builder.setTitle(getString(Res.string.waypoint_delete))
builder.setNeutralButton(getString(Res.string.cancel)) { _, _ ->
Logger.d { "User canceled marker delete dialog" }
}
builder.setNegativeButton(com.meshtastic.core.strings.getString(Res.string.delete_for_me)) { _, _ ->
builder.setNegativeButton(getString(Res.string.delete_for_me)) { _, _ ->
Logger.d { "User deleted waypoint ${waypoint.id} for me" }
mapViewModel.deleteWaypoint(waypoint.id)
}
if ((waypoint.locked_to ?: 0) in setOf(0, mapViewModel.myNodeNum ?: 0) && isConnected) {
builder.setPositiveButton(com.meshtastic.core.strings.getString(Res.string.delete_for_everyone)) { _, _ ->
builder.setPositiveButton(getString(Res.string.delete_for_everyone)) { _, _ ->
Logger.d { "User deleted waypoint ${waypoint.id} for everyone" }
mapViewModel.sendWaypoint(waypoint.copy(expire = 1))
mapViewModel.deleteWaypoint(waypoint.id)
@ -493,7 +487,7 @@ fun MapView(
}
fun getUsername(id: String?) = if (id == DataPacket.ID_LOCAL || (myId != null && id == myId)) {
com.meshtastic.core.strings.getString(Res.string.you)
getString(Res.string.you)
} else {
mapViewModel.getUser(id).long_name
}
@ -530,10 +524,7 @@ fun MapView(
MarkerWithLabel(this, label, emoji).apply {
id = "${pt.id}"
title = "${pt.name} (${getUsername(waypoint.data.from)}$lock)"
snippet =
"[$time] ${pt.description} " +
com.meshtastic.core.strings.getString(Res.string.expires) +
": $expireTimeStr"
snippet = "[$time] ${pt.description} " + getString(Res.string.expires) + ": $expireTimeStr"
position = GeoPoint((pt.latitude_i ?: 0) * 1e-7, (pt.longitude_i ?: 0) * 1e-7)
if (selectedWaypointId == pt.id) {
showInfoWindow()
@ -644,7 +635,7 @@ fun MapView(
val tileCount: Int =
CacheManager(this)
.possibleTilesInArea(downloadRegionBoundingBox, zoomLevelMin.toInt(), zoomLevelMax.toInt())
cacheEstimate = com.meshtastic.core.strings.getString(Res.string.map_cache_tiles, tileCount)
cacheEstimate = getString(Res.string.map_cache_tiles, tileCount)
}
val boxOverlayListener =
@ -988,7 +979,6 @@ private fun CacheInfoDialog(mapView: MapView, onDismiss: () -> Unit) {
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable
private fun PurgeTileSourceDialog(onDismiss: () -> Unit) {
val scope = rememberCoroutineScope()

View file

@ -17,7 +17,7 @@
package org.meshtastic.feature.map
import android.database.Cursor
import org.meshtastic.core.model.util.nowMillis
import org.meshtastic.core.common.util.nowMillis
import org.osmdroid.tileprovider.modules.DatabaseFileArchive
import org.osmdroid.tileprovider.modules.SqlTileWriter

View file

@ -60,15 +60,16 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalDateTime
import kotlinx.datetime.Month
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.nowInstant
import org.meshtastic.core.model.util.nowSeconds
import org.meshtastic.core.model.util.systemTimeZone
import org.meshtastic.core.model.util.toDate
import org.meshtastic.core.common.util.nowInstant
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.common.util.systemTimeZone
import org.meshtastic.core.common.util.toDate
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.cancel
import org.meshtastic.core.strings.date
@ -86,7 +87,6 @@ import org.meshtastic.core.ui.emoji.EmojiPickerDialog
import org.meshtastic.core.ui.theme.AppTheme
import org.meshtastic.proto.Waypoint
import kotlin.time.Duration.Companion.hours
import kotlin.time.Instant
@Suppress("LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalLayoutApi::class)

View file

@ -90,12 +90,12 @@ import com.google.maps.android.compose.rememberUpdatedMarkerState
import com.google.maps.android.compose.widgets.ScaleBar
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.common.util.nowMillis
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.metersIn
import org.meshtastic.core.model.util.mpsToKmph
import org.meshtastic.core.model.util.mpsToMph
import org.meshtastic.core.model.util.nowMillis
import org.meshtastic.core.model.util.nowSeconds
import org.meshtastic.core.model.util.toString
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.alt

View file

@ -65,8 +65,8 @@ import java.io.IOException
import java.io.InputStream
import java.net.MalformedURLException
import java.net.URL
import java.util.UUID
import javax.inject.Inject
import kotlin.uuid.Uuid
private const val TILE_SIZE = 256
@ -393,7 +393,7 @@ constructor(
if (fileName != null) {
"$layerName.$extension"
} else {
"layer_${UUID.randomUUID()}.$extension"
"layer_${Uuid.random()}.$extension"
}
val localFileUri = copyFileToInternalStorage(uri, finalFileName)
@ -550,7 +550,7 @@ enum class LayerType {
}
data class MapLayerItem(
val id: String = UUID.randomUUID().toString(),
val id: String = Uuid.random().toString(),
val name: String,
val uri: Uri? = null,
var isVisible: Boolean = true,

View file

@ -68,9 +68,9 @@ import kotlinx.datetime.number
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
import org.jetbrains.compose.resources.stringResource
import org.meshtastic.core.model.util.nowInstant
import org.meshtastic.core.model.util.systemTimeZone
import org.meshtastic.core.model.util.toDate
import org.meshtastic.core.common.util.nowInstant
import org.meshtastic.core.common.util.systemTimeZone
import org.meshtastic.core.common.util.toDate
import org.meshtastic.core.strings.Res
import org.meshtastic.core.strings.cancel
import org.meshtastic.core.strings.date

View file

@ -29,8 +29,8 @@ import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.util.nowSeconds
import org.meshtastic.core.ui.component.NodeChip
@Composable

View file

@ -28,13 +28,13 @@ import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.StringResource
import org.meshtastic.core.common.util.nowSeconds
import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository
import org.meshtastic.core.database.entity.Packet
import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.TimeConstants
import org.meshtastic.core.model.util.nowSeconds
import org.meshtastic.core.prefs.map.MapPrefs
import org.meshtastic.core.service.ServiceRepository
import org.meshtastic.core.strings.Res
@ -102,7 +102,7 @@ abstract class BaseMapViewModel(
.associateBy { packet -> packet.data.waypoint!!.id }
.filterValues {
val expire = it.data.waypoint!!.expire ?: 0
expire == 0 || expire > nowSeconds
expire == 0 || expire.toLong() > nowSeconds
}
}
.stateInWhileSubscribed(initialValue = emptyMap())