Merge pull request #997 from vector-im/feature/fga/remove_from_main_thread
Feature/fga/remove from main thread
This commit is contained in:
commit
7ecbbb0a81
10 changed files with 41 additions and 29 deletions
|
|
@ -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()
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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?>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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> {
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue