diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4b534eaa0..a1df487c9b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/maestro.yml b/.github/workflows/maestro.yml index 5ae475767e..a4bf0cc5ae 100644 --- a/.github/workflows/maestro.yml +++ b/.github/workflows/maestro.yml @@ -33,7 +33,7 @@ jobs: # Ensure we are building the branch and not the branch after being merged on develop # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 name: Use JDK 17 with: distribution: 'temurin' # See 'Supported distributions' for available options diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 7f551cb981..4b0b1ff53c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -18,7 +18,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index f88c30da5a..d19e214f99 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -21,7 +21,7 @@ jobs: uses: nschloe/action-cached-lfs-checkout@v1.2.2 - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' @@ -57,7 +57,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index e64d71b48d..c9d2ea7d18 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -35,7 +35,7 @@ jobs: # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/recordScreenshots.yml b/.github/workflows/recordScreenshots.yml index 0833728973..8f72e4ec4a 100644 --- a/.github/workflows/recordScreenshots.yml +++ b/.github/workflows/recordScreenshots.yml @@ -33,7 +33,7 @@ jobs: with: persist-credentials: false - name: ☕️ Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ada58d210..924d115046 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 1fbf9d4ec8..d73a885220 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -27,7 +27,7 @@ jobs: # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - name: Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e85a355466..49951cf350 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -39,7 +39,7 @@ jobs: # https://github.com/actions/checkout/issues/881 ref: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.ref }} - name: ☕️ Use JDK 17 - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' # See 'Supported distributions' for available options java-version: '17' diff --git a/build.gradle.kts b/build.gradle.kts index e29f7c393b..7d06c2bf18 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -376,3 +376,22 @@ subprojects { } } } + +subprojects { + tasks.withType().configureEach { + kotlinOptions { + if (project.findProperty("composeCompilerReports") == "true") { + freeCompilerArgs += listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" + ) + } + if (project.findProperty("composeCompilerMetrics") == "true") { + freeCompilerArgs += listOf( + "-P", + "plugin:androidx.compose.compiler.plugins.kotlin:metricsDestination=${project.layout.buildDirectory.asFile.get().absolutePath}/compose_compiler" + ) + } + } + } +} diff --git a/changelog.d/+make-matrix-classes-immutable.misc b/changelog.d/+make-matrix-classes-immutable.misc new file mode 100644 index 0000000000..0dcb7dedb9 --- /dev/null +++ b/changelog.d/+make-matrix-classes-immutable.misc @@ -0,0 +1 @@ +Make most code used in Compose from `:libraries:matrix` and derived classes Immutable or Stable. diff --git a/changelog.d/+set-default-power-level-to-join-calls.bugfix b/changelog.d/+set-default-power-level-to-join-calls.bugfix new file mode 100644 index 0000000000..fa143aa850 --- /dev/null +++ b/changelog.d/+set-default-power-level-to-join-calls.bugfix @@ -0,0 +1 @@ +Set a default power level to join calls. Also, create new rooms taking this power level into account. 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 1a8ae63645..a765a395a3 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 @@ -79,6 +79,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.MessageEventType +import io.element.android.libraries.matrix.api.user.CurrentSessionIdHolder import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailInfo import io.element.android.libraries.matrix.ui.components.AttachmentThumbnailType import io.element.android.libraries.matrix.ui.room.canRedactAsState @@ -108,6 +109,7 @@ class MessagesPresenter @AssistedInject constructor( private val featureFlagsService: FeatureFlagService, @Assisted private val navigator: MessagesNavigator, private val buildMeta: BuildMeta, + private val currentSessionIdHolder: CurrentSessionIdHolder, ) : Presenter { private val timelinePresenter = timelinePresenterFactory.create(navigator = navigator) @@ -133,10 +135,10 @@ class MessagesPresenter @AssistedInject constructor( val syncUpdateFlow = room.syncUpdateFlow.collectAsState() val userHasPermissionToSendMessage by room.canSendMessageAsState(type = MessageEventType.ROOM_MESSAGE, updateKey = syncUpdateFlow.value) val userHasPermissionToRedact by room.canRedactAsState(updateKey = syncUpdateFlow.value) - val roomName: Async by remember(roomInfo?.name) { + val roomName: Async by remember { derivedStateOf { roomInfo?.name?.let { Async.Success(it) } ?: Async.Uninitialized } } - val roomAvatar: Async by remember(roomInfo?.avatarUrl) { + val roomAvatar: Async by remember { derivedStateOf { roomInfo?.avatarData()?.let { Async.Success(it) } ?: Async.Uninitialized } } @@ -144,6 +146,16 @@ class MessagesPresenter @AssistedInject constructor( mutableStateOf(false) } + var canJoinCall by rememberSaveable { + mutableStateOf(false) + } + + LaunchedEffect(currentSessionIdHolder.current) { + withContext(dispatchers.io) { + canJoinCall = room.canUserJoinCall(userId = currentSessionIdHolder.current).getOrDefault(false) + } + } + val inviteProgress = remember { mutableStateOf>(Async.Uninitialized) } var showReinvitePrompt by remember { mutableStateOf(false) } LaunchedEffect(hasDismissedInviteDialog, composerState.hasFocus, syncUpdateFlow) { @@ -162,8 +174,6 @@ class MessagesPresenter @AssistedInject constructor( val enableTextFormatting by preferencesStore.isRichTextEditorEnabledFlow().collectAsState(initial = true) var enableVoiceMessages by remember { mutableStateOf(false) } - // TODO add min power level to use this feature in the future? - val enableInRoomCalls = true LaunchedEffect(featureFlagsService) { enableVoiceMessages = featureFlagsService.isFeatureEnabled(FeatureFlags.VoiceMessages) } @@ -193,6 +203,12 @@ class MessagesPresenter @AssistedInject constructor( } } + val callState = when { + !canJoinCall -> RoomCallState.DISABLED + roomInfo?.hasRoomCall == true -> RoomCallState.ONGOING + else -> RoomCallState.ENABLED + } + return MessagesState( roomId = room.roomId, roomName = roomName, @@ -213,9 +229,8 @@ class MessagesPresenter @AssistedInject constructor( inviteProgress = inviteProgress.value, enableTextFormatting = enableTextFormatting, enableVoiceMessages = enableVoiceMessages, - enableInRoomCalls = enableInRoomCalls, appName = buildMeta.applicationName, - isCallOngoing = roomInfo?.hasRoomCall ?: false, + callState = callState, eventSink = { handleEvents(it) } ) } 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 325c695988..7d342ab107 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 @@ -51,8 +51,13 @@ data class MessagesState( val showReinvitePrompt: Boolean, val enableTextFormatting: Boolean, val enableVoiceMessages: Boolean, - val enableInRoomCalls: Boolean, - val isCallOngoing: Boolean, + val callState: RoomCallState, val appName: String, val eventSink: (MessagesEvents) -> Unit ) + +enum class RoomCallState { + ENABLED, + ONGOING, + DISABLED +} 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 720c385f89..00c15fb410 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 @@ -66,7 +66,7 @@ open class MessagesStateProvider : PreviewParameterProvider { ), ), aMessagesState().copy( - isCallOngoing = true, + callState = RoomCallState.ONGOING, ), aMessagesState().copy( enableVoiceMessages = true, @@ -75,6 +75,9 @@ open class MessagesStateProvider : PreviewParameterProvider { showSendFailureDialog = true ), ), + aMessagesState().copy( + callState = RoomCallState.DISABLED, + ), ) } @@ -117,8 +120,7 @@ fun aMessagesState() = MessagesState( showReinvitePrompt = false, enableTextFormatting = true, enableVoiceMessages = true, - enableInRoomCalls = true, - isCallOngoing = false, + callState = RoomCallState.ENABLED, appName = "Element", eventSink = {} ) 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 d7e4bfedc9..3a26079a3d 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 @@ -191,10 +191,9 @@ fun MessagesView( MessagesViewTopBar( roomName = state.roomName.dataOrNull(), roomAvatar = state.roomAvatar.dataOrNull(), - inRoomCallsEnabled = state.enableInRoomCalls, + callState = state.callState, onBackPressed = onBackPressed, onRoomDetailsClicked = onRoomDetailsClicked, - isCallOngoing = state.isCallOngoing, onJoinCallClicked = onJoinCallClicked, ) } @@ -449,8 +448,7 @@ private fun MessagesViewComposerBottomSheetContents( private fun MessagesViewTopBar( roomName: String?, roomAvatar: AvatarData?, - inRoomCallsEnabled: Boolean, - isCallOngoing: Boolean, + callState: RoomCallState, onRoomDetailsClicked: () -> Unit, onJoinCallClicked: () -> Unit, onBackPressed: () -> Unit, @@ -477,13 +475,11 @@ private fun MessagesViewTopBar( } }, actions = { - if (inRoomCallsEnabled) { - if (isCallOngoing) { - JoinCallMenuItem(onJoinCallClicked = onJoinCallClicked) - } else { - IconButton(onClick = onJoinCallClicked) { - Icon(CompoundIcons.VideoCall, contentDescription = stringResource(CommonStrings.a11y_start_call)) - } + if (callState == RoomCallState.ONGOING) { + JoinCallMenuItem(onJoinCallClicked = onJoinCallClicked) + } else { + IconButton(onClick = onJoinCallClicked, enabled = callState != RoomCallState.DISABLED) { + Icon(CompoundIcons.VideoCall, contentDescription = stringResource(CommonStrings.a11y_start_call)) } } Spacer(Modifier.width(8.dp)) diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt index 8443ffafa2..8812f9ca8f 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/TimelineItemEventRowWithReplyPreview.kt @@ -45,6 +45,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VideoMessageType import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageType +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf @PreviewsDayNight @Composable @@ -127,8 +129,8 @@ class InReplyToDetailsProvider : PreviewParameterProvider { question = "Poll which are being replied.", kind = PollKind.Disclosed, maxSelections = 1u, - answers = emptyList(), - votes = emptyMap(), + answers = persistentListOf(), + votes = persistentMapOf(), endTime = null ), ).map { diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt index e75e49c1e3..7c30350cc4 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenter.kt @@ -17,7 +17,6 @@ package io.element.android.features.messages.impl.timeline.components.reactionsummary 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 @@ -38,10 +37,6 @@ class ReactionSummaryPresenter @Inject constructor( ) : Presenter { @Composable override fun present(): ReactionSummaryState { - LaunchedEffect(Unit) { - room.updateMembers() - } - val membersState by room.membersStateFlow.collectAsState() val target: MutableState = remember { 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 5855bb47a7..1bed98ae06 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 @@ -92,12 +92,14 @@ import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.waitForPredicate import io.mockk.mockk +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test import kotlin.time.Duration.Companion.milliseconds +@Suppress("LargeClass") class MessagesPresenterTest { @get:Rule @@ -125,6 +127,21 @@ class MessagesPresenterTest { } } + @Test + fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest { + val room = FakeMatrixRoom().apply { + givenCanUserJoinCall(Result.success(false)) + givenRoomInfo(aRoomInfo(hasRoomCall = true)) + } + val presenter = createMessagesPresenter(matrixRoom = room) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = consumeItemsUntilTimeout().last() + assertThat(initialState.callState).isEqualTo(RoomCallState.DISABLED) + } + } + @Test fun `present - handle toggling a reaction`() = runTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) @@ -462,7 +479,7 @@ class MessagesPresenterTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID) room.givenRoomMembersState( MatrixRoomMembersState.Ready( - listOf( + persistentListOf( aRoomMember(userId = A_SESSION_ID, membership = RoomMembershipState.JOIN), aRoomMember(userId = A_SESSION_ID_2, membership = RoomMembershipState.LEAVE), ) @@ -489,7 +506,7 @@ class MessagesPresenterTest { room.givenRoomMembersState( MatrixRoomMembersState.Error( failure = Throwable(), - prevRoomMembers = listOf( + prevRoomMembers = persistentListOf( aRoomMember(userId = A_SESSION_ID, membership = RoomMembershipState.JOIN), aRoomMember(userId = A_SESSION_ID_2, membership = RoomMembershipState.LEAVE), ) @@ -535,7 +552,7 @@ class MessagesPresenterTest { val room = FakeMatrixRoom(sessionId = A_SESSION_ID) room.givenRoomMembersState( MatrixRoomMembersState.Ready( - listOf( + persistentListOf( aRoomMember(userId = A_SESSION_ID, membership = RoomMembershipState.JOIN), aRoomMember(userId = A_SESSION_ID_2, membership = RoomMembershipState.LEAVE), ) @@ -655,6 +672,7 @@ class MessagesPresenterTest { clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(), + currentSessionIdHolder: CurrentSessionIdHolder = CurrentSessionIdHolder(FakeMatrixClient(A_SESSION_ID)), ): MessagesPresenter { val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom) val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter) @@ -723,6 +741,7 @@ class MessagesPresenterTest { featureFlagsService = FakeFeatureFlagService(), buildMeta = aBuildMeta(), dispatchers = coroutineDispatchers, + currentSessionIdHolder = currentSessionIdHolder, ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index b2b75d7e38..ef7891b953 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -78,11 +78,11 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.waitForPredicate import io.mockk.mockk +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.advanceUntilIdle import kotlinx.coroutines.test.runTest -import okhttp3.internal.immutableListOf import org.junit.Rule import org.junit.Test import uniffi.wysiwyg_composer.MentionsState @@ -734,7 +734,7 @@ class MessageComposerPresenterTest { isOneToOne = false, ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready( - immutableListOf(currentUser, invitedUser, bob, david), + persistentListOf(currentUser, invitedUser, bob, david), )) givenCanTriggerRoomNotification(Result.success(true)) } @@ -798,7 +798,7 @@ class MessageComposerPresenterTest { isOneToOne = true, ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready( - immutableListOf(currentUser, invitedUser, bob, david), + persistentListOf(currentUser, invitedUser, bob, david), )) givenCanTriggerRoomNotification(Result.success(true)) } 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 609a264e77..dea1975c6c 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 @@ -55,6 +55,7 @@ import io.element.android.tests.testutils.awaitWithLatch import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.waitForPredicate +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.delay import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -254,18 +255,18 @@ class TimelinePresenterTest { val (alice, bob, charlie) = aMatrixUserList().take(3).mapIndexed { i, user -> ReactionSender(senderId = user.userId, timestamp = now + i * minuteInMillis) } - val oneReaction = listOf( + val oneReaction = persistentListOf( EventReaction( key = "❤️", - senders = listOf(alice, charlie) + senders = persistentListOf(alice, charlie) ), EventReaction( key = "👍", - senders = listOf(alice, bob) + senders = persistentListOf(alice, bob) ), EventReaction( key = "🐶", - senders = listOf(charlie) + senders = persistentListOf(charlie) ), ) timeline.updateTimelineItems { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt index 080dfaeba6..9d70e03fcd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/components/reactionsummary/ReactionSummaryPresenterTests.kt @@ -29,6 +29,7 @@ import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -42,7 +43,7 @@ class ReactionSummaryPresenterTests { private val roomMember = aRoomMember(userId = A_USER_ID, avatarUrl = AN_AVATAR_URL, displayName = A_USER_NAME) private val summaryEvent = ReactionSummaryEvents.ShowReactionSummary(AN_EVENT_ID, listOf(aggregatedReaction), aggregatedReaction.key) private val room = FakeMatrixRoom().apply { - givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) + givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } private val presenter = ReactionSummaryPresenter(room) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt index ab2e429d10..a397acafcd 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentMessageFactoryTest.kt @@ -55,6 +55,7 @@ import io.element.android.libraries.matrix.api.timeline.item.event.VoiceMessageT import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.ui.components.A_BLUR_HASH import io.element.android.libraries.mediaviewer.api.util.FileExtensionExtractorWithoutValidation +import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.test.runTest import org.junit.Test @@ -280,7 +281,7 @@ class TimelineItemContentMessageFactoryTest { ), details = AudioDetails( duration = 1.minutes, - waveform = listOf(1f, 2f), + waveform = persistentListOf(1f, 2f), ), ) ), @@ -293,7 +294,7 @@ class TimelineItemContentMessageFactoryTest { duration = 1.minutes, mediaSource = MediaSource(url = "url", json = null), mimeType = MimeTypes.Ogg, - waveform = listOf(1f, 2f).toImmutableList() + waveform = persistentListOf(1f, 2f) ) assertThat(result).isEqualTo(expected) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactoryTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactoryTest.kt index d6673b4770..2ae88288ae 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactoryTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/factories/event/TimelineItemContentPollFactoryTest.kt @@ -38,6 +38,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID_7 import io.element.android.libraries.matrix.test.A_USER_ID_8 import io.element.android.libraries.matrix.test.A_USER_ID_9 import io.element.android.libraries.matrix.test.FakeMatrixClient +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf +import kotlinx.collections.immutable.toImmutableMap import kotlinx.coroutines.test.runTest import org.junit.Test @@ -55,7 +60,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Disclosed poll - not ended, some votes, including one from current user`() = runTest { - val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(votes = votes), eventId = null) ) @@ -87,7 +92,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Disclosed poll - ended, some votes, including one from current user (winner)`() = runTest { - val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(votes = votes, endTime = 1UL), eventId = null) ) @@ -106,7 +111,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Disclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { - val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } + val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(votes = votes, endTime = 1UL), eventId = null) ) @@ -136,7 +141,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Undisclosed poll - not ended, some votes, including one from current user`() = runTest { - val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes), eventId = null) ) @@ -172,7 +177,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Undisclosed poll - ended, some votes, including one from current user (winner)`() = runTest { - val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id } + val votes = MY_USER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(pollKind = PollKind.Undisclosed, votes = votes, endTime = 1UL), eventId = null) ) @@ -192,7 +197,7 @@ internal class TimelineItemContentPollFactoryTest { @Test fun `Undisclosed poll - ended, some votes, including one from current user (not winner) and two winning votes`() = runTest { - val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id } + val votes = OTHER_WINNING_VOTES.mapKeys { it.key.id }.toImmutableMap() Truth.assertThat( factory.create(aPollContent(PollKind.Undisclosed).copy(votes = votes, endTime = 1UL), eventId = null) ) @@ -221,13 +226,13 @@ internal class TimelineItemContentPollFactoryTest { private fun aPollContent( pollKind: PollKind = PollKind.Disclosed, - votes: Map> = emptyMap(), + votes: ImmutableMap> = persistentMapOf(), endTime: ULong? = null, ): PollContent = PollContent( question = A_POLL_QUESTION, kind = pollKind, maxSelections = 1UL, - answers = listOf(A_POLL_ANSWER_1, A_POLL_ANSWER_2, A_POLL_ANSWER_3, A_POLL_ANSWER_4), + answers = persistentListOf(A_POLL_ANSWER_1, A_POLL_ANSWER_2, A_POLL_ANSWER_3, A_POLL_ANSWER_4), votes = votes, endTime = endTime, ) @@ -275,17 +280,17 @@ internal class TimelineItemContentPollFactoryTest { private val A_POLL_ANSWER_3 = PollAnswer("id_3", "French Fries") private val A_POLL_ANSWER_4 = PollAnswer("id_4", "Hamburger") - private val MY_USER_WINNING_VOTES = mapOf( - A_POLL_ANSWER_1 to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), - A_POLL_ANSWER_2 to listOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner - A_POLL_ANSWER_3 to emptyList(), - A_POLL_ANSWER_4 to listOf(A_USER_ID_10), + private val MY_USER_WINNING_VOTES = persistentMapOf( + A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4), + A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_5, A_USER_ID_6, A_USER_ID_7, A_USER_ID_8, A_USER_ID_9), // winner + A_POLL_ANSWER_3 to persistentListOf(), + A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_10), ) - private val OTHER_WINNING_VOTES = mapOf( - A_POLL_ANSWER_1 to listOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner - A_POLL_ANSWER_2 to listOf(A_USER_ID /* my vote */, A_USER_ID_6), - A_POLL_ANSWER_3 to emptyList(), - A_POLL_ANSWER_4 to listOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner + private val OTHER_WINNING_VOTES = persistentMapOf( + A_POLL_ANSWER_1 to persistentListOf(A_USER_ID_2, A_USER_ID_3, A_USER_ID_4, A_USER_ID_5), // winner + A_POLL_ANSWER_2 to persistentListOf(A_USER_ID /* my vote */, A_USER_ID_6), + A_POLL_ANSWER_3 to persistentListOf(), + A_POLL_ANSWER_4 to persistentListOf(A_USER_ID_7, A_USER_ID_8, A_USER_ID_9, A_USER_ID_10), // winner ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt index 8e776d1eea..3a003e23a9 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/timeline/RedactedVoiceMessageManagerTest.kt @@ -30,6 +30,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.mediaplayer.api.MediaPlayer import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -88,8 +89,8 @@ fun aRedactedMatrixTimeline(eventId: EventId) = listOf( isOwn = false, isRemote = false, localSendState = null, - reactions = listOf(), - receipts = listOf(), + reactions = persistentListOf(), + receipts = persistentListOf(), sender = A_USER_ID, senderProfile = ProfileTimelineDetails.Unavailable, timestamp = 9442, diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index ae54c6a1e5..8cb55704d5 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.test.room.anEventTimelineItem import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -539,7 +540,7 @@ class CreatePollPresenterTest { private fun anExistingPoll() = aPollContent( question = "Do you like polls?", - answers = listOf( + answers = persistentListOf( PollAnswer("1", "Yes"), PollAnswer("2", "No"), PollAnswer("2", "Maybe"), diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt index 5d8b149676..1be23be3f6 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingPresenter.kt @@ -33,6 +33,7 @@ import io.element.android.libraries.matrix.api.notificationsettings.Notification import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.flow.debounce @@ -84,7 +85,7 @@ class EditDefaultNotificationSettingPresenter @AssistedInject constructor( return EditDefaultNotificationSettingState( isOneToOne = isOneToOne, mode = mode.value, - roomsWithUserDefinedMode = roomsWithUserDefinedMode.value, + roomsWithUserDefinedMode = roomsWithUserDefinedMode.value.toImmutableList(), changeNotificationSettingAction = changeNotificationSettingAction.value, eventSink = ::handleEvents ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt index e8590ec27f..60e8dfd10a 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingState.kt @@ -19,11 +19,12 @@ package io.element.android.features.preferences.impl.notifications.edit import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary +import kotlinx.collections.immutable.ImmutableList data class EditDefaultNotificationSettingState( val isOneToOne: Boolean, val mode: RoomNotificationMode?, - val roomsWithUserDefinedMode: List, + val roomsWithUserDefinedMode: ImmutableList, val changeNotificationSettingAction: Async, val eventSink: (EditDefaultNotificationSettingStateEvents) -> Unit, ) diff --git a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt index 71fbfb1e1b..e706701f8d 100644 --- a/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt +++ b/features/preferences/impl/src/main/kotlin/io/element/android/features/preferences/impl/notifications/edit/EditDefaultNotificationSettingStateProvider.kt @@ -22,6 +22,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.roomlist.RoomSummary import io.element.android.libraries.matrix.api.roomlist.RoomSummaryDetails +import kotlinx.collections.immutable.persistentListOf open class EditDefaultNotificationSettingStateProvider: PreviewParameterProvider { override val values: Sequence @@ -39,7 +40,7 @@ private fun anEditDefaultNotificationSettingsState( ) = EditDefaultNotificationSettingState( isOneToOne = isOneToOne, mode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, - roomsWithUserDefinedMode = listOf(aRoomSummary()), + roomsWithUserDefinedMode = persistentListOf(aRoomSummary()), changeNotificationSettingAction = changeNotificationSettingAction, eventSink = {} ) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt index f1e6447a10..4954af8efc 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt @@ -90,7 +90,7 @@ fun aRoomMember( isIgnored = isIgnored, ) -fun aRoomMemberList() = listOf( +fun aRoomMemberList() = persistentListOf( anAlice(), aBob(), aRoomMember(UserId("@carol:server.org"), "Carol"), diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 577b01a974..6ffba16377 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -49,6 +49,7 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest @@ -129,7 +130,7 @@ class RoomDetailsPresenterTests { isEncrypted = true, isDirect = true, ).apply { - val roomMembers = listOf(myRoomMember, otherRoomMember) + val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) } val presenter = createRoomDetailsPresenter(room) @@ -220,7 +221,7 @@ class RoomDetailsPresenterTests { isEncrypted = true, isDirect = true, ).apply { - val roomMembers = listOf(myRoomMember, otherRoomMember) + val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) @@ -253,7 +254,7 @@ class RoomDetailsPresenterTests { isDirect = true, topic = null, ).apply { - val roomMembers = listOf(myRoomMember, otherRoomMember) + val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt index 4033038b69..9763ed280b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.usersearch.test.FakeUserRepository import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -167,7 +168,7 @@ internal class RoomInviteMembersPresenterTest { matrixRoom = FakeMatrixRoom().apply { givenRoomMembersState( MatrixRoomMembersState.Ready( - listOf( + persistentListOf( aRoomMember(userId = joinedUser.userId, membership = RoomMembershipState.JOIN), aRoomMember(userId = invitedUser.userId, membership = RoomMembershipState.INVITE), ) @@ -227,7 +228,7 @@ internal class RoomInviteMembersPresenterTest { roomMemberListDataSource = createDataSource(FakeMatrixRoom().apply { givenRoomMembersState( MatrixRoomMembersState.Ready( - listOf( + persistentListOf( aRoomMember(userId = joinedUser.userId, membership = RoomMembershipState.JOIN), aRoomMember(userId = invitedUser.userId, membership = RoomMembershipState.INVITE), ) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index 1fb5cc15db..9cf01c91e2 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -39,6 +39,7 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -56,7 +57,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom().apply { givenUserDisplayNameResult(Result.success("A custom name")) givenUserAvatarUrlResult(Result.success("A custom avatar")) - givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) + givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } val presenter = createRoomMemberDetailsPresenter( room = room, @@ -83,7 +84,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom().apply { givenUserDisplayNameResult(Result.failure(Throwable())) givenUserAvatarUrlResult(Result.failure(Throwable())) - givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) + givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } val presenter = createRoomMemberDetailsPresenter( room = room, @@ -106,7 +107,7 @@ class RoomMemberDetailsPresenterTests { val room = aMatrixRoom().apply { givenUserDisplayNameResult(Result.success(null)) givenUserAvatarUrlResult(Result.success(null)) - givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) + givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } val presenter = createRoomMemberDetailsPresenter( room = room, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt index 3a445e6cdc..cfaf9b8321 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/model/RoomListRoomSummary.kt @@ -23,7 +23,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.room.RoomNotificationMode @Immutable -data class RoomListRoomSummary constructor( +data class RoomListRoomSummary( val id: String, val roomId: RoomId, val name: String = "", diff --git a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt index 403324816f..8f8795ed78 100644 --- a/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt +++ b/features/verifysession/impl/src/test/kotlin/io/element/android/features/verifysession/impl/VerifySelfSessionPresenterTests.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService import io.element.android.tests.testutils.WarmUpRule +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -132,7 +133,7 @@ class VerifySelfSessionPresenterTests { presenter.present() }.test { requestVerificationAndAwaitVerifyingState(service) - service.givenVerificationFlowState(VerificationFlowState.ReceivedVerificationData(emptyList())) + service.givenVerificationFlowState(VerificationFlowState.ReceivedVerificationData(persistentListOf())) ensureAllEventsConsumed() } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4517c809a3..552ab24d96 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ serialization_json = "1.6.1" showkase = "1.0.2" appyx = "1.4.0" sqldelight = "2.0.0" -wysiwyg = "2.19.0" +wysiwyg = "2.20.0" # DI dagger = "2.48.1" @@ -89,7 +89,7 @@ androidx_preference = "androidx.preference:preference:1.2.1" androidx_webkit = "androidx.webkit:webkit:1.8.0" androidx_compose_bom = { module = "androidx.compose:compose-bom", version.ref = "compose_bom" } -androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha11" +androidx_compose_material3 = "androidx.compose.material3:material3:1.2.0-alpha12" androidx_compose_ui = { module = "androidx.compose.ui:ui" } androidx_compose_ui_tooling = { module = "androidx.compose.ui:ui-tooling" } androidx_compose_ui_tooling_preview = { module = "androidx.compose.ui:ui-tooling-preview" } @@ -147,7 +147,7 @@ jsoup = "org.jsoup:jsoup:1.17.1" appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = "app.cash.molecule:molecule-runtime:1.3.1" timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.71" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.72" 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" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 01f330a93e..a7a990ab2a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=f2b9ed0faf8472cbe469255ae6c86eddb77076c75191741b4a462f33128dd419 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionSha256Sum=c16d517b50dd28b3f5838f0e844b7520b8f1eb610f2f29de7e4e04a1b7c9c79b +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt index 61130c43db..7e349216f4 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/BackupUploadState.kt @@ -16,6 +16,9 @@ package io.element.android.libraries.matrix.api.encryption +import androidx.compose.runtime.Immutable + +@Immutable sealed interface BackupUploadState { data object Unknown : BackupUploadState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt index 1e83344a02..88525caac2 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/encryption/SteadyStateException.kt @@ -16,6 +16,9 @@ package io.element.android.libraries.matrix.api.encryption +import androidx.compose.runtime.Immutable + +@Immutable sealed interface SteadyStateException { /** * The backup can be deleted. diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt index d8ddca14e7..d3ff0e9f83 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioDetails.kt @@ -16,9 +16,10 @@ package io.element.android.libraries.matrix.api.media +import kotlinx.collections.immutable.ImmutableList import kotlin.time.Duration data class AudioDetails( val duration: Duration, - val waveform: List, + val waveform: ImmutableList, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt index 72caa711cb..e1036011f6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkData.kt @@ -17,18 +17,21 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList /** * This sealed class represents all the permalink cases. * You don't have to instantiate yourself but should use [PermalinkParser] instead. */ +@Immutable sealed interface PermalinkData { data class RoomLink( val roomIdOrAlias: String, val isRoomAlias: Boolean, val eventId: String?, - val viaParameters: List + val viaParameters: ImmutableList ) : PermalinkData /* diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt index 4a89d05276..dcd5221de8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParser.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.api.permalink import android.net.Uri import android.net.UrlQuerySanitizer import io.element.android.libraries.matrix.api.core.MatrixPatterns +import kotlinx.collections.immutable.toImmutableList import timber.log.Timber import java.net.URLDecoder @@ -80,7 +81,7 @@ object PermalinkParser { roomIdOrAlias = decodedIdentifier, isRoomAlias = true, eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, - viaParameters = viaQueryParameters + viaParameters = viaQueryParameters.toImmutableList() ) } else -> PermalinkData.FallbackLink(uri, MatrixPatterns.isGroupId(identifier)) @@ -119,7 +120,7 @@ object PermalinkParser { roomIdOrAlias = identifier, isRoomAlias = false, eventId = extraParameter.takeIf { !it.isNullOrEmpty() && MatrixPatterns.isEventId(it) }, - viaParameters = viaQueryParameters + viaParameters = viaQueryParameters.toImmutableList() ) } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt index f9fbffa4a6..d7c8d7f49c 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoom.kt @@ -132,6 +132,8 @@ interface MatrixRoom : Closeable { suspend fun canUserTriggerRoomNotification(userId: UserId): Result + suspend fun canUserJoinCall(userId: UserId): Result + suspend fun updateAvatar(mimeType: String, data: ByteArray): Result suspend fun removeAvatar(): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt index 6690387b04..1304ca40a8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomInfo.kt @@ -16,8 +16,11 @@ package io.element.android.libraries.matrix.api.room +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem +import kotlinx.collections.immutable.ImmutableList +@Immutable data class MatrixRoomInfo( val id: String, val name: String?, @@ -28,7 +31,7 @@ data class MatrixRoomInfo( val isSpace: Boolean, val isTombstoned: Boolean, val canonicalAlias: String?, - val alternativeAliases: List, + val alternativeAliases: ImmutableList, val currentUserMembership: CurrentUserMembership, val latestEvent: EventTimelineItem?, val inviter: RoomMember?, @@ -39,5 +42,5 @@ data class MatrixRoomInfo( val notificationCount: Long, val userDefinedNotificationMode: RoomNotificationMode?, val hasRoomCall: Boolean, - val activeRoomCallParticipants: List + val activeRoomCallParticipants: ImmutableList ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt index 5597aaf1c5..9a25aaa12e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/MatrixRoomMembersState.kt @@ -17,13 +17,14 @@ package io.element.android.libraries.matrix.api.room import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList @Immutable sealed interface MatrixRoomMembersState { data object Unknown : MatrixRoomMembersState - data class Pending(val prevRoomMembers: List? = null) : MatrixRoomMembersState - data class Error(val failure: Throwable, val prevRoomMembers: List? = null) : MatrixRoomMembersState - data class Ready(val roomMembers: List) : MatrixRoomMembersState + data class Pending(val prevRoomMembers: ImmutableList? = null) : MatrixRoomMembersState + data class Error(val failure: Throwable, val prevRoomMembers: ImmutableList? = null) : MatrixRoomMembersState + data class Ready(val roomMembers: ImmutableList) : MatrixRoomMembersState } fun MatrixRoomMembersState.roomMembers(): List? { diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt index 9897c2cff5..ca38128e5d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/roomlist/RoomListService.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.api.roomlist +import androidx.compose.runtime.Immutable import kotlinx.coroutines.flow.StateFlow /** @@ -25,6 +26,7 @@ import kotlinx.coroutines.flow.StateFlow */ interface RoomListService { + @Immutable sealed interface State { data object Idle : State data object Running : State @@ -32,6 +34,7 @@ interface RoomListService { data object Terminated : State } + @Immutable sealed interface SyncIndicator { data object Show : SyncIndicator data object Hide : SyncIndicator diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt index f63953e260..8a8d087e8f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventContent.kt @@ -16,11 +16,15 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.poll.PollAnswer import io.element.android.libraries.matrix.api.poll.PollKind +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.ImmutableMap +@Immutable sealed interface EventContent data class MessageContent( @@ -43,14 +47,16 @@ data class PollContent( val question: String, val kind: PollKind, val maxSelections: ULong, - val answers: List, - val votes: Map>, + val answers: ImmutableList, + val votes: ImmutableMap>, val endTime: ULong? ) : EventContent data class UnableToDecryptContent( val data: Data ) : EventContent { + + @Immutable sealed interface Data { data class OlmV1Curve25519AesSha2( val senderKey: String diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt index a2e68d17d2..65fc5a06a9 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventReaction.kt @@ -16,7 +16,11 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList + +@Immutable data class EventReaction( val key: String, - val senders: List + val senders: ImmutableList ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt index 9f38ce9441..fa15f8f096 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/EventTimelineItem.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.TransactionId import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo +import kotlinx.collections.immutable.ImmutableList data class EventTimelineItem( val eventId: EventId?, @@ -29,8 +30,8 @@ data class EventTimelineItem( val isOwn: Boolean, val isRemote: Boolean, val localSendState: LocalEventSendState?, - val reactions: List, - val receipts: List, + val reactions: ImmutableList, + val receipts: ImmutableList, val sender: UserId, val senderProfile: ProfileTimelineDetails, val timestamp: Long, diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt index 14a84e2a90..6965ed9f1e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/InReplyTo.kt @@ -16,9 +16,11 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.UserId +@Immutable sealed interface InReplyTo { /** The event details are not loaded yet. We can fetch them. */ data class NotLoaded(val eventId: EventId) : InReplyTo diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt index 265be8af79..f74ddca3a8 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/LocalEventSendState.kt @@ -16,8 +16,10 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.core.EventId +@Immutable sealed interface LocalEventSendState { data object NotSentYet : LocalEventSendState data object Canceled : LocalEventSendState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt index 14c2302def..0b2d948c4e 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/MessageType.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable import io.element.android.libraries.matrix.api.media.AudioDetails import io.element.android.libraries.matrix.api.media.AudioInfo import io.element.android.libraries.matrix.api.media.FileInfo @@ -23,6 +24,7 @@ import io.element.android.libraries.matrix.api.media.ImageInfo import io.element.android.libraries.matrix.api.media.MediaSource import io.element.android.libraries.matrix.api.media.VideoInfo +@Immutable sealed interface MessageType data class EmoteMessageType( diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt index 2cbfaf76b4..6960b3565d 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/OtherState.kt @@ -16,6 +16,9 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable + +@Immutable sealed interface OtherState { data object PolicyRuleRoom : OtherState data object PolicyRuleServer : OtherState diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt index eddb9eb169..44664a256a 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/timeline/item/event/ProfileTimelineDetails.kt @@ -16,6 +16,9 @@ package io.element.android.libraries.matrix.api.timeline.item.event +import androidx.compose.runtime.Immutable + +@Immutable sealed interface ProfileTimelineDetails { data object Unavailable : ProfileTimelineDetails diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt index a63a31b51f..a7985c864f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/user/MatrixSearchUserResults.kt @@ -16,7 +16,9 @@ package io.element.android.libraries.matrix.api.user +import kotlinx.collections.immutable.ImmutableList + data class MatrixSearchUserResults( - val results: List, + val results: ImmutableList, val limited: Boolean, ) diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt index c463530050..639b704823 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/verification/SessionVerificationService.kt @@ -16,6 +16,8 @@ package io.element.android.libraries.matrix.api.verification +import androidx.compose.runtime.Immutable +import kotlinx.collections.immutable.ImmutableList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -75,6 +77,7 @@ interface SessionVerificationService { } /** Verification status of the current session. */ +@Immutable sealed interface SessionVerifiedStatus { /** Unknown status, we couldn't read the actual value from the SDK. */ data object Unknown : SessionVerifiedStatus @@ -87,6 +90,7 @@ sealed interface SessionVerifiedStatus { } /** States produced by the [SessionVerificationService]. */ +@Immutable sealed interface VerificationFlowState { /** Initial state. */ data object Initial : VerificationFlowState @@ -98,7 +102,7 @@ sealed interface VerificationFlowState { data object StartedSasVerification : VerificationFlowState /** Verification data for the SAS verification (emojis) received. */ - data class ReceivedVerificationData(val emoji: List) : VerificationFlowState + data class ReceivedVerificationData(val emoji: ImmutableList) : VerificationFlowState /** Verification completed successfully. */ data object Finished : VerificationFlowState diff --git a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt index d55b6aebbe..590be150c4 100644 --- a/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt +++ b/libraries/matrix/api/src/test/kotlin/io/element/android/libraries/matrix/api/permalink/PermalinkParserTest.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.api.permalink import com.google.common.truth.Truth.assertThat +import kotlinx.collections.immutable.persistentListOf import org.junit.Test import org.junit.runner.RunWith import org.robolectric.RobolectricTestRunner @@ -66,7 +67,7 @@ class PermalinkParserTest { roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, eventId = null, - viaParameters = emptyList(), + viaParameters = persistentListOf(), ) ) } @@ -79,7 +80,7 @@ class PermalinkParserTest { roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, eventId = "\$1234567890abcdef:matrix.org", - viaParameters = emptyList(), + viaParameters = persistentListOf(), ) ) } @@ -92,7 +93,7 @@ class PermalinkParserTest { roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, eventId = null, - viaParameters = emptyList(), + viaParameters = persistentListOf(), ) ) } @@ -105,7 +106,7 @@ class PermalinkParserTest { roomIdOrAlias = "!aBCD1234:matrix.org", isRoomAlias = false, eventId = "\$1234567890abcdef:matrix.org", - viaParameters = listOf("matrix.org", "matrix.com"), + viaParameters = persistentListOf("matrix.org", "matrix.com"), ) ) } @@ -118,7 +119,7 @@ class PermalinkParserTest { roomIdOrAlias = "#element-android:matrix.org", isRoomAlias = true, eventId = null, - viaParameters = emptyList(), + viaParameters = persistentListOf(), ) ) } diff --git a/libraries/matrix/impl/build.gradle.kts b/libraries/matrix/impl/build.gradle.kts index 81b5f70a11..ef590c6889 100644 --- a/libraries/matrix/impl/build.gradle.kts +++ b/libraries/matrix/impl/build.gradle.kts @@ -47,6 +47,7 @@ dependencies { implementation("net.java.dev.jna:jna:5.13.0@aar") implementation(libs.androidx.datastore.preferences) implementation(libs.serialization.json) + implementation(libs.kotlinx.collections.immutable) testImplementation(libs.test.junit) testImplementation(libs.test.truth) 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 3a7adefe41..9c03bd7a0a 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 @@ -77,6 +77,7 @@ import org.matrix.rustcomponents.sdk.BackupState import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate import org.matrix.rustcomponents.sdk.NotificationProcessSetup +import org.matrix.rustcomponents.sdk.PowerLevels import org.matrix.rustcomponents.sdk.Room import org.matrix.rustcomponents.sdk.RoomListItem import org.matrix.rustcomponents.sdk.TaskHandle @@ -274,6 +275,7 @@ class RustMatrixClient constructor( }, invite = createRoomParams.invite?.map { it.value }, avatar = createRoomParams.avatar, + powerLevelContentOverride = defaultRoomCreationPowerLevels, ) val roomId = RoomId(client.createRoom(rustParams)) @@ -296,7 +298,7 @@ class RustMatrixClient constructor( isDirect = true, visibility = RoomVisibility.PRIVATE, preset = RoomPreset.TRUSTED_PRIVATE_CHAT, - invite = listOf(userId) + invite = listOf(userId), ) return createRoom(createRoomParams) } @@ -481,3 +483,18 @@ class RustMatrixClient constructor( } } +private val defaultRoomCreationPowerLevels = PowerLevels( + usersDefault = null, + eventsDefault = null, + stateDefault = null, + ban = null, + kick = null, + redact = null, + invite = null, + notifications = null, + users = mapOf(), + events = mapOf( + "m.call.member" to 0, + "org.matrix.msc3401.call.member" to 0, + ) +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt index 2540372260..2bdcf6de0f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioDetails.kt @@ -17,13 +17,14 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.matrix.api.media.AudioDetails +import kotlinx.collections.immutable.toImmutableList import kotlin.time.toJavaDuration import kotlin.time.toKotlinDuration import org.matrix.rustcomponents.sdk.UnstableAudioDetailsContent as RustAudioDetails fun RustAudioDetails.map(): AudioDetails = AudioDetails( duration = duration.toKotlinDuration(), - waveform = waveform.fromMSC3246range(), + waveform = waveform.fromMSC3246range().toImmutableList(), ) fun AudioDetails.map(): RustAudioDetails = RustAudioDetails( diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt index c24d996714..3ea1895e22 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/MatrixRoomInfoMapper.kt @@ -20,6 +20,7 @@ import io.element.android.libraries.matrix.api.room.CurrentUserMembership import io.element.android.libraries.matrix.api.room.MatrixRoomInfo import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.impl.timeline.item.event.EventTimelineItemMapper +import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.use import org.matrix.rustcomponents.sdk.Membership as RustMembership import org.matrix.rustcomponents.sdk.RoomInfo as RustRoomInfo @@ -40,7 +41,7 @@ class MatrixRoomInfoMapper( isSpace = it.isSpace, isTombstoned = it.isTombstoned, canonicalAlias = it.canonicalAlias, - alternativeAliases = it.alternativeAliases, + alternativeAliases = it.alternativeAliases.toImmutableList(), currentUserMembership = it.membership.map(), latestEvent = it.latestEvent?.use (timelineItemMapper::map), inviter = it.inviter?.use(RoomMemberMapper::map), @@ -51,7 +52,7 @@ class MatrixRoomInfoMapper( notificationCount = it.notificationCount.toLong(), userDefinedNotificationMode = it.userDefinedNotificationMode?.map(), hasRoomCall = it.hasRoomCall, - activeRoomCallParticipants = it.activeRoomCallParticipants + activeRoomCallParticipants = it.activeRoomCallParticipants.toImmutableList() ) } } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt index 6d8578ac0c..8ec2d2b7b8 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustMatrixRoom.kt @@ -57,6 +57,7 @@ import io.element.android.libraries.matrix.impl.widget.RustWidgetDriver import io.element.android.libraries.matrix.impl.widget.generateWidgetWebViewUrl import io.element.android.libraries.sessionstorage.api.SessionData import io.element.android.services.toolbox.api.systemclock.SystemClock +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -198,7 +199,7 @@ class RustMatrixRoom( override suspend fun updateMembers(): Result = withContext(roomMembersDispatcher) { val currentState = _membersStateFlow.value - val currentMembers = currentState.roomMembers() + val currentMembers = currentState.roomMembers()?.toImmutableList() _membersStateFlow.value = MatrixRoomMembersState.Pending(prevRoomMembers = currentMembers) var rustMembers: List? = null try { @@ -213,7 +214,7 @@ class RustMatrixRoom( } } val mappedMembers = rustMembers.parallelMap(RoomMemberMapper::map) - _membersStateFlow.value = MatrixRoomMembersState.Ready(mappedMembers) + _membersStateFlow.value = MatrixRoomMembersState.Ready(mappedMembers.toImmutableList()) Result.success(Unit) } catch (exception: CancellationException) { _membersStateFlow.value = MatrixRoomMembersState.Error(prevRoomMembers = currentMembers, failure = exception) @@ -363,6 +364,12 @@ class RustMatrixRoom( } } + override suspend fun canUserJoinCall(userId: UserId): Result { + return runCatching { + innerRoom.canUserSendState(userId.value, StateEventType.ROOM_MEMBER_EVENT.map()) + } + } + override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo, progressCallback: ProgressCallback?): Result { return sendAttachment(listOf(file, thumbnailFile)) { innerTimeline.sendImage(file.path, thumbnailFile.path, imageInfo.map(), progressCallback?.toProgressWatcher()) 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 d761e91d6c..4e965fc4ef 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 @@ -27,6 +27,9 @@ import io.element.android.libraries.matrix.api.timeline.item.event.ProfileTimeli import io.element.android.libraries.matrix.api.timeline.item.event.ReactionSender import io.element.android.libraries.matrix.api.timeline.item.event.Receipt import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemEventOrigin +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.Reaction import org.matrix.rustcomponents.sdk.EventItemOrigin as RustEventItemOrigin import org.matrix.rustcomponents.sdk.EventSendState as RustEventSendState @@ -81,7 +84,7 @@ fun RustEventSendState?.map(): LocalEventSendState? { } } -private fun List?.map(): List { +private fun List?.map(): ImmutableList { return this?.map { EventReaction( key = it.key, @@ -90,18 +93,20 @@ private fun List?.map(): List { senderId = UserId(sender.senderId), timestamp = sender.timestamp.toLong() ) - } + }.toImmutableList() ) - } ?: emptyList() + }?.toImmutableList() ?: persistentListOf() } -private fun Map.map(): List { +private fun Map.map(): ImmutableList { return map { - Receipt( - userId = UserId(it.key), - timestamp = it.value.timestamp?.toLong() ?: 0 - ) - }.sortedByDescending { it.timestamp } + Receipt( + userId = UserId(it.key), + timestamp = it.value.timestamp?.toLong() ?: 0 + ) + } + .sortedByDescending { it.timestamp } + .toImmutableList() } private fun RustEventTimelineItemDebugInfo.map(): TimelineItemDebugInfo { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt index 01dd32c048..7bad7a34d2 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/TimelineEventContentMapper.kt @@ -32,6 +32,8 @@ import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecry import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.poll.map +import kotlinx.collections.immutable.toImmutableList +import kotlinx.collections.immutable.toImmutableMap import org.matrix.rustcomponents.sdk.TimelineItemContent import org.matrix.rustcomponents.sdk.TimelineItemContentKind import org.matrix.rustcomponents.sdk.use @@ -106,10 +108,10 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap question = kind.question, kind = kind.kind.map(), maxSelections = kind.maxSelections, - answers = kind.answers.map { answer -> answer.map() }, + answers = kind.answers.map { answer -> answer.map() }.toImmutableList(), votes = kind.votes.mapValues { vote -> - vote.value.map { userId -> UserId(userId) } - }, + vote.value.map { userId -> UserId(userId) }.toImmutableList() + }.toImmutableMap(), endTime = kind.endTime, ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt index 1ec0b512ec..b95cbaeed1 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/usersearch/UserSearchResultMapper.kt @@ -17,13 +17,14 @@ package io.element.android.libraries.matrix.impl.usersearch import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults +import kotlinx.collections.immutable.toImmutableList import org.matrix.rustcomponents.sdk.SearchUsersResults object UserSearchResultMapper { fun map(result: SearchUsersResults): MatrixSearchUserResults { return MatrixSearchUserResults( - results = result.results.map(UserProfileMapper::map), + results = result.results.map(UserProfileMapper::map).toImmutableList(), limited = result.limited, ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt index a4dfadfa5b..3d00c2d50e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/verification/RustSessionVerificationService.kt @@ -23,6 +23,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatu import io.element.android.libraries.matrix.api.verification.VerificationEmoji import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.impl.sync.RustSyncService +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -106,8 +107,9 @@ class RustSessionVerificationService( override fun didReceiveVerificationData(data: List) { val emojis = data.map { emoji -> - emoji.use { VerificationEmoji(it.symbol(), it.description()) } - } + emoji.use { VerificationEmoji(it.symbol(), it.description()) } + } + .toImmutableList() _verificationFlowState.value = VerificationFlowState.ReceivedVerificationData(emojis) } diff --git a/libraries/matrix/test/build.gradle.kts b/libraries/matrix/test/build.gradle.kts index 4e8893aab6..9c41948bd7 100644 --- a/libraries/matrix/test/build.gradle.kts +++ b/libraries/matrix/test/build.gradle.kts @@ -28,4 +28,5 @@ dependencies { api(libs.coroutines.core) implementation(libs.coroutines.test) implementation(projects.tests.testutils) + implementation(libs.kotlinx.collections.immutable) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index 17c351945f..f16fdfbe98 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -53,6 +53,7 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline import io.element.android.libraries.matrix.test.widget.FakeWidgetDriver import io.element.android.tests.testutils.simulateLongTask +import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow @@ -110,6 +111,7 @@ class FakeMatrixRoom( private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io") private var getWidgetDriverResult: Result = Result.success(FakeWidgetDriver()) private var canUserTriggerRoomNotificationResult: Result = Result.success(true) + private var canUserJoinCallResult: Result = Result.success(true) var sendMessageMentions = emptyList() val editMessageCalls = mutableListOf>() @@ -291,6 +293,10 @@ class FakeMatrixRoom( return canUserTriggerRoomNotificationResult } + override suspend fun canUserJoinCall(userId: UserId): Result { + return canUserJoinCallResult + } + override suspend fun sendImage( file: File, thumbnailFile: File, @@ -473,6 +479,10 @@ class FakeMatrixRoom( canUserTriggerRoomNotificationResult = result } + fun givenCanUserJoinCall(result: Result) { + canUserJoinCallResult = result + } + fun givenIgnoreResult(result: Result) { ignoreResult = result } @@ -615,7 +625,7 @@ fun aRoomInfo( isSpace = isSpace, isTombstoned = isTombstoned, canonicalAlias = canonicalAlias, - alternativeAliases = alternativeAliases, + alternativeAliases = alternativeAliases.toImmutableList(), currentUserMembership = currentUserMembership, latestEvent = latestEvent, inviter = inviter, @@ -626,5 +636,5 @@ fun aRoomInfo( notificationCount = notificationCount, userDefinedNotificationMode = userDefinedNotificationMode, hasRoomCall = hasRoomCall, - activeRoomCallParticipants = activeRoomCallParticipants + activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(), ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt index 3c7b3eb594..2ff8ef1745 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/RoomSummaryFixture.kt @@ -44,6 +44,9 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_ROOM_NAME import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.persistentMapOf fun aRoomSummaryFilled( roomId: RoomId = A_ROOM_ID, @@ -107,8 +110,8 @@ fun anEventTimelineItem( isOwn: Boolean = false, isRemote: Boolean = false, localSendState: LocalEventSendState? = null, - reactions: List = emptyList(), - receipts: List = emptyList(), + reactions: ImmutableList = persistentListOf(), + receipts: ImmutableList = persistentListOf(), sender: UserId = A_USER_ID, senderProfile: ProfileTimelineDetails = aProfileTimelineDetails(), timestamp: Long = 0L, @@ -181,12 +184,12 @@ fun aTimelineItemDebugInfo( fun aPollContent( question: String = "Do you like polls?", - answers: List = listOf(PollAnswer("1", "Yes"), PollAnswer("2", "No")), + answers: ImmutableList = persistentListOf(PollAnswer("1", "Yes"), PollAnswer("2", "No")), ) = PollContent( question = question, kind = PollKind.Disclosed, maxSelections = 1u, answers = answers, - votes = mapOf(), + votes = persistentMapOf(), endTime = null ) diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt index 62b0b39adf..34405f1e3d 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/verification/FakeSessionVerificationService.kt @@ -20,6 +20,8 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.VerificationFlowState import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus import io.element.android.libraries.matrix.api.verification.VerificationEmoji +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -29,7 +31,7 @@ class FakeSessionVerificationService : SessionVerificationService { private val _sessionVerifiedStatus = MutableStateFlow(SessionVerifiedStatus.Unknown) private var _verificationFlowState = MutableStateFlow(VerificationFlowState.Initial) private var _canVerifySessionFlow = MutableStateFlow(true) - private var emojiList = emptyList() + private var emojiList = persistentListOf() var shouldFail = false override val verificationFlowState: StateFlow =_verificationFlowState @@ -87,7 +89,7 @@ class FakeSessionVerificationService : SessionVerificationService { } fun givenEmojiList(emojis: List) { - this.emojiList = emojis + this.emojiList = emojis.toPersistentList() } override suspend fun reset() { diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt index cc4f206cbf..3a0cf54670 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/ElementRichTextEditorStyle.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.textcomposer import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp import io.element.android.libraries.designsystem.theme.bgSubtleTertiary import io.element.android.compound.theme.ElementTheme import io.element.android.wysiwyg.compose.RichTextEditorDefaults @@ -39,7 +40,8 @@ internal object ElementRichTextEditorStyle { m3colors.primary } else { m3colors.secondary - } + }, + lineHeight = 16.25.sp, ), cursor = RichTextEditorDefaults.cursorStyle( color = colors.iconAccentTertiary, diff --git a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt index 9e99163632..d9b284de6a 100644 --- a/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt +++ b/libraries/textcomposer/impl/src/main/kotlin/io/element/android/libraries/textcomposer/TextComposer.kt @@ -44,7 +44,6 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow @@ -85,7 +84,6 @@ import io.element.android.libraries.textcomposer.model.VoiceMessagePlayerEvent import io.element.android.libraries.textcomposer.model.VoiceMessageRecorderEvent import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.ui.strings.CommonStrings -import io.element.android.wysiwyg.compose.PillStyle import io.element.android.wysiwyg.compose.RichTextEditor import io.element.android.wysiwyg.compose.RichTextEditorState import io.element.android.wysiwyg.display.TextDisplay @@ -444,8 +442,6 @@ private fun TextInput( .fillMaxWidth(), style = ElementRichTextEditorStyle.create( hasFocus = state.hasFocus - ).copy( - pill = PillStyle(Color.Red) ), resolveMentionDisplay = resolveMentionDisplay, resolveRoomMentionDisplay = resolveRoomMentionDisplay, diff --git a/libraries/usersearch/impl/build.gradle.kts b/libraries/usersearch/impl/build.gradle.kts index 226860e86f..f1d936f1c7 100644 --- a/libraries/usersearch/impl/build.gradle.kts +++ b/libraries/usersearch/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.matrix.api) api(projects.libraries.usersearch.api) + implementation(libs.kotlinx.collections.immutable) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt index 793a9f7f58..76f29aebe5 100644 --- a/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt +++ b/libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserListDataSourceTest.kt @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest import org.junit.Test @@ -37,7 +38,7 @@ internal class MatrixUserListDataSourceTest { searchTerm = "test", result = Result.success( MatrixSearchUserResults( - results = listOf( + results = persistentListOf( aMatrixUserProfile(), aMatrixUserProfile(userId = A_USER_ID_2) ), diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..8fb84edc76 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Day-0_0_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6cabc1dec0b89061db2b9f5301371b839a458e7e5bb6c8bc5b4d2fd4fcc3a179 +size 54275 diff --git a/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..ef549a2345 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/ui_S_t[f.messages.impl_MessagesView_null_MessagesView-Night-0_1_null_12,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:da867f9dd0e7d21419ce52e2e0eabfc26583e9dfb1523613a932394c307b3e28 +size 52621