Request Camera permission before launching the external Camera app - when sending attachment to a room (#1395)

This commit is contained in:
Benoit Marty
2023-09-21 12:48:03 +02:00
parent b49a4a058d
commit c2bc3a7828
4 changed files with 60 additions and 15 deletions

View File

@@ -48,6 +48,7 @@ dependencies {
implementation(projects.libraries.mediapickers.api)
implementation(projects.libraries.featureflag.api)
implementation(projects.libraries.mediaupload.api)
implementation(projects.libraries.permissions.api)
implementation(projects.libraries.preferences.api)
implementation(projects.features.networkmonitor.api)
implementation(projects.services.analytics.api)
@@ -77,6 +78,7 @@ dependencies {
testImplementation(projects.libraries.featureflag.test)
testImplementation(projects.libraries.mediaupload.test)
testImplementation(projects.libraries.mediapickers.test)
testImplementation(projects.libraries.permissions.test)
testImplementation(projects.libraries.preferences.test)
testImplementation(projects.libraries.textcomposer.test)
testImplementation(libs.test.mockk)

View File

@@ -16,6 +16,7 @@
package io.element.android.features.messages.impl.messagecomposer
import android.Manifest
import android.annotation.SuppressLint
import android.net.Uri
import androidx.compose.runtime.Composable
@@ -44,6 +45,8 @@ import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.mediapickers.api.PickerProvider
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.permissions.api.PermissionsEvents
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.textcomposer.Message
import io.element.android.libraries.textcomposer.MessageComposerMode
import io.element.android.services.analytics.api.AnalyticsService
@@ -70,13 +73,18 @@ class MessageComposerPresenter @Inject constructor(
private val analyticsService: AnalyticsService,
private val messageComposerContext: MessageComposerContextImpl,
private val richTextEditorStateFactory: RichTextEditorStateFactory,
permissionsPresenterFactory: PermissionsPresenter.Factory
) : Presenter<MessageComposerState> {
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
private var pendingEvent: MessageComposerEvents? = null
@SuppressLint("UnsafeOptInUsageError")
@Composable
override fun present(): MessageComposerState {
val localCoroutineScope = rememberCoroutineScope()
val cameraPermissionState = cameraPermissionPresenter.present()
val attachmentsState = remember {
mutableStateOf<AttachmentsState>(AttachmentsState.None)
}
@@ -132,6 +140,17 @@ class MessageComposerPresenter @Inject constructor(
}
}
LaunchedEffect(cameraPermissionState.permissionGranted) {
if (cameraPermissionState.permissionGranted) {
when (pendingEvent) {
is MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> cameraPhotoPicker.launch()
is MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> cameraVideoPicker.launch()
else -> Unit
}
pendingEvent = null
}
}
fun handleEvents(event: MessageComposerEvents) {
when (event) {
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
@@ -163,11 +182,21 @@ class MessageComposerPresenter @Inject constructor(
}
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
cameraPhotoPicker.launch()
if (cameraPermissionState.permissionGranted) {
cameraPhotoPicker.launch()
} else {
pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.AskPermissionToUser)
}
}
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
showAttachmentSourcePicker = false
cameraVideoPicker.launch()
if (cameraPermissionState.permissionGranted) {
cameraVideoPicker.launch()
} else {
pendingEvent = event
cameraPermissionState.eventSink(PermissionsEvents.AskPermissionToUser)
}
}
MessageComposerEvents.PickAttachmentSource.Location -> {
showAttachmentSourcePicker = false
@@ -301,16 +330,16 @@ class MessageComposerPresenter @Inject constructor(
}
mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback).getOrThrow()
}
.onSuccess {
attachmentState.value = AttachmentsState.None
}
.onFailure { cause ->
attachmentState.value = AttachmentsState.None
if (cause is CancellationException) {
throw cause
} else {
val snackbarMessage = SnackbarMessage(sendAttachmentError(cause))
snackbarDispatcher.post(snackbarMessage)
.onSuccess {
attachmentState.value = AttachmentsState.None
}
.onFailure { cause ->
attachmentState.value = AttachmentsState.None
if (cause is CancellationException) {
throw cause
} else {
val snackbarMessage = SnackbarMessage(sendAttachmentError(cause))
snackbarDispatcher.post(snackbarMessage)
}
}
}
}

View File

@@ -70,6 +70,8 @@ import io.element.android.libraries.matrix.test.room.aRoomMember
import io.element.android.libraries.mediapickers.test.FakePickerProvider
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.textcomposer.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService
import io.element.android.tests.testutils.WarmUpRule
@@ -601,6 +603,7 @@ class MessagesPresenterTest {
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
): MessagesPresenter {
val messageComposerPresenter = MessageComposerPresenter(
appCoroutineScope = this,
@@ -613,8 +616,12 @@ class MessagesPresenterTest {
analyticsService = analyticsService,
messageComposerContext = MessageComposerContextImpl(),
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
)
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permission: String): PermissionsPresenter {
return permissionsPresenter
}
}
)
val timelinePresenter = TimelinePresenter(
timelineItemsFactory = aTimelineItemsFactory(),
room = matrixRoom,

View File

@@ -54,6 +54,8 @@ import io.element.android.libraries.mediaupload.api.MediaPreProcessor
import io.element.android.libraries.mediaupload.api.MediaSender
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
import io.element.android.libraries.permissions.api.PermissionsPresenter
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
import io.element.android.libraries.textcomposer.Message
import io.element.android.libraries.textcomposer.MessageComposerMode
import io.element.android.services.analytics.test.FakeAnalyticsService
@@ -623,6 +625,11 @@ class MessageComposerPresenterTest {
analyticsService,
MessageComposerContextImpl(),
TestRichTextEditorStateFactory(),
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
override fun create(permission: String): PermissionsPresenter {
return FakePermissionsPresenter().apply { setPermissionGranted() }
}
}
)
}