mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 05:47:30 +01:00
- Unifies Location Flows and GeoHash Flows into one
- Make them react to changing location permissions - Adds UI for when the location permission is rejected.
This commit is contained in:
@@ -20,7 +20,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.model
|
package com.vitorpamplona.amethyst.model
|
||||||
|
|
||||||
import android.location.Location
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import androidx.compose.runtime.Stable
|
import androidx.compose.runtime.Stable
|
||||||
@@ -29,10 +28,10 @@ import androidx.lifecycle.asLiveData
|
|||||||
import androidx.lifecycle.liveData
|
import androidx.lifecycle.liveData
|
||||||
import androidx.lifecycle.switchMap
|
import androidx.lifecycle.switchMap
|
||||||
import com.fasterxml.jackson.module.kotlin.readValue
|
import com.fasterxml.jackson.module.kotlin.readValue
|
||||||
import com.fonfon.kgeohash.toGeoHash
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.BuildConfig
|
import com.vitorpamplona.amethyst.BuildConfig
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
|
import com.vitorpamplona.amethyst.service.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
import com.vitorpamplona.amethyst.service.NostrLnZapPaymentResponseDataSource
|
||||||
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
import com.vitorpamplona.amethyst.service.checkNotInMainThread
|
||||||
import com.vitorpamplona.amethyst.tryAndWait
|
import com.vitorpamplona.amethyst.tryAndWait
|
||||||
@@ -170,7 +169,7 @@ class Account(
|
|||||||
val listName: String,
|
val listName: String,
|
||||||
val peopleList: StateFlow<NoteState> = MutableStateFlow(NoteState(Note(" "))),
|
val peopleList: StateFlow<NoteState> = MutableStateFlow(NoteState(Note(" "))),
|
||||||
val kind3: StateFlow<Account.LiveFollowList?> = MutableStateFlow(null),
|
val kind3: StateFlow<Account.LiveFollowList?> = MutableStateFlow(null),
|
||||||
val location: StateFlow<Location?> = MutableStateFlow(null),
|
val location: StateFlow<LocationState.LocationResult?> = MutableStateFlow(null),
|
||||||
)
|
)
|
||||||
|
|
||||||
val connectToRelaysFlow =
|
val connectToRelaysFlow =
|
||||||
@@ -541,7 +540,7 @@ class Account(
|
|||||||
AROUND_ME ->
|
AROUND_ME ->
|
||||||
FeedsBaseFlows(
|
FeedsBaseFlows(
|
||||||
listName,
|
listName,
|
||||||
location = Amethyst.instance.locationManager.locationStateFlow,
|
location = Amethyst.instance.locationManager.geohashStateFlow,
|
||||||
)
|
)
|
||||||
else -> {
|
else -> {
|
||||||
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
val note = LocalCache.checkGetOrCreateAddressableNote(listName)
|
||||||
@@ -563,19 +562,19 @@ class Account(
|
|||||||
listName: String,
|
listName: String,
|
||||||
kind3: LiveFollowList?,
|
kind3: LiveFollowList?,
|
||||||
noteState: NoteState,
|
noteState: NoteState,
|
||||||
location: Location?,
|
location: LocationState.LocationResult?,
|
||||||
): LiveFollowList? =
|
): LiveFollowList? =
|
||||||
if (listName == GLOBAL_FOLLOWS) {
|
if (listName == GLOBAL_FOLLOWS) {
|
||||||
null
|
null
|
||||||
} else if (listName == KIND3_FOLLOWS) {
|
} else if (listName == KIND3_FOLLOWS) {
|
||||||
kind3
|
kind3
|
||||||
} else if (listName == AROUND_ME) {
|
} else if (listName == AROUND_ME) {
|
||||||
val hash = location?.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits)
|
val geohashResult = location ?: Amethyst.instance.locationManager.geohashStateFlow.value
|
||||||
if (hash != null) {
|
if (geohashResult is LocationState.LocationResult.Success) {
|
||||||
// 2 neighbors deep = 25x25km
|
// 2 neighbors deep = 25x25km
|
||||||
val hashes =
|
val hashes =
|
||||||
listOf(hash.toString()) +
|
listOf(geohashResult.geoHash.toString()) +
|
||||||
hash.adjacent
|
geohashResult.geoHash.adjacent
|
||||||
.map { listOf(it.toString()) + it.adjacent.map { it.toString() } }
|
.map { listOf(it.toString()) + it.adjacent.map { it.toString() } }
|
||||||
.flatten()
|
.flatten()
|
||||||
.distinct()
|
.distinct()
|
||||||
|
|||||||
@@ -26,21 +26,28 @@ import android.location.Geocoder
|
|||||||
import android.location.Location
|
import android.location.Location
|
||||||
import android.location.LocationListener
|
import android.location.LocationListener
|
||||||
import android.location.LocationManager
|
import android.location.LocationManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
|
import com.fonfon.kgeohash.GeoHash
|
||||||
import com.fonfon.kgeohash.toGeoHash
|
import com.fonfon.kgeohash.toGeoHash
|
||||||
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_DISTANCE
|
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_DISTANCE
|
||||||
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_TIME
|
import com.vitorpamplona.amethyst.service.LocationState.Companion.MIN_TIME
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
import kotlinx.coroutines.channels.awaitClose
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.callbackFlow
|
import kotlinx.coroutines.flow.callbackFlow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.transformLatest
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class LocationFlow(
|
class LocationFlow(
|
||||||
@@ -90,27 +97,48 @@ class LocationState(
|
|||||||
const val MIN_DISTANCE: Float = 100.0f
|
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 =
|
object LackPermission : LocationResult()
|
||||||
LocationFlow(context)
|
|
||||||
.get(MIN_TIME, MIN_DISTANCE)
|
object Loading : LocationResult()
|
||||||
.onEach {
|
}
|
||||||
latestLocation = it
|
|
||||||
|
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 =
|
||||||
|
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(
|
}.stateIn(
|
||||||
scope,
|
scope,
|
||||||
SharingStarted.WhileSubscribed(5000),
|
SharingStarted.WhileSubscribed(5000),
|
||||||
latestLocation,
|
latestLocation,
|
||||||
)
|
)
|
||||||
|
|
||||||
val geohashStateFlow =
|
|
||||||
locationStateFlow
|
|
||||||
.map { it.toGeoHash(com.vitorpamplona.amethyst.ui.actions.GeohashPrecision.KM_5_X_5.digits).toString() }
|
|
||||||
.stateIn(
|
|
||||||
scope,
|
|
||||||
SharingStarted.WhileSubscribed(5000),
|
|
||||||
"",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
object CachedGeoLocations {
|
object CachedGeoLocations {
|
||||||
@@ -144,8 +172,11 @@ private class ReverseGeoLocationUtil {
|
|||||||
): String? {
|
): String? {
|
||||||
return try {
|
return try {
|
||||||
Geocoder(context)
|
Geocoder(context)
|
||||||
.getFromLocation(location.latitude, location.longitude, 1)
|
.getFromLocation(
|
||||||
?.firstOrNull()
|
location.latitude,
|
||||||
|
location.longitude,
|
||||||
|
1,
|
||||||
|
)?.firstOrNull()
|
||||||
?.let { address ->
|
?.let { address ->
|
||||||
listOfNotNull(address.locality ?: address.subAdminArea, address.countryCode)
|
listOfNotNull(address.locality ?: address.subAdminArea, address.countryCode)
|
||||||
.joinToString(", ")
|
.joinToString(", ")
|
||||||
@@ -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(", ")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ import androidx.compose.ui.text.TextRange
|
|||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.fonfon.kgeohash.toGeoHash
|
|
||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
import com.vitorpamplona.amethyst.commons.compose.insertUrlAtCursor
|
||||||
@@ -44,6 +43,7 @@ import com.vitorpamplona.amethyst.model.LocalCache
|
|||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
import com.vitorpamplona.amethyst.service.FileHeader
|
import com.vitorpamplona.amethyst.service.FileHeader
|
||||||
|
import com.vitorpamplona.amethyst.service.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.Nip96Uploader
|
import com.vitorpamplona.amethyst.service.Nip96Uploader
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
import com.vitorpamplona.amethyst.ui.components.MediaCompressor
|
||||||
@@ -75,13 +75,8 @@ import com.vitorpamplona.quartz.events.ZapSplitSetup
|
|||||||
import com.vitorpamplona.quartz.events.findURLs
|
import com.vitorpamplona.quartz.events.findURLs
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -166,7 +161,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
|
|
||||||
// GeoHash
|
// GeoHash
|
||||||
var wantsToAddGeoHash by mutableStateOf(false)
|
var wantsToAddGeoHash by mutableStateOf(false)
|
||||||
var location: StateFlow<String?>? = null
|
var location: StateFlow<LocationState.LocationResult>? = null
|
||||||
|
|
||||||
// ZapRaiser
|
// ZapRaiser
|
||||||
var canAddZapRaiser by mutableStateOf(false)
|
var canAddZapRaiser by mutableStateOf(false)
|
||||||
@@ -535,7 +530,7 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
val geoHash = location?.value
|
val geoHash = (location?.value as? LocationState.LocationResult.Success)?.geoHash?.toString()
|
||||||
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null
|
val localZapRaiserAmount = if (wantsZapraiser) zapRaiserAmount else null
|
||||||
|
|
||||||
nip95attachments.forEach {
|
nip95attachments.forEach {
|
||||||
@@ -1259,13 +1254,9 @@ open class NewPostViewModel : ViewModel() {
|
|||||||
contentToAddUrl = uri
|
contentToAddUrl = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
fun locationFlow(): StateFlow<LocationState.LocationResult> {
|
||||||
fun locationFlow(): Flow<String?> {
|
|
||||||
if (location == null) {
|
if (location == null) {
|
||||||
location =
|
location = Amethyst.instance.locationManager.geohashStateFlow
|
||||||
Amethyst.instance.locationManager.locationStateFlow
|
|
||||||
.mapLatest { it.toGeoHash(GeohashPrecision.KM_5_X_5.digits).toString() }
|
|
||||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return location!!
|
return location!!
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.vitorpamplona.amethyst.ui.dal
|
package com.vitorpamplona.amethyst.ui.dal
|
||||||
|
|
||||||
|
import com.vitorpamplona.amethyst.model.AROUND_ME
|
||||||
import com.vitorpamplona.amethyst.model.Account
|
import com.vitorpamplona.amethyst.model.Account
|
||||||
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
import com.vitorpamplona.amethyst.model.GLOBAL_FOLLOWS
|
||||||
import com.vitorpamplona.quartz.encoders.ATag
|
import com.vitorpamplona.quartz.encoders.ATag
|
||||||
@@ -33,6 +34,7 @@ import com.vitorpamplona.quartz.utils.TimeUtils
|
|||||||
class FilterByListParams(
|
class FilterByListParams(
|
||||||
val isGlobal: Boolean,
|
val isGlobal: Boolean,
|
||||||
val isHiddenList: Boolean,
|
val isHiddenList: Boolean,
|
||||||
|
val isAroundMe: Boolean,
|
||||||
val followLists: Account.LiveFollowList?,
|
val followLists: Account.LiveFollowList?,
|
||||||
val hiddenLists: Account.LiveHiddenUsers,
|
val hiddenLists: Account.LiveHiddenUsers,
|
||||||
val now: Long = TimeUtils.oneMinuteFromNow(),
|
val now: Long = TimeUtils.oneMinuteFromNow(),
|
||||||
@@ -43,6 +45,7 @@ class FilterByListParams(
|
|||||||
|
|
||||||
fun isEventInList(noteEvent: Event): Boolean {
|
fun isEventInList(noteEvent: Event): Boolean {
|
||||||
if (followLists == null) return false
|
if (followLists == null) return false
|
||||||
|
if (isAroundMe && followLists.geotags.isEmpty() == true) return false
|
||||||
|
|
||||||
return if (noteEvent is LiveActivitiesEvent) {
|
return if (noteEvent is LiveActivitiesEvent) {
|
||||||
noteEvent.participantsIntersect(followLists.authors) ||
|
noteEvent.participantsIntersect(followLists.authors) ||
|
||||||
@@ -95,6 +98,7 @@ class FilterByListParams(
|
|||||||
FilterByListParams(
|
FilterByListParams(
|
||||||
isGlobal = selectedListName == GLOBAL_FOLLOWS,
|
isGlobal = selectedListName == GLOBAL_FOLLOWS,
|
||||||
isHiddenList = showHiddenKey(selectedListName, userHex),
|
isHiddenList = showHiddenKey(selectedListName, userHex),
|
||||||
|
isAroundMe = selectedListName == AROUND_ME,
|
||||||
followLists = followLists,
|
followLists = followLists,
|
||||||
hiddenLists = hiddenUsers,
|
hiddenLists = hiddenUsers,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ import com.google.accompanist.permissions.rememberPermissionState
|
|||||||
import com.vitorpamplona.amethyst.Amethyst
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.AddressableNote
|
import com.vitorpamplona.amethyst.model.AddressableNote
|
||||||
|
import com.vitorpamplona.amethyst.service.LocationState
|
||||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||||
import com.vitorpamplona.amethyst.ui.note.LoadCityName
|
import com.vitorpamplona.amethyst.ui.note.LoadCityName
|
||||||
import com.vitorpamplona.amethyst.ui.screen.AroundMeFeedDefinition
|
import com.vitorpamplona.amethyst.ui.screen.AroundMeFeedDefinition
|
||||||
@@ -71,7 +72,6 @@ import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.quartz.events.PeopleListEvent
|
import com.vitorpamplona.quartz.events.PeopleListEvent
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class)
|
@OptIn(ExperimentalPermissionsApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -104,6 +104,12 @@ fun FeedFilterSpinner(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val locationPermissionState = rememberPermissionState(Manifest.permission.ACCESS_COARSE_LOCATION)
|
||||||
|
|
||||||
|
LaunchedEffect(locationPermissionState.status.isGranted) {
|
||||||
|
Amethyst.instance.locationManager.setLocationPermission(locationPermissionState.status.isGranted)
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
@@ -115,27 +121,45 @@ fun FeedFilterSpinner(
|
|||||||
Text(currentText)
|
Text(currentText)
|
||||||
|
|
||||||
if (selected is AroundMeFeedDefinition) {
|
if (selected is AroundMeFeedDefinition) {
|
||||||
val locationPermissionState =
|
|
||||||
rememberPermissionState(
|
|
||||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!locationPermissionState.status.isGranted) {
|
if (!locationPermissionState.status.isGranted) {
|
||||||
LaunchedEffect(locationPermissionState) { locationPermissionState.launchPermissionRequest() }
|
LaunchedEffect(locationPermissionState) { locationPermissionState.launchPermissionRequest() }
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.lack_location_permissions),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
val location by Amethyst.instance.locationManager.geohashStateFlow
|
val location by Amethyst.instance.locationManager.geohashStateFlow
|
||||||
.collectAsStateWithLifecycle(null)
|
.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
location?.let {
|
when (val myLocation = location) {
|
||||||
LoadCityName(
|
is LocationState.LocationResult.Success -> {
|
||||||
geohashStr = it,
|
LoadCityName(
|
||||||
onLoading = {
|
geohashStr = myLocation.geoHash.toString(),
|
||||||
Spacer(modifier = StdHorzSpacer)
|
onLoading = {
|
||||||
LoadingAnimation()
|
Spacer(modifier = StdHorzSpacer)
|
||||||
},
|
LoadingAnimation()
|
||||||
) { cityName ->
|
},
|
||||||
|
) { cityName ->
|
||||||
|
Text(
|
||||||
|
text = "($cityName)",
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationState.LocationResult.LackPermission -> {
|
||||||
Text(
|
Text(
|
||||||
text = "($cityName)",
|
text = stringRes(R.string.lack_location_permissions),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LocationState.LocationResult.Loading -> {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.loading_location),
|
||||||
fontSize = 12.sp,
|
fontSize = 12.sp,
|
||||||
lineHeight = 12.sp,
|
lineHeight = 12.sp,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ fun LoadCityName(
|
|||||||
CachedGeoLocations
|
CachedGeoLocations
|
||||||
.geoLocate(geohashStr, geoHash.toLocation(), context)
|
.geoLocate(geohashStr, geoHash.toLocation(), context)
|
||||||
?.ifBlank { null }
|
?.ifBlank { null }
|
||||||
|
|
||||||
if (newCityName != null && newCityName != cityName) {
|
if (newCityName != null && newCityName != cityName) {
|
||||||
cityName = newCityName
|
cityName = newCityName
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ class FollowListState(
|
|||||||
unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)),
|
unpackList = listOf(MuteListEvent.blockListFor(account.userProfile().pubkeyHex)),
|
||||||
)
|
)
|
||||||
|
|
||||||
val defaultLists = persistentListOf(kind3Follow, globalFollow, aroundMe, muteListFollow)
|
val defaultLists = persistentListOf(kind3Follow, aroundMe, globalFollow, muteListFollow)
|
||||||
|
|
||||||
fun getPeopleLists(): List<FeedDefinition> =
|
fun getPeopleLists(): List<FeedDefinition> =
|
||||||
account
|
account
|
||||||
|
|||||||
@@ -128,10 +128,12 @@ import coil3.compose.AsyncImage
|
|||||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||||
import com.google.accompanist.permissions.isGranted
|
import com.google.accompanist.permissions.isGranted
|
||||||
import com.google.accompanist.permissions.rememberPermissionState
|
import com.google.accompanist.permissions.rememberPermissionState
|
||||||
|
import com.vitorpamplona.amethyst.Amethyst
|
||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
import com.vitorpamplona.amethyst.commons.richtext.RichTextParser
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.User
|
import com.vitorpamplona.amethyst.model.User
|
||||||
|
import com.vitorpamplona.amethyst.service.LocationState
|
||||||
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
import com.vitorpamplona.amethyst.service.Nip96MediaServers
|
||||||
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
import com.vitorpamplona.amethyst.service.NostrSearchEventOrUserDataSource
|
||||||
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
import com.vitorpamplona.amethyst.ui.actions.LoadingAnimation
|
||||||
@@ -1285,6 +1287,10 @@ fun LocationAsHash(postViewModel: NewPostViewModel) {
|
|||||||
Manifest.permission.ACCESS_COARSE_LOCATION,
|
Manifest.permission.ACCESS_COARSE_LOCATION,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
LaunchedEffect(locationPermissionState.status.isGranted) {
|
||||||
|
Amethyst.instance.locationManager.setLocationPermission(locationPermissionState.status.isGranted)
|
||||||
|
}
|
||||||
|
|
||||||
if (locationPermissionState.status.isGranted) {
|
if (locationPermissionState.status.isGranted) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
@@ -1334,9 +1340,28 @@ fun LocationAsHash(postViewModel: NewPostViewModel) {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun DisplayLocationObserver(postViewModel: NewPostViewModel) {
|
fun DisplayLocationObserver(postViewModel: NewPostViewModel) {
|
||||||
val location by postViewModel.locationFlow().collectAsStateWithLifecycle(null)
|
val location by postViewModel.locationFlow().collectAsStateWithLifecycle()
|
||||||
|
|
||||||
location?.let { DisplayLocationInTitle(geohash = it) }
|
when (val myLocation = location) {
|
||||||
|
is LocationState.LocationResult.Success -> {
|
||||||
|
DisplayLocationInTitle(geohash = myLocation.geoHash.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
LocationState.LocationResult.LackPermission -> {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.lack_location_permissions),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LocationState.LocationResult.Loading -> {
|
||||||
|
Text(
|
||||||
|
text = stringRes(R.string.loading_location),
|
||||||
|
fontSize = 12.sp,
|
||||||
|
lineHeight = 12.sp,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
@@ -645,6 +645,9 @@
|
|||||||
<string name="geohash_title">Expose Location as </string>
|
<string name="geohash_title">Expose Location as </string>
|
||||||
<string name="geohash_explainer">Adds a Geohash of your location to the post. The public will know you are within 5km (3mi) of the current location</string>
|
<string name="geohash_explainer">Adds a Geohash of your location to the post. The public will know you are within 5km (3mi) of the current location</string>
|
||||||
|
|
||||||
|
<string name="loading_location">Loading location</string>
|
||||||
|
<string name="lack_location_permissions">No Location Permissions</string>
|
||||||
|
|
||||||
<string name="add_sensitive_content_explainer">Adds sensitive content warning before showing your content. This is ideal for any NSFW content or content some people may find offensive or disturbing</string>
|
<string name="add_sensitive_content_explainer">Adds sensitive content warning before showing your content. This is ideal for any NSFW content or content some people may find offensive or disturbing</string>
|
||||||
|
|
||||||
<string name="new_feature_nip17_might_not_be_available_title">New Feature</string>
|
<string name="new_feature_nip17_might_not_be_available_title">New Feature</string>
|
||||||
|
|||||||
@@ -280,7 +280,13 @@ open class Event(
|
|||||||
return PoWRank.getCommited(id, commitedPoW)
|
return PoWRank.getCommited(id, commitedPoW)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getGeoHash(): String? = tags.firstOrNull { it.size > 1 && it[0] == "g" }?.get(1)?.ifBlank { null }
|
override fun getGeoHash(): String? =
|
||||||
|
tags
|
||||||
|
.filter { it.size > 1 && it[0] == "g" }
|
||||||
|
.maxByOrNull {
|
||||||
|
it[1].length
|
||||||
|
}?.get(1)
|
||||||
|
?.ifBlank { null }
|
||||||
|
|
||||||
override fun getReward(): BigDecimal? =
|
override fun getReward(): BigDecimal? =
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user