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