Add mapping on FocusEventException.
Extract FocusRequestState to its own file and add preview.
This commit is contained in:
parent
03040e5507
commit
7b55b0e327
10 changed files with 219 additions and 47 deletions
|
|
@ -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.MatrixTimelineItem
|
||||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||||
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.SupervisorJob
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
|
@ -40,7 +41,6 @@ import kotlinx.coroutines.flow.stateIn
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import javax.inject.Inject
|
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.
|
* 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<Unit> {
|
suspend fun focusOnEvent(eventId: EventId): Result<Unit> {
|
||||||
return try {
|
return room.timelineFocusedOnEvent(eventId)
|
||||||
val newDetachedTimeline = room.timelineFocusedOnEvent(eventId)
|
.onFailure {
|
||||||
detachedTimeline.getAndUpdate { current ->
|
if (it is CancellationException) {
|
||||||
if (current.isPresent) {
|
throw it
|
||||||
current.get().close()
|
}
|
||||||
|
}
|
||||||
|
.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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -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.components.TimelineItemRow
|
||||||
import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPresenterFactories
|
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.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.NewEventState
|
||||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent
|
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.TypingNotificationState
|
||||||
import io.element.android.features.messages.impl.typing.TypingNotificationView
|
import io.element.android.features.messages.impl.typing.TypingNotificationView
|
||||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
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.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.FloatingActionButton
|
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
|
@Composable
|
||||||
private fun BoxScope.TimelineScrollHelper(
|
private fun BoxScope.TimelineScrollHelper(
|
||||||
hasAnyEvent: Boolean,
|
hasAnyEvent: Boolean,
|
||||||
|
|
|
||||||
|
|
@ -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<FocusRequestState> {
|
||||||
|
override val values: Sequence<FocusRequestState>
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -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 = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -100,7 +100,7 @@ interface MatrixRoom : Closeable {
|
||||||
|
|
||||||
val liveTimeline: Timeline
|
val liveTimeline: Timeline
|
||||||
|
|
||||||
suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline
|
suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline>
|
||||||
|
|
||||||
fun destroy()
|
fun destroy()
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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.CoroutineDispatchers
|
||||||
import io.element.android.libraries.core.coroutine.childScope
|
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.EventId
|
||||||
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
import io.element.android.libraries.matrix.api.core.ProgressCallback
|
||||||
import io.element.android.libraries.matrix.api.core.RoomAlias
|
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 unsubscribeFromSync() = roomSyncSubscriber.unsubscribe(roomId)
|
||||||
|
|
||||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline {
|
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> {
|
||||||
return innerRoom.timelineFocusedOnEvent(
|
return runCatching {
|
||||||
eventId = eventId.value,
|
innerRoom.timelineFocusedOnEvent(
|
||||||
numContextEvents = 50u,
|
eventId = eventId.value,
|
||||||
internalIdPrefix = "focus_$eventId",
|
numContextEvents = 50u,
|
||||||
).let { inner ->
|
internalIdPrefix = "focus_$eventId",
|
||||||
createTimeline(inner, isLive = false)
|
).let { inner ->
|
||||||
|
createTimeline(inner, isLive = false)
|
||||||
|
}
|
||||||
|
}.mapFailure {
|
||||||
|
it.toFocusEventException()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -442,7 +447,7 @@ class RustMatrixRoom(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
|
override suspend fun toggleReaction(emoji: String, eventId: EventId): Result<Unit> {
|
||||||
return liveTimeline.toggleReaction(emoji, eventId)
|
return liveTimeline.toggleReaction(emoji, eventId)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
|
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> {
|
||||||
|
|
|
||||||
|
|
@ -215,8 +215,8 @@ class FakeMatrixRoom(
|
||||||
|
|
||||||
override val syncUpdateFlow: StateFlow<Long> = MutableStateFlow(0L)
|
override val syncUpdateFlow: StateFlow<Long> = MutableStateFlow(0L)
|
||||||
|
|
||||||
override suspend fun timelineFocusedOnEvent(eventId: EventId): Timeline {
|
override suspend fun timelineFocusedOnEvent(eventId: EventId): Result<Timeline> {
|
||||||
return FakeTimeline()
|
return Result.success(FakeTimeline())
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun subscribeToSync() = Unit
|
override suspend fun subscribeToSync() = Unit
|
||||||
|
|
|
||||||
|
|
@ -240,6 +240,7 @@
|
||||||
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
<string name="error_failed_loading_messages">"Failed loading messages"</string>
|
||||||
<string name="error_failed_locating_user">"%1$s could not access your location. Please try again later."</string>
|
<string name="error_failed_locating_user">"%1$s could not access your location. Please try again later."</string>
|
||||||
<string name="error_failed_uploading_voice_message">"Failed to upload your voice message."</string>
|
<string name="error_failed_uploading_voice_message">"Failed to upload your voice message."</string>
|
||||||
|
<string name="error_message_not_found">"Message not found"</string>
|
||||||
<string name="error_missing_location_auth_android">"%1$s does not have permission to access your location. You can enable access in Settings."</string>
|
<string name="error_missing_location_auth_android">"%1$s does not have permission to access your location. You can enable access in Settings."</string>
|
||||||
<string name="error_missing_location_rationale_android">"%1$s does not have permission to access your location. Enable access below."</string>
|
<string name="error_missing_location_rationale_android">"%1$s does not have permission to access your location. Enable access below."</string>
|
||||||
<string name="error_missing_microphone_voice_rationale_android">"%1$s does not have permission to access your microphone. Enable access to record a voice message."</string>
|
<string name="error_missing_microphone_voice_rationale_android">"%1$s does not have permission to access your microphone. Enable access to record a voice message."</string>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue