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 8818cc6476..2ea112c1cc 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 @@ -21,7 +21,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MutableState import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -124,17 +123,12 @@ class MessagesPresenter @AssistedInject constructor( } val inviteProgress = remember { mutableStateOf>(Async.Uninitialized) } - - val showReinvitePrompt by remember( - hasDismissedInviteDialog, - composerState.hasFocus, - syncUpdateFlow, - ) { - derivedStateOf { - !hasDismissedInviteDialog && composerState.hasFocus && room.isDirect && room.activeMemberCount == 1L + var showReinvitePrompt by remember { mutableStateOf(false) } + LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) { + withContext(dispatchers.io) { + showReinvitePrompt = !hasDismissedInviteDialog && composerState.hasFocus && room.isDirect && room.activeMemberCount == 1L } } - val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index c19dbe7cdc..692d4a4e35 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -371,11 +371,15 @@ class MessagesPresenterTest { assertThat(initialState.showReinvitePrompt).isFalse() // When the input field is focused we show the alert initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) - val focusedState = awaitItem() + val focusedState = consumeItemsUntilPredicate { state -> + state.showReinvitePrompt + }.last() assertThat(focusedState.showReinvitePrompt).isTrue() // If it's dismissed then we stop showing the alert initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) - val dismissedState = awaitItem() + val dismissedState = consumeItemsUntilPredicate { state -> + !state.showReinvitePrompt + }.last() assertThat(dismissedState.showReinvitePrompt).isFalse() } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt index 2046252930..4113953203 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/notification/NotificationService.kt @@ -21,5 +21,5 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId interface NotificationService { - fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result + suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result } diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 112cc0777c..499e36988c 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -45,4 +45,5 @@ dependencies { testImplementation(libs.test.junit) testImplementation(libs.test.truth) testImplementation(projects.libraries.matrix.test) + testImplementation(libs.coroutines.test) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index 89576cad3a..09fd637dfd 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -103,7 +103,7 @@ class RustMatrixClient constructor( builder.finish() } - private val notificationService = RustNotificationService(sessionId, notificationClient, clock) + private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock) private val isLoggingOut = AtomicBoolean(false) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt index 2c4258da11..bb281ba4d7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/RustNotificationService.kt @@ -16,29 +16,32 @@ package io.element.android.libraries.matrix.impl.notification +import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.use class RustNotificationService( sessionId: SessionId, private val notificationClient: NotificationClient, + private val dispatchers: CoroutineDispatchers, clock: SystemClock, ) : NotificationService { private val notificationMapper: NotificationMapper = NotificationMapper(sessionId, clock) - override fun getNotification( + override suspend fun getNotification( userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean, - ): Result { - return runCatching { + ): Result = withContext(dispatchers.io) { + runCatching { val item = notificationClient.getNotificationWithSlidingSync(roomId.value, eventId.value) item?.use { notificationMapper.map(eventId, roomId, it) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt index 12e5164307..9ec44af998 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/RustMatrixTimeline.kt @@ -81,6 +81,7 @@ class RustMatrixTimeline( lastLoginTimestamp = lastLoginTimestamp, isRoomEncrypted = matrixRoom.isEncrypted, paginationStateFlow = _paginationState, + dispatcher = dispatcher, ) private val timelineItemFactory = MatrixTimelineItemMapper( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt index 0fe12e6391..b273bef21b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessor.kt @@ -19,18 +19,23 @@ package io.element.android.libraries.matrix.impl.timeline.postprocessor import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.getAndUpdate +import kotlinx.coroutines.withContext +import timber.log.Timber import java.util.Date class TimelineEncryptedHistoryPostProcessor( + private val dispatcher: CoroutineDispatcher, private val lastLoginTimestamp: Date?, private val isRoomEncrypted: Boolean, private val paginationStateFlow: MutableStateFlow, ) { - fun process(items: List): List { - if (!isRoomEncrypted || lastLoginTimestamp == null) return items + suspend fun process(items: List): List = withContext(dispatcher) { + Timber.d("Process on Thread=${Thread.currentThread()}") + if (!isRoomEncrypted || lastLoginTimestamp == null) return@withContext items val filteredItems = replaceWithEncryptionHistoryBannerIfNeeded(items) // Disable back pagination @@ -43,7 +48,7 @@ class TimelineEncryptedHistoryPostProcessor( ) } } - return filteredItems + filteredItems } private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List): List { diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt index 3270f6048f..63920cd6ce 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/timeline/postprocessor/TimelineEncryptedHistoryPostProcessorTest.kt @@ -22,6 +22,9 @@ import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.test.room.anEventTimelineItem import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runTest import org.junit.Test import java.util.Date @@ -30,7 +33,7 @@ class TimelineEncryptedHistoryPostProcessorTest { private val defaultLastLoginTimestamp = Date(1_689_061_264L) @Test - fun `given an unencrypted room, nothing is done`() { + fun `given an unencrypted room, nothing is done`() = runTest { val processor = createPostProcessor(isRoomEncrypted = false) val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem()) @@ -39,7 +42,7 @@ class TimelineEncryptedHistoryPostProcessorTest { } @Test - fun `given a null lastLoginTimestamp, nothing is done`() { + fun `given a null lastLoginTimestamp, nothing is done`() = runTest { val processor = createPostProcessor(lastLoginTimestamp = null) val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem()) @@ -48,14 +51,14 @@ class TimelineEncryptedHistoryPostProcessorTest { } @Test - fun `given an empty list, nothing is done`() { + fun `given an empty list, nothing is done`() = runTest { val processor = createPostProcessor() val items = emptyList() assertThat(processor.process(items)).isSameInstanceAs(items) } @Test - fun `given a list with no items before lastLoginTimestamp, nothing is done`() { + fun `given a list with no items before lastLoginTimestamp, nothing is done`() = runTest { val processor = createPostProcessor() val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)) @@ -64,7 +67,7 @@ class TimelineEncryptedHistoryPostProcessorTest { } @Test - fun `given a list with an item with equal timestamp as lastLoginTimestamp, it's replaced`() { + fun `given a list with an item with equal timestamp as lastLoginTimestamp, it's replaced`() = runTest { val processor = createPostProcessor() val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)) @@ -74,7 +77,7 @@ class TimelineEncryptedHistoryPostProcessorTest { } @Test - fun `given a list with an item with a lower timestamp than lastLoginTimestamp, it's replaced`() { + fun `given a list with an item with a lower timestamp than lastLoginTimestamp, it's replaced`() = runTest { val processor = createPostProcessor() val items = listOf( MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)) @@ -85,7 +88,7 @@ class TimelineEncryptedHistoryPostProcessorTest { } @Test - fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, they're replaced and the user can't back paginate`() { + fun `given a list with several with lower or equal timestamps than lastLoginTimestamp, they're replaced and the user can't back paginate`() = runTest { val paginationStateFlow = MutableStateFlow(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false)) val processor = createPostProcessor(paginationStateFlow = paginationStateFlow) val items = listOf( @@ -102,7 +105,7 @@ class TimelineEncryptedHistoryPostProcessorTest { assertThat(paginationStateFlow.value).isEqualTo(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = false, isBackPaginating = false)) } - private fun createPostProcessor( + private fun TestScope.createPostProcessor( lastLoginTimestamp: Date? = defaultLastLoginTimestamp, isRoomEncrypted: Boolean = true, paginationStateFlow: MutableStateFlow = @@ -111,5 +114,6 @@ class TimelineEncryptedHistoryPostProcessorTest { lastLoginTimestamp = lastLoginTimestamp, isRoomEncrypted = isRoomEncrypted, paginationStateFlow = paginationStateFlow, + dispatcher = StandardTestDispatcher(testScheduler) ) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt index 9eb5a20ba4..5b863e65f8 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/notification/FakeNotificationService.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationService class FakeNotificationService : NotificationService { - override fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result { + override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result { return Result.success(null) } }