11package com.gravatar.quickeditor.ui.components
22
3+ import android.Manifest
4+ import android.content.Context
5+ import android.content.pm.PackageManager
36import android.net.Uri
47import androidx.activity.compose.rememberLauncherForActivityResult
8+ import androidx.activity.result.ActivityResultLauncher
59import androidx.activity.result.PickVisualMediaRequest
610import androidx.activity.result.contract.ActivityResultContracts
711import androidx.annotation.StringRes
@@ -13,11 +17,14 @@ import androidx.compose.runtime.setValue
1317import androidx.compose.ui.Modifier
1418import androidx.compose.ui.platform.LocalContext
1519import androidx.compose.ui.tooling.preview.Preview
20+ import androidx.core.app.ActivityCompat
21+ import androidx.core.content.ContextCompat
1622import com.gravatar.quickeditor.QuickEditorFileProvider
1723import com.gravatar.quickeditor.R
1824import com.gravatar.quickeditor.ui.avatarpicker.AvatarUi
1925import com.gravatar.quickeditor.ui.avatarpicker.AvatarsSectionUiState
2026import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout
27+ import com.gravatar.quickeditor.ui.oauth.findComponentActivity
2128import com.gravatar.restapi.models.Avatar
2229import com.gravatar.ui.GravatarTheme
2330import java.net.URI
@@ -30,6 +37,7 @@ internal fun AvatarsSection(
3037 modifier : Modifier = Modifier ,
3138) {
3239 val context = LocalContext .current
40+ var cameraPermissionDialogRationaleVisible by rememberSaveable { mutableStateOf(false ) }
3341 var photoImageUri by rememberSaveable { mutableStateOf<Uri ?>(null ) }
3442 val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts .PickVisualMedia ()) { uri ->
3543 uri?.let { onLocalImageSelected(it) }
@@ -39,6 +47,32 @@ internal fun AvatarsSection(
3947 if (success && takenPictureUri != null ) onLocalImageSelected(takenPictureUri)
4048 }
4149
50+ val takePhotoCallback = {
51+ val imageUri = QuickEditorFileProvider .getTempCameraImageUri(context)
52+ photoImageUri = imageUri
53+ takePhoto.launch(imageUri)
54+ }
55+
56+ val cameraPermissionLauncher = rememberLauncherForActivityResult(
57+ ActivityResultContracts .RequestPermission (),
58+ ) { isGranted: Boolean ->
59+ if (isGranted) {
60+ takePhotoCallback()
61+ } else {
62+ cameraPermissionDialogRationaleVisible = true
63+ }
64+ }
65+
66+ val permissionAwareTakePhotoCallback = {
67+ context.withCameraPermission(
68+ cameraPermissionLauncher = cameraPermissionLauncher,
69+ onShowRationale = { cameraPermissionDialogRationaleVisible = true },
70+ grantedCallback = {
71+ takePhotoCallback()
72+ },
73+ )
74+ }
75+
4276 when (state.avatarPickerContentLayout) {
4377 AvatarPickerContentLayout .Vertical -> {
4478 VerticalAvatarsSection (
@@ -48,11 +82,7 @@ internal fun AvatarsSection(
4882 onChoosePhotoClick = {
4983 pickMedia.launch(PickVisualMediaRequest (ActivityResultContracts .PickVisualMedia .ImageOnly ))
5084 },
51- onTakePhotoClick = {
52- val imageUri = QuickEditorFileProvider .getTempCameraImageUri(context)
53- photoImageUri = imageUri
54- takePhoto.launch(imageUri)
55- },
85+ onTakePhotoClick = permissionAwareTakePhotoCallback,
5686 )
5787 }
5888
@@ -61,17 +91,50 @@ internal fun AvatarsSection(
6191 state = state,
6292 modifier = modifier,
6393 onAvatarSelected = onAvatarSelected,
64- onTakePhotoClick = {
65- val imageUri = QuickEditorFileProvider .getTempCameraImageUri(context)
66- photoImageUri = imageUri
67- takePhoto.launch(imageUri)
68- },
94+ onTakePhotoClick = permissionAwareTakePhotoCallback,
6995 onChoosePhotoClick = {
7096 pickMedia.launch(PickVisualMediaRequest (ActivityResultContracts .PickVisualMedia .ImageOnly ))
7197 },
7298 )
7399 }
74100 }
101+
102+ CameraPermissionRationaleDialog (
103+ cameraPermissionDialogRationaleVisible,
104+ onDismiss = { cameraPermissionDialogRationaleVisible = false },
105+ onConfirmation = {
106+ cameraPermissionDialogRationaleVisible = false
107+ },
108+ )
109+ }
110+
111+ internal fun Context.withCameraPermission (
112+ cameraPermissionLauncher : ActivityResultLauncher <String >,
113+ onShowRationale : () -> Unit = {},
114+ grantedCallback : () -> Unit ,
115+ ) {
116+ if (hasCameraPermissionInManifest()) {
117+ val activity = findComponentActivity()
118+ when {
119+ ContextCompat .checkSelfPermission(
120+ this ,
121+ Manifest .permission.CAMERA ,
122+ ) == PackageManager .PERMISSION_GRANTED -> {
123+ grantedCallback()
124+ }
125+
126+ activity != null &&
127+ ActivityCompat .shouldShowRequestPermissionRationale(activity, Manifest .permission.CAMERA ) -> {
128+ onShowRationale()
129+ }
130+
131+ else -> {
132+ cameraPermissionLauncher.launch(Manifest .permission.CAMERA )
133+ }
134+ }
135+ } else {
136+ grantedCallback()
137+ }
75138}
76139
77140internal val AvatarsSectionUiState .titleRes: Int
@@ -155,3 +218,10 @@ private fun AvatarSectionEmptyPreview() {
155218 )
156219 }
157220}
221+
222+ private fun Context.hasCameraPermissionInManifest (): Boolean {
223+ val packageInfo = packageManager.getPackageInfo(packageName, PackageManager .GET_PERMISSIONS )
224+ val permissions = packageInfo.requestedPermissions
225+
226+ return permissions?.any { perm -> perm == Manifest .permission.CAMERA } ? : false
227+ }
0 commit comments