diff --git a/app/src/main/java/com/geeksville/mesh/ui/components/IconButton.kt b/app/src/main/java/com/geeksville/mesh/ui/components/IconButton.kt
new file mode 100644
index 000000000..61c9f65f5
--- /dev/null
+++ b/app/src/main/java/com/geeksville/mesh/ui/components/IconButton.kt
@@ -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,
+ )
+}
diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt
index a82d3fa02..7d14a8bcc 100644
--- a/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt
+++ b/app/src/main/java/com/geeksville/mesh/ui/map/MapFragment.kt
@@ -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) {
diff --git a/app/src/main/java/com/geeksville/mesh/ui/map/components/MapStyleButton.kt b/app/src/main/java/com/geeksville/mesh/ui/map/components/MapStyleButton.kt
deleted file mode 100644
index 59f7faa28..000000000
--- a/app/src/main/java/com/geeksville/mesh/ui/map/components/MapStyleButton.kt
+++ /dev/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 = {})
-}
diff --git a/app/src/main/res/drawable/ic_location_dot_24.xml b/app/src/main/res/drawable/ic_location_dot_24.xml
new file mode 100644
index 000000000..74531932f
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location_dot_24.xml
@@ -0,0 +1,11 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_twotone_layers_24.xml b/app/src/main/res/drawable/ic_twotone_layers_24.xml
index 489b87756..4b26e9d3f 100644
--- a/app/src/main/res/drawable/ic_twotone_layers_24.xml
+++ b/app/src/main/res/drawable/ic_twotone_layers_24.xml
@@ -1,8 +1,14 @@
-
-
+
-
+ android:pathData="M6.26,9L12,13.47 17.74,9 12,4.53z"
+ android:strokeAlpha="0.3" />
+
diff --git a/app/src/main/res/drawable/ic_twotone_location_disabled_24.xml b/app/src/main/res/drawable/ic_twotone_location_disabled_24.xml
new file mode 100644
index 000000000..fe3907ca0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_twotone_location_disabled_24.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_twotone_my_location_24.xml b/app/src/main/res/drawable/ic_twotone_my_location_24.xml
new file mode 100644
index 000000000..7d15d13f6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_twotone_my_location_24.xml
@@ -0,0 +1,17 @@
+
+
+
+
+