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 @@ + + + + +