Request Camera permission before launching the external Camera app - when sending attachment to a room (#1395)
This commit is contained in:
parent
b49a4a058d
commit
c2bc3a7828
4 changed files with 60 additions and 15 deletions
|
|
@ -48,6 +48,7 @@ dependencies {
|
||||||
implementation(projects.libraries.mediapickers.api)
|
implementation(projects.libraries.mediapickers.api)
|
||||||
implementation(projects.libraries.featureflag.api)
|
implementation(projects.libraries.featureflag.api)
|
||||||
implementation(projects.libraries.mediaupload.api)
|
implementation(projects.libraries.mediaupload.api)
|
||||||
|
implementation(projects.libraries.permissions.api)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.features.networkmonitor.api)
|
implementation(projects.features.networkmonitor.api)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
|
|
@ -77,6 +78,7 @@ dependencies {
|
||||||
testImplementation(projects.libraries.featureflag.test)
|
testImplementation(projects.libraries.featureflag.test)
|
||||||
testImplementation(projects.libraries.mediaupload.test)
|
testImplementation(projects.libraries.mediaupload.test)
|
||||||
testImplementation(projects.libraries.mediapickers.test)
|
testImplementation(projects.libraries.mediapickers.test)
|
||||||
|
testImplementation(projects.libraries.permissions.test)
|
||||||
testImplementation(projects.libraries.preferences.test)
|
testImplementation(projects.libraries.preferences.test)
|
||||||
testImplementation(projects.libraries.textcomposer.test)
|
testImplementation(projects.libraries.textcomposer.test)
|
||||||
testImplementation(libs.test.mockk)
|
testImplementation(libs.test.mockk)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.messagecomposer
|
package io.element.android.features.messages.impl.messagecomposer
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.runtime.Composable
|
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.matrix.api.room.MatrixRoom
|
||||||
import io.element.android.libraries.mediapickers.api.PickerProvider
|
import io.element.android.libraries.mediapickers.api.PickerProvider
|
||||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
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.Message
|
||||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import io.element.android.services.analytics.api.AnalyticsService
|
import io.element.android.services.analytics.api.AnalyticsService
|
||||||
|
|
@ -70,13 +73,18 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
private val analyticsService: AnalyticsService,
|
private val analyticsService: AnalyticsService,
|
||||||
private val messageComposerContext: MessageComposerContextImpl,
|
private val messageComposerContext: MessageComposerContextImpl,
|
||||||
private val richTextEditorStateFactory: RichTextEditorStateFactory,
|
private val richTextEditorStateFactory: RichTextEditorStateFactory,
|
||||||
|
permissionsPresenterFactory: PermissionsPresenter.Factory
|
||||||
) : Presenter<MessageComposerState> {
|
) : Presenter<MessageComposerState> {
|
||||||
|
|
||||||
|
private val cameraPermissionPresenter = permissionsPresenterFactory.create(Manifest.permission.CAMERA)
|
||||||
|
private var pendingEvent: MessageComposerEvents? = null
|
||||||
|
|
||||||
@SuppressLint("UnsafeOptInUsageError")
|
@SuppressLint("UnsafeOptInUsageError")
|
||||||
@Composable
|
@Composable
|
||||||
override fun present(): MessageComposerState {
|
override fun present(): MessageComposerState {
|
||||||
val localCoroutineScope = rememberCoroutineScope()
|
val localCoroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
|
val cameraPermissionState = cameraPermissionPresenter.present()
|
||||||
val attachmentsState = remember {
|
val attachmentsState = remember {
|
||||||
mutableStateOf<AttachmentsState>(AttachmentsState.None)
|
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) {
|
fun handleEvents(event: MessageComposerEvents) {
|
||||||
when (event) {
|
when (event) {
|
||||||
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
|
MessageComposerEvents.ToggleFullScreenState -> isFullScreen.value = !isFullScreen.value
|
||||||
|
|
@ -163,11 +182,21 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch {
|
MessageComposerEvents.PickAttachmentSource.PhotoFromCamera -> localCoroutineScope.launch {
|
||||||
showAttachmentSourcePicker = false
|
showAttachmentSourcePicker = false
|
||||||
cameraPhotoPicker.launch()
|
if (cameraPermissionState.permissionGranted) {
|
||||||
|
cameraPhotoPicker.launch()
|
||||||
|
} else {
|
||||||
|
pendingEvent = event
|
||||||
|
cameraPermissionState.eventSink(PermissionsEvents.AskPermissionToUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
|
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
|
||||||
showAttachmentSourcePicker = false
|
showAttachmentSourcePicker = false
|
||||||
cameraVideoPicker.launch()
|
if (cameraPermissionState.permissionGranted) {
|
||||||
|
cameraVideoPicker.launch()
|
||||||
|
} else {
|
||||||
|
pendingEvent = event
|
||||||
|
cameraPermissionState.eventSink(PermissionsEvents.AskPermissionToUser)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
MessageComposerEvents.PickAttachmentSource.Location -> {
|
MessageComposerEvents.PickAttachmentSource.Location -> {
|
||||||
showAttachmentSourcePicker = false
|
showAttachmentSourcePicker = false
|
||||||
|
|
@ -301,16 +330,16 @@ class MessageComposerPresenter @Inject constructor(
|
||||||
}
|
}
|
||||||
mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback).getOrThrow()
|
mediaSender.sendMedia(uri, mimeType, compressIfPossible = false, progressCallback).getOrThrow()
|
||||||
}
|
}
|
||||||
.onSuccess {
|
.onSuccess {
|
||||||
attachmentState.value = AttachmentsState.None
|
attachmentState.value = AttachmentsState.None
|
||||||
}
|
}
|
||||||
.onFailure { cause ->
|
.onFailure { cause ->
|
||||||
attachmentState.value = AttachmentsState.None
|
attachmentState.value = AttachmentsState.None
|
||||||
if (cause is CancellationException) {
|
if (cause is CancellationException) {
|
||||||
throw cause
|
throw cause
|
||||||
} else {
|
} else {
|
||||||
val snackbarMessage = SnackbarMessage(sendAttachmentError(cause))
|
val snackbarMessage = SnackbarMessage(sendAttachmentError(cause))
|
||||||
snackbarDispatcher.post(snackbarMessage)
|
snackbarDispatcher.post(snackbarMessage)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.mediapickers.test.FakePickerProvider
|
||||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||||
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
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.libraries.textcomposer.MessageComposerMode
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
|
@ -601,6 +603,7 @@ class MessagesPresenterTest {
|
||||||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||||
|
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||||
): MessagesPresenter {
|
): MessagesPresenter {
|
||||||
val messageComposerPresenter = MessageComposerPresenter(
|
val messageComposerPresenter = MessageComposerPresenter(
|
||||||
appCoroutineScope = this,
|
appCoroutineScope = this,
|
||||||
|
|
@ -613,8 +616,12 @@ class MessagesPresenterTest {
|
||||||
analyticsService = analyticsService,
|
analyticsService = analyticsService,
|
||||||
messageComposerContext = MessageComposerContextImpl(),
|
messageComposerContext = MessageComposerContextImpl(),
|
||||||
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
||||||
|
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||||
)
|
override fun create(permission: String): PermissionsPresenter {
|
||||||
|
return permissionsPresenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
val timelinePresenter = TimelinePresenter(
|
val timelinePresenter = TimelinePresenter(
|
||||||
timelineItemsFactory = aTimelineItemsFactory(),
|
timelineItemsFactory = aTimelineItemsFactory(),
|
||||||
room = matrixRoom,
|
room = matrixRoom,
|
||||||
|
|
|
||||||
|
|
@ -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.MediaSender
|
||||||
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
import io.element.android.libraries.mediaupload.api.MediaUploadInfo
|
||||||
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
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.Message
|
||||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||||
|
|
@ -623,6 +625,11 @@ class MessageComposerPresenterTest {
|
||||||
analyticsService,
|
analyticsService,
|
||||||
MessageComposerContextImpl(),
|
MessageComposerContextImpl(),
|
||||||
TestRichTextEditorStateFactory(),
|
TestRichTextEditorStateFactory(),
|
||||||
|
permissionsPresenterFactory = object : PermissionsPresenter.Factory {
|
||||||
|
override fun create(permission: String): PermissionsPresenter {
|
||||||
|
return FakePermissionsPresenter().apply { setPermissionGranted() }
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue