From e5a3b112ebd1bb97bd8bcb5458a2140005881076 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Oct 2024 09:38:53 +0000 Subject: [PATCH 01/16] Update dependency com.posthog:posthog-android to v3.8.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c7af9f3120..09e57aae49 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -189,7 +189,7 @@ kotlinpoet = "com.squareup:kotlinpoet:1.18.1" zxing_cpp = "io.github.zxing-cpp:android:2.2.0" # Analytics -posthog = "com.posthog:posthog-android:3.7.5" +posthog = "com.posthog:posthog-android:3.8.0" sentry = "io.sentry:sentry-android:7.14.0" # main branch can be tested replacing the version with main-SNAPSHOT matrix_analytics_events = "com.github.matrix-org:matrix-analytics-events:0.25.0" From 1e4c30c569c002a9170d199e5e6d60f622fa0209 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 2 Oct 2024 17:49:16 +0200 Subject: [PATCH 02/16] timeline : makes typingNotification item part of the timelineItems. --- .../messages/impl/MessagesPresenter.kt | 4 --- .../features/messages/impl/MessagesState.kt | 2 -- .../messages/impl/MessagesStateProvider.kt | 2 -- .../features/messages/impl/MessagesView.kt | 1 - .../messages/impl/di/MessagesModule.kt | 6 ++++ .../list/PinnedMessagesListPresenter.kt | 9 ++++- .../impl/timeline/TimelinePresenter.kt | 6 +++- .../messages/impl/timeline/TimelineState.kt | 4 ++- .../impl/timeline/TimelineStateProvider.kt | 4 +++ .../messages/impl/timeline/TimelineView.kt | 10 ------ .../TimelineViewMessageShieldPreview.kt | 1 - .../components/TimelineItemVirtualRow.kt | 8 +++++ .../virtual/TimelineItemVirtualFactory.kt | 2 ++ .../TimelineItemTypingNotificationModel.kt | 12 +++++++ .../typing/MessagesViewWithTypingPreview.kt | 35 ------------------- .../messages/impl/MessagesPresenterTest.kt | 7 ---- .../impl/timeline/TimelinePresenterTest.kt | 6 ++-- .../impl/timeline/TimelineViewTest.kt | 1 - .../item/virtual/VirtualTimelineItem.kt | 3 ++ .../matrix/impl/timeline/RustTimeline.kt | 5 +++ .../TypingNotificationPostProcessor.kt | 35 +++++++++++++++++++ 21 files changed, 95 insertions(+), 68 deletions(-) create mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt delete mode 100644 features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt create mode 100644 libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt index 8c0616431b..b0029fe4ee 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesPresenter.kt @@ -46,7 +46,6 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent -import io.element.android.features.messages.impl.typing.TypingNotificationPresenter import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus @@ -91,7 +90,6 @@ class MessagesPresenter @AssistedInject constructor( private val composerPresenter: MessageComposerPresenter, private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter, timelinePresenterFactory: TimelinePresenter.Factory, - private val typingNotificationPresenter: TypingNotificationPresenter, private val actionListPresenterFactory: ActionListPresenter.Factory, private val customReactionPresenter: CustomReactionPresenter, private val reactionSummaryPresenter: ReactionSummaryPresenter, @@ -125,7 +123,6 @@ class MessagesPresenter @AssistedInject constructor( val composerState = composerPresenter.present() val voiceMessageComposerState = voiceMessageComposerPresenter.present() val timelineState = timelinePresenter.present() - val typingNotificationState = typingNotificationPresenter.present() val actionListState = actionListPresenter.present() val customReactionState = customReactionPresenter.present() val reactionSummaryState = reactionSummaryPresenter.present() @@ -216,7 +213,6 @@ class MessagesPresenter @AssistedInject constructor( userEventPermissions = userEventPermissions, voiceMessageComposerState = voiceMessageComposerState, timelineState = timelineState, - typingNotificationState = typingNotificationState, actionListState = actionListState, customReactionState = customReactionState, reactionSummaryState = reactionSummaryState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt index 546e558ba8..2c5bae6d3b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesState.kt @@ -15,7 +15,6 @@ import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState -import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.designsystem.components.avatar.AvatarData @@ -33,7 +32,6 @@ data class MessagesState( val composerState: MessageComposerState, val voiceMessageComposerState: VoiceMessageComposerState, val timelineState: TimelineState, - val typingNotificationState: TypingNotificationState, val actionListState: ActionListState, val customReactionState: CustomReactionState, val reactionSummaryState: ReactionSummaryState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt index 96e55aac91..2c1a487440 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesStateProvider.kt @@ -26,7 +26,6 @@ import io.element.android.features.messages.impl.timeline.components.receipt.bot import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.features.messages.impl.typing.aTypingNotificationState import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessagePreviewState @@ -122,7 +121,6 @@ fun aMessagesState( userEventPermissions = userEventPermissions, composerState = composerState, voiceMessageComposerState = voiceMessageComposerState, - typingNotificationState = aTypingNotificationState(), timelineState = timelineState, readReceiptBottomSheetState = readReceiptBottomSheetState, actionListState = actionListState, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 777412e5e3..25ecbcc758 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -379,7 +379,6 @@ private fun MessagesViewContent( val scrollBehavior = PinnedMessagesBannerViewDefaults.rememberExitOnScrollBehavior() TimelineView( state = state.timelineState, - typingNotificationState = state.typingNotificationState, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, onMessageClick = onMessageClick, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt index 8c488d61da..ee5ce58366 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt @@ -14,6 +14,8 @@ import io.element.android.features.messages.impl.crypto.sendfailure.resolve.Reso import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState +import io.element.android.features.messages.impl.typing.TypingNotificationPresenter +import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.di.RoomScope @@ -25,4 +27,8 @@ interface MessagesModule { @Binds fun bindResolveVerifiedUserSendFailurePresenter(presenter: ResolveVerifiedUserSendFailurePresenter): Presenter + + @Binds + fun bindTypingNotificationPresenter(presenter: TypingNotificationPresenter): Presenter + } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt index 8684c16e5a..1e557f84bd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListPresenter.kt @@ -30,6 +30,7 @@ import io.element.android.features.messages.impl.timeline.TimelineRoomInfo import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.model.TimelineItem +import io.element.android.features.messages.impl.typing.TypingNotificationState import io.element.android.libraries.architecture.AsyncData import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -44,6 +45,7 @@ import io.element.android.libraries.ui.strings.CommonStrings import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.analyticsproviders.api.trackers.captureInteraction import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.flowOf @@ -87,7 +89,12 @@ class PinnedMessagesListPresenter @AssistedInject constructor( userHasPermissionToSendReaction = false, isCallOngoing = false, // don't compute this value or the pin icon will be shown - pinnedEventIds = emptyList() + pinnedEventIds = emptyList(), + typingNotificationState = TypingNotificationState( + renderTypingNotifications = false, + typingMembers = persistentListOf(), + reserveSpace = false, + ) ) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 85f517b1b0..6a980026b2 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -28,6 +28,7 @@ import io.element.android.features.messages.impl.timeline.factories.TimelineItem import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig 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.typing.TypingNotificationState import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.poll.api.actions.EndPollAction import io.element.android.features.poll.api.actions.SendPollResponseAction @@ -70,6 +71,7 @@ class TimelinePresenter @AssistedInject constructor( private val sessionPreferencesStore: SessionPreferencesStore, private val timelineController: TimelineController, private val resolveVerifiedUserSendFailurePresenter: Presenter, + private val typingNotificationPresenter: Presenter, ) : Presenter { @AssistedFactory interface Factory { @@ -225,7 +227,8 @@ class TimelinePresenter @AssistedInject constructor( .launchIn(this) } - val timelineRoomInfo by remember { + val typingNotificationState = typingNotificationPresenter.present() + val timelineRoomInfo by remember(typingNotificationState) { derivedStateOf { TimelineRoomInfo( name = room.displayName, @@ -234,6 +237,7 @@ class TimelinePresenter @AssistedInject constructor( userHasPermissionToSendReaction = userHasPermissionToSendReaction, isCallOngoing = roomInfo?.hasRoomCall.orFalse(), pinnedEventIds = roomInfo?.pinnedEventIds.orEmpty(), + typingNotificationState = typingNotificationState, ) } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index cd27959395..cfdf5618d0 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.Immutable import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState 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.typing.TypingNotificationState import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import kotlinx.collections.immutable.ImmutableList @@ -67,5 +68,6 @@ data class TimelineRoomInfo( val userHasPermissionToSendMessage: Boolean, val userHasPermissionToSendReaction: Boolean, val isCallOngoing: Boolean, - val pinnedEventIds: List + val pinnedEventIds: List, + val typingNotificationState: TypingNotificationState, ) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt index e0a9d660ca..fb96e75e28 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineStateProvider.kt @@ -21,6 +21,8 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemStateEventContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent import io.element.android.features.messages.impl.timeline.model.virtual.aTimelineItemDaySeparatorModel +import io.element.android.features.messages.impl.typing.TypingNotificationState +import io.element.android.features.messages.impl.typing.aTypingNotificationState 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.EventId @@ -241,6 +243,7 @@ internal fun aTimelineRoomInfo( isDm: Boolean = false, userHasPermissionToSendMessage: Boolean = true, pinnedEventIds: List = emptyList(), + typingNotificationState: TypingNotificationState = aTypingNotificationState(), ) = TimelineRoomInfo( isDm = isDm, name = name, @@ -248,4 +251,5 @@ internal fun aTimelineRoomInfo( userHasPermissionToSendReaction = true, isCallOngoing = false, pinnedEventIds = pinnedEventIds, + typingNotificationState = typingNotificationState, ) 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 7abf443c04..9e383facd7 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 @@ -55,9 +55,6 @@ 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 import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContentProvider -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.dialogs.AlertDialog import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight @@ -73,7 +70,6 @@ import kotlin.math.abs @Composable fun TimelineView( state: TimelineState, - typingNotificationState: TypingNotificationState, onUserDataClick: (UserId) -> Unit, onLinkClick: (String) -> Unit, onMessageClick: (TimelineItem.Event) -> Unit, @@ -131,11 +127,6 @@ fun TimelineView( reverseLayout = useReverseLayout, contentPadding = PaddingValues(vertical = 8.dp), ) { - if (state.isLive) { - item { - TypingNotificationView(state = typingNotificationState) - } - } items( items = state.timelineItems, contentType = { timelineItem -> timelineItem.contentType() }, @@ -323,7 +314,6 @@ internal fun TimelineViewPreview( ), focusedEventIndex = 0, ), - typingNotificationState = aTypingNotificationState(), onUserDataClick = {}, onLinkClick = {}, onMessageClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt index 81686ed4d2..8c4e774029 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt @@ -36,7 +36,6 @@ internal fun TimelineViewMessageShieldPreview() = ElementPreview { timelineItems = items.toImmutableList(), messageShield = messageShield, ), - typingNotificationState = aTypingNotificationState(), onUserDataClick = {}, onLinkClick = {}, onMessageClick = {}, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt index 7ff428223b..9b0fada9db 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemVirtualRow.kt @@ -26,6 +26,8 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel +import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel +import io.element.android.features.messages.impl.typing.TypingNotificationView @Composable fun TimelineItemVirtualRow( @@ -46,9 +48,15 @@ fun TimelineItemVirtualRow( latestEventSink(TimelineEvents.LoadMore(virtual.model.direction)) } } + // Empty model trick to avoid timeline jumping during forward pagination. is TimelineItemLastForwardIndicatorModel -> { Spacer(modifier = Modifier) } + is TimelineItemTypingNotificationModel -> { + TypingNotificationView( + state = timelineRoomInfo.typingNotificationState, + ) + } } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt index 8a3c5e2caf..39f1d334fd 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/virtual/TimelineItemVirtualFactory.kt @@ -12,6 +12,7 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemReadMarkerModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemRoomBeginningModel +import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemTypingNotificationModel import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemVirtualModel import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem @@ -39,6 +40,7 @@ class TimelineItemVirtualFactory @Inject constructor( timestamp = inner.timestamp ) is VirtualTimelineItem.LastForwardIndicator -> TimelineItemLastForwardIndicatorModel + VirtualTimelineItem.TypingNotification -> TimelineItemTypingNotificationModel } } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt new file mode 100644 index 0000000000..a91042e3f7 --- /dev/null +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/model/virtual/TimelineItemTypingNotificationModel.kt @@ -0,0 +1,12 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.features.messages.impl.timeline.model.virtual + +data object TimelineItemTypingNotificationModel : TimelineItemVirtualModel { + override val type: String = "TimelineItemTypingNotificationModel" +} diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt deleted file mode 100644 index b6c08daf24..0000000000 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/typing/MessagesViewWithTypingPreview.kt +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2024 New Vector Ltd. - * - * SPDX-License-Identifier: AGPL-3.0-only - * Please see LICENSE in the repository root for full details. - */ - -package io.element.android.features.messages.impl.typing - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.PreviewParameter -import io.element.android.features.messages.impl.MessagesView -import io.element.android.features.messages.impl.aMessagesState -import io.element.android.libraries.designsystem.preview.ElementPreview -import io.element.android.libraries.designsystem.preview.PreviewsDayNight - -@PreviewsDayNight -@Composable -internal fun MessagesViewWithTypingPreview( - @PreviewParameter(TypingNotificationStateForMessagesProvider::class) typingState: TypingNotificationState -) = ElementPreview { - MessagesView( - state = aMessagesState().copy(typingNotificationState = typingState), - onBackClick = {}, - onRoomDetailsClick = {}, - onEventClick = { false }, - onUserDataClick = {}, - onLinkClick = {}, - onPreviewAttachments = {}, - onSendLocationClick = {}, - onCreatePollClick = {}, - onJoinCallClick = {}, - onViewAllPinnedMessagesClick = {}, - ) -} diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index be1b4aa7a8..ddd06f4f2f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -40,7 +40,6 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.features.messages.impl.typing.TypingNotificationPresenter import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter @@ -1055,11 +1054,6 @@ class MessagesPresenterTest { } } val featureFlagService = FakeFeatureFlagService() - val typingNotificationPresenter = TypingNotificationPresenter( - room = matrixRoom, - sessionPreferencesStore = sessionPreferencesStore, - ) - val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter() val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider()) val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom) @@ -1069,7 +1063,6 @@ class MessagesPresenterTest { composerPresenter = messageComposerPresenter, voiceMessageComposerPresenter = voiceMessageComposerPresenter, timelinePresenterFactory = timelinePresenterFactory, - typingNotificationPresenter = typingNotificationPresenter, actionListPresenterFactory = FakeActionListPresenter.Factory, customReactionPresenter = customReactionPresenter, reactionSummaryPresenter = reactionSummaryPresenter, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index a0712907cc..b82299eb9c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -19,6 +19,7 @@ import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryC import io.element.android.features.messages.impl.timeline.components.aCriticalShield 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.typing.aTypingNotificationState import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.RedactedVoiceMessageManager import io.element.android.features.messages.impl.voicemessages.timeline.aRedactedMatrixTimeline @@ -503,7 +504,7 @@ import kotlin.time.Duration.Companion.seconds assertThat(state.timelineItems).isNotEmpty() } initialState.eventSink.invoke(TimelineEvents.JumpToLive) - skipItems(1) + skipItems(2) awaitItem().also { state -> // Event stays focused assertThat(state.focusedEventId).isEqualTo(AN_EVENT_ID) @@ -670,7 +671,7 @@ import kotlin.time.Duration.Companion.seconds timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), ): TimelinePresenter { return TimelinePresenter( - timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(), + timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(timelineItemIndexer), room = room, dispatchers = testCoroutineDispatchers(), appScope = this, @@ -682,6 +683,7 @@ import kotlin.time.Duration.Companion.seconds timelineItemIndexer = timelineItemIndexer, timelineController = TimelineController(room), resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() }, + typingNotificationPresenter = { aTypingNotificationState() }, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 4dc61b7b80..48f242d24f 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -155,7 +155,6 @@ private fun AndroidComposeTestRule.setTimel setSafeContent { TimelineView( state = state, - typingNotificationState = typingNotificationState, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, onMessageClick = onMessageClick, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt index 98d6d9fc76..80d627c7ab 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -24,4 +24,7 @@ sealed interface VirtualTimelineItem { val direction: Timeline.PaginationDirection, val timestamp: Long, ) : VirtualTimelineItem + + data object TypingNotification : VirtualTimelineItem + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt index e0fd82bfcb..517d83ff80 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustTimeline.kt @@ -40,6 +40,7 @@ import io.element.android.libraries.matrix.impl.timeline.item.virtual.VirtualTim import io.element.android.libraries.matrix.impl.timeline.postprocessor.LastForwardIndicatorsPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.LoadingIndicatorsPostProcessor import io.element.android.libraries.matrix.impl.timeline.postprocessor.RoomBeginningPostProcessor +import io.element.android.libraries.matrix.impl.timeline.postprocessor.TypingNotificationPostProcessor import io.element.android.libraries.matrix.impl.timeline.reply.InReplyToMapper import io.element.android.libraries.matrix.impl.util.MessageEventContent import io.element.android.services.toolbox.api.systemclock.SystemClock @@ -121,6 +122,7 @@ class RustTimeline( private val roomBeginningPostProcessor = RoomBeginningPostProcessor(mode) private val loadingIndicatorsPostProcessor = LoadingIndicatorsPostProcessor(systemClock) private val lastForwardIndicatorsPostProcessor = LastForwardIndicatorsPostProcessor(mode) + private val typingNotificationPostProcessor = TypingNotificationPostProcessor(mode) private val backPaginationStatus = MutableStateFlow( Timeline.PaginationStatus(isPaginating = false, hasMoreToLoad = mode != Timeline.Mode.PINNED_EVENTS) @@ -235,6 +237,9 @@ class RustTimeline( hasMoreToLoadForward = hasMoreToLoadForward ) } + .let { items -> + typingNotificationPostProcessor.process(items = items) + } // Keep lastForwardIndicatorsPostProcessor last .let { items -> lastForwardIndicatorsPostProcessor.process( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt new file mode 100644 index 0000000000..c9aa7e67fb --- /dev/null +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.timeline.postprocessor + +import io.element.android.libraries.matrix.api.core.UniqueId +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.item.virtual.VirtualTimelineItem + +/** + * This post processor is responsible for adding a typing notification item to the timeline items when the timeline is in live mode. + */ +class TypingNotificationPostProcessor(private val mode: Timeline.Mode) { + + fun process(items: List): List { + return if (mode == Timeline.Mode.LIVE) { + buildList { + addAll(items) + add( + MatrixTimelineItem.Virtual( + uniqueId = UniqueId("TypingNotification"), + virtual = VirtualTimelineItem.TypingNotification + ) + ) + } + } else { + items + } + } +} From b88e65a3edf26f63dbebda83906f48227f4142c4 Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 2 Oct 2024 17:49:51 +0200 Subject: [PATCH 03/16] timeline : fix lastOutgoingMessage after last changes --- .../features/messages/impl/timeline/TimelineState.kt | 10 +++++++++- .../features/messages/impl/timeline/TimelineView.kt | 3 +-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index cfdf5618d0..d46d6a6866 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -13,6 +13,7 @@ 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.typing.TypingNotificationState 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.timeline.item.event.MessageShield import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration @@ -30,8 +31,15 @@ data class TimelineState( val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState, val eventSink: (TimelineEvents) -> Unit, ) { - val hasAnyEvent = timelineItems.any { it is TimelineItem.Event } + val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event} as? TimelineItem.Event + val hasAnyEvent = lastTimelineEvent != null val focusedEventId = focusRequestState.eventId() + + + fun isLastOutgoingMessage(uniqueId: UniqueId): Boolean { + return lastTimelineEvent != null && lastTimelineEvent.isMine && lastTimelineEvent.id == uniqueId + } + } @Immutable 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 9e383facd7..59df0cb70d 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 @@ -136,8 +136,7 @@ fun TimelineView( timelineItem = timelineItem, timelineRoomInfo = state.timelineRoomInfo, renderReadReceipts = state.renderReadReceipts, - isLastOutgoingMessage = (timelineItem as? TimelineItem.Event)?.isMine == true && - state.timelineItems.first().identifier() == timelineItem.identifier(), + isLastOutgoingMessage = state.isLastOutgoingMessage(timelineItem.identifier()), focusedEventId = state.focusedEventId, onUserDataClick = onUserDataClick, onLinkClick = onLinkClick, From 8b6fba8512621a9bb765f73c584324c0b5628c0f Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 2 Oct 2024 17:52:06 +0200 Subject: [PATCH 04/16] timeline : add synchronisation around timelineItemIndexer --- .../impl/timeline/TimelineItemIndexer.kt | 28 ++++++++++++++----- .../impl/timeline/TimelineItemIndexerTest.kt | 3 +- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt index 877b4ea385..2db5003f4a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt @@ -11,26 +11,39 @@ import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.EventId +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import timber.log.Timber import javax.inject.Inject @SingleIn(RoomScope::class) class TimelineItemIndexer @Inject constructor() { + // This is a latch to wait for the first process call + private val firstProcessLatch = CompletableDeferred() private val timelineEventsIndexes = mutableMapOf() - fun isKnown(eventId: EventId): Boolean { - return timelineEventsIndexes.containsKey(eventId).also { - Timber.d("$eventId isKnown = $it") + private val mutex = Mutex() + + suspend fun isKnown(eventId: EventId): Boolean { + firstProcessLatch.await() + return mutex.withLock { + timelineEventsIndexes.containsKey(eventId).also { + Timber.d("$eventId isKnown = $it") + } } } - fun indexOf(eventId: EventId): Int { - return (timelineEventsIndexes[eventId] ?: -1).also { - Timber.d("indexOf $eventId= $it") + suspend fun indexOf(eventId: EventId): Int { + firstProcessLatch.await() + return mutex.withLock { + (timelineEventsIndexes[eventId] ?: -1).also { + Timber.d("indexOf $eventId= $it") + } } } - fun process(timelineItems: List) { + suspend fun process(timelineItems: List) = mutex.withLock { Timber.d("process ${timelineItems.size} items") timelineEventsIndexes.clear() timelineItems.forEachIndexed { index, timelineItem -> @@ -46,6 +59,7 @@ class TimelineItemIndexer @Inject constructor() { else -> Unit } } + firstProcessLatch.complete(Unit) } private fun processEvent(event: TimelineItem.Event, index: Int) { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt index 3e20250910..2e3687b74e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexerTest.kt @@ -13,11 +13,12 @@ import io.element.android.features.messages.impl.timeline.model.virtual.Timeline import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UniqueId import io.element.android.libraries.matrix.test.AN_EVENT_ID +import kotlinx.coroutines.test.runTest import org.junit.Test class TimelineItemIndexerTest { @Test - fun `test TimelineItemIndexer`() { + fun `test TimelineItemIndexer`() = runTest { val eventIds = mutableListOf() val data = listOf( aTimelineItemEvent().also { eventIds.add(it.eventId!!) }, From 88e01e7c2eb1dc94b7d562bd7888795765852c1e Mon Sep 17 00:00:00 2001 From: ganfra Date: Wed, 2 Oct 2024 18:44:38 +0200 Subject: [PATCH 05/16] timeline : fix jumpToBottom for not live timeline. --- .../features/messages/impl/timeline/TimelineView.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) 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 59df0cb70d..bce3bb3f5e 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 @@ -30,9 +30,11 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.rotate @@ -206,6 +208,7 @@ private fun BoxScope.TimelineScrollHelper( lazyListState.firstVisibleItemIndex < 3 && isLive } } + var jumpToLiveHandled by remember { mutableStateOf(true) } fun scrollToBottom() { coroutineScope.launch { @@ -221,10 +224,18 @@ private fun BoxScope.TimelineScrollHelper( if (isLive) { scrollToBottom() } else { + jumpToLiveHandled = false onJumpToLive() } } + LaunchedEffect(jumpToLiveHandled, isLive) { + if (!jumpToLiveHandled && isLive) { + lazyListState.scrollToItem(0) + jumpToLiveHandled = true + } + } + val latestOnFocusEventRender by rememberUpdatedState(onFocusEventRender) LaunchedEffect(focusRequestState) { if (focusRequestState is FocusRequestState.Success && focusRequestState.isIndexed) { From adc03c96761eec67f1cd401d7bff0e5ebc40d8d8 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 3 Oct 2024 12:49:24 +0200 Subject: [PATCH 06/16] timeline : improve jumpTo precision (introducing animateScrollToItemCenter) --- .../messages/impl/di/MessagesModule.kt | 1 - .../impl/timeline/TimelineItemIndexer.kt | 3 - .../impl/timeline/TimelinePresenter.kt | 81 ++++++++++--------- .../messages/impl/timeline/TimelineState.kt | 6 +- .../messages/impl/timeline/TimelineView.kt | 10 +-- .../TimelineViewMessageShieldPreview.kt | 1 - .../factories/TimelineItemsFactory.kt | 3 - .../messages/impl/MessagesPresenterTest.kt | 2 + .../fixtures/TimelineItemsFactoryFixtures.kt | 9 +-- .../impl/timeline/TimelinePresenterTest.kt | 2 +- .../impl/timeline/TimelineViewTest.kt | 3 - .../designsystem/utils/LazyListState.kt | 37 +++++++++ .../item/virtual/VirtualTimelineItem.kt | 1 - .../TypingNotificationPostProcessor.kt | 1 - 14 files changed, 88 insertions(+), 72 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt index ee5ce58366..a6cbd68cf6 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/di/MessagesModule.kt @@ -30,5 +30,4 @@ interface MessagesModule { @Binds fun bindTypingNotificationPresenter(presenter: TypingNotificationPresenter): Presenter - } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt index 2db5003f4a..2c22e714ae 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineItemIndexer.kt @@ -8,8 +8,6 @@ package io.element.android.features.messages.impl.timeline import io.element.android.features.messages.impl.timeline.model.TimelineItem -import io.element.android.libraries.di.RoomScope -import io.element.android.libraries.di.SingleIn import io.element.android.libraries.matrix.api.core.EventId import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.sync.Mutex @@ -17,7 +15,6 @@ import kotlinx.coroutines.sync.withLock import timber.log.Timber import javax.inject.Inject -@SingleIn(RoomScope::class) class TimelineItemIndexer @Inject constructor() { // This is a latch to wait for the first process call private val firstProcessLatch = CompletableDeferred() diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt index 6a980026b2..819f7a1f3a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenter.kt @@ -55,12 +55,12 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import timber.log.Timber const val FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS = 200L class TimelinePresenter @AssistedInject constructor( timelineItemsFactoryCreator: TimelineItemsFactory.Creator, - private val timelineItemIndexer: TimelineItemIndexer, private val room: MatrixRoom, private val dispatchers: CoroutineDispatchers, private val appScope: CoroutineScope, @@ -70,6 +70,7 @@ class TimelinePresenter @AssistedInject constructor( private val endPollAction: EndPollAction, private val sessionPreferencesStore: SessionPreferencesStore, private val timelineController: TimelineController, + private val timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), private val resolveVerifiedUserSendFailurePresenter: Presenter, private val typingNotificationPresenter: Presenter, ) : Presenter { @@ -89,13 +90,7 @@ class TimelinePresenter @AssistedInject constructor( @Composable override fun present(): TimelineState { val localScope = rememberCoroutineScope() - val focusRequestState: MutableState = remember { - mutableStateOf(FocusRequestState.None) - } - - LaunchedEffect(Unit) { - timelineItemsFactory.timelineItems.collect { timelineItems = it } - } + var focusRequestState: FocusRequestState by remember { mutableStateOf(FocusRequestState.None) } val lastReadReceiptId = rememberSaveable { mutableStateOf(null) } @@ -154,13 +149,13 @@ class TimelinePresenter @AssistedInject constructor( navigator.onEditPollClick(event.pollStartId) } is TimelineEvents.FocusOnEvent -> { - focusRequestState.value = FocusRequestState.Requested(event.eventId, event.debounce) + focusRequestState = FocusRequestState.Requested(event.eventId, event.debounce) } is TimelineEvents.OnFocusEventRender -> { - focusRequestState.value = focusRequestState.value.onFocusEventRender() + focusRequestState = focusRequestState.onFocusEventRender() } is TimelineEvents.ClearFocusRequestState -> { - focusRequestState.value = FocusRequestState.None + focusRequestState = FocusRequestState.None } is TimelineEvents.JumpToLive -> { timelineController.focusOnLive() @@ -173,28 +168,46 @@ class TimelinePresenter @AssistedInject constructor( } } - LaunchedEffect(focusRequestState.value) { - when (val currentFocusRequestState = focusRequestState.value) { + LaunchedEffect(Unit) { + timelineItemsFactory.timelineItems + .onEach { newTimelineItems -> + timelineItemIndexer.process(newTimelineItems) + timelineItems = newTimelineItems + } + .launchIn(this) + + combine(timelineController.timelineItems(), room.membersStateFlow) { items, membersState -> + timelineItemsFactory.replaceWith( + timelineItems = items, + roomMembers = membersState.roomMembers().orEmpty() + ) + items + } + .onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem) + .launchIn(this) + } + + LaunchedEffect(focusRequestState) { + Timber.d("## focusRequestState: $focusRequestState") + when (val currentFocusRequestState = focusRequestState) { is FocusRequestState.Requested -> { delay(currentFocusRequestState.debounce) if (timelineItemIndexer.isKnown(currentFocusRequestState.eventId)) { val index = timelineItemIndexer.indexOf(currentFocusRequestState.eventId) - focusRequestState.value = FocusRequestState.Success(eventId = currentFocusRequestState.eventId, index = index) + focusRequestState = FocusRequestState.Success(eventId = currentFocusRequestState.eventId, index = index) } else { - focusRequestState.value = FocusRequestState.Loading(eventId = currentFocusRequestState.eventId) + focusRequestState = FocusRequestState.Loading(eventId = currentFocusRequestState.eventId) } } is FocusRequestState.Loading -> { val eventId = currentFocusRequestState.eventId timelineController.focusOnEvent(eventId) - .fold( - onSuccess = { - focusRequestState.value = FocusRequestState.Success(eventId = eventId) - }, - onFailure = { - focusRequestState.value = FocusRequestState.Failure(throwable = it) - } - ) + .onSuccess { + focusRequestState = FocusRequestState.Success(eventId = eventId) + } + .onFailure { + focusRequestState = FocusRequestState.Failure(it) + } } else -> Unit } @@ -204,29 +217,17 @@ class TimelinePresenter @AssistedInject constructor( computeNewItemState(timelineItems, prevMostRecentItemId, newEventState) } - LaunchedEffect(timelineItems.size, focusRequestState.value) { - val currentFocusRequestState = focusRequestState.value - if (currentFocusRequestState is FocusRequestState.Success && !currentFocusRequestState.isIndexed) { + LaunchedEffect(timelineItems.size, focusRequestState) { + val currentFocusRequestState = focusRequestState + if (currentFocusRequestState is FocusRequestState.Success && !currentFocusRequestState.rendered) { val eventId = currentFocusRequestState.eventId if (timelineItemIndexer.isKnown(eventId)) { val index = timelineItemIndexer.indexOf(eventId) - focusRequestState.value = FocusRequestState.Success(eventId = eventId, index = index) + focusRequestState = FocusRequestState.Success(eventId = eventId, index = index) } } } - LaunchedEffect(Unit) { - combine(timelineController.timelineItems(), room.membersStateFlow) { items, membersState -> - timelineItemsFactory.replaceWith( - timelineItems = items, - roomMembers = membersState.roomMembers().orEmpty() - ) - items - } - .onEach(redactedVoiceMessageManager::onEachMatrixTimelineItem) - .launchIn(this) - } - val typingNotificationState = typingNotificationPresenter.present() val timelineRoomInfo by remember(typingNotificationState) { derivedStateOf { @@ -247,7 +248,7 @@ class TimelinePresenter @AssistedInject constructor( renderReadReceipts = renderReadReceipts, newEventState = newEventState.value, isLive = isLive, - focusRequestState = focusRequestState.value, + focusRequestState = focusRequestState, messageShield = messageShield.value, resolveVerifiedUserSendFailureState = resolveVerifiedUserSendFailureState, eventSink = { handleEvents(it) } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt index d46d6a6866..bfb357b579 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineState.kt @@ -31,15 +31,13 @@ data class TimelineState( val resolveVerifiedUserSendFailureState: ResolveVerifiedUserSendFailureState, val eventSink: (TimelineEvents) -> Unit, ) { - val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event} as? TimelineItem.Event + private val lastTimelineEvent = timelineItems.firstOrNull { it is TimelineItem.Event } as? TimelineItem.Event val hasAnyEvent = lastTimelineEvent != null val focusedEventId = focusRequestState.eventId() - fun isLastOutgoingMessage(uniqueId: UniqueId): Boolean { - return lastTimelineEvent != null && lastTimelineEvent.isMine && lastTimelineEvent.id == uniqueId + return isLive && lastTimelineEvent != null && lastTimelineEvent.isMine && lastTimelineEvent.id == uniqueId } - } @Immutable 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 bce3bb3f5e..0781cc3ece 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 @@ -62,12 +62,12 @@ 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 import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.utils.animateScrollToItemCenter import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.event.MessageShield import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.coroutines.launch -import kotlin.math.abs @Composable fun TimelineView( @@ -238,12 +238,8 @@ private fun BoxScope.TimelineScrollHelper( val latestOnFocusEventRender by rememberUpdatedState(onFocusEventRender) LaunchedEffect(focusRequestState) { - if (focusRequestState is FocusRequestState.Success && focusRequestState.isIndexed) { - if (abs(lazyListState.firstVisibleItemIndex - focusRequestState.index) < 10) { - lazyListState.animateScrollToItem(focusRequestState.index) - } else { - lazyListState.scrollToItem(focusRequestState.index) - } + if (focusRequestState is FocusRequestState.Success && focusRequestState.isIndexed && !focusRequestState.rendered) { + lazyListState.animateScrollToItemCenter(focusRequestState.index) latestOnFocusEventRender() } } diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt index 8c4e774029..4566bf88bc 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewMessageShieldPreview.kt @@ -14,7 +14,6 @@ import io.element.android.features.messages.impl.timeline.di.LocalTimelineItemPr import io.element.android.features.messages.impl.timeline.di.aFakeTimelineItemPresenterFactories import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent -import io.element.android.features.messages.impl.typing.aTypingNotificationState import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.PreviewsDayNight import kotlinx.collections.immutable.toImmutableList diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt index f9857328cd..c507e311f9 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/factories/TimelineItemsFactory.kt @@ -10,7 +10,6 @@ package io.element.android.features.messages.impl.timeline.factories import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.diff.TimelineItemsCacheInvalidator import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemEventFactory import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory @@ -36,7 +35,6 @@ class TimelineItemsFactory @AssistedInject constructor( private val dispatchers: CoroutineDispatchers, private val virtualItemFactory: TimelineItemVirtualFactory, private val timelineItemGrouper: TimelineItemGrouper, - private val timelineItemIndexer: TimelineItemIndexer, ) { @AssistedFactory interface Creator { @@ -96,7 +94,6 @@ class TimelineItemsFactory @AssistedInject constructor( } } val result = timelineItemGrouper.group(newTimelineItemStates).toPersistentList() - timelineItemIndexer.process(result) this._timelineItems.emit(result) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index ddd06f4f2f..79792f4db1 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -40,6 +40,7 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt import io.element.android.features.messages.impl.timeline.model.event.TimelineItemVideoContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent +import io.element.android.features.messages.impl.typing.aTypingNotificationState import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter @@ -1047,6 +1048,7 @@ class MessagesPresenterTest { timelineItemIndexer = TimelineItemIndexer(), timelineController = TimelineController(matrixRoom), resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() }, + typingNotificationPresenter = { aTypingNotificationState() }, ) val timelinePresenterFactory = object : TimelinePresenter.Factory { override fun create(navigator: MessagesNavigator): TimelinePresenter { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt index c9439e0a3f..405d356edf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/fixtures/TimelineItemsFactoryFixtures.kt @@ -7,7 +7,6 @@ package io.element.android.features.messages.impl.fixtures -import io.element.android.features.messages.impl.timeline.TimelineItemIndexer import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactoryConfig import io.element.android.features.messages.impl.timeline.factories.event.TimelineItemContentFactory @@ -40,19 +39,16 @@ import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorW import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.test.TestScope -internal fun TestScope.aTimelineItemsFactoryCreator( - timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), -): TimelineItemsFactory.Creator { +internal fun TestScope.aTimelineItemsFactoryCreator(): TimelineItemsFactory.Creator { return object : TimelineItemsFactory.Creator { override fun create(config: TimelineItemsFactoryConfig): TimelineItemsFactory { - return aTimelineItemsFactory(config, timelineItemIndexer) + return aTimelineItemsFactory(config) } } } internal fun TestScope.aTimelineItemsFactory( config: TimelineItemsFactoryConfig, - timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), ): TimelineItemsFactory { val timelineEventFormatter = aTimelineEventFormatter() val matrixClient = FakeMatrixClient() @@ -96,7 +92,6 @@ internal fun TestScope.aTimelineItemsFactory( ), ), timelineItemGrouper = TimelineItemGrouper(), - timelineItemIndexer = timelineItemIndexer, config = config ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index b82299eb9c..a008feac24 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -671,7 +671,7 @@ import kotlin.time.Duration.Companion.seconds timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(), ): TimelinePresenter { return TimelinePresenter( - timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(timelineItemIndexer), + timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(), room = room, dispatchers = testCoroutineDispatchers(), appScope = this, diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt index 48f242d24f..88f4edf003 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineViewTest.kt @@ -17,8 +17,6 @@ import io.element.android.features.messages.impl.timeline.components.aCriticalSh import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemImageContent import io.element.android.features.messages.impl.timeline.model.virtual.TimelineItemLoadingIndicatorModel -import io.element.android.features.messages.impl.typing.TypingNotificationState -import io.element.android.features.messages.impl.typing.aTypingNotificationState 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.timeline.Timeline @@ -139,7 +137,6 @@ class TimelineViewTest { private fun AndroidComposeTestRule.setTimelineView( state: TimelineState, - typingNotificationState: TypingNotificationState = aTypingNotificationState(), onUserDataClick: (UserId) -> Unit = EnsureNeverCalledWithParam(), onLinkClick: (String) -> Unit = EnsureNeverCalledWithParam(), onMessageClick: (TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(), diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt index e793a37ef1..73bb290357 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/utils/LazyListState.kt @@ -7,6 +7,8 @@ package io.element.android.libraries.designsystem.utils +import androidx.compose.foundation.gestures.Orientation +import androidx.compose.foundation.lazy.LazyListLayoutInfo import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable import androidx.compose.runtime.derivedStateOf @@ -35,3 +37,38 @@ fun LazyListState.isScrollingUp(): Boolean { } }.value } + +suspend fun LazyListState.animateScrollToItemCenter(index: Int) { + fun LazyListLayoutInfo.containerSize(): Int { + return if (orientation == Orientation.Vertical) { + viewportSize.height + } else { + viewportSize.width + } - beforeContentPadding - afterContentPadding + } + + fun LazyListLayoutInfo.resolveItemOffsetToCenter(index: Int): Int? { + val itemInfo = visibleItemsInfo.firstOrNull { it.index == index } ?: return null + val containerSize = containerSize() + val itemSize = itemInfo.size + return if (itemSize > containerSize) { + itemSize - containerSize / 2 + } else { + -(containerSize() - itemInfo.size) / 2 + } + } + + // await for the first layout. + scroll { } + layoutInfo.resolveItemOffsetToCenter(index)?.let { offset -> + // Item is already visible, just scroll to center. + animateScrollToItem(index, offset) + return + } + // Item is not visible, jump to it... + scrollToItem(index) + // and then adjust according to the actual item size. + layoutInfo.resolveItemOffsetToCenter(index)?.let { offset -> + animateScrollToItem(index, offset) + } +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt index 80d627c7ab..07458e13eb 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/virtual/VirtualTimelineItem.kt @@ -26,5 +26,4 @@ sealed interface VirtualTimelineItem { ) : VirtualTimelineItem data object TypingNotification : VirtualTimelineItem - } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt index c9aa7e67fb..1b16027b35 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TypingNotificationPostProcessor.kt @@ -16,7 +16,6 @@ import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTime * This post processor is responsible for adding a typing notification item to the timeline items when the timeline is in live mode. */ class TypingNotificationPostProcessor(private val mode: Timeline.Mode) { - fun process(items: List): List { return if (mode == Timeline.Mode.LIVE) { buildList { From 4d6b37f1578a67545b01daa79cdb8d4e1417fa97 Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Thu, 3 Oct 2024 15:38:08 +0200 Subject: [PATCH 07/16] Merge unit, screenshot tests and coverage in a single CI call (#3593) * Merge unit, screenshot tests and coverage tasks in a single CI call * Disable gradle daemon too since it's all in a single gradle call now * Make Kover upload the HTML reports on failure too --- .github/workflows/tests.yml | 14 ++++---------- tests/uitests/build.gradle.kts | 10 ---------- 2 files changed, 4 insertions(+), 20 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 897abf8d55..92fecbe8bd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC - CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-daemon jobs: tests: @@ -56,14 +56,8 @@ jobs: with: cache-read-only: ${{ github.ref != 'refs/heads/develop' }} - - name: ⚙️ Run unit tests for debug variant - run: ./gradlew testDebugUnitTest $CI_GRADLE_ARG_PROPERTIES - - - name: 📸 Run screenshot tests - run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES - - - name: 📈Generate kover report and verify coverage - run: ./gradlew :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES + - name: ⚙️ Check coverage for debug variant (includes unit & screenshot tests) + run: ./gradlew :tests:uitests:verifyPaparazziDebug :app:koverXmlReportGplayDebug :app:koverHtmlReportGplayDebug :app:koverVerifyAll $CI_GRADLE_ARG_PROPERTIES - name: 🚫 Upload kover failed coverage reports if: failure() @@ -71,7 +65,7 @@ jobs: with: name: kover-error-report path: | - app/build/reports/kover/verifyGplayDebug.err + app/build/reports/kover - name: ✅ Upload kover report (disabled) if: always() diff --git a/tests/uitests/build.gradle.kts b/tests/uitests/build.gradle.kts index 8da1f47a38..3d65a1be7f 100644 --- a/tests/uitests/build.gradle.kts +++ b/tests/uitests/build.gradle.kts @@ -19,16 +19,6 @@ android { namespace = "ui" } -// Workaround: `kover` tasks somehow trigger the screenshot tests with a broken configuration, removing -// any previous test results and not creating new ones. This is a workaround to disable the screenshot tests -// when the `kover` tasks are detected. -tasks.withType { - if (project.gradle.startParameter.taskNames.any { it.contains("kover", ignoreCase = true) }) { - println("WARNING: Kover task detected, disabling screenshot test task $name.") - isEnabled = false - } -} - dependencies { // Paparazzi 1.3.2 workaround (see https://github.com/cashapp/paparazzi/blob/master/CHANGELOG.md#132---2024-01-13) constraints.add("testImplementation", "com.google.guava:guava") { From a91eaba1533faa3be1e71c8a42fc8193027de9ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 07:25:39 +0000 Subject: [PATCH 08/16] Update dependencyAnalysis to v2.1.3 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 09e57aae49..9d25c4b723 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,7 @@ test_core = "1.6.1" #other coil = "2.7.0" datetime = "0.6.0" -dependencyAnalysis = "2.1.0" +dependencyAnalysis = "2.1.3" serialization_json = "1.6.3" showkase = "1.0.3" appyx = "1.4.0" From cafc04b740cfe3c4560309114f7dc7328befdf32 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 4 Oct 2024 11:50:01 +0200 Subject: [PATCH 09/16] dependency: Bump rust sdk to 0.2.51 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 09e57aae49..b52a729d95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -167,7 +167,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:2.0.0" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.50" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.2.51" matrix_richtexteditor = { module = "io.element.android:wysiwyg", version.ref = "wysiwyg" } matrix_richtexteditor_compose = { module = "io.element.android:wysiwyg-compose", version.ref = "wysiwyg" } sqldelight-driver-android = { module = "app.cash.sqldelight:android-driver", version.ref = "sqldelight" } From 1e91e8b35671e11a2af350ef8e68dc8e9ecab346 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 4 Oct 2024 12:01:30 +0200 Subject: [PATCH 10/16] Fix building the app using a local SDK. Inject `AnalyticsService` instead of `UtdTracker` since `UtdTracker` requires access to `org.matrix.rustcomponents.sdk.UnableToDecryptDelegate` --- app/build.gradle.kts | 2 -- .../android/libraries/matrix/impl/RustMatrixClientFactory.kt | 5 +++-- .../android/libraries/matrix/impl/analytics/UtdTracker.kt | 3 +-- .../io/element/android/samples/minimal/MainActivity.kt | 3 +-- 4 files changed, 5 insertions(+), 8 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a63de43391..26ad452dc3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -275,8 +275,6 @@ dependencies { implementation(libs.serialization.json) implementation(libs.matrix.emojibase.bindings) - // Needed for UtdTracker - implementation(libs.matrix.sdk) testImplementation(libs.test.junit) testImplementation(libs.test.robolectric) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt index 4d320ed810..27d33d5bbf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClientFactory.kt @@ -21,6 +21,7 @@ import io.element.android.libraries.matrix.impl.util.anonymizedTokens import io.element.android.libraries.network.useragent.UserAgentProvider import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.services.analytics.api.AnalyticsService import io.element.android.services.toolbox.api.systemclock.SystemClock import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext @@ -44,7 +45,7 @@ class RustMatrixClientFactory @Inject constructor( private val userCertificatesProvider: UserCertificatesProvider, private val proxyProvider: ProxyProvider, private val clock: SystemClock, - private val utdTracker: UtdTracker, + private val analyticsService: AnalyticsService, private val featureFlagService: FeatureFlagService, private val timelineEventTypeFilterFactory: TimelineEventTypeFilterFactory, private val clientBuilderProvider: ClientBuilderProvider, @@ -64,7 +65,7 @@ class RustMatrixClientFactory @Inject constructor( client.restoreSession(sessionData.toSession()) val syncService = client.syncService() - .withUtdHook(utdTracker) + .withUtdHook(UtdTracker(analyticsService)) .finish() val (anonymizedAccessToken, anonymizedRefreshToken) = sessionData.anonymizedTokens() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt index 9a0abb7403..ec9b546496 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/analytics/UtdTracker.kt @@ -13,9 +13,8 @@ import org.matrix.rustcomponents.sdk.UnableToDecryptDelegate import org.matrix.rustcomponents.sdk.UnableToDecryptInfo import timber.log.Timber import uniffi.matrix_sdk_crypto.UtdCause -import javax.inject.Inject -class UtdTracker @Inject constructor( +class UtdTracker( private val analyticsService: AnalyticsService, ) : UnableToDecryptDelegate { override fun onUtd(info: UnableToDecryptInfo) { diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index efaecb0762..1a3c68b478 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -21,7 +21,6 @@ import io.element.android.compound.theme.ElementTheme import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService import io.element.android.libraries.matrix.impl.RustClientBuilderProvider import io.element.android.libraries.matrix.impl.RustMatrixClientFactory -import io.element.android.libraries.matrix.impl.analytics.UtdTracker import io.element.android.libraries.matrix.impl.auth.OidcConfigurationProvider import io.element.android.libraries.matrix.impl.auth.RustMatrixAuthenticationService import io.element.android.libraries.matrix.impl.paths.SessionPathsFactory @@ -56,7 +55,7 @@ class MainActivity : ComponentActivity() { userCertificatesProvider = userCertificatesProvider, proxyProvider = proxyProvider, clock = DefaultSystemClock(), - utdTracker = UtdTracker(NoopAnalyticsService()), + analyticsService = NoopAnalyticsService(), featureFlagService = AlwaysEnabledFeatureFlagService(), timelineEventTypeFilterFactory = RustTimelineEventTypeFilterFactory(), clientBuilderProvider = RustClientBuilderProvider(), From 2230163bbed9ee269b46750c4a76d3c110b35852 Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 4 Oct 2024 12:37:33 +0200 Subject: [PATCH 11/16] dep | Adapt rust-sdk, rename PreviouslyVerified to VerificationViolation --- .../messages/impl/timeline/components/MessageShieldView.kt | 6 +++--- .../matrix/api/timeline/item/event/MessageShield.kt | 6 +++--- .../impl/timeline/item/event/EventTimelineItemMapper.kt | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt index 4e2b2f3eee..c1a4837c12 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/MessageShieldView.kt @@ -79,7 +79,7 @@ internal fun MessageShield.toText(): String { is MessageShield.UnsignedDevice -> CommonStrings.event_shield_reason_unsigned_device is MessageShield.UnverifiedIdentity -> CommonStrings.event_shield_reason_unverified_identity is MessageShield.SentInClear -> CommonStrings.event_shield_reason_sent_in_clear - is MessageShield.PreviouslyVerified -> CommonStrings.event_shield_reason_previously_verified + is MessageShield.VerificationViolation -> CommonStrings.event_shield_reason_previously_verified } ) } @@ -91,7 +91,7 @@ internal fun MessageShield.toIcon(): ImageVector { is MessageShield.UnknownDevice, is MessageShield.UnsignedDevice, is MessageShield.UnverifiedIdentity, - is MessageShield.PreviouslyVerified -> CompoundIcons.HelpSolid() + is MessageShield.VerificationViolation -> CompoundIcons.HelpSolid() is MessageShield.SentInClear -> CompoundIcons.LockOff() } } @@ -120,7 +120,7 @@ internal fun MessageShieldViewPreview() { shield = MessageShield.SentInClear(false) ) MessageShieldView( - shield = MessageShield.PreviouslyVerified(false) + shield = MessageShield.VerificationViolation(false) ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt index dd1fdd12ee..d2c0c6bd3c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageShield.kt @@ -26,8 +26,8 @@ sealed interface MessageShield { /** An unencrypted event in an encrypted room. */ data class SentInClear(val isCritical: Boolean) : MessageShield - /** The sender was previously verified but changed their identity. */ - data class PreviouslyVerified(val isCritical: Boolean) : MessageShield + /** The sender was previously verified but is not anymore. */ + data class VerificationViolation(val isCritical: Boolean) : MessageShield } val MessageShield.isCritical: Boolean @@ -37,5 +37,5 @@ val MessageShield.isCritical: Boolean is MessageShield.UnsignedDevice -> isCritical is MessageShield.UnverifiedIdentity -> isCritical is MessageShield.SentInClear -> isCritical - is MessageShield.PreviouslyVerified -> isCritical + is MessageShield.VerificationViolation -> isCritical } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt index 9fae19bc1d..1291c9e6b8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventTimelineItemMapper.kt @@ -164,7 +164,7 @@ private fun ShieldState?.map(): MessageShield? { ShieldStateCode.UNSIGNED_DEVICE -> MessageShield.UnsignedDevice(isCritical) ShieldStateCode.UNVERIFIED_IDENTITY -> MessageShield.UnverifiedIdentity(isCritical) ShieldStateCode.SENT_IN_CLEAR -> MessageShield.SentInClear(isCritical) - ShieldStateCode.PREVIOUSLY_VERIFIED -> MessageShield.PreviouslyVerified(isCritical) + ShieldStateCode.VERIFICATION_VIOLATION -> MessageShield.VerificationViolation(isCritical) } } From f9cdb5af7060ccf3fe303cd4225ec89258e7cbee Mon Sep 17 00:00:00 2001 From: Jorge Martin Espinosa Date: Fri, 4 Oct 2024 13:57:46 +0200 Subject: [PATCH 12/16] Disable configuration cache in the CI by default (#3601) --- .github/workflows/build.yml | 2 +- .github/workflows/build_enterprise.yml | 2 +- .github/workflows/maestro.yml | 2 +- .github/workflows/nightly.yml | 2 +- .github/workflows/nightlyReports.yml | 2 +- .github/workflows/nightly_enterprise.yml | 2 +- .github/workflows/quality.yml | 2 +- .github/workflows/recordScreenshots.yml | 2 +- .github/workflows/release.yml | 2 +- .github/workflows/sonar.yml | 2 +- .github/workflows/tests.yml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 98660acf30..39e4364658 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: debug: diff --git a/.github/workflows/build_enterprise.yml b/.github/workflows/build_enterprise.yml index c20d87c809..4370c75d0c 100644 --- a/.github/workflows/build_enterprise.yml +++ b/.github/workflows/build_enterprise.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: build: diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index 6ba8279e60..3b11b427ae 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -9,7 +9,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: build-apk: diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index e661dc02ff..3ad597e658 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -8,7 +8,7 @@ on: env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: nightly: diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 3b8184f6af..e49bcb9663 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -9,7 +9,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: nightlyReports: diff --git a/.github/workflows/nightly_enterprise.yml b/.github/workflows/nightly_enterprise.yml index 677977e5c4..7d12ac66a8 100644 --- a/.github/workflows/nightly_enterprise.yml +++ b/.github/workflows/nightly_enterprise.yml @@ -8,7 +8,7 @@ on: env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: nightly: diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index 5d69ac3c64..b69c693f19 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: checkScript: diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index e7a30ec9fb..ef3630ee39 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -7,7 +7,7 @@ on: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: record: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 87ffcb76fe..99ee377c00 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -8,7 +8,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: gplay: diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index cda6219c26..c3b8cdf497 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g - CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --stacktrace --warn -Dsonar.gradle.skipCompile=true --no-configuration-cache GROUP: ${{ format('sonar-{0}', github.ref) }} jobs: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 92fecbe8bd..778a173e19 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,7 +10,7 @@ on: # Enrich gradle.properties for CI/CD env: GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx7g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options=-Xmx2g -XX:+UseG1GC - CI_GRADLE_ARG_PROPERTIES: --stacktrace -Dsonar.gradle.skipCompile=true --no-daemon + CI_GRADLE_ARG_PROPERTIES: --stacktrace --no-daemon -Dsonar.gradle.skipCompile=true --no-configuration-cache jobs: tests: From eb78f32e8cab9f082a07d60672e35903fcebe876 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Fri, 4 Oct 2024 15:45:23 +0200 Subject: [PATCH 13/16] Fix screenshot recording in CI --- .github/workflows/recordScreenshots.yml | 4 +++- .github/workflows/scripts/recordScreenshots.sh | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index ef3630ee39..161e2ade89 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -7,7 +7,8 @@ on: # Enrich gradle.properties for CI/CD env: - GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true --no-configuration-cache + GRADLE_OPTS: -Dorg.gradle.jvmargs=-Xmx9g -XX:MaxMetaspaceSize=512m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError -XX:+UseG1GC -Dkotlin.daemon.jvm.options=-Xmx4g -Dsonar.gradle.skipCompile=true + CI_GRADLE_ARG_PROPERTIES: --no-configuration-cache jobs: record: @@ -48,3 +49,4 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.DANGER_GITHUB_API_TOKEN || secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ secrets.GITHUB_REPOSITORY }} + GRADLE_ARGS: ${{ env.CI_GRADLE_ARG_PROPERTIES }} diff --git a/.github/workflows/scripts/recordScreenshots.sh b/.github/workflows/scripts/recordScreenshots.sh index 5af6d04e38..490eda4493 100755 --- a/.github/workflows/scripts/recordScreenshots.sh +++ b/.github/workflows/scripts/recordScreenshots.sh @@ -51,10 +51,10 @@ if [[ -z ${REPO} ]]; then fi echo "Deleting previous screenshots" -./gradlew removeOldSnapshots --stacktrace --warn +./gradlew removeOldSnapshots --stacktrace --warn $GRADLE_ARGS echo "Record screenshots" -./gradlew recordPaparazziDebug --stacktrace +./gradlew recordPaparazziDebug --stacktrace $GRADLE_ARGS echo "Committing changes" git config http.sslVerify false From 02a173f441596ebd374bb1b2bb6fb2d662e8c0a0 Mon Sep 17 00:00:00 2001 From: ElementBot Date: Fri, 4 Oct 2024 14:05:07 +0000 Subject: [PATCH 14/16] Update screenshots --- ...es.messages.impl.typing_MessagesViewWithTyping_Day_0_en.png | 3 --- ...es.messages.impl.typing_MessagesViewWithTyping_Day_1_en.png | 3 --- ...es.messages.impl.typing_MessagesViewWithTyping_Day_2_en.png | 3 --- ....messages.impl.typing_MessagesViewWithTyping_Night_0_en.png | 3 --- ....messages.impl.typing_MessagesViewWithTyping_Night_1_en.png | 3 --- ....messages.impl.typing_MessagesViewWithTyping_Night_2_en.png | 3 --- 6 files changed, 18 deletions(-) delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_2_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_0_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_1_en.png delete mode 100644 tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_2_en.png diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_0_en.png deleted file mode 100644 index b24db82a01..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a3362ec1abcd1847d44fabdd31537666fe08e0999037064237d4b8cc2af653c4 -size 56202 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_1_en.png deleted file mode 100644 index 0c35f04e3a..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:40b55ace0d2dc9409bbc4f455632697743b0675b6a76892952baae7fabcfc2d7 -size 57043 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_2_en.png deleted file mode 100644 index 0b868e8904..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Day_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:4ba8c98bc110958f2503cc3373bc1e383edfbeb89bbda4e240456ecfa9b8db93 -size 53089 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_0_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_0_en.png deleted file mode 100644 index 1219d812cf..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_0_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:663e3766f80fcd1bfa0a8c983f365887dd3738a0114c5b3bb1d70abf352c5eb3 -size 56654 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_1_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_1_en.png deleted file mode 100644 index a686834219..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_1_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2ca3590ef781d41cce91811f796409436beedaa9d62c1f6b325c03300f36f4c6 -size 57480 diff --git a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_2_en.png b/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_2_en.png deleted file mode 100644 index af7e08335d..0000000000 --- a/tests/uitests/src/test/snapshots/images/features.messages.impl.typing_MessagesViewWithTyping_Night_2_en.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:387b6a484615c291cf015e2da41ea9c4d70bcfd81f01468aefb6c5a9ad43c0f1 -size 53599 From d371c49fad849665f06166b68d0161993c2e9faf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 14:49:39 +0000 Subject: [PATCH 15/16] Update wysiwyg to v2.37.13 (#3596) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update wysiwyg to v2.37.13 * Update licenses to include AGPL3 and remove unused ones --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín --- app/build.gradle.kts | 3 +-- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 26ad452dc3..74e8abcad8 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -298,14 +298,13 @@ tasks.withType().configureEach { licensee { allow("Apache-2.0") allow("MIT") - allow("GPL-2.0-with-classpath-exception") allow("BSD-2-Clause") allowUrl("https://opensource.org/licenses/MIT") allowUrl("https://developer.android.com/studio/terms.html") - allowUrl("http://openjdk.java.net/legal/gplv2+ce.html") allowUrl("https://www.zetetic.net/sqlcipher/license/") allowUrl("https://jsoup.org/license") allowUrl("https://asm.ow2.io/license.html") + allowUrl("https://www.gnu.org/licenses/agpl-3.0.txt") ignoreDependencies("com.github.matrix-org", "matrix-analytics-events") } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9b4a2d07b0..dbfcc7855e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,7 +45,7 @@ serialization_json = "1.6.3" showkase = "1.0.3" appyx = "1.4.0" sqldelight = "2.0.2" -wysiwyg = "2.37.8" +wysiwyg = "2.37.13" telephoto = "0.13.0" # DI From 98d9abecd9efcd1e303b957a446b0fa7f17802f5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 4 Oct 2024 16:58:19 +0200 Subject: [PATCH 16/16] fix(deps): update dependency io.nlopez.compose.rules:detekt to v0.4.15 (#3595) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update dependency io.nlopez.compose.rules:detekt to v0.4.15 * Fix new detekt issues * Fix KtLint --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Jorge Martín --- build.gradle.kts | 2 +- .../android/features/ftue/impl/welcome/WelcomeView.kt | 2 +- .../features/location/api/internal/StaticMapPlaceholder.kt | 2 +- .../login/impl/accountprovider/AccountProviderView.kt | 2 +- .../messages/impl/ExpandableBottomSheetScaffold.kt | 2 ++ .../element/android/features/messages/impl/MessagesView.kt | 2 +- .../messages/impl/pinned/list/PinnedMessagesListView.kt | 2 +- .../impl/timeline/components/TimelineItemEventRow.kt | 3 +++ .../designsystem/atomic/pages/HeaderFooterPage.kt | 7 +++++++ .../designsystem/components/async/AsyncActionView.kt | 1 + 10 files changed, 19 insertions(+), 6 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 9c90da30ce..01a1c7164c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -48,7 +48,7 @@ allprojects { config.from(files("$rootDir/tools/detekt/detekt.yml")) } dependencies { - detektPlugins("io.nlopez.compose.rules:detekt:0.4.12") + detektPlugins("io.nlopez.compose.rules:detekt:0.4.15") } // KtLint diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt index e9d6e7ae1e..e93a4e7145 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/welcome/WelcomeView.kt @@ -42,8 +42,8 @@ import kotlinx.collections.immutable.persistentListOf @Composable fun WelcomeView( applicationName: String, - modifier: Modifier = Modifier, onContinueClick: () -> Unit, + modifier: Modifier = Modifier, ) { BackHandler(onBack = onContinueClick) OnBoardingPage( diff --git a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt index 4d5c853a3c..8a7a734c2d 100644 --- a/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt +++ b/features/location/api/src/main/kotlin/io/element/android/features/location/api/internal/StaticMapPlaceholder.kt @@ -38,8 +38,8 @@ internal fun StaticMapPlaceholder( contentDescription: String?, width: Dp, height: Dp, - modifier: Modifier = Modifier, onLoadMapClick: () -> Unit, + modifier: Modifier = Modifier, ) { Box( contentAlignment = Alignment.Center, diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt index 25e52de2fd..e13a5b736f 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/accountprovider/AccountProviderView.kt @@ -38,8 +38,8 @@ import io.element.android.libraries.designsystem.theme.components.Text @Composable fun AccountProviderView( item: AccountProvider, - modifier: Modifier = Modifier, onClick: () -> Unit, + modifier: Modifier = Modifier, ) { Column( modifier = modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt index df7c4a60f1..5d9214900a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/ExpandableBottomSheetScaffold.kt @@ -58,6 +58,8 @@ import kotlin.math.roundToInt @Composable internal fun ExpandableBottomSheetScaffold( content: @Composable (padding: PaddingValues) -> Unit, + // False positive, it's not being reused + @Suppress("ContentSlotReused") sheetContent: @Composable (subcomposing: Boolean) -> Unit, sheetDragHandle: @Composable () -> Unit, sheetSwipeEnabled: Boolean, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt index 25ecbcc758..067d0da87b 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/MessagesView.kt @@ -319,8 +319,8 @@ private fun MessagesViewContent( onJoinCallClick: () -> Unit, onViewAllPinnedMessagesClick: () -> Unit, forceJumpToBottomVisibility: Boolean, - modifier: Modifier = Modifier, onSwipeToReply: (TimelineItem.Event) -> Unit, + modifier: Modifier = Modifier, ) { Box( modifier = modifier diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt index 7c1e6a724b..ad62ad616a 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/pinned/list/PinnedMessagesListView.kt @@ -239,8 +239,8 @@ private fun PinnedMessagesListLoaded( private fun TimelineItemEventContentViewWrapper( event: TimelineItem.Event, onLinkClick: (String) -> Unit, - modifier: Modifier = Modifier, onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit, + modifier: Modifier = Modifier, ) { if (event.content is TimelineItemPollContent) { PollTitleView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt index 85af8ff6e9..5a5fd6d470 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRow.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -455,6 +456,8 @@ private fun MessageEventBubbleContent( canShrinkContent: Boolean = false, content: @Composable (onContentLayoutChange: (ContentAvoidingLayoutData) -> Unit) -> Unit, ) { + @Suppress("NAME_SHADOWING") + val content = remember { movableContentOf(content) } when (timestampPosition) { TimestampPosition.Overlay -> Box(modifier, contentAlignment = Alignment.Center) { diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt index e51c2ec761..25adde1cbc 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/atomic/pages/HeaderFooterPage.kt @@ -18,6 +18,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable +import androidx.compose.runtime.movableContentOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -39,6 +41,7 @@ import io.element.android.libraries.designsystem.theme.components.Text * @param footer optional footer. * @param content main content. */ +@Suppress("NAME_SHADOWING") @Composable fun HeaderFooterPage( modifier: Modifier = Modifier, @@ -51,6 +54,10 @@ fun HeaderFooterPage( footer: @Composable () -> Unit = {}, content: @Composable () -> Unit = {}, ) { + val topBar = remember { movableContentOf(topBar) } + val header = remember { movableContentOf(header) } + val footer = remember { movableContentOf(footer) } + val content = remember { movableContentOf(content) } Scaffold( modifier = modifier, topBar = topBar, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt index 6e42909f0b..be3e2ef7d8 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/async/AsyncActionView.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.designsystem.preview.PreviewsDayNight * closed, [onErrorDismiss] will be invoked. If [onRetry] is not null, a retry button will be displayed. * - When loading, display a loading dialog using [progressDialog]. Pass empty lambda to disable. */ +@Suppress("ContentSlotReused") // False positive, the lambdas don't add composable views @Composable fun AsyncActionView( async: AsyncAction,