diff --git a/demo-app/src/main/AndroidManifest.xml b/demo-app/src/main/AndroidManifest.xml index 890bf3fd7..4e1f70ae6 100644 --- a/demo-app/src/main/AndroidManifest.xml +++ b/demo-app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools"> + ()V + public final fun getLambda-1$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function2; + public final fun getLambda-3$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function2; +} + public final class com/gravatar/quickeditor/ui/components/ComposableSingletons$EmailLabelKt { public static final field INSTANCE Lcom/gravatar/quickeditor/ui/components/ComposableSingletons$EmailLabelKt; public static field lambda-1 Lkotlin/jvm/functions/Function2; diff --git a/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/AvatarsSection.kt b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/AvatarsSection.kt index 3f843615a..1f5b88014 100644 --- a/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/AvatarsSection.kt +++ b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/AvatarsSection.kt @@ -1,7 +1,11 @@ package com.gravatar.quickeditor.ui.components +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager import android.net.Uri import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.StringRes @@ -13,11 +17,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.tooling.preview.Preview +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat import com.gravatar.quickeditor.QuickEditorFileProvider import com.gravatar.quickeditor.R import com.gravatar.quickeditor.ui.avatarpicker.AvatarUi import com.gravatar.quickeditor.ui.avatarpicker.AvatarsSectionUiState import com.gravatar.quickeditor.ui.editor.AvatarPickerContentLayout +import com.gravatar.quickeditor.ui.oauth.findComponentActivity import com.gravatar.restapi.models.Avatar import com.gravatar.ui.GravatarTheme import java.net.URI @@ -30,6 +37,7 @@ internal fun AvatarsSection( modifier: Modifier = Modifier, ) { val context = LocalContext.current + var cameraPermissionDialogRationaleVisible by rememberSaveable { mutableStateOf(false) } var photoImageUri by rememberSaveable { mutableStateOf(null) } val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> uri?.let { onLocalImageSelected(it) } @@ -39,6 +47,32 @@ internal fun AvatarsSection( if (success && takenPictureUri != null) onLocalImageSelected(takenPictureUri) } + val takePhotoCallback = { + val imageUri = QuickEditorFileProvider.getTempCameraImageUri(context) + photoImageUri = imageUri + takePhoto.launch(imageUri) + } + + val cameraPermissionLauncher = rememberLauncherForActivityResult( + ActivityResultContracts.RequestPermission(), + ) { isGranted: Boolean -> + if (isGranted) { + takePhotoCallback() + } else { + cameraPermissionDialogRationaleVisible = true + } + } + + val permissionAwareTakePhotoCallback = { + context.withCameraPermission( + cameraPermissionLauncher = cameraPermissionLauncher, + onShowRationale = { cameraPermissionDialogRationaleVisible = true }, + grantedCallback = { + takePhotoCallback() + }, + ) + } + when (state.avatarPickerContentLayout) { AvatarPickerContentLayout.Vertical -> { VerticalAvatarsSection( @@ -48,11 +82,7 @@ internal fun AvatarsSection( onChoosePhotoClick = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, - onTakePhotoClick = { - val imageUri = QuickEditorFileProvider.getTempCameraImageUri(context) - photoImageUri = imageUri - takePhoto.launch(imageUri) - }, + onTakePhotoClick = permissionAwareTakePhotoCallback, ) } @@ -61,17 +91,50 @@ internal fun AvatarsSection( state = state, modifier = modifier, onAvatarSelected = onAvatarSelected, - onTakePhotoClick = { - val imageUri = QuickEditorFileProvider.getTempCameraImageUri(context) - photoImageUri = imageUri - takePhoto.launch(imageUri) - }, + onTakePhotoClick = permissionAwareTakePhotoCallback, onChoosePhotoClick = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, ) } } + + CameraPermissionRationaleDialog( + cameraPermissionDialogRationaleVisible, + onDismiss = { cameraPermissionDialogRationaleVisible = false }, + onConfirmation = { + cameraPermissionDialogRationaleVisible = false + }, + ) +} + +internal fun Context.withCameraPermission( + cameraPermissionLauncher: ActivityResultLauncher, + onShowRationale: () -> Unit = {}, + grantedCallback: () -> Unit, +) { + if (hasCameraPermissionInManifest()) { + val activity = findComponentActivity() + when { + ContextCompat.checkSelfPermission( + this, + Manifest.permission.CAMERA, + ) == PackageManager.PERMISSION_GRANTED -> { + grantedCallback() + } + + activity != null && + ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA) -> { + onShowRationale() + } + + else -> { + cameraPermissionLauncher.launch(Manifest.permission.CAMERA) + } + } + } else { + grantedCallback() + } } internal val AvatarsSectionUiState.titleRes: Int @@ -155,3 +218,10 @@ private fun AvatarSectionEmptyPreview() { ) } } + +private fun Context.hasCameraPermissionInManifest(): Boolean { + val packageInfo = packageManager.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS) + val permissions = packageInfo.requestedPermissions + + return permissions?.any { perm -> perm == Manifest.permission.CAMERA } ?: false +} diff --git a/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/CameraPermissionRationaleDialog.kt b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/CameraPermissionRationaleDialog.kt new file mode 100644 index 000000000..ce257f12a --- /dev/null +++ b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/components/CameraPermissionRationaleDialog.kt @@ -0,0 +1,38 @@ +package com.gravatar.quickeditor.ui.components + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.res.stringResource +import com.gravatar.quickeditor.R + +@Composable +internal fun CameraPermissionRationaleDialog( + isVisible: Boolean, + onConfirmation: () -> Unit, + onDismiss: () -> Unit = {}, +) { + if (isVisible) { + AlertDialog( + title = { + Text(text = stringResource(R.string.gravatar_qe_permission_required_alert_title)) + }, + text = { + Text( + text = stringResource(R.string.gravatar_qe_camera_permission_rationale_message), + ) + }, + onDismissRequest = onDismiss, + confirmButton = { + TextButton( + onClick = { + onConfirmation() + }, + ) { + Text(stringResource(R.string.gravatar_qe_dismiss)) + } + }, + ) + } +} diff --git a/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/oauth/OAuthPage.kt b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/oauth/OAuthPage.kt index b32b257bd..019a108de 100644 --- a/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/oauth/OAuthPage.kt +++ b/gravatar-quickeditor/src/main/java/com/gravatar/quickeditor/ui/oauth/OAuthPage.kt @@ -172,7 +172,7 @@ private fun launchCustomTab(context: Context, oauthParams: OAuthParams, email: E ) } -private fun Context.findComponentActivity(): ComponentActivity? = when (this) { +internal fun Context.findComponentActivity(): ComponentActivity? = when (this) { is ComponentActivity -> this is ContextWrapper -> baseContext.findComponentActivity() else -> null diff --git a/gravatar-quickeditor/src/main/res/values/strings.xml b/gravatar-quickeditor/src/main/res/values/strings.xml index fdf5bc915..fe8b63956 100644 --- a/gravatar-quickeditor/src/main/res/values/strings.xml +++ b/gravatar-quickeditor/src/main/res/values/strings.xml @@ -34,4 +34,7 @@ Couldn\'t upload image Remove upload Something went wrong when verifying your email. + Permission required + To take a picture, you need to grant camera permission. You can grant it in the app settings. + Dismiss \ No newline at end of file