Merge pull request #997 from vector-im/feature/fga/remove_from_main_thread

Feature/fga/remove from main thread
This commit is contained in:
ganfra 2023-07-31 13:39:06 +02:00 committed by GitHub
commit 7ecbbb0a81
10 changed files with 41 additions and 29 deletions

View file

@ -21,7 +21,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState import androidx.compose.runtime.MutableState
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@ -124,17 +123,12 @@ class MessagesPresenter @AssistedInject constructor(
} }
val inviteProgress = remember { mutableStateOf<Async<Unit>>(Async.Uninitialized) } val inviteProgress = remember { mutableStateOf<Async<Unit>>(Async.Uninitialized) }
var showReinvitePrompt by remember { mutableStateOf(false) }
val showReinvitePrompt by remember( LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) {
hasDismissedInviteDialog, withContext(dispatchers.io) {
composerState.hasFocus, showReinvitePrompt = !hasDismissedInviteDialog && composerState.hasFocus && room.isDirect && room.activeMemberCount == 1L
syncUpdateFlow,
) {
derivedStateOf {
!hasDismissedInviteDialog && composerState.hasFocus && room.isDirect && room.activeMemberCount == 1L
} }
} }
val networkConnectionStatus by networkMonitor.connectivity.collectAsState() val networkConnectionStatus by networkMonitor.connectivity.collectAsState()
val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState() val snackbarMessage by snackbarDispatcher.collectSnackbarMessageAsState()

View file

@ -371,11 +371,15 @@ class MessagesPresenterTest {
assertThat(initialState.showReinvitePrompt).isFalse() assertThat(initialState.showReinvitePrompt).isFalse()
// When the input field is focused we show the alert // When the input field is focused we show the alert
initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true)) initialState.composerState.eventSink(MessageComposerEvents.FocusChanged(true))
val focusedState = awaitItem() val focusedState = consumeItemsUntilPredicate { state ->
state.showReinvitePrompt
}.last()
assertThat(focusedState.showReinvitePrompt).isTrue() assertThat(focusedState.showReinvitePrompt).isTrue()
// If it's dismissed then we stop showing the alert // If it's dismissed then we stop showing the alert
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel)) initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel))
val dismissedState = awaitItem() val dismissedState = consumeItemsUntilPredicate { state ->
!state.showReinvitePrompt
}.last()
assertThat(dismissedState.showReinvitePrompt).isFalse() assertThat(dismissedState.showReinvitePrompt).isFalse()
} }
} }

View file

@ -21,5 +21,5 @@ 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.core.SessionId
interface NotificationService { interface NotificationService {
fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?> suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?>
} }

View file

@ -45,4 +45,5 @@ dependencies {
testImplementation(libs.test.junit) testImplementation(libs.test.junit)
testImplementation(libs.test.truth) testImplementation(libs.test.truth)
testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.matrix.test)
testImplementation(libs.coroutines.test)
} }

View file

@ -103,7 +103,7 @@ class RustMatrixClient constructor(
builder.finish() builder.finish()
} }
private val notificationService = RustNotificationService(sessionId, notificationClient, clock) private val notificationService = RustNotificationService(sessionId, notificationClient, dispatchers, clock)
private val isLoggingOut = AtomicBoolean(false) private val isLoggingOut = AtomicBoolean(false)

View file

@ -16,29 +16,32 @@
package io.element.android.libraries.matrix.impl.notification 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.EventId
import io.element.android.libraries.matrix.api.core.RoomId 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.core.SessionId
import io.element.android.libraries.matrix.api.notification.NotificationData import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.services.toolbox.api.systemclock.SystemClock import io.element.android.services.toolbox.api.systemclock.SystemClock
import kotlinx.coroutines.withContext
import org.matrix.rustcomponents.sdk.NotificationClient import org.matrix.rustcomponents.sdk.NotificationClient
import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.use
class RustNotificationService( class RustNotificationService(
sessionId: SessionId, sessionId: SessionId,
private val notificationClient: NotificationClient, private val notificationClient: NotificationClient,
private val dispatchers: CoroutineDispatchers,
clock: SystemClock, clock: SystemClock,
) : NotificationService { ) : NotificationService {
private val notificationMapper: NotificationMapper = NotificationMapper(sessionId, clock) private val notificationMapper: NotificationMapper = NotificationMapper(sessionId, clock)
override fun getNotification( override suspend fun getNotification(
userId: SessionId, userId: SessionId,
roomId: RoomId, roomId: RoomId,
eventId: EventId, eventId: EventId,
filterByPushRules: Boolean, filterByPushRules: Boolean,
): Result<NotificationData?> { ): Result<NotificationData?> = withContext(dispatchers.io) {
return runCatching { runCatching {
val item = notificationClient.getNotificationWithSlidingSync(roomId.value, eventId.value) val item = notificationClient.getNotificationWithSlidingSync(roomId.value, eventId.value)
item?.use { item?.use {
notificationMapper.map(eventId, roomId, it) notificationMapper.map(eventId, roomId, it)

View file

@ -81,6 +81,7 @@ class RustMatrixTimeline(
lastLoginTimestamp = lastLoginTimestamp, lastLoginTimestamp = lastLoginTimestamp,
isRoomEncrypted = matrixRoom.isEncrypted, isRoomEncrypted = matrixRoom.isEncrypted,
paginationStateFlow = _paginationState, paginationStateFlow = _paginationState,
dispatcher = dispatcher,
) )
private val timelineItemFactory = MatrixTimelineItemMapper( private val timelineItemFactory = MatrixTimelineItemMapper(

View file

@ -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.MatrixTimeline
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem import io.element.android.libraries.matrix.api.timeline.item.virtual.VirtualTimelineItem
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.getAndUpdate import kotlinx.coroutines.flow.getAndUpdate
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Date import java.util.Date
class TimelineEncryptedHistoryPostProcessor( class TimelineEncryptedHistoryPostProcessor(
private val dispatcher: CoroutineDispatcher,
private val lastLoginTimestamp: Date?, private val lastLoginTimestamp: Date?,
private val isRoomEncrypted: Boolean, private val isRoomEncrypted: Boolean,
private val paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState>, private val paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState>,
) { ) {
fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> { suspend fun process(items: List<MatrixTimelineItem>): List<MatrixTimelineItem> = withContext(dispatcher) {
if (!isRoomEncrypted || lastLoginTimestamp == null) return items Timber.d("Process on Thread=${Thread.currentThread()}")
if (!isRoomEncrypted || lastLoginTimestamp == null) return@withContext items
val filteredItems = replaceWithEncryptionHistoryBannerIfNeeded(items) val filteredItems = replaceWithEncryptionHistoryBannerIfNeeded(items)
// Disable back pagination // Disable back pagination
@ -43,7 +48,7 @@ class TimelineEncryptedHistoryPostProcessor(
) )
} }
} }
return filteredItems filteredItems
} }
private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List<MatrixTimelineItem>): List<MatrixTimelineItem> { private fun replaceWithEncryptionHistoryBannerIfNeeded(list: List<MatrixTimelineItem>): List<MatrixTimelineItem> {

View file

@ -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.api.timeline.item.virtual.VirtualTimelineItem
import io.element.android.libraries.matrix.test.room.anEventTimelineItem import io.element.android.libraries.matrix.test.room.anEventTimelineItem
import kotlinx.coroutines.flow.MutableStateFlow 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 org.junit.Test
import java.util.Date import java.util.Date
@ -30,7 +33,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
private val defaultLastLoginTimestamp = Date(1_689_061_264L) private val defaultLastLoginTimestamp = Date(1_689_061_264L)
@Test @Test
fun `given an unencrypted room, nothing is done`() { fun `given an unencrypted room, nothing is done`() = runTest {
val processor = createPostProcessor(isRoomEncrypted = false) val processor = createPostProcessor(isRoomEncrypted = false)
val items = listOf( val items = listOf(
MatrixTimelineItem.Event(0L, anEventTimelineItem()) MatrixTimelineItem.Event(0L, anEventTimelineItem())
@ -39,7 +42,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @Test
fun `given a null lastLoginTimestamp, nothing is done`() { fun `given a null lastLoginTimestamp, nothing is done`() = runTest {
val processor = createPostProcessor(lastLoginTimestamp = null) val processor = createPostProcessor(lastLoginTimestamp = null)
val items = listOf( val items = listOf(
MatrixTimelineItem.Event(0L, anEventTimelineItem()) MatrixTimelineItem.Event(0L, anEventTimelineItem())
@ -48,14 +51,14 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @Test
fun `given an empty list, nothing is done`() { fun `given an empty list, nothing is done`() = runTest {
val processor = createPostProcessor() val processor = createPostProcessor()
val items = emptyList<MatrixTimelineItem>() val items = emptyList<MatrixTimelineItem>()
assertThat(processor.process(items)).isSameInstanceAs(items) assertThat(processor.process(items)).isSameInstanceAs(items)
} }
@Test @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 processor = createPostProcessor()
val items = listOf( val items = listOf(
MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1)) MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time + 1))
@ -64,7 +67,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @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 processor = createPostProcessor()
val items = listOf( val items = listOf(
MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time)) MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time))
@ -74,7 +77,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @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 processor = createPostProcessor()
val items = listOf( val items = listOf(
MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1)) MatrixTimelineItem.Event(0L, anEventTimelineItem(timestamp = defaultLastLoginTimestamp.time - 1))
@ -85,7 +88,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
} }
@Test @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 paginationStateFlow = MutableStateFlow(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = true, isBackPaginating = false))
val processor = createPostProcessor(paginationStateFlow = paginationStateFlow) val processor = createPostProcessor(paginationStateFlow = paginationStateFlow)
val items = listOf( val items = listOf(
@ -102,7 +105,7 @@ class TimelineEncryptedHistoryPostProcessorTest {
assertThat(paginationStateFlow.value).isEqualTo(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = false, isBackPaginating = false)) assertThat(paginationStateFlow.value).isEqualTo(MatrixTimeline.PaginationState(hasMoreToLoadBackwards = false, isBackPaginating = false))
} }
private fun createPostProcessor( private fun TestScope.createPostProcessor(
lastLoginTimestamp: Date? = defaultLastLoginTimestamp, lastLoginTimestamp: Date? = defaultLastLoginTimestamp,
isRoomEncrypted: Boolean = true, isRoomEncrypted: Boolean = true,
paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState> = paginationStateFlow: MutableStateFlow<MatrixTimeline.PaginationState> =
@ -111,5 +114,6 @@ class TimelineEncryptedHistoryPostProcessorTest {
lastLoginTimestamp = lastLoginTimestamp, lastLoginTimestamp = lastLoginTimestamp,
isRoomEncrypted = isRoomEncrypted, isRoomEncrypted = isRoomEncrypted,
paginationStateFlow = paginationStateFlow, paginationStateFlow = paginationStateFlow,
dispatcher = StandardTestDispatcher(testScheduler)
) )
} }

View file

@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService import io.element.android.libraries.matrix.api.notification.NotificationService
class FakeNotificationService : NotificationService { class FakeNotificationService : NotificationService {
override fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?> { override suspend fun getNotification(userId: SessionId, roomId: RoomId, eventId: EventId, filterByPushRules: Boolean): Result<NotificationData?> {
return Result.success(null) return Result.success(null)
} }
} }