Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions demo-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<!-- <uses-permission android:name="android.permission.CAMERA" />-->

<application
android:allowBackup="true"
Expand Down
11 changes: 11 additions & 0 deletions gravatar-quickeditor/api/gravatar-quickeditor.api
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,17 @@ public final class com/gravatar/quickeditor/ui/components/ComposableSingletons$A
public final fun getLambda-3$gravatar_quickeditor_release ()Lkotlin/jvm/functions/Function2;
}

public final class com/gravatar/quickeditor/ui/components/ComposableSingletons$CameraPermissionRationaleDialogKt {
public static final field INSTANCE Lcom/gravatar/quickeditor/ui/components/ComposableSingletons$CameraPermissionRationaleDialogKt;
public static field lambda-1 Lkotlin/jvm/functions/Function3;
public static field lambda-2 Lkotlin/jvm/functions/Function2;
public static field lambda-3 Lkotlin/jvm/functions/Function2;
public fun <init> ()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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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
Expand All @@ -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<Uri?>(null) }
val pickMedia = rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri ->
uri?.let { onLocalImageSelected(it) }
Expand All @@ -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(
Expand All @@ -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,
)
}

Expand All @@ -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<String>,
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
Expand Down Expand Up @@ -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
}
Original file line number Diff line number Diff line change
@@ -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))
}
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions gravatar-quickeditor/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@
<string name="avatar_upload_failure_dialog_title">Couldn\'t upload image</string>
<string name="avatar_upload_failure_dialog_remove_upload">Remove upload</string>
<string name="oauth_email_associated_error_message">Something went wrong when verifying your email.</string>
<string name="gravatar_qe_permission_required_alert_title">Permission required</string>
<string name="gravatar_qe_camera_permission_rationale_message">To take a picture, you need to grant camera permission. You can grant it in the app settings.</string>
<string name="gravatar_qe_dismiss">Dismiss</string>
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mlumeau We will have to wait for the translations.

</resources>