Track state of camera permission
This commit is contained in:
committed by
pynicolas
parent
2c64ebc972
commit
eb1f3b64ed
@@ -22,27 +22,34 @@ import androidx.activity.compose.ManagedActivityResultLauncher
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.core.content.ContextCompat
|
||||
|
||||
fun hasCameraPermission(context: Context): Boolean {
|
||||
private fun hasCameraPermission(context: Context): Boolean {
|
||||
val camera = Manifest.permission.CAMERA
|
||||
return ContextCompat.checkSelfPermission(context, camera) == PackageManager.PERMISSION_GRANTED
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberCameraPermissionLauncher(
|
||||
onGranted: () -> Unit = {},
|
||||
onDenied: () -> Unit = {}
|
||||
): ManagedActivityResultLauncher<String, Boolean> {
|
||||
val context = LocalContext.current
|
||||
return rememberLauncherForActivityResult (
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { isGranted ->
|
||||
if (isGranted) {
|
||||
onGranted()
|
||||
} else {
|
||||
onDenied()
|
||||
@Stable
|
||||
class CameraPermissionState internal constructor(
|
||||
private val context: Context,
|
||||
private val launcher: ManagedActivityResultLauncher<String, Boolean>
|
||||
) {
|
||||
var isGranted by mutableStateOf(hasCameraPermission(context))
|
||||
private set
|
||||
|
||||
fun request() {
|
||||
launcher.launch(Manifest.permission.CAMERA)
|
||||
}
|
||||
|
||||
internal fun update(granted: Boolean) {
|
||||
isGranted = granted
|
||||
if (!granted) {
|
||||
Toast.makeText(
|
||||
context,
|
||||
context.getString(R.string.camera_permission_denied),
|
||||
@@ -51,3 +58,21 @@ fun rememberCameraPermissionLauncher(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun rememberCameraPermissionState(): CameraPermissionState {
|
||||
val context = LocalContext.current
|
||||
lateinit var state: CameraPermissionState
|
||||
|
||||
val launcher = rememberLauncherForActivityResult(
|
||||
ActivityResultContracts.RequestPermission()
|
||||
) { granted ->
|
||||
state.update(granted)
|
||||
}
|
||||
|
||||
state = remember {
|
||||
CameraPermissionState(context, launcher)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
@@ -60,6 +60,7 @@ class MainActivity : ComponentActivity() {
|
||||
val currentScreen by viewModel.currentScreen.collectAsStateWithLifecycle()
|
||||
val liveAnalysisState by viewModel.liveAnalysisState.collectAsStateWithLifecycle()
|
||||
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
|
||||
val cameraPermission = rememberCameraPermissionState()
|
||||
MyScanTheme {
|
||||
val navigation = Navigation(
|
||||
toHomeScreen = { viewModel.navigateTo(Screen.Home) },
|
||||
@@ -72,7 +73,7 @@ class MainActivity : ComponentActivity() {
|
||||
when (val screen = currentScreen) {
|
||||
is Screen.Home -> {
|
||||
HomeScreen(
|
||||
hasCameraPermission = hasCameraPermission(this),
|
||||
cameraPermission = cameraPermission,
|
||||
currentDocument = document,
|
||||
navigation = navigation,
|
||||
onStartNewScan = navigation.toCameraScreen,
|
||||
@@ -85,6 +86,7 @@ class MainActivity : ComponentActivity() {
|
||||
liveAnalysisState,
|
||||
onImageAnalyzed = { image -> viewModel.liveAnalysis(image) },
|
||||
onFinalizePressed = { viewModel.navigateTo(Screen.Document()) },
|
||||
cameraPermission = cameraPermission
|
||||
)
|
||||
}
|
||||
is Screen.Document -> {
|
||||
|
||||
@@ -52,10 +52,9 @@ import androidx.core.graphics.scale
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import org.mydomain.myscan.CameraPermissionState
|
||||
import org.mydomain.myscan.LiveAnalysisState
|
||||
import org.mydomain.myscan.Point
|
||||
import org.mydomain.myscan.hasCameraPermission
|
||||
import org.mydomain.myscan.rememberCameraPermissionLauncher
|
||||
import org.mydomain.myscan.scaledTo
|
||||
import java.util.concurrent.ExecutorService
|
||||
import java.util.concurrent.Executors
|
||||
@@ -66,13 +65,12 @@ fun CameraPreview(
|
||||
onImageAnalyzed: (ImageProxy) -> Unit,
|
||||
captureController: CameraCaptureController,
|
||||
onPreviewViewReady: (PreviewView) -> Unit,
|
||||
cameraPermission: CameraPermissionState,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val requestPermissionLauncher = rememberCameraPermissionLauncher(onGranted = {}, onDenied = {})
|
||||
LaunchedEffect(Unit) {
|
||||
val camera = android.Manifest.permission.CAMERA
|
||||
if (!hasCameraPermission(context)) {
|
||||
requestPermissionLauncher.launch(camera)
|
||||
if (!cameraPermission.isGranted) {
|
||||
cameraPermission.request()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -75,6 +75,7 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import kotlinx.coroutines.delay
|
||||
import org.mydomain.myscan.CameraPermissionState
|
||||
import org.mydomain.myscan.LiveAnalysisState
|
||||
import org.mydomain.myscan.MainViewModel
|
||||
import org.mydomain.myscan.MainViewModel.CaptureState
|
||||
@@ -102,6 +103,7 @@ fun CameraScreen(
|
||||
liveAnalysisState: LiveAnalysisState,
|
||||
onImageAnalyzed: (ImageProxy) -> Unit,
|
||||
onFinalizePressed: () -> Unit,
|
||||
cameraPermission: CameraPermissionState,
|
||||
) {
|
||||
var previewView by remember { mutableStateOf<PreviewView?>(null) }
|
||||
val document by viewModel.documentUiModel.collectAsStateWithLifecycle()
|
||||
@@ -145,7 +147,8 @@ fun CameraScreen(
|
||||
CameraPreview(
|
||||
onImageAnalyzed = onImageAnalyzed,
|
||||
captureController = captureController,
|
||||
onPreviewViewReady = { view -> previewView = view }
|
||||
onPreviewViewReady = { view -> previewView = view },
|
||||
cameraPermission = cameraPermission,
|
||||
)
|
||||
},
|
||||
pageListState =
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
*/
|
||||
package org.mydomain.myscan.view
|
||||
|
||||
import android.Manifest
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@@ -46,15 +45,16 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import org.mydomain.myscan.CameraPermissionState
|
||||
import org.mydomain.myscan.Navigation
|
||||
import org.mydomain.myscan.R
|
||||
import org.mydomain.myscan.rememberCameraPermissionLauncher
|
||||
import org.mydomain.myscan.rememberCameraPermissionState
|
||||
import org.mydomain.myscan.ui.theme.MyScanTheme
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
fun HomeScreen(
|
||||
hasCameraPermission: Boolean,
|
||||
cameraPermission: CameraPermissionState,
|
||||
currentDocument: DocumentUiModel,
|
||||
navigation: Navigation,
|
||||
onStartNewScan: () -> Unit
|
||||
@@ -95,8 +95,8 @@ fun HomeScreen(
|
||||
.fillMaxSize()
|
||||
.verticalScroll(rememberScrollState())
|
||||
) {
|
||||
if (!hasCameraPermission) {
|
||||
CameraPermissionRationale()
|
||||
if (!cameraPermission.isGranted) {
|
||||
CameraPermissionRationale(cameraPermission)
|
||||
}
|
||||
|
||||
if (!currentDocument.isEmpty()) {
|
||||
@@ -115,8 +115,7 @@ fun HomeScreen(
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun CameraPermissionRationale() {
|
||||
val requestPermissionLauncher = rememberCameraPermissionLauncher(onGranted = {}, onDenied = {})
|
||||
private fun CameraPermissionRationale(cameraPermission: CameraPermissionState) {
|
||||
Card(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
@@ -128,9 +127,7 @@ private fun CameraPermissionRationale() {
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
Spacer(Modifier.height(8.dp))
|
||||
Button(onClick = {
|
||||
requestPermissionLauncher.launch(Manifest.permission.CAMERA)
|
||||
}) {
|
||||
Button(onClick = { cameraPermission.request() }) {
|
||||
Text(stringResource(R.string.grant_permission))
|
||||
}
|
||||
}
|
||||
@@ -183,7 +180,7 @@ private fun SectionTitle(text: String) {
|
||||
fun HomeScreenPreviewOnFirstLaunch() {
|
||||
MyScanTheme {
|
||||
HomeScreen(
|
||||
hasCameraPermission = false,
|
||||
cameraPermission = rememberCameraPermissionState(),
|
||||
currentDocument = DocumentUiModel(listOf()) { _ -> null },
|
||||
navigation = dummyNavigation(),
|
||||
onStartNewScan = {}
|
||||
@@ -196,7 +193,7 @@ fun HomeScreenPreviewOnFirstLaunch() {
|
||||
fun HomeScreenPreviewWithCurrentDocument() {
|
||||
MyScanTheme {
|
||||
HomeScreen(
|
||||
hasCameraPermission = true,
|
||||
cameraPermission = rememberCameraPermissionState(),
|
||||
currentDocument = fakeDocument(
|
||||
listOf("gallica.bnf.fr-bpt6k5530456s-1.jpg"),
|
||||
LocalContext.current),
|
||||
|
||||
Reference in New Issue
Block a user