Phil Oliver 2025-10-03 20:57:07 +00:00 zatwierdzone przez GitHub
commit 1ca7cea350
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
28 zmienionych plików z 145 dodań i 146 usunięć

Wyświetl plik

@ -331,7 +331,6 @@
<ID>UnusedParameter:ChannelSettingsItemList.kt$onBack: () -> Unit</ID> <ID>UnusedParameter:ChannelSettingsItemList.kt$onBack: () -> Unit</ID>
<ID>UnusedParameter:ChannelSettingsItemList.kt$title: String</ID> <ID>UnusedParameter:ChannelSettingsItemList.kt$title: String</ID>
<ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID> <ID>UtilityClassWithPublicConstructor:NetworkRepositoryModule.kt$NetworkRepositoryModule</ID>
<ID>ViewModelForwarding:Main.kt$ScannedQrCodeDialog(uIViewModel, newChannelSet)</ID>
<ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID> <ID>ViewModelForwarding:Main.kt$VersionChecks(uIViewModel)</ID>
<ID>ViewModelInjection:DebugSearch.kt$viewModel</ID> <ID>ViewModelInjection:DebugSearch.kt$viewModel</ID>
<ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID> <ID>Wrapping:Message.kt${ event -> when (event) { is MessageScreenEvent.SendMessage -> { viewModel.sendMessage(event.text, contactKey, event.replyingToPacketId) if (event.replyingToPacketId != null) replyingToPacketId = null messageInputState.clearText() } is MessageScreenEvent.SendReaction -> viewModel.sendReaction(event.emoji, event.messageId, contactKey) is MessageScreenEvent.DeleteMessages -> { viewModel.deleteMessages(event.ids) selectedMessageIds.value = emptySet() showDeleteDialog = false } is MessageScreenEvent.ClearUnreadCount -> viewModel.clearUnreadCount(contactKey, event.lastReadMessageId) is MessageScreenEvent.NodeDetails -> navigateToNodeDetails(event.node.num) is MessageScreenEvent.SetTitle -> viewModel.setTitle(event.title) is MessageScreenEvent.NavigateToMessages -> navigateToMessages(event.contactKey) is MessageScreenEvent.NavigateToNodeDetails -> navigateToNodeDetails(event.nodeNum) MessageScreenEvent.NavigateBack -> onNavigateBack() is MessageScreenEvent.CopyToClipboard -> { clipboardManager.nativeClipboard.setPrimaryClip(ClipData.newPlainText(event.text, event.text)) selectedMessageIds.value = emptySet() } } }</ID>

Wyświetl plik

@ -27,12 +27,12 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.ui.map.NodeMapViewModel
import com.geeksville.mesh.ui.map.rememberMapViewWithLifecycle
import org.meshtastic.feature.map.addCopyright import org.meshtastic.feature.map.addCopyright
import org.meshtastic.feature.map.addPolyline import org.meshtastic.feature.map.addPolyline
import org.meshtastic.feature.map.addPositionMarkers import org.meshtastic.feature.map.addPositionMarkers
import org.meshtastic.feature.map.addScaleBarOverlay import org.meshtastic.feature.map.addScaleBarOverlay
import org.meshtastic.feature.map.node.NodeMapViewModel
import org.meshtastic.feature.map.rememberMapViewWithLifecycle
import org.osmdroid.util.BoundingBox import org.osmdroid.util.BoundingBox
import org.osmdroid.util.GeoPoint import org.osmdroid.util.GeoPoint

Wyświetl plik

@ -27,11 +27,9 @@ import androidx.compose.ui.Modifier
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.MetricsViewModel
import com.geeksville.mesh.ui.map.MapView
import com.geeksville.mesh.ui.map.NodeMapViewModel
import org.meshtastic.core.ui.component.MainAppBar import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.feature.map.MapView
const val DEG_D = 1e-7 import org.meshtastic.feature.map.node.NodeMapViewModel
@Composable @Composable
fun NodeMapScreen( fun NodeMapScreen(

Wyświetl plik

@ -1,35 +0,0 @@
/*
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
*/
package com.geeksville.mesh.android
import android.app.Activity
import android.content.Context
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
// / show a toast
fun Context.toast(message: CharSequence) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
// / Utility function to hide the soft keyboard per stack overflow
fun Activity.hideKeyboard() {
// Check if no view has focus:
currentFocus?.let { v ->
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as? InputMethodManager
imm?.hideSoftInputFromWindow(v.windowToken, 0)
}
}

Wyświetl plik

@ -21,10 +21,10 @@ import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavHostController import androidx.navigation.NavHostController
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.navDeepLink import androidx.navigation.navDeepLink
import com.geeksville.mesh.ui.map.MapScreen
import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI import org.meshtastic.core.navigation.DEEP_LINK_BASE_URI
import org.meshtastic.core.navigation.MapRoutes import org.meshtastic.core.navigation.MapRoutes
import org.meshtastic.core.navigation.NodesRoutes import org.meshtastic.core.navigation.NodesRoutes
import org.meshtastic.feature.map.MapScreen
fun NavGraphBuilder.mapGraph(navController: NavHostController) { fun NavGraphBuilder.mapGraph(navController: NavHostController) {
composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) { composable<MapRoutes.Map>(deepLinks = listOf(navDeepLink<MapRoutes.Map>(basePath = "$DEEP_LINK_BASE_URI/map"))) {

Wyświetl plik

@ -28,7 +28,6 @@ import androidx.annotation.RequiresPermission
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.coroutineScope import androidx.lifecycle.coroutineScope
import com.geeksville.mesh.CoroutineDispatchers import com.geeksville.mesh.CoroutineDispatchers
import com.geeksville.mesh.android.hasBluetoothPermission
import com.geeksville.mesh.util.registerReceiverCompat import com.geeksville.mesh.util.registerReceiverCompat
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -37,6 +36,7 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.meshtastic.core.common.hasBluetoothPermission
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton

Wyświetl plik

@ -49,7 +49,6 @@ import com.geeksville.mesh.StoreAndForwardProtos
import com.geeksville.mesh.TelemetryProtos import com.geeksville.mesh.TelemetryProtos
import com.geeksville.mesh.TelemetryProtos.LocalStats import com.geeksville.mesh.TelemetryProtos.LocalStats
import com.geeksville.mesh.XmodemProtos import com.geeksville.mesh.XmodemProtos
import com.geeksville.mesh.android.hasLocationPermission
import com.geeksville.mesh.concurrent.handledLaunch import com.geeksville.mesh.concurrent.handledLaunch
import com.geeksville.mesh.copy import com.geeksville.mesh.copy
import com.geeksville.mesh.fromRadio import com.geeksville.mesh.fromRadio
@ -78,6 +77,7 @@ import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import org.meshtastic.core.analytics.DataPair import org.meshtastic.core.analytics.DataPair
import org.meshtastic.core.common.hasLocationPermission
import org.meshtastic.core.data.repository.MeshLogRepository import org.meshtastic.core.data.repository.MeshLogRepository
import org.meshtastic.core.data.repository.NodeRepository import org.meshtastic.core.data.repository.NodeRepository
import org.meshtastic.core.data.repository.PacketRepository import org.meshtastic.core.data.repository.PacketRepository

Wyświetl plik

@ -67,11 +67,11 @@ import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.model.MetricsViewModel import com.geeksville.mesh.model.MetricsViewModel
import org.meshtastic.core.model.util.metersIn import org.meshtastic.core.model.util.metersIn
import org.meshtastic.core.model.util.toString import org.meshtastic.core.model.util.toString
import org.meshtastic.core.proto.formatPositionTime
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.MainAppBar import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.core.ui.theme.AppTheme import org.meshtastic.core.ui.theme.AppTheme
import java.text.DateFormat import java.text.DateFormat
import kotlin.time.Duration.Companion.days
@Composable @Composable
private fun RowScope.PositionText(text: String, weight: Float) { private fun RowScope.PositionText(text: String, weight: Float) {
@ -106,7 +106,6 @@ private fun HeaderItem(compactWidth: Boolean) {
const val DEG_D = 1e-7 const val DEG_D = 1e-7
const val HEADING_DEG = 1e-5 const val HEADING_DEG = 1e-5
private const val SECONDS_TO_MILLIS = 1000L
@Composable @Composable
fun PositionItem(compactWidth: Boolean, position: MeshProtos.Position, dateFormat: DateFormat, system: DisplayUnits) { fun PositionItem(compactWidth: Boolean, position: MeshProtos.Position, dateFormat: DateFormat, system: DisplayUnits) {
@ -122,24 +121,10 @@ fun PositionItem(compactWidth: Boolean, position: MeshProtos.Position, dateForma
PositionText("${position.groundSpeed} Km/h", WEIGHT_15) PositionText("${position.groundSpeed} Km/h", WEIGHT_15)
PositionText("%.0f°".format(position.groundTrack * HEADING_DEG), WEIGHT_15) PositionText("%.0f°".format(position.groundTrack * HEADING_DEG), WEIGHT_15)
} }
PositionText(formatPositionTime(position, dateFormat), WEIGHT_40) PositionText(position.formatPositionTime(dateFormat), WEIGHT_40)
} }
} }
@Composable
fun formatPositionTime(position: MeshProtos.Position, dateFormat: DateFormat): String {
val currentTime = System.currentTimeMillis()
val sixMonthsAgo = currentTime - 180.days.inWholeMilliseconds
val isOlderThanSixMonths = position.time * SECONDS_TO_MILLIS < sixMonthsAgo
val timeText =
if (isOlderThanSixMonths) {
stringResource(id = R.string.unknown_age)
} else {
dateFormat.format(position.time * SECONDS_TO_MILLIS)
}
return timeText
}
@Composable @Composable
private fun ActionButtons( private fun ActionButtons(
clearButtonEnabled: Boolean, clearButtonEnabled: Boolean,

Wyświetl plik

@ -60,7 +60,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.BuildConfig import com.geeksville.mesh.BuildConfig
import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile import com.geeksville.mesh.ClientOnlyProtos.DeviceProfile
import com.geeksville.mesh.android.gpsDisabled
import com.geeksville.mesh.navigation.getNavRouteFrom import com.geeksville.mesh.navigation.getNavRouteFrom
import com.geeksville.mesh.ui.settings.radio.RadioConfigItemList import com.geeksville.mesh.ui.settings.radio.RadioConfigItemList
import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel import com.geeksville.mesh.ui.settings.radio.RadioConfigViewModel
@ -71,6 +70,7 @@ import com.geeksville.mesh.util.LanguageUtils.getLanguageMap
import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.ExperimentalPermissionsApi
import com.google.accompanist.permissions.rememberMultiplePermissionsState import com.google.accompanist.permissions.rememberMultiplePermissionsState
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import org.meshtastic.core.common.gpsDisabled
import org.meshtastic.core.navigation.Route import org.meshtastic.core.navigation.Route
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.MainAppBar import org.meshtastic.core.ui.component.MainAppBar

Wyświetl plik

@ -22,4 +22,4 @@ plugins {
android { namespace = "org.meshtastic.core.common" } android { namespace = "org.meshtastic.core.common" }
dependencies {} dependencies { implementation(libs.core.ktx) }

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.android package org.meshtastic.core.common
import android.Manifest import android.Manifest
import android.content.Context import android.content.Context

Wyświetl plik

@ -34,6 +34,7 @@
plugins { plugins {
alias(libs.plugins.meshtastic.android.library) alias(libs.plugins.meshtastic.android.library)
alias(libs.plugins.meshtastic.android.library.compose)
alias(libs.plugins.protobuf) alias(libs.plugins.protobuf)
} }
@ -53,6 +54,8 @@ protobuf {
} }
dependencies { dependencies {
implementation(projects.core.strings)
// This needs to be API for consuming modules // This needs to be API for consuming modules
api(libs.protobuf.kotlin) api(libs.protobuf.kotlin)
} }

Wyświetl plik

@ -0,0 +1,40 @@
/*
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.core.proto
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import com.geeksville.mesh.MeshProtos
import java.text.DateFormat
import kotlin.time.Duration.Companion.days
private const val SECONDS_TO_MILLIS = 1000L
@Composable
fun MeshProtos.Position.formatPositionTime(dateFormat: DateFormat): String {
val currentTime = System.currentTimeMillis()
val sixMonthsAgo = currentTime - 180.days.inWholeMilliseconds
val isOlderThanSixMonths = time * SECONDS_TO_MILLIS < sixMonthsAgo
val timeText =
if (isOlderThanSixMonths) {
stringResource(id = org.meshtastic.core.strings.R.string.unknown_age)
} else {
dateFormat.format(time * SECONDS_TO_MILLIS)
}
return timeText
}

Wyświetl plik

@ -41,6 +41,7 @@ dependencies {
implementation(libs.bundles.osm) implementation(libs.bundles.osm)
googleImplementation(libs.bundles.maps.compose) googleImplementation(libs.bundles.maps.compose)
implementation(libs.accompanist.permissions)
implementation(libs.annotation) implementation(libs.annotation)
implementation(libs.timber) implementation(libs.timber)
} }

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map package org.meshtastic.feature.map
import android.Manifest // Added for Accompanist import android.Manifest // Added for Accompanist
import android.content.Context import android.content.Context
@ -63,31 +63,25 @@ import androidx.compose.ui.viewinterop.AndroidView
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.geeksville.mesh.MeshProtos.Waypoint import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.android.gpsDisabled
import com.geeksville.mesh.android.hasGps
import com.geeksville.mesh.copy import com.geeksville.mesh.copy
import com.geeksville.mesh.ui.map.components.EditWaypointDialog
import com.geeksville.mesh.util.SqlTileWriterExt
import com.geeksville.mesh.waypoint import com.geeksville.mesh.waypoint
import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist import com.google.accompanist.permissions.ExperimentalPermissionsApi // Added for Accompanist
import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist import com.google.accompanist.permissions.rememberMultiplePermissionsState // Added for Accompanist
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.meshtastic.core.common.gpsDisabled
import org.meshtastic.core.common.hasGps
import org.meshtastic.core.database.entity.Packet import org.meshtastic.core.database.entity.Packet
import org.meshtastic.core.database.model.Node import org.meshtastic.core.database.model.Node
import org.meshtastic.core.model.DataPacket import org.meshtastic.core.model.DataPacket
import org.meshtastic.core.model.util.formatAgo import org.meshtastic.core.model.util.formatAgo
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.feature.map.MapViewModel
import org.meshtastic.feature.map.addCopyright
import org.meshtastic.feature.map.addScaleBarOverlay
import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer import org.meshtastic.feature.map.cluster.RadiusMarkerClusterer
import org.meshtastic.feature.map.component.CacheLayout import org.meshtastic.feature.map.component.CacheLayout
import org.meshtastic.feature.map.component.DownloadButton import org.meshtastic.feature.map.component.DownloadButton
import org.meshtastic.feature.map.component.EditWaypointDialog
import org.meshtastic.feature.map.component.MapButton import org.meshtastic.feature.map.component.MapButton
import org.meshtastic.feature.map.createLatLongGrid
import org.meshtastic.feature.map.model.CustomTileSource import org.meshtastic.feature.map.model.CustomTileSource
import org.meshtastic.feature.map.model.MarkerWithLabel import org.meshtastic.feature.map.model.MarkerWithLabel
import org.meshtastic.feature.map.zoomIn
import org.osmdroid.bonuspack.utils.BonusPackHelper.getBitmapFromVectorDrawable import org.osmdroid.bonuspack.utils.BonusPackHelper.getBitmapFromVectorDrawable
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import org.osmdroid.events.MapEventsReceiver import org.osmdroid.events.MapEventsReceiver
@ -277,10 +271,11 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
MyLocationNewOverlay(this).apply { MyLocationNewOverlay(this).apply {
enableMyLocation() enableMyLocation()
enableFollowLocation() enableFollowLocation()
getBitmapFromVectorDrawable(context, com.geeksville.mesh.R.drawable.ic_map_location_dot_24)?.let { getBitmapFromVectorDrawable(context, org.meshtastic.core.ui.R.drawable.ic_map_location_dot_24)
setPersonIcon(it) ?.let {
setPersonAnchor(0.5f, 0.5f) setPersonIcon(it)
} setPersonAnchor(0.5f, 0.5f)
}
getBitmapFromVectorDrawable(context, org.meshtastic.core.ui.R.drawable.ic_map_navigation_24)?.let { getBitmapFromVectorDrawable(context, org.meshtastic.core.ui.R.drawable.ic_map_navigation_24)?.let {
setDirectionIcon(it) setDirectionIcon(it)
setDirectionAnchor(0.5f, 0.5f) setDirectionAnchor(0.5f, 0.5f)
@ -309,7 +304,7 @@ fun MapView(mapViewModel: MapViewModel = hiltViewModel(), navigateToNodeDetails:
val waypoints by mapViewModel.waypoints.collectAsStateWithLifecycle(emptyMap()) val waypoints by mapViewModel.waypoints.collectAsStateWithLifecycle(emptyMap())
val markerIcon = remember { val markerIcon = remember {
AppCompatResources.getDrawable(context, com.geeksville.mesh.R.drawable.ic_baseline_location_on_24) AppCompatResources.getDrawable(context, org.meshtastic.core.ui.R.drawable.ic_baseline_location_on_24)
} }
fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> { fun MapView.onNodesChanged(nodes: Collection<Node>): List<MarkerWithLabel> {

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map package org.meshtastic.feature.map
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
@ -33,7 +33,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import org.meshtastic.feature.map.requiredZoomLevel
import org.osmdroid.config.Configuration import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.ITileSource import org.osmdroid.tileprovider.tilesource.ITileSource
import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.tileprovider.tilesource.TileSourceFactory
@ -72,7 +71,7 @@ private const val DEFAULT_ZOOM_LEVEL = 15.0
@Suppress("MagicNumber") @Suppress("MagicNumber")
@Composable @Composable
internal fun rememberMapViewWithLifecycle( fun rememberMapViewWithLifecycle(
applicationId: String, applicationId: String,
box: BoundingBox, box: BoundingBox,
tileSource: ITileSource = TileSourceFactory.DEFAULT_TILE_SOURCE, tileSource: ITileSource = TileSourceFactory.DEFAULT_TILE_SOURCE,

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.util package org.meshtastic.feature.map
import android.database.Cursor import android.database.Cursor
import org.osmdroid.tileprovider.modules.DatabaseFileArchive import org.osmdroid.tileprovider.modules.DatabaseFileArchive

Wyświetl plik

@ -15,9 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map.components package org.meshtastic.feature.map.component
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.TimePickerDialog
import android.text.format.DateFormat
import android.widget.DatePicker import android.widget.DatePicker
import android.widget.TimePicker import android.widget.TimePicker
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -76,7 +78,7 @@ import java.util.Locale
@Suppress("LongMethod") @Suppress("LongMethod")
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
internal fun EditWaypointDialog( fun EditWaypointDialog(
waypoint: Waypoint, waypoint: Waypoint,
onSendClicked: (Waypoint) -> Unit, onSendClicked: (Waypoint) -> Unit,
onDeleteClicked: (Waypoint) -> Unit, onDeleteClicked: (Waypoint) -> Unit,

Wyświetl plik

@ -17,7 +17,7 @@
@file:Suppress("MagicNumber") @file:Suppress("MagicNumber")
package com.geeksville.mesh.ui.map package org.meshtastic.feature.map
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
@ -67,13 +67,6 @@ import com.geeksville.mesh.ConfigProtos.Config.DisplayConfig.DisplayUnits
import com.geeksville.mesh.MeshProtos.Position import com.geeksville.mesh.MeshProtos.Position
import com.geeksville.mesh.MeshProtos.Waypoint import com.geeksville.mesh.MeshProtos.Waypoint
import com.geeksville.mesh.copy import com.geeksville.mesh.copy
import com.geeksville.mesh.ui.map.components.ClusterItemsListDialog
import com.geeksville.mesh.ui.map.components.EditWaypointDialog
import com.geeksville.mesh.ui.map.components.NodeClusterMarkers
import com.geeksville.mesh.ui.map.components.WaypointMarkers
import com.geeksville.mesh.ui.metrics.HEADING_DEG
import com.geeksville.mesh.ui.metrics.formatPositionTime
import com.geeksville.mesh.ui.node.DEG_D
import com.geeksville.mesh.waypoint import com.geeksville.mesh.waypoint
import com.google.android.gms.location.LocationCallback import com.google.android.gms.location.LocationCallback
import com.google.android.gms.location.LocationRequest import com.google.android.gms.location.LocationRequest
@ -87,7 +80,6 @@ import com.google.android.gms.maps.model.CameraPosition
import com.google.android.gms.maps.model.JointType import com.google.android.gms.maps.model.JointType
import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.LatLngBounds import com.google.android.gms.maps.model.LatLngBounds
import com.google.maps.android.clustering.ClusterItem
import com.google.maps.android.compose.ComposeMapColorScheme import com.google.maps.android.compose.ComposeMapColorScheme
import com.google.maps.android.compose.GoogleMap import com.google.maps.android.compose.GoogleMap
import com.google.maps.android.compose.MapEffect import com.google.maps.android.compose.MapEffect
@ -110,19 +102,23 @@ import org.meshtastic.core.model.util.metersIn
import org.meshtastic.core.model.util.mpsToKmph import org.meshtastic.core.model.util.mpsToKmph
import org.meshtastic.core.model.util.mpsToMph import org.meshtastic.core.model.util.mpsToMph
import org.meshtastic.core.model.util.toString import org.meshtastic.core.model.util.toString
import org.meshtastic.core.proto.formatPositionTime
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.NodeChip import org.meshtastic.core.ui.component.NodeChip
import org.meshtastic.feature.map.LastHeardFilter import org.meshtastic.feature.map.component.ClusterItemsListDialog
import org.meshtastic.feature.map.LayerType
import org.meshtastic.feature.map.LocationPermissionsHandler
import org.meshtastic.feature.map.MapViewModel
import org.meshtastic.feature.map.component.CustomMapLayersSheet import org.meshtastic.feature.map.component.CustomMapLayersSheet
import org.meshtastic.feature.map.component.CustomTileProviderManagerSheet import org.meshtastic.feature.map.component.CustomTileProviderManagerSheet
import org.meshtastic.feature.map.component.EditWaypointDialog
import org.meshtastic.feature.map.component.MapControlsOverlay import org.meshtastic.feature.map.component.MapControlsOverlay
import org.meshtastic.feature.map.component.NodeClusterMarkers
import org.meshtastic.feature.map.component.WaypointMarkers
import org.meshtastic.feature.map.model.NodeClusterItem
import timber.log.Timber import timber.log.Timber
import java.text.DateFormat import java.text.DateFormat
private const val MIN_TRACK_POINT_DISTANCE_METERS = 20f private const val MIN_TRACK_POINT_DISTANCE_METERS = 20f
private const val DEG_D = 1e-7
private const val HEADING_DEG = 1e-5
@Suppress("CyclomaticComplexMethod", "LongMethod") @Suppress("CyclomaticComplexMethod", "LongMethod")
@OptIn(MapsComposeExperimentalApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class) @OptIn(MapsComposeExperimentalApi::class, ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@ -651,34 +647,6 @@ fun Uri.getFileName(context: android.content.Context): String {
return name return name
} }
data class NodeClusterItem(val node: Node, val nodePosition: LatLng, val nodeTitle: String, val nodeSnippet: String) :
ClusterItem {
override fun getPosition(): LatLng = nodePosition
override fun getTitle(): String = nodeTitle
override fun getSnippet(): String = nodeSnippet
override fun getZIndex(): Float? = null
fun getPrecisionMeters(): Double? {
val precisionMap =
mapOf(
10 to 23345.484932,
11 to 11672.7369,
12 to 5836.36288,
13 to 2918.175876,
14 to 1459.0823719999053,
15 to 729.53562,
16 to 364.7622,
17 to 182.375556,
18 to 91.182212,
19 to 45.58554,
)
return precisionMap[this.node.position.precisionBits]
}
}
@OptIn(ExperimentalMaterial3ExpressiveApi::class) @OptIn(ExperimentalMaterial3ExpressiveApi::class)
@Composable @Composable
@Suppress("LongMethod") @Suppress("LongMethod")
@ -698,15 +666,9 @@ private fun PositionInfoWindowContent(
Card { Card {
Column(modifier = Modifier.padding(8.dp)) { Column(modifier = Modifier.padding(8.dp)) {
PositionRow( PositionRow(label = stringResource(R.string.latitude), value = "%.5f".format(position.latitudeI * DEG_D))
label = stringResource(R.string.latitude),
value = "%.5f".format(position.latitudeI * com.geeksville.mesh.ui.metrics.DEG_D),
)
PositionRow( PositionRow(label = stringResource(R.string.longitude), value = "%.5f".format(position.longitudeI * DEG_D))
label = stringResource(R.string.longitude),
value = "%.5f".format(position.longitudeI * com.geeksville.mesh.ui.metrics.DEG_D),
)
PositionRow(label = stringResource(R.string.sats), value = position.satsInView.toString()) PositionRow(label = stringResource(R.string.sats), value = position.satsInView.toString())
@ -722,7 +684,7 @@ private fun PositionInfoWindowContent(
value = "%.0f°".format(position.groundTrack * HEADING_DEG), value = "%.0f°".format(position.groundTrack * HEADING_DEG),
) )
PositionRow(label = stringResource(R.string.timestamp), value = formatPositionTime(position, dateFormat)) PositionRow(label = stringResource(R.string.timestamp), value = position.formatPositionTime(dateFormat))
} }
} }
} }

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map.components package org.meshtastic.feature.map.component
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -31,9 +31,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.geeksville.mesh.ui.map.NodeClusterItem
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.NodeChip import org.meshtastic.core.ui.component.NodeChip
import org.meshtastic.feature.map.model.NodeClusterItem
@Composable @Composable
fun ClusterItemsListDialog( fun ClusterItemsListDialog(

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map.components package org.meshtastic.feature.map.component
import android.app.DatePickerDialog import android.app.DatePickerDialog
import android.app.TimePickerDialog import android.app.TimePickerDialog

Wyświetl plik

@ -15,12 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map.components package org.meshtastic.feature.map.component
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.key import androidx.compose.runtime.key
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import com.geeksville.mesh.ui.map.NodeClusterItem
import com.google.maps.android.clustering.Cluster import com.google.maps.android.clustering.Cluster
import com.google.maps.android.clustering.view.DefaultClusterRenderer import com.google.maps.android.clustering.view.DefaultClusterRenderer
import com.google.maps.android.compose.Circle import com.google.maps.android.compose.Circle
@ -28,6 +27,7 @@ import com.google.maps.android.compose.MapsComposeExperimentalApi
import com.google.maps.android.compose.clustering.Clustering import com.google.maps.android.compose.clustering.Clustering
import org.meshtastic.core.ui.component.NodeChip import org.meshtastic.core.ui.component.NodeChip
import org.meshtastic.feature.map.BaseMapViewModel import org.meshtastic.feature.map.BaseMapViewModel
import org.meshtastic.feature.map.model.NodeClusterItem
@OptIn(MapsComposeExperimentalApi::class) @OptIn(MapsComposeExperimentalApi::class)
@Suppress("NestedBlockDepth") @Suppress("NestedBlockDepth")

Wyświetl plik

@ -15,13 +15,12 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map.components package org.meshtastic.feature.map.component
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import com.geeksville.mesh.MeshProtos import com.geeksville.mesh.MeshProtos
import com.geeksville.mesh.ui.node.DEG_D
import com.google.android.gms.maps.model.BitmapDescriptor import com.google.android.gms.maps.model.BitmapDescriptor
import com.google.android.gms.maps.model.LatLng import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.compose.Marker import com.google.maps.android.compose.Marker
@ -29,6 +28,8 @@ import com.google.maps.android.compose.rememberUpdatedMarkerState
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.feature.map.BaseMapViewModel import org.meshtastic.feature.map.BaseMapViewModel
private const val DEG_D = 1e-7
@Composable @Composable
fun WaypointMarkers( fun WaypointMarkers(
displayableWaypoints: List<MeshProtos.Waypoint>, displayableWaypoints: List<MeshProtos.Waypoint>,

Wyświetl plik

@ -0,0 +1,50 @@
/*
* Copyright (c) 2025 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 <https://www.gnu.org/licenses/>.
*/
package org.meshtastic.feature.map.model
import com.google.android.gms.maps.model.LatLng
import com.google.maps.android.clustering.ClusterItem
import org.meshtastic.core.database.model.Node
data class NodeClusterItem(val node: Node, val nodePosition: LatLng, val nodeTitle: String, val nodeSnippet: String) :
ClusterItem {
override fun getPosition(): LatLng = nodePosition
override fun getTitle(): String = nodeTitle
override fun getSnippet(): String = nodeSnippet
override fun getZIndex(): Float? = null
fun getPrecisionMeters(): Double? {
val precisionMap =
mapOf(
10 to 23345.484932,
11 to 11672.7369,
12 to 5836.36288,
13 to 2918.175876,
14 to 1459.0823719999053,
15 to 729.53562,
16 to 364.7622,
17 to 182.375556,
18 to 91.182212,
19 to 45.58554,
)
return precisionMap[this.node.position.precisionBits]
}
}

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map package org.meshtastic.feature.map
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -28,7 +28,6 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import org.meshtastic.core.strings.R import org.meshtastic.core.strings.R
import org.meshtastic.core.ui.component.MainAppBar import org.meshtastic.core.ui.component.MainAppBar
import org.meshtastic.feature.map.MapViewModel
@Composable @Composable
fun MapScreen( fun MapScreen(

Wyświetl plik

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.geeksville.mesh.ui.map package org.meshtastic.feature.map.node
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel