commit d901cba8288986674db0acaf0474b4a1ae0403ce
parent 4623c5cb1ae3aabb6dcdb3ca04383a13526b96f7
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Wed,  5 Jun 2024 01:22:05 +0200

feat(better_location): edit coordinates

Signed-off-by: rhunk <101876869+rhunk@users.noreply.github.com>

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt | 2+-
Dapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/BetterLocationRoot.kt | 389-------------------------------------------------------------------------------
Aapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/AddCoordinatesDialog.kt | 92+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aapp/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/BetterLocationRoot.kt | 446+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mcommon/src/main/assets/lang/en_US.json | 2++
5 files changed, 541 insertions(+), 390 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt @@ -11,7 +11,7 @@ import androidx.navigation.NavDestination.Companion.hierarchy import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavGraphBuilder import me.rhunk.snapenhance.RemoteSideContext -import me.rhunk.snapenhance.ui.manager.pages.BetterLocationRoot +import me.rhunk.snapenhance.ui.manager.pages.location.BetterLocationRoot import me.rhunk.snapenhance.ui.manager.pages.FileImportsRoot import me.rhunk.snapenhance.ui.manager.pages.LoggerHistoryRoot import me.rhunk.snapenhance.ui.manager.pages.TasksRootSection diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/BetterLocationRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/BetterLocationRoot.kt @@ -1,388 +0,0 @@ -package me.rhunk.snapenhance.ui.manager.pages - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn -import androidx.compose.foundation.lazy.items -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.DeleteOutline -import androidx.compose.material3.* -import androidx.compose.runtime.* -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.navigation.NavBackStackEntry -import kotlinx.coroutines.launch -import me.rhunk.snapenhance.bridge.location.FriendLocation -import me.rhunk.snapenhance.bridge.location.LocationCoordinates -import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList -import me.rhunk.snapenhance.common.ui.rememberAsyncUpdateDispatcher -import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie -import me.rhunk.snapenhance.storage.addOrUpdateLocationCoordinate -import me.rhunk.snapenhance.storage.getLocationCoordinates -import me.rhunk.snapenhance.storage.removeLocationCoordinate -import me.rhunk.snapenhance.ui.manager.Routes -import me.rhunk.snapenhance.ui.util.AlertDialogs -import me.rhunk.snapenhance.ui.util.DialogProperties -import me.rhunk.snapenhance.ui.util.coil.BitmojiImage -import org.osmdroid.util.GeoPoint -import org.osmdroid.views.MapView -import org.osmdroid.views.overlay.Marker - -class BetterLocationRoot : Routes.Route() { - private val alertDialogs by lazy { AlertDialogs(context.translation) } - - @Composable - private fun FriendLocationItem( - friendLocation: FriendLocation, - dismiss: () -> Unit - ) { - ElevatedCard(onClick = { - context.config.root.global.betterLocation.coordinates.setAny(friendLocation.latitude to friendLocation.longitude) - dismiss() - }, modifier = Modifier.padding(4.dp)) { - Row( - modifier = Modifier - .padding(8.dp) - .fillMaxWidth(), - verticalAlignment = Alignment.CenterVertically - ) { - BitmojiImage( - context = context, - url = BitmojiSelfie.getBitmojiSelfie( - friendLocation.bitmojiSelfieId, - friendLocation.bitmojiId, - BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D - ), - size = 48, - modifier = Modifier.padding(6.dp) - ) - Column( - modifier = Modifier.weight(1f), - ) { - Text(friendLocation.displayName?.let { "$it (${friendLocation.username})" } - ?: friendLocation.username, fontSize = 16.sp, fontWeight = FontWeight.Bold) - Text( - text = buildString { - append(friendLocation.localityPieces.joinToString(", ")) - append("\n") - append("Lat: ${friendLocation.latitude.toFloat()}, Lng: ${friendLocation.longitude.toFloat()}") - }, - fontSize = 10.sp, - fontWeight = FontWeight.Light, - lineHeight = 15.sp - ) - } - } - } - } - - @Composable - private fun FriendLocationsDialogs( - friendsLocation: List<FriendLocation>, - dismiss: () -> Unit - ) { - var search by remember { mutableStateOf("") } - val filteredFriendsLocation = rememberAsyncMutableStateList(defaultValue = friendsLocation, keys = arrayOf(search)) { - search.takeIf { it.isNotBlank() }?.let { - friendsLocation.filter { - it.displayName?.contains(search, ignoreCase = true) == true || it.username.contains(search, ignoreCase = true) - } - } ?: friendsLocation - } - - ElevatedCard( - shape = MaterialTheme.shapes.large, - modifier = Modifier.padding(top = 32.dp, bottom = 32.dp) - ) { - Text( - translation["teleport_to_friend_title"], - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - ) - OutlinedTextField( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - value = search, - onValueChange = { search = it }, - label = { Text(translation["search_bar"]) } - ) - LazyColumn( - modifier = Modifier - .fillMaxSize() - ) { - item { - if (friendsLocation.isEmpty()) { - Text( - translation["no_friends_map"], - fontSize = 16.sp, - modifier = Modifier.padding(16.dp), - fontWeight = FontWeight.Light - ) - } else if (filteredFriendsLocation.isEmpty()) { - Text( - translation["no_friends_found"], - fontSize = 16.sp, - modifier = Modifier.padding(16.dp), - fontWeight = FontWeight.Light - ) - } - } - items(filteredFriendsLocation) { friendLocation -> - FriendLocationItem(friendLocation, dismiss) - } - } - } - } - - override val content: @Composable (NavBackStackEntry) -> Unit = { - val coordinatesProperty = remember { - context.config.root.global.betterLocation.getPropertyPair("coordinates") - } - - val updateDispatcher = rememberAsyncUpdateDispatcher() - val savedCoordinates = rememberAsyncMutableStateList( - defaultValue = listOf(), - updateDispatcher = updateDispatcher - ) { - context.database.getLocationCoordinates() - } - var showMap by remember { mutableStateOf(false) } - var addSavedCoordinateDialog by remember { mutableStateOf(false) } - var showTeleportDialog by remember { mutableStateOf(false) } - - val marker = remember { mutableStateOf<Marker?>(null) } - val mapView = remember { mutableStateOf<MapView?>(null) } - var spoofedCoordinates by remember(showTeleportDialog, showMap) { mutableStateOf(coordinatesProperty.value.get() as? Pair<*, *>) } - - fun addSavedCoordinate(id: Int?, locationCoordinates: LocationCoordinates) { - context.coroutineScope.launch { - context.database.addOrUpdateLocationCoordinate(id, locationCoordinates) - updateDispatcher.dispatch() - } - } - - if (showTeleportDialog) { - me.rhunk.snapenhance.ui.util.Dialog( - properties = DialogProperties(usePlatformDefaultWidth = false), - onDismissRequest = { showTeleportDialog = false }, - content = { - FriendLocationsDialogs(remember { context.locationManager.friendsLocation }) { - showTeleportDialog = false - context.coroutineScope.launch { - context.config.writeConfig() - } - } - } - ) - } - - Column( - modifier = Modifier - .fillMaxSize() - ) { - Text( - translation.format( - "spoofed_coordinates_title", - "latitude" to ((spoofedCoordinates?.first as? Double)?.toFloat() ?: "0.0").toString(), - "longitude" to ((spoofedCoordinates?.second as? Double)?.toFloat() ?: "0.0").toString() - ), - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - textAlign = TextAlign.Center, - modifier = Modifier - .fillMaxWidth() - .padding(12.dp) - ) - - if (addSavedCoordinateDialog) { - var savedName by remember { mutableStateOf("") } - me.rhunk.snapenhance.ui.util.Dialog( - onDismissRequest = { addSavedCoordinateDialog = false }, - content = { - alertDialogs.DefaultDialogCard { - val focusRequester = remember { FocusRequester() } - Column( - modifier = Modifier.padding(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - Text(translation["save_coordinates_dialog_title"], fontSize = 20.sp, fontWeight = FontWeight.Bold) - OutlinedTextField( - modifier = Modifier - .focusRequester(focusRequester) - .onGloballyPositioned { - focusRequester.requestFocus() - }, - value = savedName, - onValueChange = { savedName = it }, - label = { Text(translation["saved_name_dialog_hint"]) } - ) - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.End - ) { - Button( - onClick = { - addSavedCoordinateDialog = false - addSavedCoordinate(null, LocationCoordinates().apply { - this.name = savedName - this.latitude = marker.value?.position?.latitude as Double - this.longitude = marker.value?.position?.longitude as Double - }) - }, - enabled = savedName.isNotBlank() - ) { - Text(translation["save_dialog_button"]) - } - } - } - } - } - ) - } - - if (showMap) { - me.rhunk.snapenhance.ui.util.Dialog( - onDismissRequest = { showMap = false }, - content = { - alertDialogs.ChooseLocationDialog(property = coordinatesProperty, marker, mapView, saveCoordinates = { - addSavedCoordinateDialog = true - }) { - showMap = false - context.config.writeConfig() - } - } - ) - } - - LazyColumn( - modifier = Modifier - .fillMaxSize() - .clipToBounds() - ) { - item { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(16.dp), - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically - ) { - Button(onClick = { showMap = true }) { - Text(translation["choose_location_button"]) - } - Button(onClick = { showTeleportDialog = true }) { - Text(translation["teleport_to_friend_button"]) - } - } - } - item { - Row( - modifier = Modifier.padding(16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text(text = translation["spoof_location_toggle"]) - Spacer(modifier = Modifier.weight(1f)) - var isSpoofing by remember { mutableStateOf(context.config.root.global.betterLocation.spoofLocation.get()) } - Switch( - checked = isSpoofing, - onCheckedChange = { - isSpoofing = it - context.config.root.global.betterLocation.spoofLocation.set(it) - } - ) - } - } - item { - Text( - translation["saved_coordinates_title"], - fontSize = 20.sp, - fontWeight = FontWeight.Bold, - modifier = Modifier.padding(start = 16.dp) - ) - } - item { - if (savedCoordinates.isEmpty()) { - Text( - translation["no_saved_coordinates_hint"], - fontSize = 16.sp, - modifier = Modifier.padding(start = 20.dp), - fontWeight = FontWeight.Light - ) - } - } - items(savedCoordinates) { coordinates -> - var showDeleteDialog by remember { mutableStateOf(false) } - - if (showDeleteDialog) { - me.rhunk.snapenhance.ui.util.Dialog( - onDismissRequest = { showDeleteDialog = false }, - content = { - alertDialogs.ConfirmDialog( - title = translation["delete_dialog_title"], - message = translation["delete_dialog_message"], - onConfirm = { - showDeleteDialog = false - context.coroutineScope.launch { - context.database.removeLocationCoordinate(coordinates.id) - updateDispatcher.dispatch() - } - }, - onDismiss = { showDeleteDialog = false } - ) - } - ) - } - - Row( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp), - verticalAlignment = Alignment.CenterVertically - ) { - Text( - text = "${coordinates.name} (${coordinates.latitude.toFloat()}, ${coordinates.longitude.toFloat()})", - fontWeight = if (spoofedCoordinates == coordinates.latitude to coordinates.longitude) FontWeight.Bold else FontWeight.Light, - modifier = Modifier - .padding(8.dp) - .weight(1f) - .clickable { - spoofedCoordinates = - coordinates.latitude to coordinates.longitude - coordinatesProperty.value.setAny(spoofedCoordinates) - context.coroutineScope.launch { - context.config.writeConfig() - } - GeoPoint(coordinates.latitude, coordinates.longitude).also { - marker.value?.position = it - mapView.value?.controller?.apply { - animateTo(it) - setZoom(16.0) - } - } - }, - fontSize = 16.sp - ) - FilledIconButton(onClick = { - showDeleteDialog = true - }) { - Icon(Icons.Default.DeleteOutline, contentDescription = "Delete") - } - } - } - } - } - } -}- \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/AddCoordinatesDialog.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/AddCoordinatesDialog.kt @@ -0,0 +1,91 @@ +package me.rhunk.snapenhance.ui.manager.pages.location + +import androidx.compose.foundation.layout.* +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.text.TextRange +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kotlinx.coroutines.delay +import me.rhunk.snapenhance.bridge.location.LocationCoordinates +import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper +import me.rhunk.snapenhance.ui.util.AlertDialogs + + +@Composable +fun AddCoordinatesDialog( + alertDialogs: AlertDialogs, + translation: LocaleWrapper, + locationCoordinates: LocationCoordinates, + confirm: (locationCoordinates: LocationCoordinates) -> Unit +) { + var savedName by remember { + mutableStateOf( + (locationCoordinates.name ?: "").let { + TextFieldValue(it, selection = TextRange(it.length)) + } + ) + } + var savedLatitude by remember { mutableStateOf(locationCoordinates.latitude.toFloat().toString()) } + var savedLongitude by remember { mutableStateOf(locationCoordinates.longitude.toFloat().toString()) } + + alertDialogs.DefaultDialogCard { + val focusRequester = remember { FocusRequester() } + Column( + modifier = Modifier.padding(16.dp), + verticalArrangement = Arrangement.spacedBy(2.dp) + ) { + Text(translation["save_coordinates_dialog_title"], fontSize = 20.sp, fontWeight = FontWeight.Bold) + OutlinedTextField( + modifier = Modifier + .focusRequester(focusRequester), + value = savedName, + onValueChange = { savedName = it }, + label = { Text(translation["saved_name_dialog_hint"]) } + ) + + LaunchedEffect(Unit) { + delay(200) + focusRequester.requestFocus() + } + + OutlinedTextField( + value = savedLatitude, + onValueChange = { savedLatitude = it }, + label = { Text(translation["latitude_dialog_hint"]) } + ) + OutlinedTextField( + value = savedLongitude, + onValueChange = { savedLongitude = it }, + label = { Text(translation["longitude_dialog_hint"]) } + ) + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + horizontalArrangement = Arrangement.End + ) { + Button( + onClick = { + confirm(LocationCoordinates().apply { + this.name = savedName.text + this.latitude = savedLatitude.toDoubleOrNull() ?: 0.0 + this.longitude = savedLongitude.toDoubleOrNull() ?: 0.0 + }) + }, + enabled = savedName.text.isNotBlank() && savedLatitude.isNotBlank() && savedLongitude.isNotBlank() + ) { + Text(translation["save_dialog_button"]) + } + } + } + } +}+ \ No newline at end of file diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/BetterLocationRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/location/BetterLocationRoot.kt @@ -0,0 +1,445 @@ +package me.rhunk.snapenhance.ui.manager.pages.location + +import android.os.Parcel +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add +import androidx.compose.material.icons.filled.DeleteOutline +import androidx.compose.material.icons.filled.Edit +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.navigation.NavBackStackEntry +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import me.rhunk.snapenhance.bridge.location.FriendLocation +import me.rhunk.snapenhance.bridge.location.LocationCoordinates +import me.rhunk.snapenhance.common.ui.rememberAsyncMutableStateList +import me.rhunk.snapenhance.common.ui.rememberAsyncUpdateDispatcher +import me.rhunk.snapenhance.common.util.snap.BitmojiSelfie +import me.rhunk.snapenhance.storage.addOrUpdateLocationCoordinate +import me.rhunk.snapenhance.storage.getLocationCoordinates +import me.rhunk.snapenhance.storage.removeLocationCoordinate +import me.rhunk.snapenhance.ui.manager.Routes +import me.rhunk.snapenhance.ui.util.AlertDialogs +import me.rhunk.snapenhance.ui.util.DialogProperties +import me.rhunk.snapenhance.ui.util.coil.BitmojiImage +import org.osmdroid.util.GeoPoint +import org.osmdroid.views.MapView +import org.osmdroid.views.overlay.Marker + +class BetterLocationRoot : Routes.Route() { + private val alertDialogs by lazy { AlertDialogs(context.translation) } + + @Composable + private fun FriendLocationItem( + friendLocation: FriendLocation, + dismiss: () -> Unit + ) { + ElevatedCard(onClick = { + context.config.root.global.betterLocation.coordinates.setAny(friendLocation.latitude to friendLocation.longitude) + dismiss() + }, modifier = Modifier.padding(4.dp)) { + Row( + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + BitmojiImage( + context = context, + url = BitmojiSelfie.getBitmojiSelfie( + friendLocation.bitmojiSelfieId, + friendLocation.bitmojiId, + BitmojiSelfie.BitmojiSelfieType.NEW_THREE_D + ), + size = 48, + modifier = Modifier.padding(6.dp) + ) + Column( + modifier = Modifier.weight(1f), + ) { + Text(friendLocation.displayName?.let { "$it (${friendLocation.username})" } + ?: friendLocation.username, fontSize = 16.sp, fontWeight = FontWeight.Bold) + Text( + text = buildString { + append(friendLocation.localityPieces.joinToString(", ")) + append("\n") + append("Lat: ${friendLocation.latitude.toFloat()}, Lng: ${friendLocation.longitude.toFloat()}") + }, + fontSize = 10.sp, + fontWeight = FontWeight.Light, + lineHeight = 15.sp + ) + } + } + } + } + + @Composable + private fun FriendLocationsDialogs( + friendsLocation: List<FriendLocation>, + dismiss: () -> Unit + ) { + var search by remember { mutableStateOf("") } + val filteredFriendsLocation = rememberAsyncMutableStateList(defaultValue = friendsLocation, keys = arrayOf(search)) { + search.takeIf { it.isNotBlank() }?.let { + friendsLocation.filter { + it.displayName?.contains(search, ignoreCase = true) == true || it.username.contains(search, ignoreCase = true) + } + } ?: friendsLocation + } + + ElevatedCard( + shape = MaterialTheme.shapes.large, + modifier = Modifier.padding(top = 32.dp, bottom = 32.dp) + ) { + Text( + translation["teleport_to_friend_title"], + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(12.dp) + ) + OutlinedTextField( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + value = search, + onValueChange = { search = it }, + label = { Text(translation["search_bar"]) } + ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + ) { + item { + if (friendsLocation.isEmpty()) { + Text( + translation["no_friends_map"], + fontSize = 16.sp, + modifier = Modifier.padding(16.dp), + fontWeight = FontWeight.Light + ) + } else if (filteredFriendsLocation.isEmpty()) { + Text( + translation["no_friends_found"], + fontSize = 16.sp, + modifier = Modifier.padding(16.dp), + fontWeight = FontWeight.Light + ) + } + } + items(filteredFriendsLocation) { friendLocation -> + FriendLocationItem(friendLocation, dismiss) + } + } + } + } + + override val content: @Composable (NavBackStackEntry) -> Unit = { + val coordinatesProperty = remember { + context.config.root.global.betterLocation.getPropertyPair("coordinates") + } + + val updateDispatcher = rememberAsyncUpdateDispatcher() + val savedCoordinates = rememberAsyncMutableStateList( + defaultValue = listOf(), + updateDispatcher = updateDispatcher + ) { + context.database.getLocationCoordinates() + } + var showMap by remember { mutableStateOf(false) } + var addSavedCoordinateDialog by remember { mutableStateOf(false) } + var showTeleportDialog by remember { mutableStateOf(false) } + + val marker = remember { mutableStateOf<Marker?>(null) } + val mapView = remember { mutableStateOf<MapView?>(null) } + var spoofedCoordinates by remember(showTeleportDialog, showMap) { mutableStateOf(coordinatesProperty.value.get() as? Pair<*, *>) } + + fun addSavedCoordinate(id: Int?, locationCoordinates: LocationCoordinates, onSuccess: suspend (id: Int) -> Unit = {}) { + context.coroutineScope.launch { + onSuccess(context.database.addOrUpdateLocationCoordinate(id, locationCoordinates)) + } + } + + if (showTeleportDialog) { + me.rhunk.snapenhance.ui.util.Dialog( + properties = DialogProperties(usePlatformDefaultWidth = false), + onDismissRequest = { showTeleportDialog = false }, + content = { + FriendLocationsDialogs(remember { context.locationManager.friendsLocation }) { + showTeleportDialog = false + context.coroutineScope.launch { + context.config.writeConfig() + } + } + } + ) + } + + Column( + modifier = Modifier + .fillMaxSize() + ) { + Text( + translation.format( + "spoofed_coordinates_title", + "latitude" to ((spoofedCoordinates?.first as? Double)?.toFloat() ?: "0.0").toString(), + "longitude" to ((spoofedCoordinates?.second as? Double)?.toFloat() ?: "0.0").toString() + ), + fontSize = 18.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + ) + + if (addSavedCoordinateDialog) { + me.rhunk.snapenhance.ui.util.Dialog( + onDismissRequest = { addSavedCoordinateDialog = false }, + content = { + AddCoordinatesDialog( + alertDialogs, + translation, + LocationCoordinates().apply { + this.latitude = marker.value?.position?.latitude ?: 0.0 + this.longitude = marker.value?.position?.longitude ?: 0.0 + }, + ) { coordinates -> + addSavedCoordinateDialog = false + addSavedCoordinate(null, coordinates) { + withContext(Dispatchers.Main) { + savedCoordinates.add(0, coordinates.apply { id = it }) + } + } + } + } + ) + } + + if (showMap) { + me.rhunk.snapenhance.ui.util.Dialog( + onDismissRequest = { showMap = false }, + content = { + alertDialogs.ChooseLocationDialog(property = coordinatesProperty, marker, mapView, saveCoordinates = { + addSavedCoordinateDialog = true + }) { + showMap = false + context.config.writeConfig() + } + DisposableEffect(Unit) { + onDispose { + marker.value = null + } + } + } + ) + } + + LazyColumn( + modifier = Modifier + .fillMaxSize() + .clipToBounds() + ) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceEvenly, + verticalAlignment = Alignment.CenterVertically + ) { + Button(onClick = { showMap = true }) { + Text(translation["choose_location_button"]) + } + Button(onClick = { showTeleportDialog = true }) { + Text(translation["teleport_to_friend_button"]) + } + } + } + item { + Row( + modifier = Modifier.padding(start = 16.dp, end = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = translation["spoof_location_toggle"]) + Spacer(modifier = Modifier.weight(1f)) + var isSpoofing by remember { mutableStateOf(context.config.root.global.betterLocation.spoofLocation.get()) } + Switch( + checked = isSpoofing, + onCheckedChange = { + isSpoofing = it + context.config.root.global.betterLocation.spoofLocation.set(it) + } + ) + } + } + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(start = 12.dp, end = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Text( + translation["saved_coordinates_title"], + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + modifier = Modifier.weight(1f), + lineHeight = 20.sp + ) + IconButton( + onClick = { + addSavedCoordinateDialog = true + } + ) { + Icon(Icons.Default.Add, contentDescription = "Add") + } + } + } + item { + if (savedCoordinates.isEmpty()) { + Text( + translation["no_saved_coordinates_hint"], + fontSize = 16.sp, + modifier = Modifier.padding(start = 20.dp), + fontWeight = FontWeight.Light + ) + } + } + items(savedCoordinates, key = { it.id }) { coordinates -> + var mutableCoordinates by remember { mutableStateOf(coordinates) } + val isSelected = spoofedCoordinates == mutableCoordinates.latitude to mutableCoordinates.longitude + var showDeleteDialog by remember { mutableStateOf(false) } + var showEditDialog by remember { mutableStateOf(false) } + + fun setSpoofedCoordinates() { + spoofedCoordinates = mutableCoordinates.latitude to mutableCoordinates.longitude + coordinatesProperty.value.setAny(spoofedCoordinates) + context.coroutineScope.launch { + context.config.writeConfig() + } + } + + if (showDeleteDialog) { + me.rhunk.snapenhance.ui.util.Dialog( + onDismissRequest = { showDeleteDialog = false }, + content = { + alertDialogs.ConfirmDialog( + title = translation["delete_dialog_title"], + message = translation["delete_dialog_message"], + onConfirm = { + showDeleteDialog = false + context.coroutineScope.launch { + context.database.removeLocationCoordinate(coordinates.id) + savedCoordinates.remove(coordinates) + } + }, + onDismiss = { showDeleteDialog = false } + ) + } + ) + } + + if (showEditDialog) { + me.rhunk.snapenhance.ui.util.Dialog( + onDismissRequest = { showEditDialog = false }, + content = { + AddCoordinatesDialog( + alertDialogs, + translation, + mutableCoordinates + ) { + val itemId = coordinates.id + context.coroutineScope.launch { + addSavedCoordinate(itemId, it) + } + Parcel.obtain().apply { + it.writeToParcel(this, 0) + setDataPosition(0) + coordinates.readFromParcel(this) + coordinates.id = itemId + recycle() + } + mutableCoordinates = it + if (isSelected) setSpoofedCoordinates() + showEditDialog = false + } + } + ) + } + + ElevatedCard( + onClick = { + mutableCoordinates = coordinates + setSpoofedCoordinates() + GeoPoint(coordinates.latitude, coordinates.longitude).also { + marker.value?.position = it + mapView.value?.controller?.apply { + animateTo(it) + setZoom(16.0) + } + } + }, + modifier = Modifier + .fillMaxWidth() + .padding(5.dp), + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(4.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Column( + modifier = Modifier + .padding(2.dp) + .weight(1f) + ) { + Text( + text = remember(mutableCoordinates) { mutableCoordinates.name }, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Light, + fontSize = 16.sp, + lineHeight = 20.sp, + overflow = TextOverflow.Ellipsis + ) + Text( + text = remember(mutableCoordinates) { "(${mutableCoordinates.latitude.toFloat()}, ${mutableCoordinates.longitude.toFloat()})" }, + fontWeight = if (isSelected) FontWeight.Bold else FontWeight.Light, + fontSize = 12.sp, + lineHeight = 15.sp, + overflow = TextOverflow.Ellipsis + ) + } + FilledIconButton(onClick = { + showEditDialog = true + }) { + Icon(Icons.Default.Edit, contentDescription = "Delete") + } + Spacer(modifier = Modifier.width(4.dp)) + FilledIconButton(onClick = { + showDeleteDialog = true + }) { + Icon(Icons.Default.DeleteOutline, contentDescription = "Delete") + } + } + } + } + } + } + } +}+ \ No newline at end of file diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -152,6 +152,8 @@ "spoofed_coordinates_title": "Spoofed Coordinates\nLat {latitude}, Lng {longitude}", "save_coordinates_dialog_title": "Save Coordinates", "saved_name_dialog_hint": "Saved Name", + "latitude_dialog_hint": "Latitude", + "longitude_dialog_hint": "Longitude", "save_dialog_button": "Save", "choose_location_button": "Choose Location", "teleport_to_friend_button": "Teleport to Friend",