From a4c6e6c281eb3a75d621d289b4d83fa45035a01a Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Apr 2024 16:08:08 +0200 Subject: [PATCH] Add mapping on FocusEventException. Extract FocusRequestState to its own file and add preview. --- .../impl/timeline/TimelineController.kt | 26 +++---- .../messages/impl/timeline/TimelineView.kt | 24 +------ .../focus/FocusRequestStateProvider.kt | 45 +++++++++++++ .../timeline/focus/FocusRequestStateView.kt | 67 +++++++++++++++++++ .../libraries/matrix/api/room/MatrixRoom.kt | 2 +- .../api/room/errors/FocusEventException.kt | 34 ++++++++++ .../matrix/impl/room/FocusEventException.kt | 42 ++++++++++++ .../matrix/impl/room/RustMatrixRoom.kt | 21 +++--- .../matrix/test/room/FakeMatrixRoom.kt | 4 +- .../src/main/res/values/localazy.xml | 1 + 10 files changed, 219 insertions(+), 47 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt create mode 100644 libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt index 2d3a4fbf7c..bc12524045 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineController.kt @@ -24,6 +24,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.Timeline import io.element.android.libraries.matrix.api.timeline.TimelineProvider +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.SupervisorJob @@ -40,7 +41,6 @@ import kotlinx.coroutines.flow.stateIn import java.io.Closeable import java.util.Optional import javax.inject.Inject -import kotlin.coroutines.cancellation.CancellationException /** * This controller is responsible of using the right timeline to display messages and make associated actions. @@ -72,20 +72,20 @@ class TimelineController @Inject constructor( } suspend fun focusOnEvent(eventId: EventId): Result { - return try { - val newDetachedTimeline = room.timelineFocusedOnEvent(eventId) - detachedTimeline.getAndUpdate { current -> - if (current.isPresent) { - current.get().close() + return room.timelineFocusedOnEvent(eventId) + .onFailure { + if (it is CancellationException) { + throw it + } + } + .map { newDetachedTimeline -> + detachedTimeline.getAndUpdate { current -> + if (current.isPresent) { + current.get().close() + } + Optional.of(newDetachedTimeline) } - Optional.of(newDetachedTimeline) } - Result.success(Unit) - } catch (cancellation: CancellationException) { - throw cancellation - } catch (exception: Exception) { - Result.failure(exception) - } } /** diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt index 039062dc87..7f76dfe730 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineView.kt @@ -57,6 +57,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.messages.impl.timeline.components.TimelineItemRow import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.di.aFakeTimelineItemPresenterFactories +import io.element.android.features.messages.impl.timeline.focus.FocusRequestStateView import io.element.android.features.messages.impl.timeline.model.NewEventState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent @@ -64,8 +65,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.features.messages.impl.typing.TypingNotificationView import io.element.android.features.messages.impl.typing.aTypingNotificationState -import io.element.android.libraries.designsystem.components.ProgressDialog -import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.theme.components.FloatingActionButton @@ -176,27 +175,6 @@ fun TimelineView( } } -@Composable -private fun FocusRequestStateView( - focusRequestState: FocusRequestState, - onClearFocusRequestState: () -> Unit, - modifier: Modifier = Modifier, -) { - when (focusRequestState) { - is FocusRequestState.Failure -> { - ErrorDialog( - content = stringResource(id = CommonStrings.common_failed), - onDismiss = onClearFocusRequestState, - modifier = modifier, - ) - } - FocusRequestState.Fetching -> { - ProgressDialog(modifier = modifier, onDismissRequest = onClearFocusRequestState) - } - else -> Unit - } -} - @Composable private fun BoxScope.TimelineScrollHelper( hasAnyEvent: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt new file mode 100644 index 0000000000..cb5b4a10f2 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateProvider.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.focus + +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import io.element.android.features.messages.impl.timeline.FocusRequestState +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.errors.FocusEventException + +open class FocusRequestStateProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + FocusRequestState.Fetching, + FocusRequestState.Failure( + FocusEventException.EventNotFound( + eventId = EventId("\$anEventId"), + ) + ), + FocusRequestState.Failure( + FocusEventException.InvalidEventId( + eventId = "invalid", + err = "An error" + ) + ), + FocusRequestState.Failure( + FocusEventException.Other( + msg = "An error" + ) + ), + ) +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt new file mode 100644 index 0000000000..4a4381d269 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/focus/FocusRequestStateView.kt @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.messages.impl.timeline.focus + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.PreviewParameter +import io.element.android.features.messages.impl.timeline.FocusRequestState +import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.dialogs.ErrorDialog +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight +import io.element.android.libraries.matrix.api.room.errors.FocusEventException +import io.element.android.libraries.ui.strings.CommonStrings + +@Composable +fun FocusRequestStateView( + focusRequestState: FocusRequestState, + onClearFocusRequestState: () -> Unit, + modifier: Modifier = Modifier, +) { + when (focusRequestState) { + is FocusRequestState.Failure -> { + val errorMessage = when (focusRequestState.throwable) { + is FocusEventException.EventNotFound, + is FocusEventException.InvalidEventId -> stringResource(id = CommonStrings.error_message_not_found) + is FocusEventException.Other -> stringResource(id = CommonStrings.error_unknown) + else -> stringResource(id = CommonStrings.error_unknown) + } + ErrorDialog( + content = errorMessage, + onDismiss = onClearFocusRequestState, + modifier = modifier, + ) + } + FocusRequestState.Fetching -> { + ProgressDialog(modifier = modifier, onDismissRequest = onClearFocusRequestState) + } + else -> Unit + } +} + +@PreviewsDayNight +@Composable +internal fun FocusRequestStateViewPreview( + @PreviewParameter(FocusRequestStateProvider::class) state: FocusRequestState, +) = ElementPreview { + FocusRequestStateView( + focusRequestState = state, + onClearFocusRequestState = {}, + ) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index ad27a0eb36..86e943fea9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -100,7 +100,7 @@ interface MatrixRoom : Closeable { val liveTimeline: Timeline - suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline + suspend fun timelineFocusedOnEvent(eventId: EventId): Result fun destroy() diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt new file mode 100644 index 0000000000..51859db1fb --- /dev/null +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/errors/FocusEventException.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.api.room.errors + +import io.element.android.libraries.matrix.api.core.EventId + +sealed class FocusEventException : Exception() { + data class InvalidEventId( + val eventId: String, + val err: String + ) : FocusEventException() + + data class EventNotFound( + val eventId: EventId + ) : FocusEventException() + + data class Other( + val msg: String + ) : FocusEventException() +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt new file mode 100644 index 0000000000..c2a4456f55 --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/FocusEventException.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.matrix.impl.room + +import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.room.errors.FocusEventException +import org.matrix.rustcomponents.sdk.FocusEventException as RustFocusEventException + +fun Throwable.toFocusEventException(): Throwable { + return when (this) { + is RustFocusEventException -> { + when (this) { + is RustFocusEventException.InvalidEventId -> { + FocusEventException.InvalidEventId(eventId, err) + } + is RustFocusEventException.EventNotFound -> { + FocusEventException.EventNotFound(EventId(eventId)) + } + is RustFocusEventException.Other -> { + FocusEventException.Other(msg) + } + } + } + else -> { + this + } + } +} diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 0043d52545..e0f992a367 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.impl.room import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.core.coroutine.childScope +import io.element.android.libraries.core.extensions.mapFailure import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.core.RoomAlias @@ -169,13 +170,17 @@ class RustMatrixRoom( override suspend fun unsubscribeFromSync() = roomSyncSubscriber.unsubscribe(roomId) - override suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline { - return innerRoom.timelineFocusedOnEvent( - eventId = eventId.value, - numContextEvents = 50u, - internalIdPrefix = "focus_$eventId", - ).let { inner -> - createTimeline(inner, isLive = false) + override suspend fun timelineFocusedOnEvent(eventId: EventId): Result { + return runCatching { + innerRoom.timelineFocusedOnEvent( + eventId = eventId.value, + numContextEvents = 50u, + internalIdPrefix = "focus_$eventId", + ).let { inner -> + createTimeline(inner, isLive = false) + } + }.mapFailure { + it.toFocusEventException() } } @@ -442,7 +447,7 @@ class RustMatrixRoom( } override suspend fun toggleReaction(emoji: String, eventId: EventId): Result { - return liveTimeline.toggleReaction(emoji, eventId) + return liveTimeline.toggleReaction(emoji, eventId) } override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 17f9358df7..d3d89a0788 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -215,8 +215,8 @@ class FakeMatrixRoom( override val syncUpdateFlow: StateFlow = MutableStateFlow(0L) - override suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline { - return FakeTimeline() + override suspend fun timelineFocusedOnEvent(eventId: EventId): Result { + return Result.success(FakeTimeline()) } override suspend fun subscribeToSync() = Unit diff --git a/libraries/ui-strings/src/main/res/values/localazy.xml b/libraries/ui-strings/src/main/res/values/localazy.xml index 832a3df104..f25969332e 100644 --- a/libraries/ui-strings/src/main/res/values/localazy.xml +++ b/libraries/ui-strings/src/main/res/values/localazy.xml @@ -240,6 +240,7 @@ "Failed loading messages" "%1$s could not access your location. Please try again later." "Failed to upload your voice message." + "Message not found" "%1$s does not have permission to access your location. You can enable access in Settings." "%1$s does not have permission to access your location. Enable access below." "%1$s does not have permission to access your microphone. Enable access to record a voice message."