All Downloads are FREE. Search and download functionalities are using the official Maven repository.

iosMain.kmm.essentials.service.IosPermissionService.kt Maven / Gradle / Ivy

package kmm.essentials.service

import kmm.essentials.system.LocationManager
import kmm.essentials.system.mainContinuation
import platform.AVFoundation.*
import platform.CoreLocation.*
import platform.Foundation.NSBundle
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine

internal class IosPermissionService : PermissionService {
    override fun getPermission(type: PermissionType): Permission = when (type) {
        PermissionType.Camera -> CameraPermission()
        PermissionType.Microphone -> MicrophonePermission()
        PermissionType.ForegroundLocation -> ForegroundLocationPermission()
        PermissionType.BackgroundLocation -> BackgroundLocationPermission()
    }
}

private class CameraPermission : IosPermission() {
    override fun checkStatus(): PermissionStatus {
        checkPlist("NSCameraUsageDescription")
        return getMediaTypePermissionStatus(AVMediaTypeVideo)
    }

    override suspend fun request(): PermissionStatus {
        if (checkStatus() == PermissionStatus.Granted) {
            return PermissionStatus.Granted
        }
        return requestMediaTypePermission(AVMediaTypeVideo)
    }
}

private class MicrophonePermission : IosPermission() {
    override fun checkStatus(): PermissionStatus {
        checkPlist("NSMicrophoneUsageDescription")
        return getMediaTypePermissionStatus(AVMediaTypeAudio)
    }

    override suspend fun request(): PermissionStatus {
        if (checkStatus() == PermissionStatus.Granted) {
            return PermissionStatus.Granted
        }
        return requestMediaTypePermission(AVMediaTypeAudio)
    }
}

private class ForegroundLocationPermission : IosPermission() {
    override fun checkStatus(): PermissionStatus {
        checkPlist("NSLocationWhenInUseUsageDescription")

        val serviceEnabled = CLLocationManager.locationServicesEnabled()
        if (!serviceEnabled) return PermissionStatus.Disabled

        return getLocationManagerStatus(false)
    }

    override suspend fun request(): PermissionStatus {
        if (checkStatus() == PermissionStatus.Granted) {
            return PermissionStatus.Granted
        }
        return requestLocationPermission(false)
    }
}

private class BackgroundLocationPermission : IosPermission() {
    override fun checkStatus(): PermissionStatus {
        checkPlist(
            "NSLocationWhenInUseUsageDescription",
            "NSLocationAlwaysAndWhenInUseUsageDescription"
        )

        val serviceEnabled = CLLocationManager.locationServicesEnabled()
        if (!serviceEnabled) return PermissionStatus.Disabled

        return getLocationManagerStatus(true)
    }

    override suspend fun request(): PermissionStatus {
        if (checkStatus() == PermissionStatus.Granted) {
            return PermissionStatus.Granted
        }
        return requestLocationPermission(true)
    }
}

private abstract class IosPermission : Permission {
    override fun shouldShowRationale(): Boolean = false

    protected fun getMediaTypePermissionStatus(mediaType: AVMediaType): PermissionStatus =
        when (AVCaptureDevice.authorizationStatusForMediaType(mediaType)) {
            AVAuthorizationStatusAuthorized -> PermissionStatus.Granted
            AVAuthorizationStatusDenied -> PermissionStatus.Denied
            AVAuthorizationStatusRestricted -> PermissionStatus.Restricted
            else -> PermissionStatus.Unknown
        }

    protected fun getLocationManagerStatus(background: Boolean): PermissionStatus =
        when (CLLocationManager.authorizationStatus()) {
            kCLAuthorizationStatusAuthorizedAlways -> PermissionStatus.Granted
            kCLAuthorizationStatusAuthorizedWhenInUse -> if (background) PermissionStatus.Denied else PermissionStatus.Granted
            kCLAuthorizationStatusDenied -> PermissionStatus.Denied
            kCLAuthorizationStatusRestricted -> PermissionStatus.Restricted
            else -> PermissionStatus.Unknown
        }

    protected suspend fun requestMediaTypePermission(mediaType: AVMediaType): PermissionStatus =
        suspendCoroutine { continuation ->
            AVCaptureDevice.requestAccessForMediaType(mediaType, mainContinuation { result ->
                continuation.resume(
                    if (result) PermissionStatus.Granted else PermissionStatus.Denied
                )
            })
        }

    protected suspend fun requestLocationPermission(background: Boolean): PermissionStatus {
        val locationManager = LocationManager()

        suspendCoroutine { continuation ->
            if (background) {
                locationManager.requestAlwaysAuthorization { continuation.resume(it) }
            } else {
                locationManager.requestWhenInUseAuthorization { continuation.resume(it) }
            }
        }

        return getLocationManagerStatus(background)
    }

    protected fun checkPlist(vararg key: String) {
        val declaredKeys = NSBundle.mainBundle.infoDictionary?.keys ?: emptySet()
        if (!declaredKeys.containsAll(key.toList())) {
            error("You need to declare using the permission: `${key.joinToString()}` in your Info.plist")
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy