commit 0f2edca45959d9b441f9b24ea186ed2a93d55505
parent c57fbdb6448cecd0ba570b91627a1d5e2ee5aa88
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Tue,  1 Jul 2025 12:30:39 +0200

feat(app/better_location): search location by name

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

Diffstat:
Mapp/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt | 149+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 123 insertions(+), 26 deletions(-)

diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/util/AlertDialogs.kt @@ -4,17 +4,9 @@ import android.content.Context import android.view.MotionEvent import android.widget.Toast import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll @@ -23,17 +15,7 @@ import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.DeleteOutline import androidx.compose.material.icons.filled.Edit import androidx.compose.material.icons.filled.Save -import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.FilledIconButton -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.RadioButton -import androidx.compose.material3.Switch -import androidx.compose.material3.Text -import androidx.compose.material3.TextField -import androidx.compose.material3.TextFieldDefaults +import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -47,19 +29,26 @@ import androidx.compose.ui.layout.onGloballyPositioned import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.compose.ui.viewinterop.AndroidView -import com.github.skydoves.colorpicker.compose.AlphaSlider -import com.github.skydoves.colorpicker.compose.AlphaTile -import com.github.skydoves.colorpicker.compose.BrightnessSlider -import com.github.skydoves.colorpicker.compose.ColorPickerController -import com.github.skydoves.colorpicker.compose.HsvColorPicker +import androidx.core.net.toUri +import com.github.skydoves.colorpicker.compose.* +import com.google.gson.JsonParser +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import me.rhunk.snapenhance.common.Constants import me.rhunk.snapenhance.common.bridge.wrapper.LocaleWrapper import me.rhunk.snapenhance.common.config.DataProcessors import me.rhunk.snapenhance.common.config.PropertyPair +import me.rhunk.snapenhance.common.ui.AutoClearKeyboardFocus +import me.rhunk.snapenhance.common.util.ktx.await +import okhttp3.OkHttpClient +import okhttp3.Request import org.osmdroid.config.Configuration import org.osmdroid.tileprovider.tilesource.TileSourceFactory import org.osmdroid.util.GeoPoint @@ -539,6 +528,10 @@ class AlertDialogs( var customCoordinatesDialog by remember { mutableStateOf(false) } + + val coroutineScope = rememberCoroutineScope { Dispatchers.IO } + val okHttpClient by lazy { OkHttpClient() } + Box( modifier = Modifier .fillMaxWidth() @@ -548,6 +541,110 @@ class AlertDialogs( AndroidView( factory = { mapView.value!! }, ) + Column( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + ) { + var locationName by remember { mutableStateOf<String>("") } + var addressResults by remember { mutableStateOf<List<Triple<String, String, String>>>(emptyList()) } + var searchJob by remember { mutableStateOf<kotlinx.coroutines.Job?>(null) } + + suspend fun search() { + okHttpClient.newCall(Request.Builder() + .url("https://nominatim.openstreetmap.org/search".toUri().buildUpon().appendQueryParameter("q", locationName).appendQueryParameter("format", "jsonv2").build().toString()) + .header("User-Agent", Constants.USER_AGENT) + .build() + ).await().use { response -> + if (!response.isSuccessful) { + return@use + } + + runCatching { + val body = JsonParser.parseString(response.body.string()).asJsonArray + addressResults = body.take(5).map { jsonElement -> + val jsonObject = jsonElement.asJsonObject + Triple( + jsonObject.get("display_name").asString, + jsonObject.get("lat").asString, + jsonObject.get("lon").asString + ) + } + } + } + + searchJob = null + } + + TextField( + modifier = Modifier + .fillMaxWidth(), + value = locationName, + onValueChange = { + locationName = it.replace("\n", "") + if (locationName == "") { + addressResults = emptyList() + searchJob?.cancel() + searchJob = null + return@TextField + } + searchJob?.cancel() + searchJob = coroutineScope.launch { + delay(500) + search() + } + }, + label = { Text(text = "Search") }, + singleLine = true, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.None) + ) + + AutoClearKeyboardFocus(onFocusClear = { + locationName = "" + addressResults = emptyList() + searchJob?.cancel() + searchJob = null + }) + + Column( + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.background) + .verticalScroll(ScrollState(0)), + ) { + if (addressResults.isNotEmpty()) { + addressResults.forEach { address -> + Row( + modifier = Modifier + .fillMaxWidth() + .clickable { + marker.value?.position = GeoPoint(address.second.toDouble(), address.third.toDouble()) + mapView.value?.controller?.setCenter(marker.value?.position) + mapView.value?.invalidate() + } + ) { + Text( + text = address.first, + modifier = Modifier + .padding(10.dp) + .fillMaxWidth(), + ) + } + } + } else { + if (searchJob?.isActive == true) { + Row( + modifier = Modifier.fillMaxWidth().padding(10.dp), + horizontalArrangement = Arrangement.Center + ) { + CircularProgressIndicator() + } + } + } + } + } + + Row( modifier = Modifier .align(Alignment.BottomEnd)