mirror of
https://github.com/meshtastic/Meshtastic-Android.git
synced 2026-04-20 22:23:37 +00:00
feat: add option to show phone GPS location on map
This commit is contained in:
parent
a51e6afd4e
commit
377c6a18e0
7 changed files with 192 additions and 55 deletions
|
|
@ -0,0 +1,66 @@
|
|||
package com.geeksville.mesh.ui.components
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import com.geeksville.mesh.R
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun IconButton(
|
||||
onClick: () -> Unit,
|
||||
@DrawableRes drawableRes: Int,
|
||||
@StringRes contentDescription: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
IconButton(
|
||||
onClick = onClick,
|
||||
drawableRes = drawableRes,
|
||||
contentDescription = stringResource(contentDescription),
|
||||
modifier = modifier,
|
||||
enabled = enabled,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun IconButton(
|
||||
onClick: () -> Unit,
|
||||
@DrawableRes drawableRes: Int,
|
||||
contentDescription: String?,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
enabled = enabled,
|
||||
modifier = modifier.size(48.dp),
|
||||
colors = ButtonDefaults.buttonColors(backgroundColor = MaterialTheme.colors.primary),
|
||||
) {
|
||||
Icon(
|
||||
painterResource(id = drawableRes),
|
||||
contentDescription,
|
||||
modifier = Modifier.scale(1.5f),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun IconButtonsPreview() {
|
||||
IconButton(
|
||||
onClick = {},
|
||||
drawableRes = R.drawable.ic_twotone_layers_24,
|
||||
R.string.map_style_selection,
|
||||
)
|
||||
}
|
||||
|
|
@ -7,7 +7,11 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.Scaffold
|
||||
|
|
@ -25,8 +29,10 @@ import androidx.compose.ui.platform.ComposeView
|
|||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.platform.ViewCompositionStrategy
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.toBitmap
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import com.geeksville.mesh.BuildConfig
|
||||
|
|
@ -36,6 +42,10 @@ import com.geeksville.mesh.NodeInfo
|
|||
import com.geeksville.mesh.R
|
||||
import com.geeksville.mesh.android.BuildUtils.debug
|
||||
import com.geeksville.mesh.android.Logging
|
||||
import com.geeksville.mesh.android.getLocationPermissions
|
||||
import com.geeksville.mesh.android.gpsDisabled
|
||||
import com.geeksville.mesh.android.hasGps
|
||||
import com.geeksville.mesh.android.hasLocationPermission
|
||||
import com.geeksville.mesh.copy
|
||||
import com.geeksville.mesh.database.entity.Packet
|
||||
import com.geeksville.mesh.model.UIViewModel
|
||||
|
|
@ -46,7 +56,7 @@ import com.geeksville.mesh.ui.ScreenFragment
|
|||
import com.geeksville.mesh.ui.map.components.CacheLayout
|
||||
import com.geeksville.mesh.ui.map.components.DownloadButton
|
||||
import com.geeksville.mesh.ui.map.components.EditWaypointDialog
|
||||
import com.geeksville.mesh.ui.map.components.MapStyleButton
|
||||
import com.geeksville.mesh.ui.components.IconButton
|
||||
import com.geeksville.mesh.util.EnableWakeLock
|
||||
import com.geeksville.mesh.util.SqlTileWriterExt
|
||||
import com.geeksville.mesh.util.requiredZoomLevel
|
||||
|
|
@ -79,6 +89,7 @@ import org.osmdroid.views.overlay.Polygon
|
|||
import org.osmdroid.views.overlay.TilesOverlay
|
||||
import org.osmdroid.views.overlay.gridlines.LatLonGridlineOverlay2
|
||||
import org.osmdroid.views.overlay.infowindow.InfoWindow
|
||||
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
|
||||
import java.io.File
|
||||
import java.text.DateFormat
|
||||
|
||||
|
|
@ -126,6 +137,7 @@ fun MapView(model: UIViewModel = viewModel()) {
|
|||
|
||||
// Map Elements
|
||||
var downloadRegionBoundingBox: BoundingBox? by remember { mutableStateOf(null) }
|
||||
var myLocationOverlay: MyLocationNewOverlay? by remember { mutableStateOf(null) }
|
||||
|
||||
val context = LocalContext.current
|
||||
val mPrefs = context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
|
||||
|
|
@ -133,6 +145,8 @@ fun MapView(model: UIViewModel = viewModel()) {
|
|||
val haptic = LocalHapticFeedback.current
|
||||
fun performHapticFeedback() = haptic.performHapticFeedback(HapticFeedbackType.LongPress)
|
||||
|
||||
val hasGps = context.hasGps()
|
||||
|
||||
EnableWakeLock(context)
|
||||
|
||||
val map = remember {
|
||||
|
|
@ -140,6 +154,43 @@ fun MapView(model: UIViewModel = viewModel()) {
|
|||
clipToOutline = true
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleMyLocation() {
|
||||
if (context.gpsDisabled()) {
|
||||
debug("Telling user we need location turned on for MyLocationNewOverlay")
|
||||
model.showSnackbar(R.string.location_disabled)
|
||||
return
|
||||
}
|
||||
debug("user clicked MyLocationNewOverlay ${myLocationOverlay == null}")
|
||||
if (myLocationOverlay == null) {
|
||||
myLocationOverlay = MyLocationNewOverlay(map).apply {
|
||||
enableMyLocation()
|
||||
enableFollowLocation()
|
||||
AppCompatResources.getDrawable(context, R.drawable.ic_location_dot_24)?.let {
|
||||
setPersonIcon(it.toBitmap())
|
||||
setPersonAnchor(0.5f, 0.5f)
|
||||
}
|
||||
}
|
||||
map.overlays.add(myLocationOverlay)
|
||||
} else {
|
||||
myLocationOverlay?.apply {
|
||||
disableMyLocation()
|
||||
disableFollowLocation()
|
||||
}
|
||||
map.overlays.remove(myLocationOverlay)
|
||||
myLocationOverlay = null
|
||||
}
|
||||
}
|
||||
|
||||
val requestPermissionAndToggleLauncher =
|
||||
rememberLauncherForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { permissions ->
|
||||
if (permissions.entries.all { it.value }) toggleMyLocation()
|
||||
}
|
||||
|
||||
fun requestPermissionAndToggle() {
|
||||
requestPermissionAndToggleLauncher.launch(context.getLocationPermissions())
|
||||
}
|
||||
|
||||
val nodes by model.nodeDB.nodes.observeAsState(emptyMap())
|
||||
val waypoints by model.waypoints.observeAsState(emptyMap())
|
||||
|
||||
|
|
@ -388,6 +439,9 @@ fun MapView(model: UIViewModel = viewModel()) {
|
|||
if (overlays.none { it is MapEventsOverlay }) {
|
||||
overlays.add(0, MapEventsOverlay(mapEventsReceiver))
|
||||
}
|
||||
if (myLocationOverlay != null && overlays.none { it is MyLocationNewOverlay }) {
|
||||
overlays.add(myLocationOverlay)
|
||||
}
|
||||
addCopyright() // Copyright is required for certain map sources
|
||||
createLatLongGrid(false)
|
||||
|
||||
|
|
@ -601,10 +655,28 @@ fun MapView(model: UIViewModel = viewModel()) {
|
|||
}
|
||||
},
|
||||
modifier = Modifier.align(Alignment.BottomCenter)
|
||||
) else MapStyleButton(
|
||||
onClick = { showMapStyleDialog() },
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
)
|
||||
) else Column(
|
||||
modifier = Modifier
|
||||
.padding(top = 16.dp, end = 16.dp)
|
||||
.align(Alignment.TopEnd),
|
||||
) {
|
||||
IconButton(
|
||||
onClick = { showMapStyleDialog() },
|
||||
drawableRes = R.drawable.ic_twotone_layers_24,
|
||||
contentDescription = R.string.map_style_selection,
|
||||
)
|
||||
IconButton(
|
||||
onClick = {
|
||||
if (context.hasLocationPermission()) toggleMyLocation()
|
||||
else requestPermissionAndToggle()
|
||||
},
|
||||
enabled = hasGps,
|
||||
drawableRes = if (myLocationOverlay == null) R.drawable.ic_twotone_my_location_24
|
||||
else R.drawable.ic_twotone_location_disabled_24,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(top = 8.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showEditWaypointDialog != null) {
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
package com.geeksville.mesh.ui.map.components
|
||||
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.material.Button
|
||||
import androidx.compose.material.ButtonDefaults
|
||||
import androidx.compose.material.Icon
|
||||
import androidx.compose.material.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import com.geeksville.mesh.R
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@Composable
|
||||
fun MapStyleButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Button(
|
||||
onClick = onClick,
|
||||
modifier = modifier
|
||||
.padding(16.dp)
|
||||
.size(48.dp),
|
||||
colors = ButtonDefaults.buttonColors(
|
||||
backgroundColor = MaterialTheme.colors.primary
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
painterResource(id = R.drawable.ic_twotone_layers_24),
|
||||
stringResource(R.string.map_style_selection),
|
||||
modifier = Modifier.scale(1.5f)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Composable
|
||||
private fun MapStyleButtonPreview() {
|
||||
MapStyleButton(onClick = {})
|
||||
}
|
||||
11
app/src/main/res/drawable/ic_location_dot_24.xml
Normal file
11
app/src/main/res/drawable/ic_location_dot_24.xml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#3388ff"
|
||||
android:pathData="M12,12m-8,0a8,8 0,1 1,16 0a8,8 0,1 1,-16 0"
|
||||
android:strokeWidth="2.5"
|
||||
android:strokeColor="@android:color/white" />
|
||||
</vector>
|
||||
|
|
@ -1,8 +1,14 @@
|
|||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillAlpha="0.3"
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M6.26,9L12,13.47 17.74,9 12,4.53z" android:strokeAlpha="0.3"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19.37,12.8l-7.38,5.74 -7.37,-5.73L3,14.07l9,7 9,-7zM12,2L3,9l1.63,1.27L12,16l7.36,-5.73L21,9l-9,-7zM12,13.47L6.26,9 12,4.53 17.74,9 12,13.47z"/>
|
||||
android:pathData="M6.26,9L12,13.47 17.74,9 12,4.53z"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.37,12.8l-7.38,5.74 -7.37,-5.73L3,14.07l9,7 9,-7zM12,2L3,9l1.63,1.27L12,16l7.36,-5.73L21,9l-9,-7zM12,13.47L6.26,9 12,4.53 17.74,9 12,13.47z" />
|
||||
</vector>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M23,13v-2h-2.06c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94V1h-2v2.06c-0.98,0.11 -1.91,0.38 -2.77,0.78l1.53,1.53C10.46,5.13 11.22,5 12,5c3.87,0 7,3.13 7,7 0,0.79 -0.13,1.54 -0.37,2.24l1.53,1.53c0.4,-0.86 0.67,-1.79 0.78,-2.77H23zM4.41,2.86L3,4.27l2.04,2.04C3.97,7.62 3.26,9.23 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c1.77,-0.2 3.38,-0.91 4.69,-1.98L19.73,21l1.41,-1.41L4.41,2.86zM12,19c-3.87,0 -7,-3.13 -7,-7 0,-1.61 0.55,-3.09 1.46,-4.27l9.81,9.81C15.09,18.45 13.61,19 12,19z" />
|
||||
</vector>
|
||||
17
app/src/main/res/drawable/ic_twotone_my_location_24.xml
Normal file
17
app/src/main/res/drawable/ic_twotone_my_location_24.xml
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M13,3.06V1h-2v2.06C6.83,3.52 3.52,6.83 3.06,11H1v2h2.06c0.46,4.17 3.77,7.48 7.94,7.94V23h2v-2.06c4.17,-0.46 7.48,-3.77 7.94,-7.94H23v-2h-2.06c-0.46,-4.17 -3.77,-7.48 -7.94,-7.94zM12,19c-3.87,0 -7,-3.13 -7,-7s3.13,-7 7,-7 7,3.13 7,7 -3.13,7 -7,7z" />
|
||||
<path
|
||||
android:fillAlpha="0.3"
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,12m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0"
|
||||
android:strokeAlpha="0.3" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,8c-2.21,0 -4,1.79 -4,4s1.79,4 4,4 4,-1.79 4,-4 -1.79,-4 -4,-4zM12,14c-1.1,0 -2,-0.9 -2,-2s0.9,-2 2,-2 2,0.9 2,2 -0.9,2 -2,2z" />
|
||||
</vector>
|
||||
Loading…
Add table
Add a link
Reference in a new issue