Skip to content

Commit

Permalink
- Unifies Location Flows and GeoHash Flows into one
Browse files Browse the repository at this point in the history
- Make them react to changing location permissions
- Adds UI for when the location permission is rejected.
  • Loading branch information
vitorpamplona committed Nov 11, 2024
1 parent 062e4af commit 37a92c2
Show file tree
Hide file tree
Showing 10 changed files with 194 additions and 61 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
*/
package com.vitorpamplona.amethyst.model

import android.location.Location
import android.util.Log
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.Stable
Expand All @@ -29,10 +28,10 @@ import androidx.lifecycle.asLiveData
import androidx.lifecycle.liveData
import androidx.lifecycle.switchMap
import com.fasterxml.jackson.module.kotlin.readValue
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.BuildConfig
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.LocationState
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
import com.vitorpamplona.amethyst.service.checkNotInMainThread
import com.vitorpamplona.amethyst.tryAndWait
Expand Down Expand Up @@ -170,7 +169,7 @@ class Account(
val listName: String,
val peopleList: StateFlow<NoteState> = MutableStateFlow(NoteState(Note(" "))),
val kind3: StateFlow<Account.LiveFollowList?> = MutableStateFlow(null),
val location: StateFlow<Location?> = MutableStateFlow(null),
val location: StateFlow<LocationState.LocationResult?> = MutableStateFlow(null),
)

val connectToRelaysFlow =
Expand Down Expand Up @@ -541,7 +540,7 @@ class Account(
AROUND_ME ->
FeedsBaseFlows(
listName,
location = Amethyst.instance.locationManager.locationStateFlow,
location = Amethyst.instance.locationManager.geohashStateFlow,
)
else -> {
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
Expand All @@ -563,19 +562,19 @@ class Account(
listName: String,
kind3: LiveFollowList?,
noteState: NoteState,
location: Location?,
location: LocationState.LocationResult?,
): LiveFollowList? =
if (listName == GLOBAL_FOLLOWS) {
null
} else if (listName == KIND3_FOLLOWS) {
kind3
} else if (listName == AROUND_ME) {
val hash = location?.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits)
if (hash != null) {
val geohashResult = location ?: Amethyst.instance.locationManager.geohashStateFlow.value
if (geohashResult is LocationState.LocationResult.Success) {
// 2 neighbors deep = 25x25km
val hashes =
listOf(hash.toString()) +
hash.adjacent
listOf(geohashResult.geoHash.toString()) +
geohashResult.geoHash.adjacent
.map { listOf(it.toString()) + it.adjacent.map { it.toString() } }
.flatten()
.distinct()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,28 @@ import android.location.Geocoder
import android.location.Location
import android.location.LocationListener
import android.location.LocationManager
import android.os.Build
import android.os.Looper
import android.util.Log
import android.util.LruCache
import com.fonfon.kgeohash.GeoHash
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_DISTANCE
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_TIME
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.emitAll
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.transformLatest
import kotlinx.coroutines.launch

class LocationFlow(
Expand Down Expand Up @@ -90,26 +97,47 @@ class LocationState(
const val MIN_DISTANCE: Float = 100.0f
}

private var latestLocation: Location = Location(LocationManager.NETWORK_PROVIDER)
sealed class LocationResult {
data class Success(
val geoHash: GeoHash,
) : LocationResult()

val locationStateFlow =
LocationFlow(context)
.get(MIN_TIME, MIN_DISTANCE)
.onEach {
latestLocation = it
}.stateIn(
scope,
SharingStarted.WhileSubscribed(5000),
latestLocation,
)
object LackPermission : LocationResult()

object Loading : LocationResult()
}

private var hasLocationPermission = MutableStateFlow<Boolean>(false)
private var latestLocation: LocationResult = LocationResult.Loading

fun setLocationPermission(newValue: Boolean) {
if (newValue != hasLocationPermission.value) {
hasLocationPermission.tryEmit(newValue)
}
}

@OptIn(ExperimentalCoroutinesApi::class)
val geohashStateFlow =
locationStateFlow
.map { it.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits).toString() }
.stateIn(
hasLocationPermission
.transformLatest {
emitAll(
LocationFlow
(context)
.get(MIN_TIME, MIN_DISTANCE)
.map {
LocationResult.Success(it.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits)) as LocationResult
}.onEach {
latestLocation = it
}.catch { e ->
e.printStackTrace()
latestLocation = LocationResult.LackPermission
emit(LocationResult.LackPermission)
},
)
}.stateIn(
scope,
SharingStarted.WhileSubscribed(5000),
"",
latestLocation,
)
}

Expand Down Expand Up @@ -144,8 +172,11 @@ private class ReverseGeoLocationUtil {
): String? {
return try {
Geocoder(context)
.getFromLocation(location.latitude, location.longitude, 1)
?.firstOrNull()
.getFromLocation(
location.latitude,
location.longitude,
1,
)?.firstOrNull()
?.let { address ->
listOfNotNull(address.locality ?: address.subAdminArea, address.countryCode)
.joinToString(", ")
Expand All @@ -157,3 +188,52 @@ private class ReverseGeoLocationUtil {
}
}
}

class ReverseGeoLocationFlow(
private val context: Context,
) {
@SuppressLint("MissingPermission")
fun get(location: Location): Flow<String?> =
callbackFlow {
val locationManager = Geocoder(context)

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val locationCallback =
(
Geocoder.GeocodeListener { addresses ->
launch {
send(
addresses.firstOrNull()?.let {
listOfNotNull(it.locality ?: it.subAdminArea, it.countryCode).joinToString(", ")
},
)
}
}
)
Log.d("GeoLocation Service", "LocationState Start")

locationManager
.getFromLocation(
location.latitude,
location.longitude,
1,
locationCallback,
)
} else {
launch {
send(
Geocoder(context)
.getFromLocation(
location.latitude,
location.longitude,
1,
)?.firstOrNull()
?.let { address ->
listOfNotNull(address.locality ?: address.subAdminArea, address.countryCode)
.joinToString(", ")
},
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fonfon.kgeohash.toGeoHash
import com.vitorpamplona.amethyst.Amethyst
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
Expand All @@ -44,6 +43,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.FileHeader
import com.vitorpamplona.amethyst.service.LocationState
import com.vitorpamplona.amethyst.service.Nip96Uploader
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
Expand Down Expand Up @@ -75,13 +75,8 @@ import com.vitorpamplona.quartz.events.ZapSplitSetup
import com.vitorpamplona.quartz.events.findURLs
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.UUID
Expand Down Expand Up @@ -166,7 +161,7 @@ open class NewPostViewModel : ViewModel() {

// GeoHash
var wantsToAddGeoHash by mutableStateOf(false)
var location: StateFlow<String?>? = null
var location: StateFlow<LocationState.LocationResult>? = null

// ZapRaiser
var canAddZapRaiser by mutableStateOf(false)
Expand Down Expand Up @@ -535,7 +530,7 @@ open class NewPostViewModel : ViewModel() {
null
}

val geoHash = location?.value
val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null

nip95attachments.forEach {
Expand Down Expand Up @@ -1259,13 +1254,9 @@ open class NewPostViewModel : ViewModel() {
contentToAddUrl = uri
}

@OptIn(ExperimentalCoroutinesApi::class)
fun locationFlow(): Flow<String?> {
fun locationFlow(): StateFlow<LocationState.LocationResult> {
if (location == null) {
location =
Amethyst.instance.locationManager.locationStateFlow
.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() }
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
location = Amethyst.instance.locationManager.geohashStateFlow
}

return location!!
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
*/
package com.vitorpamplona.amethyst.ui.dal

import com.vitorpamplona.amethyst.model.AROUND_ME
import com.vitorpamplona.amethyst.model.Account
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
import com.vitorpamplona.quartz.encoders.ATag
Expand All @@ -33,6 +34,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
class FilterByListParams(
val isGlobal: Boolean,
val isHiddenList: Boolean,
val isAroundMe: Boolean,
val followLists: Account.LiveFollowList?,
val hiddenLists: Account.LiveHiddenUsers,
val now: Long = TimeUtils.oneMinuteFromNow(),
Expand All @@ -43,6 +45,7 @@ class FilterByListParams(

fun isEventInList(noteEvent: Event): Boolean {
if (followLists == null) return false
if (isAroundMe && followLists.geotags.isEmpty() == true) return false

return if (noteEvent is LiveActivitiesEvent) {
noteEvent.participantsIntersect(followLists.authors) ||
Expand Down Expand Up @@ -95,6 +98,7 @@ class FilterByListParams(
FilterByListParams(
isGlobal = selectedListName == GLOBAL_FOLLOWS,
isHiddenList = showHiddenKey(selectedListName, userHex),
isAroundMe = selectedListName == AROUND_ME,
followLists = followLists,
hiddenLists = hiddenUsers,
)
Expand Down
Loading

0 comments on commit 37a92c2

Please sign in to comment.