package com.vgmlr.stave
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.location.Geocoder
import android.location.Location
import android.location.LocationManager
import android.os.CancellationSignal
import androidx.core.content.ContextCompat
import androidx.core.location.LocationManagerCompat
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import java.util.Locale
import kotlin.coroutines.resume
data class StaveFix(val city: String, val lat: Double, val lng: Double)
object StaveLocator {
val PERMISSIONS = arrayOf(
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_FINE_LOCATION
)
fun hasPermission(context: Context): Boolean = PERMISSIONS.any {
ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED
}
suspend fun fix(context: Context): StaveFix? {
if (!hasPermission(context)) return null
val lm = context.getSystemService(Context.LOCATION_SERVICE) as? LocationManager ?: return null
val loc = lastKnown(lm) ?: current(context, lm) ?: return null
val city = geocode(context, loc) ?: return null
return StaveFix(city, loc.latitude, loc.longitude)
}
@SuppressLint("MissingPermission")
private fun lastKnown(lm: LocationManager): Location? =
listOf(
LocationManager.GPS_PROVIDER,
LocationManager.NETWORK_PROVIDER,
LocationManager.PASSIVE_PROVIDER
)
.mapNotNull { runCatching { lm.getLastKnownLocation(it) }.getOrNull() }
.maxByOrNull { it.time }
@SuppressLint("MissingPermission")
private suspend fun current(context: Context, lm: LocationManager): Location? =
suspendCancellableCoroutine { cont ->
val provider = listOf(LocationManager.NETWORK_PROVIDER, LocationManager.GPS_PROVIDER)
.firstOrNull { runCatching { lm.isProviderEnabled(it) }.getOrDefault(false) }
if (provider == null) {
cont.resume(null)
return@suspendCancellableCoroutine
}
val signal = CancellationSignal()
cont.invokeOnCancellation { signal.cancel() }
LocationManagerCompat.getCurrentLocation(
lm, provider, signal, ContextCompat.getMainExecutor(context)
) { loc -> if (cont.isActive) cont.resume(loc) }
}
@Suppress("DEPRECATION")
private suspend fun geocode(context: Context, loc: Location): String? = withContext(Dispatchers.IO) {
val a = runCatching {
Geocoder(context, Locale.getDefault()).getFromLocation(loc.latitude, loc.longitude, 1)
}.getOrNull()?.firstOrNull() ?: return@withContext null
val city = a.locality ?: a.subAdminArea ?: a.subLocality
listOfNotNull(city, a.adminArea).joinToString(", ").ifBlank { null }
}
}