Implemented asking for Camera permission

This commit is contained in:
dankito 2024-10-04 05:48:11 +02:00
parent 316a0027f7
commit e1bb7722ff
5 changed files with 57 additions and 11 deletions

View File

@ -106,6 +106,7 @@ kotlin {
androidMain.dependencies { androidMain.dependencies {
implementation(compose.preview) implementation(compose.preview)
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.fragment) // to fix bug IllegalArgumentException: Can only use lower 16 bits for requestCode
implementation(libs.androidx.biometric) implementation(libs.androidx.biometric)
implementation(libs.favre.bcrypt) implementation(libs.favre.bcrypt)

View File

@ -2,6 +2,7 @@ package net.codinux.banking.ui
import android.os.Bundle import android.os.Bundle
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
@ -11,6 +12,11 @@ import net.codinux.banking.ui.service.BiometricAuthenticationService
import net.codinux.banking.ui.service.ImageService import net.codinux.banking.ui.service.ImageService
class MainActivity : FragmentActivity() { class MainActivity : FragmentActivity() {
private val request = ActivityResultContracts.RequestMultiplePermissions()
private val activityResultLauncher = registerForActivityResult(request) { }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -23,8 +29,29 @@ class MainActivity : FragmentActivity() {
App() App()
} }
} }
fun requestPermissions(requiredPermissions: List<String>): Boolean {
val requiredPermissionsArray = requiredPermissions.toTypedArray()
activityResultLauncher.launch(requiredPermissionsArray)
var result = request.getSynchronousResult(baseContext, requiredPermissionsArray)
while (result == null) {
result = request.getSynchronousResult(baseContext, requiredPermissionsArray)
} }
return if (result.value != null) {
val allPermissionsGranted = result.value.entries.filter { it.key in requiredPermissions }.all { it.value == true }
allPermissionsGranted
} else {
false
}
}
}
@Preview @Preview
@Composable @Composable
fun AppAndroidPreview() { fun AppAndroidPreview() {

View File

@ -0,0 +1,13 @@
package net.codinux.banking.ui.service
import android.content.Context
import android.content.pm.PackageManager
import androidx.core.content.ContextCompat
object PermissionsService {
fun allPermissionsGranted(baseContext: Context, permissions: List<String>) = permissions.all {
ContextCompat.checkSelfPermission(baseContext, it) == PackageManager.PERMISSION_GRANTED
}
}

View File

@ -1,5 +1,6 @@
package net.codinux.banking.ui.service package net.codinux.banking.ui.service
import android.Manifest
import androidx.camera.core.* import androidx.camera.core.*
import androidx.camera.lifecycle.ProcessCameraProvider import androidx.camera.lifecycle.ProcessCameraProvider
import androidx.camera.view.PreviewView import androidx.camera.view.PreviewView
@ -7,15 +8,14 @@ import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.google.zxing.* import com.google.zxing.*
import com.google.zxing.common.HybridBinarizer import com.google.zxing.common.HybridBinarizer
import com.google.zxing.qrcode.QRCodeReader import com.google.zxing.qrcode.QRCodeReader
import net.codinux.banking.persistence.AndroidContext import net.codinux.banking.persistence.AndroidContext
import net.codinux.banking.ui.MainActivity
import net.codinux.banking.ui.config.DI import net.codinux.banking.ui.config.DI
import net.codinux.log.logger import net.codinux.log.logger
import java.nio.ByteBuffer import java.nio.ByteBuffer
@ -23,6 +23,8 @@ import java.util.concurrent.Executors
actual object QrCodeService { actual object QrCodeService {
private val RequiredPermissions = listOf(Manifest.permission.CAMERA)
private val cameraExecutor = Executors.newCachedThreadPool() private val cameraExecutor = Executors.newCachedThreadPool()
private val log by logger() private val log by logger()
@ -32,16 +34,17 @@ actual object QrCodeService {
@Composable @Composable
actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) { actual fun readQrCodeFromCamera(resultCallback: (QrCodeReadResult) -> Unit) {
val context = AndroidContext.mainActivity val mainActivity = LocalLifecycleOwner.current as MainActivity // we only have MainActivity, so we can be sure that LocalLifecycleOwner.current is MainActivity
val cameraProviderFuture = ProcessCameraProvider.getInstance(context)
val localContext = LocalContext.current if (PermissionsService.allPermissionsGranted(AndroidContext.applicationContext, RequiredPermissions) == false &&
log.info { "LocalContext.current = ${localContext.javaClass} ${localContext}" } mainActivity.requestPermissions(RequiredPermissions) == false) {
return // we don't have the permission to start the camera
}
val cameraProviderFuture = ProcessCameraProvider.getInstance(mainActivity)
val lifecycleOwner = LocalLifecycleOwner.current
// val context = LocalContext.current
val previewView = remember { val previewView = remember {
PreviewView(context) PreviewView(mainActivity)
} }
cameraProviderFuture.addListener({ cameraProviderFuture.addListener({
@ -69,13 +72,13 @@ actual object QrCodeService {
cameraProvider.unbindAll() cameraProvider.unbindAll()
// Bind use cases to camera // Bind use cases to camera
cameraProvider.bindToLifecycle(context as FragmentActivity, cameraSelector, preview, imageAnalyzer) cameraProvider.bindToLifecycle(mainActivity, cameraSelector, preview, imageAnalyzer)
} catch(e: Exception) { } catch(e: Exception) {
log.error(e) { "Use case binding failed" } log.error(e) { "Use case binding failed" }
} }
}, ContextCompat.getMainExecutor(context)) }, ContextCompat.getMainExecutor(mainActivity))
AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize()) AndroidView(factory = { previewView }, modifier = Modifier.fillMaxSize())

View File

@ -24,6 +24,7 @@ android-targetSdk = "34"
androidx-activityCompose = "1.9.2" androidx-activityCompose = "1.9.2"
androidx-lifecycle = "2.8.2" androidx-lifecycle = "2.8.2"
androidx-fragment = "1.8.3"
androidx-biometric = "1.1.0" androidx-biometric = "1.1.0"
compose-plugin = "1.6.11" compose-plugin = "1.6.11"
@ -57,6 +58,7 @@ sqldelight-native-driver = { module = "app.cash.sqldelight:native-driver", versi
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activityCompose" }
androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" } androidx-lifecycle-viewmodel = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-viewmodel", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" } androidx-lifecycle-runtime-compose = { group = "org.jetbrains.androidx.lifecycle", name = "lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
androidx-fragment = { group = "androidx.fragment", name = "fragment", version.ref = "androidx-fragment" }
androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" } androidx-biometric = { group = "androidx.biometric", name = "biometric", version.ref = "androidx-biometric" }
kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" } kotlinx-coroutines-swing = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-swing", version.ref = "kotlinx-coroutines" }