Merge pull request #1400 from vector-im/feature/bma/fixCrashPhoto
Fix crash when user wants to use the camera
This commit is contained in:
commit
5119ca33dd
47 changed files with 597 additions and 143 deletions
|
|
@ -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.RequestPermissions)
|
||||
}
|
||||
}
|
||||
MessageComposerEvents.PickAttachmentSource.VideoFromCamera -> localCoroutineScope.launch {
|
||||
showAttachmentSourcePicker = false
|
||||
cameraVideoPicker.launch()
|
||||
if (cameraPermissionState.permissionGranted) {
|
||||
cameraVideoPicker.launch()
|
||||
} else {
|
||||
pendingEvent = event
|
||||
cameraPermissionState.eventSink(PermissionsEvents.RequestPermissions)
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,6 +70,9 @@ 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.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
|
|
@ -601,6 +604,7 @@ class MessagesPresenterTest {
|
|||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
): MessagesPresenter {
|
||||
val messageComposerPresenter = MessageComposerPresenter(
|
||||
appCoroutineScope = this,
|
||||
|
|
@ -613,8 +617,8 @@ class MessagesPresenterTest {
|
|||
analyticsService = analyticsService,
|
||||
messageComposerContext = MessageComposerContextImpl(),
|
||||
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
||||
|
||||
)
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter),
|
||||
)
|
||||
val timelinePresenter = TimelinePresenter(
|
||||
timelineItemsFactory = aTimelineItemsFactory(),
|
||||
room = matrixRoom,
|
||||
|
|
|
|||
|
|
@ -54,6 +54,9 @@ 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.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.textcomposer.Message
|
||||
import io.element.android.libraries.textcomposer.MessageComposerMode
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
|
|
@ -67,6 +70,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
import java.io.File
|
||||
|
||||
@Suppress("LargeClass")
|
||||
class MessageComposerPresenterTest {
|
||||
|
||||
@get:Rule
|
||||
|
|
@ -487,18 +491,84 @@ class MessageComposerPresenterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `present - Take photo`() = runTest {
|
||||
fun `present - create poll`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||
val attachmentOpenState = awaitItem()
|
||||
assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Poll)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showAttachmentSourcePicker).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - share location`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createPresenter(this, room = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.AddAttachment)
|
||||
val attachmentOpenState = awaitItem()
|
||||
assertThat(attachmentOpenState.showAttachmentSourcePicker).isTrue()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.Location)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showAttachmentSourcePicker).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Take photo`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
room = room,
|
||||
permissionPresenter = permissionPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera)
|
||||
val previewingState = awaitItem()
|
||||
assertThat(previewingState.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(previewingState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Take photo with permission request`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
room = room,
|
||||
permissionPresenter = permissionPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.PhotoFromCamera)
|
||||
val permissionState = awaitItem()
|
||||
assertThat(permissionState.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(permissionState.attachmentsState).isInstanceOf(AttachmentsState.None::class.java)
|
||||
permissionPresenter.setPermissionGranted()
|
||||
skipItems(1)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
|
@ -506,16 +576,47 @@ class MessageComposerPresenterTest {
|
|||
@Test
|
||||
fun `present - Record video`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val presenter = createPresenter(this, room = room)
|
||||
val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() }
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
room = room,
|
||||
permissionPresenter = permissionPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera)
|
||||
val previewingState = awaitItem()
|
||||
assertThat(previewingState.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(previewingState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - Record video with permission request`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
val permissionPresenter = FakePermissionsPresenter()
|
||||
val presenter = createPresenter(
|
||||
this,
|
||||
room = room,
|
||||
permissionPresenter = permissionPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessageComposerEvents.PickAttachmentSource.VideoFromCamera)
|
||||
val permissionState = awaitItem()
|
||||
assertThat(permissionState.showAttachmentSourcePicker).isFalse()
|
||||
assertThat(permissionState.attachmentsState).isInstanceOf(AttachmentsState.None::class.java)
|
||||
permissionPresenter.setPermissionGranted()
|
||||
skipItems(1)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.attachmentsState).isInstanceOf(AttachmentsState.Previewing::class.java)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -612,6 +713,7 @@ class MessageComposerPresenterTest {
|
|||
featureFlagService: FeatureFlagService = this.featureFlagService,
|
||||
mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor,
|
||||
snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher,
|
||||
permissionPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
) = MessageComposerPresenter(
|
||||
coroutineScope,
|
||||
room,
|
||||
|
|
@ -623,6 +725,7 @@ class MessageComposerPresenterTest {
|
|||
analyticsService,
|
||||
MessageComposerContextImpl(),
|
||||
TestRichTextEditorStateFactory(),
|
||||
permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionPresenter),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue