Pith - stave
stave/app/src/main/kotlin/com/vgmlr/stave/StaveLocator.kt [3.0 kb]
Modified: 23:41:07 158 026 (24 Aug 026)
0 Days Ago
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 }
    }
}
Updates
Stave - Android 158.026
Kerf - Android 157.026
Kiln - Android 157.026
Wedge - Android 156.026
Whittle - Linux 155.026

Menu
Calendar
Project Tin (024/029)
Miter
RSS Feed
User Avatar
@vgmlr
=SUM(parts)
0.00155
257,431 (+41)