diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt index cd741c52a..cf02079bf 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/LocationUtil.kt @@ -27,6 +27,7 @@ import android.location.Location import android.location.LocationListener import android.location.LocationManager import android.os.HandlerThread +import android.util.LruCache import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import kotlinx.coroutines.CancellationException @@ -92,7 +93,33 @@ class LocationUtil(context: Context) { } } -class ReverseGeoLocationUtil { +object CachedGeoLocations { + val locationNames = LruCache(20) + + fun cached(geoHashStr: String): String? { + return locationNames[geoHashStr] + } + + suspend fun geoLocate( + geoHashStr: String, + location: Location, + context: Context, + ): String? { + locationNames[geoHashStr]?.let { + return it + } + + val name = ReverseGeoLocationUtil().execute(location, context)?.ifBlank { null } + + if (name != null) { + locationNames.put(geoHashStr, name) + } + + return name + } +} + +private class ReverseGeoLocationUtil { suspend fun execute( location: Location, context: Context, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index ef685f603..e8bdad62f 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -124,7 +124,6 @@ import androidx.compose.ui.window.DialogProperties import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import coil.compose.AsyncImage -import com.fonfon.kgeohash.toGeoHash import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.isGranted import com.google.accompanist.permissions.rememberPermissionState @@ -134,7 +133,6 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.Nip96MediaServers import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource -import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil import com.vitorpamplona.amethyst.ui.components.BechLink import com.vitorpamplona.amethyst.ui.components.CreateTextWithEmoji import com.vitorpamplona.amethyst.ui.components.InvoiceRequest @@ -144,6 +142,7 @@ import com.vitorpamplona.amethyst.ui.components.ZapRaiserRequest import com.vitorpamplona.amethyst.ui.note.BaseUserPicture import com.vitorpamplona.amethyst.ui.note.CancelIcon import com.vitorpamplona.amethyst.ui.note.CloseIcon +import com.vitorpamplona.amethyst.ui.note.LoadCityName import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.note.PollIcon import com.vitorpamplona.amethyst.ui.note.RegularPostIcon @@ -1172,23 +1171,12 @@ fun FowardZapTo( @OptIn(ExperimentalPermissionsApi::class) @Composable fun LocationAsHash(postViewModel: NewPostViewModel) { - val context = LocalContext.current - val locationPermissionState = rememberPermissionState( Manifest.permission.ACCESS_COARSE_LOCATION, ) if (locationPermissionState.status.isGranted) { - var locationDescriptionFlow by remember(postViewModel) { mutableStateOf?>(null) } - - DisposableEffect(key1 = Unit) { - postViewModel.startLocation(context = context) - locationDescriptionFlow = postViewModel.location - - onDispose { postViewModel.stopLocation() } - } - Column( modifier = Modifier.fillMaxWidth(), ) { @@ -1219,7 +1207,7 @@ fun LocationAsHash(postViewModel: NewPostViewModel) { modifier = Modifier.padding(start = 10.dp), ) - locationDescriptionFlow?.let { geoLocation -> DisplayLocationObserver(geoLocation) } + DisplayLocationObserver(postViewModel) } Divider() @@ -1236,41 +1224,39 @@ fun LocationAsHash(postViewModel: NewPostViewModel) { } @Composable -fun DisplayLocationObserver(geoLocation: Flow) { - val location by geoLocation.collectAsStateWithLifecycle(null) +fun DisplayLocationObserver(postViewModel: NewPostViewModel) { + val context = LocalContext.current + var locationDescriptionFlow by remember(postViewModel) { mutableStateOf?>(null) } - location?.let { DisplayLocationInTitle(geohash = it) } + DisposableEffect(key1 = context) { + postViewModel.startLocation(context = context) + locationDescriptionFlow = postViewModel.location + + onDispose { postViewModel.stopLocation() } + } + + locationDescriptionFlow?.let { + val location by it.collectAsStateWithLifecycle(null) + + location?.let { DisplayLocationInTitle(geohash = it) } + } } @Composable fun DisplayLocationInTitle(geohash: String) { - val context = LocalContext.current - - var cityName by remember(geohash) { mutableStateOf(geohash) } - - LaunchedEffect(key1 = geohash) { - launch(Dispatchers.IO) { - val newCityName = - ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)?.ifBlank { - null - } - - if (newCityName != null && newCityName != cityName) { - cityName = newCityName - } - } - } - - if (geohash != "s0000") { + LoadCityName( + geohashStr = geohash, + onLoading = { + Spacer(modifier = StdHorzSpacer) + LoadingAnimation() + }, + ) { cityName -> Text( text = cityName, fontSize = 20.sp, fontWeight = FontWeight.W500, modifier = Modifier.padding(start = Size5dp), ) - } else { - Spacer(modifier = StdHorzSpacer) - LoadingAnimation() } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index c5dfcf639..ca5e7ff4c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -74,7 +74,6 @@ import androidx.lifecycle.map import androidx.lifecycle.viewModelScope import androidx.navigation.NavBackStackEntry import coil.Coil -import com.fonfon.kgeohash.toGeoHash import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.AddressableNote @@ -817,15 +816,12 @@ fun SimpleTextSpinner( fun RenderOption(option: Name) { when (option) { is GeoHashName -> { - val geohash = runCatching { option.geoHashTag.toGeoHash() }.getOrNull() - if (geohash != null) { - LoadCityName(geohash) { - Row( - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth(), - ) { - Text(text = "/g/$it", color = MaterialTheme.colorScheme.onSurface) - } + LoadCityName(option.geoHashTag) { + Row( + horizontalArrangement = Arrangement.Center, + modifier = Modifier.fillMaxWidth(), + ) { + Text(text = "/g/$it", color = MaterialTheme.colorScheme.onSurface) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt index 69d567ccd..8ad6a0d5b 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/navigation/Routes.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.navigation import android.os.Bundle import androidx.compose.foundation.layout.size -import androidx.compose.runtime.Composable import androidx.compose.runtime.Immutable import androidx.compose.runtime.State import androidx.compose.runtime.getValue @@ -32,7 +31,6 @@ import androidx.navigation.NavBackStackEntry import androidx.navigation.NavDestination import androidx.navigation.NavHostController import androidx.navigation.NavType -import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.navArgument import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Account diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index ee4584be8..b81624a5a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -101,7 +101,6 @@ import coil.compose.AsyncImagePainter import coil.request.SuccessResult import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -import com.fonfon.kgeohash.GeoHash import com.fonfon.kgeohash.toGeoHash import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.commons.BaseMediaContent @@ -115,7 +114,7 @@ import com.vitorpamplona.amethyst.model.Channel import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil +import com.vitorpamplona.amethyst.service.CachedGeoLocations import com.vitorpamplona.amethyst.ui.actions.NewRelayListView import com.vitorpamplona.amethyst.ui.components.ClickableUrl import com.vitorpamplona.amethyst.ui.components.CreateClickableTextWithEmoji @@ -2768,23 +2767,34 @@ fun LoadOts( @Composable fun LoadCityName( - geohash: GeoHash, + geohashStr: String, + onLoading: (@Composable () -> Unit)? = null, content: @Composable (String) -> Unit, ) { - val context = LocalContext.current - var cityName by remember(geohash) { mutableStateOf(geohash.toString()) } + var cityName by remember(geohashStr) { mutableStateOf(CachedGeoLocations.cached(geohashStr)) } - LaunchedEffect(key1 = geohash) { - launch(Dispatchers.IO) { - val newCityName = - ReverseGeoLocationUtil().execute(geohash.toLocation(), context)?.ifBlank { null } - if (newCityName != null && newCityName != cityName) { - cityName = newCityName + if (cityName == null) { + if (onLoading != null) { + onLoading() + } + + val context = LocalContext.current + + LaunchedEffect(key1 = geohashStr, context) { + launch(Dispatchers.IO) { + val geoHash = runCatching { geohashStr.toGeoHash() }.getOrNull() + if (geoHash != null) { + val newCityName = + CachedGeoLocations.geoLocate(geohashStr, geoHash.toLocation(), context)?.ifBlank { null } + if (newCityName != null && newCityName != cityName) { + cityName = newCityName + } + } } } + } else { + cityName?.let { content(it) } } - - content(cityName) } @Composable @@ -2792,24 +2802,21 @@ fun DisplayLocation( geohashStr: String, nav: (String) -> Unit, ) { - val geoHash = runCatching { geohashStr.toGeoHash() }.getOrNull() - if (geoHash != null) { - LoadCityName(geoHash) { cityName -> - ClickableText( - text = AnnotatedString(cityName), - onClick = { nav("Geohash/$geoHash") }, - style = - LocalTextStyle.current.copy( - color = - MaterialTheme.colorScheme.primary.copy( - alpha = 0.52f, - ), - fontSize = Font14SP, - fontWeight = FontWeight.Bold, - ), - maxLines = 1, - ) - } + LoadCityName(geohashStr) { cityName -> + ClickableText( + text = AnnotatedString(cityName), + onClick = { nav("Geohash/$geohashStr") }, + style = + LocalTextStyle.current.copy( + color = + MaterialTheme.colorScheme.primary.copy( + alpha = 0.52f, + ), + fontSize = Font14SP, + fontWeight = FontWeight.Bold, + ), + maxLines = 1, + ) } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt index 33b0dc61c..e1f4515b0 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/GeoHashScreen.kt @@ -31,32 +31,26 @@ import androidx.compose.material3.Divider import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel -import com.fonfon.kgeohash.toGeoHash import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrGeohashDataSource -import com.vitorpamplona.amethyst.service.ReverseGeoLocationUtil +import com.vitorpamplona.amethyst.ui.note.LoadCityName import com.vitorpamplona.amethyst.ui.screen.NostrGeoHashFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.StdPadding -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch @Composable fun GeoHashScreen( @@ -174,27 +168,13 @@ fun DislayGeoTagHeader( geohash: String, modifier: Modifier, ) { - val context = LocalContext.current - - var cityName by remember(geohash) { mutableStateOf(geohash) } - - LaunchedEffect(key1 = geohash) { - launch(Dispatchers.IO) { - val newCityName = - ReverseGeoLocationUtil().execute(geohash.toGeoHash().toLocation(), context)?.ifBlank { - null - } - if (newCityName != null && newCityName != cityName) { - cityName = "$newCityName ($geohash)" - } - } + LoadCityName(geohashStr = geohash) { cityName -> + Text( + cityName, + fontWeight = FontWeight.Bold, + modifier = modifier, + ) } - - Text( - cityName, - fontWeight = FontWeight.Bold, - modifier = modifier, - ) } @Composable