Merge pull request #4862 from element-hq/feature/fga/room-version-upgrade
Feature : room version upgrade
This commit is contained in:
commit
2bdb3f8a1f
85 changed files with 494 additions and 138 deletions
|
|
@ -9,6 +9,7 @@ package io.element.android.features.messages.impl
|
|||
|
||||
import io.element.android.features.messages.impl.attachments.Attachment
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
|
@ -19,4 +20,5 @@ interface MessagesNavigator {
|
|||
fun onReportContentClick(eventId: EventId, senderId: UserId)
|
||||
fun onEditPollClick(eventId: EventId)
|
||||
fun onPreviewAttachment(attachments: ImmutableList<Attachment>)
|
||||
fun onNavigateToRoom(roomId: RoomId)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,18 +49,21 @@ import io.element.android.libraries.architecture.NodeInputs
|
|||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.core.bool.orFalse
|
||||
import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
||||
import io.element.android.libraries.di.ApplicationContext
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.di.annotations.SessionCoroutineScope
|
||||
import io.element.android.libraries.matrix.api.analytics.toAnalyticsViewRoom
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.core.toRoomIdOrAlias
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkData
|
||||
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
|
||||
import io.element.android.libraries.matrix.api.room.BaseRoom
|
||||
import io.element.android.libraries.matrix.api.room.alias.matches
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.mediaplayer.api.MediaPlayer
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -70,6 +73,7 @@ import kotlinx.coroutines.launch
|
|||
class MessagesNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
@ApplicationContext private val context: Context,
|
||||
@SessionCoroutineScope
|
||||
private val sessionCoroutineScope: CoroutineScope,
|
||||
private val room: BaseRoom,
|
||||
|
|
@ -157,7 +161,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.onUserDataClick(permalink.userId) }
|
||||
}
|
||||
is PermalinkData.RoomLink -> {
|
||||
handleRoomLinkClick(activity, permalink, eventSink)
|
||||
handleRoomLinkClick(permalink, eventSink)
|
||||
}
|
||||
is PermalinkData.FallbackLink -> {
|
||||
if (customTab) {
|
||||
|
|
@ -173,7 +177,6 @@ class MessagesNode @AssistedInject constructor(
|
|||
}
|
||||
|
||||
private fun handleRoomLinkClick(
|
||||
context: Context,
|
||||
roomLink: PermalinkData.RoomLink,
|
||||
eventSink: (TimelineEvents) -> Unit,
|
||||
) {
|
||||
|
|
@ -183,7 +186,7 @@ class MessagesNode @AssistedInject constructor(
|
|||
eventSink(TimelineEvents.FocusOnEvent(eventId))
|
||||
} else {
|
||||
// Click on the same room, ignore
|
||||
context.toast("Already viewing this room!")
|
||||
displaySameRoomToast()
|
||||
}
|
||||
} else {
|
||||
callbacks.forEach { it.onPermalinkClick(roomLink) }
|
||||
|
|
@ -210,6 +213,15 @@ class MessagesNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.onPreviewAttachments(attachments) }
|
||||
}
|
||||
|
||||
override fun onNavigateToRoom(roomId: RoomId) {
|
||||
if (roomId == room.roomId) {
|
||||
displaySameRoomToast()
|
||||
} else {
|
||||
val permalinkData = PermalinkData.RoomLink(roomId.toRoomIdOrAlias())
|
||||
callbacks.forEach { it.onPermalinkClick(permalinkData) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun onViewAllPinnedMessagesClick() {
|
||||
callbacks.forEach { it.onViewAllPinnedEvents() }
|
||||
}
|
||||
|
|
@ -230,6 +242,10 @@ class MessagesNode @AssistedInject constructor(
|
|||
callbacks.forEach { it.onViewKnockRequests() }
|
||||
}
|
||||
|
||||
private fun displaySameRoomToast() {
|
||||
context.toast(CommonStrings.screen_room_permalink_same_room_android)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val activity = requireNotNull(LocalActivity.current)
|
||||
|
|
@ -255,13 +271,13 @@ class MessagesNode @AssistedInject constructor(
|
|||
onCreatePollClick = this::onCreatePollClick,
|
||||
onJoinCallClick = this::onJoinCallClick,
|
||||
onViewAllPinnedMessagesClick = this::onViewAllPinnedMessagesClick,
|
||||
modifier = modifier,
|
||||
knockRequestsBannerView = {
|
||||
knockRequestsBannerRenderer.View(
|
||||
modifier = Modifier,
|
||||
onViewRequestsClick = this::onViewKnockRequestsClick
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
roomMemberModerationRenderer.Render(
|
||||
state = state.roomMemberModerationState,
|
||||
|
|
|
|||
|
|
@ -270,6 +270,7 @@ class MessagesPresenter @AssistedInject constructor(
|
|||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||
dmUserVerificationState = dmUserVerificationState,
|
||||
roomMemberModerationState = roomMemberModerationState,
|
||||
successorRoom = roomInfo.successorRoom,
|
||||
eventSink = { handleEvents(it) }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarMessage
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
|
|
@ -56,5 +57,6 @@ data class MessagesState(
|
|||
val pinnedMessagesBannerState: PinnedMessagesBannerState,
|
||||
val dmUserVerificationState: IdentityState?,
|
||||
val roomMemberModerationState: RoomMemberModerationState,
|
||||
val successorRoom: SuccessorRoom?,
|
||||
val eventSink: (MessagesEvents) -> Unit
|
||||
)
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
|||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.textcomposer.model.aTextEditorStateRich
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -87,6 +88,7 @@ open class MessagesStateProvider : PreviewParameterProvider<MessagesState> {
|
|||
),
|
||||
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.Verified),
|
||||
aMessagesState(roomName = AsyncData.Success("A DM with a very looong name"), dmUserVerificationState = IdentityState.VerificationViolation),
|
||||
aMessagesState(successorRoom = SuccessorRoom(RoomId("!id:domain"), null)),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -119,6 +121,7 @@ fun aMessagesState(
|
|||
pinnedMessagesBannerState: PinnedMessagesBannerState = aLoadedPinnedMessagesBannerState(),
|
||||
dmUserVerificationState: IdentityState? = null,
|
||||
roomMemberModerationState: RoomMemberModerationState = aRoomMemberModerationState(),
|
||||
successorRoom: SuccessorRoom? = null,
|
||||
eventSink: (MessagesEvents) -> Unit = {},
|
||||
) = MessagesState(
|
||||
roomId = RoomId("!id:domain"),
|
||||
|
|
@ -147,6 +150,7 @@ fun aMessagesState(
|
|||
pinnedMessagesBannerState = pinnedMessagesBannerState,
|
||||
dmUserVerificationState = dmUserVerificationState,
|
||||
roomMemberModerationState = roomMemberModerationState,
|
||||
successorRoom = successorRoom,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -82,6 +82,7 @@ import io.element.android.features.messages.impl.voicemessages.composer.VoiceMes
|
|||
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
|
||||
import io.element.android.features.roomcall.api.RoomCallState
|
||||
import io.element.android.libraries.androidutils.ui.hideKeyboard
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitlePlaceholdersRowMolecule
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
|
|
@ -90,6 +91,7 @@ import io.element.android.libraries.designsystem.components.button.BackButton
|
|||
import io.element.android.libraries.designsystem.components.dialogs.ConfirmationDialog
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.BottomSheetDragHandle
|
||||
import io.element.android.libraries.designsystem.theme.components.Icon
|
||||
import io.element.android.libraries.designsystem.theme.components.Scaffold
|
||||
|
|
@ -101,8 +103,10 @@ import io.element.android.libraries.designsystem.utils.OnLifecycleEvent
|
|||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarHost
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.rememberSnackbarHostState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
|
||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -211,8 +215,8 @@ fun MessagesView(
|
|||
onMessageLongClick = ::onMessageLongClick,
|
||||
onUserDataClick = {
|
||||
hidingKeyboard {
|
||||
state.eventSink(MessagesEvents.OnUserClicked(it))
|
||||
}
|
||||
state.eventSink(MessagesEvents.OnUserClicked(it))
|
||||
}
|
||||
},
|
||||
onLinkClick = { link, customTab ->
|
||||
if (customTab) {
|
||||
|
|
@ -410,6 +414,9 @@ private fun MessagesViewContent(
|
|||
MessagesViewComposerBottomSheetContents(
|
||||
subcomposing = subcomposing,
|
||||
state = state,
|
||||
onRoomSuccessorClick = { roomId ->
|
||||
state.timelineState.eventSink(TimelineEvents.NavigateToRoom(roomId = roomId))
|
||||
},
|
||||
onLinkClick = { url, customTab -> onLinkClick(Link(url), customTab) },
|
||||
)
|
||||
},
|
||||
|
|
@ -424,52 +431,59 @@ private fun MessagesViewContent(
|
|||
private fun MessagesViewComposerBottomSheetContents(
|
||||
subcomposing: Boolean,
|
||||
state: MessagesState,
|
||||
onRoomSuccessorClick: (RoomId) -> Unit,
|
||||
onLinkClick: (String, Boolean) -> Unit,
|
||||
) {
|
||||
if (state.userEventPermissions.canSendMessage) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SuggestionsPickerView(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 230.dp)
|
||||
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
|
||||
.nestedScroll(object : NestedScrollConnection {
|
||||
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||
return available
|
||||
}
|
||||
}),
|
||||
roomId = state.roomId,
|
||||
roomName = state.roomName.dataOrNull(),
|
||||
roomAvatarData = state.roomAvatar.dataOrNull(),
|
||||
suggestions = state.composerState.suggestions,
|
||||
onSelectSuggestion = {
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
|
||||
when {
|
||||
state.successorRoom != null -> {
|
||||
SuccessorRoomBanner(roomSuccessor = state.successorRoom, onRoomSuccessorClick = onRoomSuccessorClick)
|
||||
}
|
||||
state.userEventPermissions.canSendMessage -> {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
SuggestionsPickerView(
|
||||
modifier = Modifier
|
||||
.heightIn(max = 230.dp)
|
||||
// Consume all scrolling, preventing the bottom sheet from being dragged when interacting with the list of suggestions
|
||||
.nestedScroll(object : NestedScrollConnection {
|
||||
override fun onPostScroll(consumed: Offset, available: Offset, source: NestedScrollSource): Offset {
|
||||
return available
|
||||
}
|
||||
}),
|
||||
roomId = state.roomId,
|
||||
roomName = state.roomName.dataOrNull(),
|
||||
roomAvatarData = state.roomAvatar.dataOrNull(),
|
||||
suggestions = state.composerState.suggestions,
|
||||
onSelectSuggestion = {
|
||||
state.composerState.eventSink(MessageComposerEvents.InsertSuggestion(it))
|
||||
}
|
||||
)
|
||||
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
||||
if (state.composerState.suggestions.isEmpty() &&
|
||||
state.composerState.textEditorState is TextEditorState.Markdown) {
|
||||
IdentityChangeStateView(
|
||||
state = state.identityChangeState,
|
||||
onLinkClick = onLinkClick,
|
||||
)
|
||||
}
|
||||
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
|
||||
it.identityState == IdentityState.VerificationViolation
|
||||
}
|
||||
if (verificationViolation != null) {
|
||||
DisabledComposerView(modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
MessageComposerView(
|
||||
state = state.composerState,
|
||||
voiceMessageState = state.voiceMessageComposerState,
|
||||
subcomposing = subcomposing,
|
||||
enableVoiceMessages = state.enableVoiceMessages,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
)
|
||||
// Do not show the identity change if user is composing a Rich message or is seeing suggestion(s).
|
||||
if (state.composerState.suggestions.isEmpty() &&
|
||||
state.composerState.textEditorState is TextEditorState.Markdown) {
|
||||
IdentityChangeStateView(
|
||||
state = state.identityChangeState,
|
||||
onLinkClick = onLinkClick,
|
||||
)
|
||||
}
|
||||
val verificationViolation = state.identityChangeState.roomMemberIdentityStateChanges.firstOrNull {
|
||||
it.identityState == IdentityState.VerificationViolation
|
||||
}
|
||||
if (verificationViolation != null) {
|
||||
DisabledComposerView(modifier = Modifier.fillMaxWidth())
|
||||
} else {
|
||||
MessageComposerView(
|
||||
state = state.composerState,
|
||||
voiceMessageState = state.voiceMessageComposerState,
|
||||
subcomposing = subcomposing,
|
||||
enableVoiceMessages = state.enableVoiceMessages,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
CantSendMessageBanner()
|
||||
else -> {
|
||||
CantSendMessageBanner()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -572,9 +586,9 @@ private fun RoomAvatarAndNameRow(
|
|||
private fun CantSendMessageBanner() {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.background(ElementTheme.colors.bgSubtleSecondary)
|
||||
.padding(16.dp),
|
||||
.fillMaxWidth()
|
||||
.background(ElementTheme.colors.bgSubtleSecondary)
|
||||
.padding(16.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
|
|
@ -588,6 +602,22 @@ private fun CantSendMessageBanner() {
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SuccessorRoomBanner(
|
||||
roomSuccessor: SuccessorRoom,
|
||||
onRoomSuccessorClick: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ComposerAlertMolecule(
|
||||
avatar = null,
|
||||
content = stringResource(R.string.screen_room_timeline_tombstoned_room_message).toAnnotatedString(),
|
||||
onSubmitClick = { onRoomSuccessorClick(roomSuccessor.roomId) },
|
||||
modifier = modifier,
|
||||
isCritical = false,
|
||||
submitText = stringResource(R.string.screen_room_timeline_tombstoned_room_action)
|
||||
)
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun MessagesViewPreview(@PreviewParameter(MessagesStateProvider::class) state: MessagesState) = ElementPreview {
|
||||
|
|
|
|||
|
|
@ -106,7 +106,8 @@ class PinnedMessagesListPresenter @AssistedInject constructor(
|
|||
renderTypingNotifications = false,
|
||||
typingMembers = persistentListOf(),
|
||||
reserveSpace = false,
|
||||
)
|
||||
),
|
||||
predecessorRoom = room.predecessorRoom(),
|
||||
)
|
||||
}
|
||||
val timelineProtectionState = timelineProtectionPresenter.present()
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ package io.element.android.features.messages.impl.timeline
|
|||
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import kotlin.time.Duration
|
||||
|
|
@ -30,6 +31,7 @@ sealed interface TimelineEvents {
|
|||
data class ComputeVerifiedUserSendFailure(val event: TimelineItem.Event) : EventFromTimelineItem
|
||||
data class ShowShieldDialog(val messageShield: MessageShield) : EventFromTimelineItem
|
||||
data class LoadMore(val direction: Timeline.PaginationDirection) : EventFromTimelineItem
|
||||
data class NavigateToRoom(val roomId: RoomId) : EventFromTimelineItem
|
||||
|
||||
/**
|
||||
* Events coming from a poll item.
|
||||
|
|
|
|||
|
|
@ -178,6 +178,9 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
is TimelineEvents.ComputeVerifiedUserSendFailure -> {
|
||||
resolveVerifiedUserSendFailureState.eventSink(ResolveVerifiedUserSendFailureEvents.ComputeForMessage(event.event))
|
||||
}
|
||||
is TimelineEvents.NavigateToRoom -> {
|
||||
navigator.onNavigateToRoom(event.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -257,8 +260,9 @@ class TimelinePresenter @AssistedInject constructor(
|
|||
userHasPermissionToSendMessage = userHasPermissionToSendMessage,
|
||||
userHasPermissionToSendReaction = userHasPermissionToSendReaction,
|
||||
roomCallState = roomCallState,
|
||||
pinnedEventIds = roomInfo.pinnedEventIds.orEmpty(),
|
||||
pinnedEventIds = roomInfo.pinnedEventIds,
|
||||
typingNotificationState = typingNotificationState,
|
||||
predecessorRoom = room.predecessorRoom(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import io.element.android.features.messages.impl.typing.TypingNotificationState
|
|||
import io.element.android.features.roomcall.api.RoomCallState
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlin.time.Duration
|
||||
|
|
@ -77,4 +78,5 @@ data class TimelineRoomInfo(
|
|||
val roomCallState: RoomCallState,
|
||||
val pinnedEventIds: List<EventId>,
|
||||
val typingNotificationState: TypingNotificationState,
|
||||
val predecessorRoom: PredecessorRoom?,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
import io.element.android.libraries.matrix.api.core.UniqueId
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield
|
||||
|
|
@ -246,6 +247,7 @@ internal fun aTimelineRoomInfo(
|
|||
userHasPermissionToSendMessage: Boolean = true,
|
||||
pinnedEventIds: List<EventId> = emptyList(),
|
||||
typingNotificationState: TypingNotificationState = aTypingNotificationState(),
|
||||
predecessorRoom: PredecessorRoom? = null,
|
||||
) = TimelineRoomInfo(
|
||||
isDm = isDm,
|
||||
name = name,
|
||||
|
|
@ -254,4 +256,5 @@ internal fun aTimelineRoomInfo(
|
|||
roomCallState = aStandByCallState(),
|
||||
pinnedEventIds = pinnedEventIds,
|
||||
typingNotificationState = typingNotificationState,
|
||||
predecessorRoom = predecessorRoom,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -41,7 +41,15 @@ fun TimelineItemVirtualRow(
|
|||
when (virtual.model) {
|
||||
is TimelineItemDaySeparatorModel -> TimelineItemDaySeparatorView(virtual.model)
|
||||
TimelineItemReadMarkerModel -> TimelineItemReadMarkerView()
|
||||
TimelineItemRoomBeginningModel -> TimelineItemRoomBeginningView(roomName = timelineRoomInfo.name)
|
||||
TimelineItemRoomBeginningModel -> {
|
||||
TimelineItemRoomBeginningView(
|
||||
predecessorRoom = timelineRoomInfo.predecessorRoom,
|
||||
roomName = timelineRoomInfo.name,
|
||||
onPredecessorRoomClick = { roomId ->
|
||||
eventSink(TimelineEvents.NavigateToRoom(roomId))
|
||||
},
|
||||
)
|
||||
}
|
||||
is TimelineItemLoadingIndicatorModel -> {
|
||||
TimelineLoadingMoreIndicator(virtual.model.direction)
|
||||
val latestEventSink by rememberUpdatedState(eventSink)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
package io.element.android.features.messages.impl.timeline.components.virtual
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement.spacedBy
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
|
|
@ -19,44 +20,74 @@ import androidx.compose.ui.text.style.TextAlign
|
|||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.compound.theme.ElementTheme
|
||||
import io.element.android.features.messages.impl.R
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ComposerAlertMolecule
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.designsystem.text.toAnnotatedString
|
||||
import io.element.android.libraries.designsystem.theme.components.Text
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
|
||||
|
||||
@Composable
|
||||
fun TimelineItemRoomBeginningView(
|
||||
roomName: String?,
|
||||
predecessorRoom: PredecessorRoom?,
|
||||
onPredecessorRoomClick: (RoomId) -> Unit,
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
Column(
|
||||
modifier = modifier.fillMaxWidth()
|
||||
) {
|
||||
val text = if (roomName == null) {
|
||||
stringResource(id = R.string.screen_room_timeline_beginning_of_room_no_name)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_room_timeline_beginning_of_room, roomName)
|
||||
if (predecessorRoom != null) {
|
||||
ComposerAlertMolecule(
|
||||
avatar = null,
|
||||
content = stringResource(R.string.screen_room_timeline_upgraded_room_message).toAnnotatedString(),
|
||||
onSubmitClick = { onPredecessorRoomClick(predecessorRoom.roomId) },
|
||||
isCritical = false,
|
||||
submitText = stringResource(R.string.screen_room_timeline_upgraded_room_action)
|
||||
)
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
val text = if (roomName == null) {
|
||||
stringResource(id = R.string.screen_room_timeline_beginning_of_room_no_name)
|
||||
} else {
|
||||
stringResource(id = R.string.screen_room_timeline_beginning_of_room, roomName)
|
||||
}
|
||||
Text(
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = text,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
color = ElementTheme.colors.textSecondary,
|
||||
style = ElementTheme.typography.fontBodyMdRegular,
|
||||
text = text,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun TimelineItemRoomBeginningViewPreview() = ElementPreview {
|
||||
Column {
|
||||
Column(verticalArrangement = spacedBy(16.dp)) {
|
||||
TimelineItemRoomBeginningView(
|
||||
predecessorRoom = null,
|
||||
roomName = null,
|
||||
onPredecessorRoomClick = {},
|
||||
)
|
||||
TimelineItemRoomBeginningView(
|
||||
predecessorRoom = null,
|
||||
roomName = "Room Name",
|
||||
onPredecessorRoomClick = {},
|
||||
)
|
||||
TimelineItemRoomBeginningView(
|
||||
predecessorRoom = PredecessorRoom(RoomId("!roomId:matrix.org"), EventId("\$eventId:matrix.org")),
|
||||
roomName = "Room Name",
|
||||
onPredecessorRoomClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,10 @@
|
|||
<item quantity="few">"%1$d změny místnosti"</item>
|
||||
<item quantity="other">"%1$d změn místnosti"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_timeline_tombstoned_room_action">"Přejít do nové místnosti"</string>
|
||||
<string name="screen_room_timeline_tombstoned_room_message">"Tato místnost byla nahrazena a již není aktivní"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_action">"Zobrazit staré zprávy"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_message">"Tato místnost je pokračováním jiné místnosti"</string>
|
||||
<plurals name="screen_room_typing_many_members">
|
||||
<item quantity="one">"%1$s, %2$s a %3$d další"</item>
|
||||
<item quantity="few">"%1$s, %2$s a %3$d další"</item>
|
||||
|
|
|
|||
|
|
@ -51,6 +51,10 @@
|
|||
<item quantity="one">"%1$d room change"</item>
|
||||
<item quantity="other">"%1$d room changes"</item>
|
||||
</plurals>
|
||||
<string name="screen_room_timeline_tombstoned_room_action">"Jump to new room"</string>
|
||||
<string name="screen_room_timeline_tombstoned_room_message">"This room has been replaced and is no longer active"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_action">"See old messages"</string>
|
||||
<string name="screen_room_timeline_upgraded_room_message">"This room is a continuation of another room"</string>
|
||||
<plurals name="screen_room_typing_many_members">
|
||||
<item quantity="one">"%1$s, %2$s and %3$d other"</item>
|
||||
<item quantity="other">"%1$s, %2$s and %3$d others"</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue