Send failure verified user : display in ActionListView
This commit is contained in:
@@ -241,6 +241,9 @@ fun MessagesView(
|
||||
state.customReactionState.eventSink(CustomReactionEvents.ShowCustomReactionSheet(event))
|
||||
},
|
||||
onEmojiReactionClick = ::onEmojiReactionClick,
|
||||
onVerifiedUserSendFailureClick = { event ->
|
||||
|
||||
}
|
||||
)
|
||||
|
||||
CustomReactionBottomSheet(
|
||||
|
||||
@@ -36,6 +36,7 @@ import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.preferences.api.store.AppPreferencesStore
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -115,12 +116,14 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
isEventPinned = pinnedEventIds.contains(timelineItem.eventId),
|
||||
)
|
||||
|
||||
val displayEmojiReactions = usersEventPermissions.canSendReaction &&
|
||||
timelineItem.content.canReact()
|
||||
if (actions.isNotEmpty() || displayEmojiReactions) {
|
||||
val verifiedUserSendFailure = buildVerifiedUserSendFailure(timelineItem)
|
||||
val displayEmojiReactions = usersEventPermissions.canSendReaction && timelineItem.content.canReact()
|
||||
|
||||
if (actions.isNotEmpty() || displayEmojiReactions || verifiedUserSendFailure != ActionListState.VerifiedUserSendFailure.None) {
|
||||
target.value = ActionListState.Target.Success(
|
||||
event = timelineItem,
|
||||
displayEmojiReactions = displayEmojiReactions,
|
||||
verifiedUserSendFailure = verifiedUserSendFailure,
|
||||
actions = actions.toImmutableList()
|
||||
)
|
||||
} else {
|
||||
@@ -128,6 +131,32 @@ class DefaultActionListPresenter @AssistedInject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun buildVerifiedUserSendFailure(
|
||||
timelineItem: TimelineItem.Event,
|
||||
): ActionListState.VerifiedUserSendFailure {
|
||||
return when (val sendState = timelineItem.localSendState) {
|
||||
is LocalEventSendState.Failed.VerifiedUserHasUnsignedDevice -> {
|
||||
val userId = sendState.devices.keys.firstOrNull()
|
||||
if (userId == null) {
|
||||
ActionListState.VerifiedUserSendFailure.None
|
||||
} else {
|
||||
val displayName = room.userDisplayName(userId).getOrNull() ?: userId.value
|
||||
ActionListState.VerifiedUserSendFailure.UnsignedDevice(displayName)
|
||||
}
|
||||
}
|
||||
is LocalEventSendState.Failed.VerifiedUserChangedIdentity -> {
|
||||
val userId = sendState.users.firstOrNull()
|
||||
if (userId == null) {
|
||||
ActionListState.VerifiedUserSendFailure.None
|
||||
} else {
|
||||
val displayName = room.userDisplayName(userId).getOrNull() ?: userId.value
|
||||
ActionListState.VerifiedUserSendFailure.ChangedIdentity(displayName)
|
||||
}
|
||||
}
|
||||
else -> ActionListState.VerifiedUserSendFailure.None
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildActions(
|
||||
timelineItem: TimelineItem.Event,
|
||||
usersEventPermissions: UserEventPermissions,
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
|
||||
package io.element.android.features.messages.impl.actionlist
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
@@ -17,13 +20,31 @@ data class ActionListState(
|
||||
val target: Target,
|
||||
val eventSink: (ActionListEvents) -> Unit,
|
||||
) {
|
||||
@Immutable
|
||||
sealed interface Target {
|
||||
data object None : Target
|
||||
data class Loading(val event: TimelineItem.Event) : Target
|
||||
data class Success(
|
||||
val event: TimelineItem.Event,
|
||||
val displayEmojiReactions: Boolean,
|
||||
val verifiedUserSendFailure: VerifiedUserSendFailure,
|
||||
val actions: ImmutableList<TimelineItemAction>,
|
||||
) : Target
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface VerifiedUserSendFailure {
|
||||
data object None : VerifiedUserSendFailure
|
||||
data class UnsignedDevice(val displayName: String) : VerifiedUserSendFailure
|
||||
data class ChangedIdentity(val displayName: String) : VerifiedUserSendFailure
|
||||
|
||||
@Composable
|
||||
fun formatted(): String {
|
||||
return when (this) {
|
||||
is None -> ""
|
||||
is UnsignedDevice -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_unsigned_device, displayName)
|
||||
is ChangedIdentity -> stringResource(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, displayName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ import io.element.android.features.messages.impl.timeline.model.event.aTimelineI
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVideoContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemVoiceContent
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
@@ -35,6 +37,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -47,6 +50,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState,
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -56,6 +60,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -65,6 +70,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -74,6 +80,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -83,6 +90,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -92,6 +100,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
@@ -101,6 +110,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
),
|
||||
),
|
||||
@@ -110,6 +120,7 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
reactionsState = reactionsState
|
||||
),
|
||||
displayEmojiReactions = false,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemPollActionList(),
|
||||
),
|
||||
),
|
||||
@@ -120,6 +131,15 @@ open class ActionListStateProvider : PreviewParameterProvider<ActionListState> {
|
||||
messageShield = MessageShield.UnknownDevice(isCritical = true)
|
||||
),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.None,
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
anActionListState().copy(
|
||||
target = ActionListState.Target.Success(
|
||||
event = aTimelineItemEvent(),
|
||||
displayEmojiReactions = true,
|
||||
verifiedUserSendFailure = ActionListState.VerifiedUserSendFailure.UnsignedDevice(displayName = "Alice"),
|
||||
actions = aTimelineItemActionList(),
|
||||
)
|
||||
),
|
||||
|
||||
@@ -27,6 +27,7 @@ import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material.ripple.rememberRipple
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
import androidx.compose.material3.ListItemDefaults
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.rememberModalBottomSheetState
|
||||
import androidx.compose.runtime.Composable
|
||||
@@ -90,6 +91,7 @@ fun ActionListView(
|
||||
onSelectAction: (action: TimelineItemAction, TimelineItem.Event) -> Unit,
|
||||
onEmojiReactionClick: (String, TimelineItem.Event) -> Unit,
|
||||
onCustomReactionClick: (TimelineItem.Event) -> Unit,
|
||||
onVerifiedUserSendFailureClick: (TimelineItem.Event) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val sheetState = rememberModalBottomSheetState()
|
||||
@@ -126,6 +128,14 @@ fun ActionListView(
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
}
|
||||
|
||||
fun onVerifiedUserSendFailureClick() {
|
||||
if (targetItem == null) return
|
||||
sheetState.hide(coroutineScope) {
|
||||
state.eventSink(ActionListEvents.Clear)
|
||||
onVerifiedUserSendFailureClick(targetItem)
|
||||
}
|
||||
}
|
||||
|
||||
if (targetItem != null) {
|
||||
ModalBottomSheet(
|
||||
sheetState = sheetState,
|
||||
@@ -137,6 +147,7 @@ fun ActionListView(
|
||||
onActionClick = ::onItemActionClick,
|
||||
onEmojiReactionClick = ::onEmojiReactionClick,
|
||||
onCustomReactionClick = ::onCustomReactionClick,
|
||||
onVerifiedUserSendFailureClick = ::onVerifiedUserSendFailureClick,
|
||||
modifier = Modifier
|
||||
.navigationBarsPadding()
|
||||
.imePadding()
|
||||
@@ -151,6 +162,7 @@ private fun SheetContent(
|
||||
onActionClick: (TimelineItemAction) -> Unit,
|
||||
onEmojiReactionClick: (String) -> Unit,
|
||||
onCustomReactionClick: () -> Unit,
|
||||
onVerifiedUserSendFailureClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (val target = state.target) {
|
||||
@@ -184,6 +196,16 @@ private fun SheetContent(
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
if (target.verifiedUserSendFailure != ActionListState.VerifiedUserSendFailure.None) {
|
||||
item {
|
||||
VerifiedUserSendFailureView(
|
||||
sendFailure = target.verifiedUserSendFailure,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onVerifiedUserSendFailureClick
|
||||
)
|
||||
HorizontalDivider()
|
||||
}
|
||||
}
|
||||
if (target.displayEmojiReactions) {
|
||||
item {
|
||||
EmojiReactionsRow(
|
||||
@@ -338,6 +360,33 @@ private fun EmojiReactionsRow(
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun VerifiedUserSendFailureView(
|
||||
sendFailure: ActionListState.VerifiedUserSendFailure,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ListItem(
|
||||
modifier = modifier
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Error())),
|
||||
trailingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.ChevronRight())),
|
||||
headlineContent = {
|
||||
Text(
|
||||
text = sendFailure.formatted(),
|
||||
style = ElementTheme.typography.fontBodySmMedium,
|
||||
)
|
||||
},
|
||||
colors = ListItemDefaults.colors(
|
||||
containerColor = Color.Transparent,
|
||||
leadingIconColor = ElementTheme.colors.iconCriticalPrimary,
|
||||
trailingIconColor = ElementTheme.colors.iconPrimary,
|
||||
headlineColor = ElementTheme.colors.textCriticalPrimary,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EmojiButton(
|
||||
emoji: String,
|
||||
@@ -387,5 +436,6 @@ internal fun SheetContentPreview(
|
||||
onActionClick = {},
|
||||
onEmojiReactionClick = {},
|
||||
onCustomReactionClick = {},
|
||||
onVerifiedUserSendFailureClick = {},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -181,6 +181,7 @@ private fun PinnedMessagesListLoaded(
|
||||
onSelectAction = ::onActionSelected,
|
||||
onCustomReactionClick = {},
|
||||
onEmojiReactionClick = { _, _ -> },
|
||||
onVerifiedUserSendFailureClick = {}
|
||||
)
|
||||
LazyColumn(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
<string name="action_back">"Back"</string>
|
||||
<string name="action_call">"Call"</string>
|
||||
<string name="action_cancel">"Cancel"</string>
|
||||
<string name="action_cancel_for_now">"Cancel for now"</string>
|
||||
<string name="action_choose_photo">"Choose photo"</string>
|
||||
<string name="action_clear">"Clear"</string>
|
||||
<string name="action_close">"Close"</string>
|
||||
@@ -283,6 +284,12 @@ Reason: %1$s."</string>
|
||||
<string name="screen_pinned_timeline_screen_title_empty">"Pinned messages"</string>
|
||||
<string name="screen_reset_identity_confirmation_subtitle">"You\'re about to go to your %1$s account to reset your identity. Afterwards you\'ll be taken back to the app."</string>
|
||||
<string name="screen_reset_identity_confirmation_title">"Can\'t confirm? Go to your account to reset your identity."</string>
|
||||
<string name="screen_resolve_send_failure_changed_identity_primary_button_title">"Withdraw verification and send"</string>
|
||||
<string name="screen_resolve_send_failure_changed_identity_subtitle">"You can withdraw your verification and send this message anyway, or you can cancel for now and try again later after reverifying %1$s."</string>
|
||||
<string name="screen_resolve_send_failure_changed_identity_title">"Your message was not sent because %1$s’s verified identity has changed"</string>
|
||||
<string name="screen_resolve_send_failure_unsigned_device_primary_button_title">"Send message anyway"</string>
|
||||
<string name="screen_resolve_send_failure_unsigned_device_subtitle">"%1$s is using one or more unverified devices. You can send the message anyway, or you can cancel for now and try again later after %2$s has verified all their devices."</string>
|
||||
<string name="screen_resolve_send_failure_unsigned_device_title">"Your message was not sent because %1$s has not verified one or more devices"</string>
|
||||
<string name="screen_room_details_pinned_events_row_title">"Pinned messages"</string>
|
||||
<string name="screen_room_error_failed_processing_media">"Failed processing media to upload, please try again."</string>
|
||||
<string name="screen_room_error_failed_retrieving_user_details">"Could not retrieve user details"</string>
|
||||
@@ -304,6 +311,8 @@ Reason: %1$s."</string>
|
||||
<string name="screen_share_open_google_maps">"Open in Google Maps"</string>
|
||||
<string name="screen_share_open_osm_maps">"Open in OpenStreetMap"</string>
|
||||
<string name="screen_share_this_location_action">"Share this location"</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_changed_identity">"Message not sent because %1$s’s verified identity has changed."</string>
|
||||
<string name="screen_timeline_item_menu_send_failure_unsigned_device">"Message not sent because %1$s has not verified one or more devices."</string>
|
||||
<string name="screen_view_location_title">"Location"</string>
|
||||
<string name="settings_version_number">"Version: %1$s (%2$s)"</string>
|
||||
<string name="test_language_identifier">"en"</string>
|
||||
|
||||
Reference in New Issue
Block a user