diff --git a/.github/renovate.json b/.github/renovate.json index 11f993516f..f9e1469496 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -4,7 +4,6 @@ "config:base" ], "labels": ["dependencies"], - "reviewers": ["team:element-x-android-reviewers"], "ignoreDeps": ["string:app_name"], "packageRules": [ { diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 2f7c10a3d0..7d240080b0 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -1,6 +1,7 @@ name: Build and release nightly APK on: + workflow_dispatch: schedule: # Every nights at 4 - cron: "0 4 * * *" diff --git a/.github/workflows/nightly_manual.yml b/.github/workflows/nightly_manual.yml deleted file mode 100644 index 6e8ef7c684..0000000000 --- a/.github/workflows/nightly_manual.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: Build and release nightly APK manually - -on: - workflow_dispatch - -env: - GRADLE_OPTS: -Dorg.gradle.jvmargs="-Xmx3072m -Dfile.encoding=UTF-8 -XX:+HeapDumpOnOutOfMemoryError" -Dkotlin.daemon.jvm.options="-Xmx2560m" -Dkotlin.incremental=false - CI_GRADLE_ARG_PROPERTIES: --stacktrace -PpreDexEnable=false --max-workers 2 --no-daemon - -jobs: - nightly: - name: Build and publish nightly APK to Firebase - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Use JDK 17 - uses: actions/setup-java@v3 - with: - distribution: 'temurin' # See 'Supported distributions' for available options - java-version: '17' - - name: Install towncrier - run: | - python3 -m pip install towncrier - - name: Prepare changelog file - run: | - mv towncrier.toml towncrier.toml.bak - sed 's/CHANGES\.md/CHANGES_NIGHTLY\.md/' towncrier.toml.bak > towncrier.toml - rm towncrier.toml.bak - yes n | towncrier build --version nightly - - name: Build and upload Nightly APK - run: | - ./gradlew assembleNightly appDistributionUploadNightly $CI_GRADLE_ARG_PROPERTIES - env: - ELEMENT_ANDROID_NIGHTLY_KEYID: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYID }} - ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_KEYPASSWORD }} - ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_STOREPASSWORD }} - FIREBASE_TOKEN: ${{ secrets.ELEMENT_ANDROID_NIGHTLY_FIREBASE_TOKEN }} - - name: Additionally upload Nightly APK to browserstack for testing - continue-on-error: true # don't block anything by this upload failing (for now) - run: curl -u "$BROWSERSTACK_USERNAME:$BROWSERSTACK_PASSWORD" -X POST "https://api-cloud.browserstack.com/app-automate/upload" -F "file=@app/build/outputs/apk/nightly/app-universal-nightly.apk" -F "custom_id=element-x-android-nightly" - env: - BROWSERSTACK_USERNAME: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_USERNAME }} - BROWSERSTACK_PASSWORD: ${{ secrets.ELEMENT_ANDROID_BROWSERSTACK_ACCESS_KEY }} diff --git a/.maestro/tests/assertions/assertRoomListSynced.yaml b/.maestro/tests/assertions/assertRoomListSynced.yaml new file mode 100644 index 0000000000..2d13c17df9 --- /dev/null +++ b/.maestro/tests/assertions/assertRoomListSynced.yaml @@ -0,0 +1,5 @@ +appId: ${APP_ID} +--- +- extendedWaitUntil: + visible: ${ROOM_NAME} + timeout: 10_000 diff --git a/.maestro/tests/roomList/searchRoomList.yaml b/.maestro/tests/roomList/searchRoomList.yaml index fe859defbb..45138eb9aa 100644 --- a/.maestro/tests/roomList/searchRoomList.yaml +++ b/.maestro/tests/roomList/searchRoomList.yaml @@ -1,5 +1,6 @@ appId: ${APP_ID} --- +- runFlow: ../assertions/assertRoomListSynced.yaml - tapOn: "search" - inputText: ${ROOM_NAME.substring(0, 3)} - takeScreenshot: build/maestro/400-SearchRoom diff --git a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt index 2938ce41df..7c45620afd 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/RootPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.appnav import app.cash.molecule.RecompositionClock @@ -30,7 +28,6 @@ import io.element.android.features.rageshake.test.crash.FakeCrashDataStore import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt index 71d303150b..2fd6d2d8f7 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/LoggedInPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.appnav.loggedin import app.cash.molecule.RecompositionClock @@ -29,7 +27,6 @@ import io.element.android.libraries.permissions.noop.NoopPermissionsPresenter import io.element.android.libraries.push.api.PushService import io.element.android.libraries.push.providers.api.Distributor import io.element.android.libraries.push.providers.api.PushProvider -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/changelog.d/385.feature b/changelog.d/385.feature new file mode 100644 index 0000000000..5b6cfbfff1 --- /dev/null +++ b/changelog.d/385.feature @@ -0,0 +1 @@ +Show pending invitations in room members list diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt index ebf3e9d508..3746fef4df 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeopleView.kt @@ -41,7 +41,6 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextButton import io.element.android.libraries.ui.strings.R as StringR -@OptIn(ExperimentalMaterial3Api::class) @Composable fun AddPeopleView( state: UserListState, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt index b019e693e0..6f7f21875b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomView.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) - package io.element.android.features.createroom.impl.configureroom import android.net.Uri @@ -61,6 +59,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.coroutines.launch import io.element.android.libraries.ui.strings.R as StringR +@OptIn(ExperimentalMaterialApi::class) @Composable fun ConfigureRoomView( state: ConfigureRoomState, @@ -150,6 +149,7 @@ fun ConfigureRoomView( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ConfigureRoomToolbar( isNextActionEnabled: Boolean, diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt index 60056872db..0ac9611fc7 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenter.kt @@ -65,11 +65,12 @@ class CreateRoomRootPresenter @Inject constructor( fun startDm(matrixUser: MatrixUser) { startDmAction.value = Async.Uninitialized - val existingDM = matrixClient.findDM(matrixUser.userId) - if (existingDM == null) { - localCoroutineScope.createDM(matrixUser, startDmAction) - } else { - startDmAction.value = Async.Success(existingDM.roomId) + matrixClient.findDM(matrixUser.userId).use { existingDM -> + if (existingDM == null) { + localCoroutineScope.createDM(matrixUser, startDmAction) + } else { + startDmAction.value = Async.Success(existingDM.roomId) + } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt index a80cf821fd..2de52b89f7 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootView.kt @@ -55,7 +55,6 @@ import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.designsystem.R as DrawableR import io.element.android.libraries.ui.strings.R as StringR -@OptIn(ExperimentalMaterial3Api::class) @Composable fun CreateRoomRootView( state: CreateRoomRootState, diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt index f20e46d1ba..043ad06361 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/AllMatrixUsersDataSourceTest.kt @@ -25,11 +25,9 @@ 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.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) internal class AllMatrixUsersDataSourceTest { @Test diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt index 7ca2f0147f..d7ee836453 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/addpeople/AddPeoplePresenterTests.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.createroom.impl.addpeople import app.cash.molecule.RecompositionClock @@ -26,7 +24,6 @@ import io.element.android.features.createroom.impl.CreateRoomDataStore import io.element.android.features.userlist.api.UserListDataStore import io.element.android.features.userlist.test.FakeUserListDataSource import io.element.android.features.userlist.test.FakeUserListPresenterFactory -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt index eadad19d23..0c3f64b056 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/configureroom/ConfigureRoomPresenterTests.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.createroom.impl.configureroom import android.net.Uri @@ -44,7 +42,6 @@ import io.mockk.mockkStatic import io.mockk.unmockkAll import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Before diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index 238fa8f58d..2a06278934 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.createroom.impl.root import app.cash.molecule.RecompositionClock @@ -35,7 +33,6 @@ import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test diff --git a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt index dc9302b908..db9fec3153 100644 --- a/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt +++ b/features/invitelist/impl/src/main/kotlin/io/element/android/features/invitelist/impl/InviteListPresenter.kt @@ -131,14 +131,18 @@ class InviteListPresenter @Inject constructor( private fun CoroutineScope.acceptInvite(roomId: RoomId, acceptedAction: MutableState>) = launch { suspend { - client.getRoom(roomId)?.acceptInvitation()?.getOrThrow() + client.getRoom(roomId)?.use { + it.acceptInvitation().getOrThrow() + } roomId }.execute(acceptedAction) } private fun CoroutineScope.declineInvite(roomId: RoomId, declinedAction: MutableState>) = launch { suspend { - client.getRoom(roomId)?.rejectInvitation()?.getOrThrow() ?: Unit + client.getRoom(roomId)?.use { + it.rejectInvitation().getOrThrow() + } ?: Unit }.execute(declinedAction) } diff --git a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt index b8a99b40cc..462faa405f 100644 --- a/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt +++ b/features/invitelist/impl/src/test/kotlin/io/element/android/features/invitelist/impl/InviteListPresenterTests.kt @@ -38,11 +38,9 @@ import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class InviteListPresenterTests { @Test @@ -509,5 +507,4 @@ class InviteListPresenterTests { unreadNotificationCount = 0, ) ) - } diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt index c961b7fce7..cc216cc9dd 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/changeserver/ChangeServerPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.login.impl.changeserver import app.cash.molecule.RecompositionClock @@ -28,11 +26,9 @@ import io.element.android.libraries.matrix.test.A_HOMESERVER_URL import io.element.android.libraries.matrix.test.A_HOMESERVER_URL_2 import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class ChangeServerPresenterTest { @Test fun `present - should start with default homeserver`() = runTest { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt index 6c825d0edd..be940feacf 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/root/LoginRootPresenterTest.kt @@ -14,25 +14,18 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.login.impl.root import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.root.LoggedInState -import io.element.android.features.login.impl.root.LoginFormState -import io.element.android.features.login.impl.root.LoginRootEvents -import io.element.android.features.login.impl.root.LoginRootPresenter import io.element.android.libraries.matrix.test.A_HOMESERVER import io.element.android.libraries.matrix.test.A_PASSWORD import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.A_USER_NAME import io.element.android.libraries.matrix.test.auth.FakeAuthenticationService -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt index 6429ff1938..7a3556389f 100644 --- a/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt +++ b/features/logout/impl/src/test/kotlin/io/element/android/features/logout/impl/LogoutPreferencePresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.logout.impl import app.cash.molecule.RecompositionClock @@ -28,7 +26,6 @@ import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test 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 4b0a6b2238..13f36c2ab6 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 @@ -41,6 +41,8 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.textcomposer.MessageComposerMode import io.element.android.features.networkmonitor.api.NetworkMonitor import io.element.android.features.networkmonitor.api.NetworkStatus +import io.element.android.libraries.designsystem.utils.SnackbarDispatcher +import io.element.android.libraries.designsystem.utils.handleSnackbarMessage import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import timber.log.Timber @@ -52,6 +54,7 @@ class MessagesPresenter @Inject constructor( private val timelinePresenter: TimelinePresenter, private val actionListPresenter: ActionListPresenter, private val networkMonitor: NetworkMonitor, + private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { @Composable @@ -71,6 +74,8 @@ class MessagesPresenter @Inject constructor( val networkConnectionStatus by networkMonitor.connectivity.collectAsState(initial = networkMonitor.currentConnectivityStatus) + val snackbarMessage = handleSnackbarMessage(snackbarDispatcher) + LaunchedEffect(syncUpdateFlow) { roomAvatar.value = AvatarData( @@ -97,6 +102,7 @@ class MessagesPresenter @Inject constructor( timelineState = timelineState, actionListState = actionListState, hasNetworkConnection = networkConnectionStatus == NetworkStatus.Online, + snackbarMessage = snackbarMessage, eventSink = ::handleEvents ) } 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 88b25dd2d6..34e5f664ea 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 @@ -21,6 +21,7 @@ import io.element.android.features.messages.impl.actionlist.ActionListState import io.element.android.features.messages.impl.textcomposer.MessageComposerState import io.element.android.features.messages.impl.timeline.TimelineState import io.element.android.libraries.designsystem.components.avatar.AvatarData +import io.element.android.libraries.designsystem.utils.SnackbarMessage import io.element.android.libraries.matrix.api.core.RoomId @Immutable @@ -32,5 +33,6 @@ data class MessagesState( val timelineState: TimelineState, val actionListState: ActionListState, val hasNetworkConnection: Boolean, + val snackbarMessage: SnackbarMessage?, val eventSink: (MessagesEvents) -> Unit ) 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 8426f79721..9249b808a1 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 @@ -52,5 +52,6 @@ fun aMessagesState() = MessagesState( ), actionListState = anActionListState(), hasNetworkConnection = true, + snackbarMessage = null, 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 5bf26d88bc..f98b19575a 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 @@ -14,11 +14,6 @@ * limitations under the License. */ -@file:OptIn( - ExperimentalMaterial3Api::class, - ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class, -) - package io.element.android.features.messages.impl import androidx.activity.compose.BackHandler @@ -46,6 +41,7 @@ import androidx.compose.material3.SnackbarHost import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.SideEffect import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment @@ -83,6 +79,7 @@ import io.element.android.libraries.designsystem.utils.LogCompositions import kotlinx.coroutines.launch import timber.log.Timber +@OptIn(ExperimentalMaterialApi::class) @Composable fun MessagesView( state: MessagesState, @@ -94,7 +91,6 @@ fun MessagesView( val itemActionsBottomSheetState = rememberModalBottomSheetState( initialValue = ModalBottomSheetValue.Hidden, ) - val snackbarHostState = remember { SnackbarHostState() } val composerState = state.composerState val initialBottomSheetState = if (LocalInspectionMode.current && composerState.attachmentSourcePicker != null) { ModalBottomSheetValue.Expanded @@ -110,6 +106,19 @@ fun MessagesView( } } + val snackbarHostState = remember { SnackbarHostState() } + val snackbarMessageText = state.snackbarMessage?.let { stringResource(it.messageResId) } + if (snackbarMessageText != null) { + SideEffect { + coroutineScope.launch { + snackbarHostState.showSnackbar( + message = snackbarMessageText, + duration = state.snackbarMessage.duration + ) + } + } + } + // This is needed because the composer is inside an AndroidView that can't be affected by the FocusManager in Compose val localView = LocalView.current @@ -226,6 +235,7 @@ fun MessagesViewContent( } } +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MessagesViewTopBar( roomTitle: String?, diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt index c2becf71cf..9dcabd8af7 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/actionlist/ActionListView.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterialApi::class) - package io.element.android.features.messages.impl.actionlist import androidx.compose.foundation.clickable @@ -49,6 +47,7 @@ import io.element.android.libraries.designsystem.theme.components.ModalBottomShe import kotlinx.coroutines.flow.filter import kotlinx.coroutines.launch +@OptIn(ExperimentalMaterialApi::class) @Composable fun ActionListView( state: ActionListState, @@ -90,6 +89,7 @@ fun ActionListView( ) } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun SheetContent( state: ActionListState, @@ -145,6 +145,7 @@ fun SheetContentLightPreview(@PreviewParameter(ActionListStateProvider::class) s fun SheetContentDarkPreview(@PreviewParameter(ActionListStateProvider::class) state: ActionListState) = ElementPreviewDark { ContentToPreview(state) } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun ContentToPreview(state: ActionListState) { ActionListView( diff --git a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenter.kt b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenter.kt index 8193c0c0b2..99d0f98b66 100644 --- a/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenter.kt +++ b/features/messages/impl/src/main/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenter.kt @@ -32,6 +32,8 @@ import io.element.android.libraries.core.data.toStableCharSequence import io.element.android.libraries.core.mimetype.MimeTypes import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo +import io.element.android.libraries.designsystem.utils.SnackbarDispatcher +import io.element.android.libraries.designsystem.utils.SnackbarMessage import io.element.android.libraries.di.RoomScope import io.element.android.libraries.di.SingleIn import io.element.android.libraries.featureflag.api.FeatureFlagService @@ -40,11 +42,13 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaType +import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.textcomposer.MessageComposerMode import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import timber.log.Timber import javax.inject.Inject +import io.element.android.libraries.ui.strings.R as StringR @SingleIn(RoomScope::class) class MessageComposerPresenter @Inject constructor( @@ -53,6 +57,7 @@ class MessageComposerPresenter @Inject constructor( private val mediaPickerProvider: PickerProvider, private val featureFlagService: FeatureFlagService, private val mediaPreProcessor: MediaPreProcessor, + private val snackbarDispatcher: SnackbarDispatcher, ) : Presenter { @Composable @@ -60,6 +65,7 @@ class MessageComposerPresenter @Inject constructor( val localCoroutineScope = rememberCoroutineScope() val galleryMediaPicker = mediaPickerProvider.registerGalleryPicker(onResult = { uri, mimeType -> + if (uri == null) return@registerGalleryPicker Timber.d("Media picked from $uri") // We don't know which type of media was retrieved, so we need this check val mediaType = when { @@ -67,22 +73,25 @@ class MessageComposerPresenter @Inject constructor( mimeType.isMimeTypeVideo() -> MediaType.Video else -> error("MimeType must be either image/* or video/*") } - localCoroutineScope.handleMediaPreProcessing(uri, mediaType) + appCoroutineScope.sendMedia(uri, mediaType) }) val filesPicker = mediaPickerProvider.registerFilePicker(mimeType = MimeTypes.Any) { uri -> + if (uri == null) return@registerFilePicker Timber.d("File picked from $uri") - localCoroutineScope.handleMediaPreProcessing(uri, MediaType.File) + appCoroutineScope.sendMedia(uri, MediaType.File) } val cameraPhotoPicker = mediaPickerProvider.registerCameraPhotoPicker { uri -> + if (uri == null) return@registerCameraPhotoPicker Timber.d("Photo saved at $uri") - localCoroutineScope.handleMediaPreProcessing(uri, MediaType.Image, deleteOriginal = true) + appCoroutineScope.sendMedia(uri, MediaType.Image, deleteOriginal = true) } val cameraVideoPicker = mediaPickerProvider.registerCameraVideoPicker { uri -> + if (uri == null) return@registerCameraVideoPicker Timber.d("Video saved at $uri") - localCoroutineScope.handleMediaPreProcessing(uri, MediaType.Video, deleteOriginal = true) + appCoroutineScope.sendMedia(uri, MediaType.Video, deleteOriginal = true) } val isFullScreen = rememberSaveable { @@ -181,14 +190,44 @@ class MessageComposerPresenter @Inject constructor( } } - private fun CoroutineScope.handleMediaPreProcessing( - uri: Uri?, + private fun CoroutineScope.sendMedia( + uri: Uri, mediaType: MediaType, deleteOriginal: Boolean = false ) = launch { - if (uri == null) return@launch + runCatching { + val info = handleMediaPreProcessing(uri, mediaType, deleteOriginal).getOrNull() ?: return@runCatching + when (info) { + is MediaUploadInfo.Image -> { + room.sendImage(info.file, info.thumbnailInfo.file, info.info) + } - val result = mediaPreProcessor.process(uri, mediaType, deleteOriginal = deleteOriginal) + is MediaUploadInfo.Video -> { + room.sendVideo(info.file, info.thumbnailInfo.file, info.info) + } + + is MediaUploadInfo.AnyFile -> { + room.sendFile(info.file, info.info) + } + else -> error("Unexpected MediaUploadInfo format: $info") + }.getOrThrow() + }.onFailure { + snackbarDispatcher.post(SnackbarMessage(StringR.string.screen_media_upload_preview_error_failed_sending)) + Timber.e(it, "Couldn't upload media") + }.onSuccess { + Timber.d("Media uploaded") + } + } + + private suspend fun handleMediaPreProcessing( + uri: Uri, + mediaType: MediaType, + deleteOriginal: Boolean, + ): Result { + val result = mediaPreProcessor.process(uri, mediaType, deleteOriginal = deleteOriginal) Timber.d("Pre-processed media result: $result") + return result.onFailure { + snackbarDispatcher.post(SnackbarMessage(StringR.string.screen_media_upload_preview_error_failed_processing)) + } } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt index d2ba50ad5a..e6ee61ee52 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/MessagesPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.messages import app.cash.molecule.RecompositionClock @@ -31,6 +29,7 @@ import io.element.android.features.messages.impl.actionlist.model.TimelineItemAc import io.element.android.features.messages.impl.textcomposer.MessageComposerPresenter import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.features.networkmonitor.test.FakeNetworkMonitor +import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -39,7 +38,6 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Test @@ -136,6 +134,7 @@ class MessagesPresenterTest { mediaPickerProvider = FakePickerProvider(), featureFlagService = FakeFeatureFlagService(), mediaPreProcessor = FakeMediaPreProcessor(), + snackbarDispatcher = SnackbarDispatcher(), ) val timelinePresenter = TimelinePresenter( timelineItemsFactory = aTimelineItemsFactory(), @@ -148,6 +147,7 @@ class MessagesPresenterTest { timelinePresenter = timelinePresenter, actionListPresenter = actionListPresenter, networkMonitor = FakeNetworkMonitor(), + snackbarDispatcher = SnackbarDispatcher(), ) } } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt index 410f7186aa..cd85418bff 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/actionlist/ActionListPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.messages.actionlist import app.cash.molecule.RecompositionClock @@ -23,9 +21,9 @@ import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.actionlist.ActionListEvents -import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.actionlist.ActionListPresenter import io.element.android.features.messages.impl.actionlist.ActionListState +import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction import io.element.android.features.messages.impl.timeline.model.TimelineItem import io.element.android.features.messages.impl.timeline.model.TimelineItemReactions import io.element.android.features.messages.impl.timeline.model.event.TimelineItemEventContent @@ -37,7 +35,6 @@ import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_USER_ID import io.element.android.libraries.matrix.test.A_USER_NAME import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt index 2d4ee3842c..058406d513 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/fixtures/timelineItemsFactory.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.messages.fixtures import io.element.android.features.messages.impl.timeline.factories.TimelineItemsFactory @@ -34,7 +32,6 @@ import io.element.android.features.messages.impl.timeline.factories.virtual.Time import io.element.android.features.messages.impl.timeline.factories.virtual.TimelineItemVirtualFactory import io.element.android.libraries.dateformatter.test.FakeDaySeparatorFormatter import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi internal fun aTimelineItemsFactory() = TimelineItemsFactory( dispatchers = testCoroutineDispatchers(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt index dc1efd7d88..9639759529 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/textcomposer/MessageComposerPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.messages.textcomposer import app.cash.molecule.RecompositionClock @@ -29,9 +27,13 @@ import io.element.android.features.messages.impl.textcomposer.MessageComposerPre import io.element.android.features.messages.impl.textcomposer.MessageComposerState import io.element.android.libraries.core.data.StableCharSequence import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.ThumbnailInfo +import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.test.ANOTHER_MESSAGE import io.element.android.libraries.matrix.test.AN_EVENT_ID @@ -42,14 +44,17 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediapickers.api.PickerProvider import io.element.android.libraries.mediapickers.test.FakePickerProvider import io.element.android.libraries.mediaupload.api.MediaPreProcessor +import io.element.android.libraries.mediaupload.api.MediaUploadInfo +import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.textcomposer.MessageComposerMode import io.mockk.mockk import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runCurrent import kotlinx.coroutines.test.runTest import org.junit.Test +import java.io.File class MessageComposerPresenterTest { @@ -62,6 +67,7 @@ class MessageComposerPresenterTest { } } private val mediaPreProcessor = FakeMediaPreProcessor() + private val snackbarDispatcher = SnackbarDispatcher() @Test fun `present - initial state`() = runTest { @@ -285,16 +291,87 @@ class MessageComposerPresenterTest { } @Test - fun `present - Pick media from gallery`() = runTest { - val presenter = createPresenter(this) + fun `present - Pick image from gallery`() = runTest { + val room = FakeMatrixRoom() + val presenter = createPresenter(this, room = room) pickerProvider.givenMimeType(MimeTypes.Images) + mediaPreProcessor.givenResult( + Result.success( + MediaUploadInfo.Image( + file = File("/some/path"), + info = ImageInfo( + width = null, + height = null, + mimetype = null, + size = null, + thumbnailInfo = null, + thumbnailUrl = null, + blurhash = null, + ), + thumbnailInfo = ThumbnailProcessingInfo( + file = File("/some/path"), + info = ThumbnailInfo( + width = null, + height = null, + mimetype = null, + size = null, + ), + blurhash = "", + ) + ) + ) + ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + // Wait for the launched upload coroutine to run + runCurrent() + assertThat(room.sendMediaCount).isEqualTo(1) + } + } - // TODO verify some post processing of the selected media is done + @Test + fun `present - Pick video from gallery`() = runTest { + val room = FakeMatrixRoom() + val presenter = createPresenter(this, room = room) + pickerProvider.givenMimeType(MimeTypes.Videos) + mediaPreProcessor.givenResult( + Result.success( + MediaUploadInfo.Video( + file = File("/some/path"), + info = VideoInfo( + width = null, + height = null, + mimetype = null, + duration = null, + size = null, + thumbnailInfo = null, + thumbnailUrl = null, + blurhash = null, + ), + thumbnailInfo = ThumbnailProcessingInfo( + file = File("/some/path"), + info = ThumbnailInfo( + width = null, + height = null, + mimetype = null, + size = null, + ), + blurhash = "", + ) + ) + ) + ) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromGallery) + // Wait for the launched upload coroutine to run + runCurrent() + assertThat(room.sendMediaCount).isEqualTo(1) } } @@ -314,7 +391,7 @@ class MessageComposerPresenterTest { @Test fun `present - Pick media from gallery & cancel does nothing`() = runTest { val presenter = createPresenter(this) - with(pickerProvider){ + with(pickerProvider) { givenResult(null) // Simulate a user canceling the flow givenMimeType(MimeTypes.Images) } @@ -329,40 +406,67 @@ class MessageComposerPresenterTest { @Test fun `present - Pick file from storage`() = runTest { - val presenter = createPresenter(this) + val room = FakeMatrixRoom() + val presenter = createPresenter(this, room = room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) + // Wait for the launched upload coroutine to run + runCurrent() + assertThat(room.sendMediaCount).isEqualTo(1) + } + } + + @Test + fun `present - Take photo`() = runTest { + val room = FakeMatrixRoom() + val presenter = createPresenter(this, room = room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(MessageComposerEvents.PickCameraAttachmentSource.Photo) + // Wait for the launched upload coroutine to run + runCurrent() + assertThat(room.sendMediaCount).isEqualTo(1) + } + } + + @Test + fun `present - Record video`() = runTest { + val room = FakeMatrixRoom() + val presenter = createPresenter(this, room = room) + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + initialState.eventSink(MessageComposerEvents.PickCameraAttachmentSource.Video) + // Wait for the launched upload coroutine to run + runCurrent() + assertThat(room.sendMediaCount).isEqualTo(1) + } + } + + @Test + fun `present - Uploading media failure can be recovered from`() = runTest { + val room = FakeMatrixRoom().apply { + givenSendMediaResult(Result.failure(Exception())) + } + val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() initialState.eventSink(MessageComposerEvents.PickAttachmentSource.FromFiles) - // TODO verify some post processing of the selected media is done - } - } - - @Test - fun `present - Take photo`() = runTest { - val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(MessageComposerEvents.PickCameraAttachmentSource.Photo) - - // TODO verify some post processing of the captured image is done - } - } - - @Test - fun `present - Record video`() = runTest { - val presenter = createPresenter(this) - moleculeFlow(RecompositionClock.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - initialState.eventSink(MessageComposerEvents.PickCameraAttachmentSource.Video) - - // TODO verify some post processing of the captured video is done + snackbarDispatcher.snackbarMessage.test { + // Initial value is always null + skipItems(1) + // Assert error message received + assertThat(awaitItem()).isNotNull() + } } } @@ -381,8 +485,9 @@ class MessageComposerPresenterTest { pickerProvider: PickerProvider = this.pickerProvider, featureFlagService: FeatureFlagService = this.featureFlagService, mediaPreProcessor: MediaPreProcessor = this.mediaPreProcessor, + snackbarDispatcher: SnackbarDispatcher = this.snackbarDispatcher, ) = MessageComposerPresenter( - coroutineScope, room, pickerProvider, featureFlagService, mediaPreProcessor + coroutineScope, room, pickerProvider, featureFlagService, mediaPreProcessor, snackbarDispatcher ) } diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt index 41f37158d9..a88b2251a4 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/timeline/TimelinePresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.messages.timeline import app.cash.molecule.RecompositionClock @@ -27,8 +25,6 @@ import io.element.android.features.messages.impl.timeline.TimelineEvents import io.element.android.features.messages.impl.timeline.TimelinePresenter import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.timeline.FakeMatrixTimeline -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingScreen.kt b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingScreen.kt index 9fb54c7b45..8694865938 100644 --- a/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingScreen.kt +++ b/features/onboarding/impl/src/main/kotlin/io/element/android/features/onboarding/impl/OnBoardingScreen.kt @@ -41,7 +41,6 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.HorizontalPager import com.google.accompanist.pager.HorizontalPagerIndicator import com.google.accompanist.pager.rememberPagerState @@ -53,9 +52,7 @@ import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.testtags.testTag import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import io.element.android.libraries.ui.strings.R as StringR -@OptIn(ExperimentalPagerApi::class) @Composable fun OnBoardingScreen( modifier: Modifier = Modifier, diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt index 2e4bd005ca..6b7c8c2df4 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/developer/DeveloperSettingsPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.preferences.impl.developer import app.cash.molecule.RecompositionClock @@ -24,7 +22,6 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt index 38e97148ca..791b0cf533 100644 --- a/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt +++ b/features/preferences/impl/src/test/kotlin/io/element/android/features/preferences/impl/root/PreferencesRootPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.preferences.impl.root import app.cash.molecule.RecompositionClock @@ -29,7 +27,6 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto import io.element.android.libraries.architecture.Async import io.element.android.libraries.core.meta.BuildType import io.element.android.libraries.matrix.test.FakeMatrixClient -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt index 1390dd1c8c..3df6c62287 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/bugreport/BugReportPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.rageshake.impl.bugreport import app.cash.molecule.RecompositionClock @@ -28,7 +26,6 @@ import io.element.android.features.rageshake.test.screenshot.A_SCREENSHOT_URI import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.test.A_FAILURE_REASON -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt index 3a47ab7797..2d9834607f 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/crash/ui/CrashDetectionPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.rageshake.impl.crash.ui import app.cash.molecule.RecompositionClock @@ -26,7 +24,6 @@ import io.element.android.features.rageshake.api.crash.CrashDetectionEvents import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter import io.element.android.features.rageshake.test.crash.A_CRASH_DATA import io.element.android.features.rageshake.test.crash.FakeCrashDataStore -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt index cca82d64d5..369cb0833b 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/detection/RageshakeDetectionPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.rageshake.impl.detection import android.graphics.Bitmap @@ -31,10 +29,10 @@ import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataSto import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.mockk.mockk -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Test +import kotlin.time.Duration.Companion.seconds class RageshakeDetectionPresenterTest { @Test @@ -101,7 +99,7 @@ class RageshakeDetectionPresenterTest { ) moleculeFlow(RecompositionClock.Immediate) { presenter.present() - }.test { + }.test(timeout = 30.seconds) { skipItems(1) val initialState = awaitItem() assertThat(initialState.isStarted).isFalse() @@ -155,7 +153,7 @@ class RageshakeDetectionPresenterTest { } @Test - fun `present - screenshot then disable`() = runTest { + fun `present - screenshot then disable`() = runTest(timeout = 1.seconds) { val screenshotHolder = FakeScreenshotHolder(screenshotUri = null) val rageshake = FakeRageShake(isAvailableValue = true) val rageshakeDataStore = FakeRageshakeDataStore(isEnabled = true) diff --git a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt index e10e485d79..b01ce22645 100644 --- a/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt +++ b/features/rageshake/impl/src/test/kotlin/io/element/android/features/rageshake/impl/preferences/RageshakePreferencesPresenterTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.features.rageshake.impl.preferences import app.cash.molecule.RecompositionClock @@ -26,7 +24,6 @@ import io.element.android.features.rageshake.api.preferences.RageshakePreference import io.element.android.features.rageshake.test.rageshake.A_SENSITIVITY import io.element.android.features.rageshake.test.rageshake.FakeRageShake import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 41f117d6a4..7b072a5faf 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -40,7 +40,6 @@ dependencies { implementation(projects.libraries.designsystem) implementation(projects.libraries.elementresources) implementation(projects.libraries.uiStrings) - implementation(projects.features.userlist.api) implementation(projects.libraries.androidutils) api(projects.features.roomdetails.api) implementation(libs.coil.compose) @@ -51,7 +50,6 @@ dependencies { testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) - testImplementation(projects.features.userlist.impl) testImplementation(projects.features.userlist.test) testImplementation(projects.tests.testutils) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt index 8f96487583..81b2e1e383 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/RoomDetailsPresenter.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMembershipObserver +import io.element.android.libraries.matrix.api.room.RoomMembershipState import io.element.android.libraries.matrix.ui.room.getDirectRoomMember import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch @@ -124,7 +125,7 @@ class RoomDetailsPresenter @Inject constructor( MatrixRoomMembersState.Unknown -> Async.Uninitialized is MatrixRoomMembersState.Pending -> Async.Loading(prevState = membersState.prevRoomMembers?.size) is MatrixRoomMembersState.Error -> Async.Failure(membersState.failure, prevState = membersState.prevRoomMembers?.size) - is MatrixRoomMembersState.Ready -> Async.Success(membersState.roomMembers.size) + is MatrixRoomMembersState.Ready -> Async.Success(membersState.roomMembers.count { it.membership == RoomMembershipState.JOIN }) } } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt similarity index 75% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt rename to features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt index 2cd3b7eb08..ca462c6507 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModules.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/di/RoomMemberModule.kt @@ -17,31 +17,17 @@ package io.element.android.features.roomdetails.impl.di import com.squareup.anvil.annotations.ContributesTo -import dagger.Binds import dagger.Module import dagger.Provides -import io.element.android.features.roomdetails.impl.members.RoomUserListDataSource import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter -import io.element.android.features.userlist.api.UserListDataSource import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.room.RoomMember -import javax.inject.Named @Module @ContributesTo(RoomScope::class) -interface RoomMemberBindsModule { - - @Binds - @Named("RoomMembers") - fun bindRoomMemberUserListDataSource(dataSource: RoomUserListDataSource): UserListDataSource -} - -@Module -@ContributesTo(RoomScope::class) -object RoomMemberProvidesModule { +object RoomMemberModule { @Provides fun provideRoomMemberDetailsPresenterFactory( diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt similarity index 75% rename from features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt rename to features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt index b8c73526ed..5a9c30698b 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomUserListDataSource.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListDataSource.kt @@ -16,27 +16,23 @@ package io.element.android.features.roomdetails.impl.members -import io.element.android.features.userlist.api.UserListDataSource import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.roomMembers -import io.element.android.libraries.matrix.api.room.toMatrixUser -import io.element.android.libraries.matrix.api.user.MatrixUser import kotlinx.coroutines.flow.dropWhile import kotlinx.coroutines.flow.first import kotlinx.coroutines.withContext import javax.inject.Inject -class RoomUserListDataSource @Inject constructor( +class RoomMemberListDataSource @Inject constructor( private val room: MatrixRoom, private val coroutineDispatchers: CoroutineDispatchers, -) : UserListDataSource { +) { - override suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { + suspend fun search(query: String): List = withContext(coroutineDispatchers.io) { val roomMembers = room.membersStateFlow .dropWhile { it !is MatrixRoomMembersState.Ready } .first() @@ -50,11 +46,7 @@ class RoomUserListDataSource @Inject constructor( || member.displayName?.contains(query, ignoreCase = true).orFalse() } } - filteredMembers.map(RoomMember::toMatrixUser) - } - - override suspend fun getProfile(userId: UserId): MatrixUser? { - return null + filteredMembers } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt index 964a87eaf0..43716660eb 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListEvents.kt @@ -16,8 +16,7 @@ package io.element.android.features.roomdetails.impl.members -import io.element.android.libraries.matrix.api.user.MatrixUser - sealed interface RoomMemberListEvents { - data class SelectUser(val user: MatrixUser) : RoomMemberListEvents + data class UpdateSearchQuery(val query: String) : RoomMemberListEvents + data class OnSearchActiveChanged(val active: Boolean) : RoomMemberListEvents } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt index 0cc893f9e1..3a719983b6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt @@ -18,54 +18,73 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import io.element.android.features.userlist.api.SelectionMode -import io.element.android.features.userlist.api.UserListDataSource -import io.element.android.features.userlist.api.UserListDataStore -import io.element.android.features.userlist.api.UserListPresenter -import io.element.android.features.userlist.api.UserListPresenterArgs +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.room.MatrixRoom -import io.element.android.libraries.matrix.api.user.MatrixUser -import kotlinx.collections.immutable.ImmutableList +import io.element.android.libraries.matrix.api.room.RoomMembershipState import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.withContext import javax.inject.Inject -import javax.inject.Named class RoomMemberListPresenter @Inject constructor( - private val userListPresenterFactory: UserListPresenter.Factory, - @Named("RoomMembers") private val userListDataSource: UserListDataSource, - private val userListDataStore: UserListDataStore, - private val room: MatrixRoom, + private val roomMemberListDataSource: RoomMemberListDataSource, private val coroutineDispatchers: CoroutineDispatchers, ) : Presenter { - private val userListPresenter by lazy { - userListPresenterFactory.create( - UserListPresenterArgs(selectionMode = SelectionMode.Single), - userListDataSource, - userListDataStore, - ) - } - @Composable override fun present(): RoomMemberListState { - val userListState = userListPresenter.present() - val allUsers = remember { mutableStateOf>>(Async.Loading()) } + var roomMembers by remember { mutableStateOf>(Async.Loading()) } + var searchQuery by rememberSaveable { mutableStateOf("") } + var searchResults by remember { + mutableStateOf(RoomMemberSearchResultState.NotSearching) + } + var isSearchActive by rememberSaveable { mutableStateOf(false) } LaunchedEffect(Unit) { withContext(coroutineDispatchers.io) { - allUsers.value = Async.Success(userListDataSource.search("").toImmutableList()) + val members = roomMemberListDataSource.search("").groupBy { it.membership } + roomMembers = Async.Success( + RoomMembers( + invited = members.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), + joined = members.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + ) + ) + } + } + + LaunchedEffect(searchQuery) { + withContext(coroutineDispatchers.io) { + searchResults = if (searchQuery.isEmpty()) { + RoomMemberSearchResultState.NotSearching + } else { + val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership } + if (results.isEmpty()) RoomMemberSearchResultState.NoResults + else RoomMemberSearchResultState.Results( + RoomMembers( + invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(), + joined = results.getOrDefault(RoomMembershipState.JOIN, emptyList()).toImmutableList(), + ) + ) + } } } return RoomMemberListState( - allUsers = allUsers.value, - userListState = userListState, + roomMembers = roomMembers, + searchQuery = searchQuery, + searchResults = searchResults, + isSearchActive = isSearchActive, + eventSink = { event -> + when (event) { + is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active + is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query + } + }, ) } } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt index ef9d1f3839..a689ad1fd2 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListState.kt @@ -16,12 +16,30 @@ package io.element.android.features.roomdetails.impl.members -import io.element.android.features.userlist.api.UserListState import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.api.room.RoomMember import kotlinx.collections.immutable.ImmutableList data class RoomMemberListState( - val allUsers: Async>, - val userListState: UserListState, + val roomMembers: Async, + val searchQuery: String, + val searchResults: RoomMemberSearchResultState, + val isSearchActive: Boolean, + val eventSink: (RoomMemberListEvents) -> Unit, ) + +data class RoomMembers( + val invited: ImmutableList, + val joined: ImmutableList +) + +sealed interface RoomMemberSearchResultState { + /** No search results are available yet (e.g. because the user hasn't entered a (long enough) search term). */ + object NotSearching : RoomMemberSearchResultState + + /** The search has completed, but no results were found. */ + object NoResults : RoomMemberSearchResultState + + /** The search has completed, and some matching users were found. */ + data class Results(val results: RoomMembers) : RoomMemberSearchResultState +} 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 a9babf57d8..7f91969bcd 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 @@ -17,27 +17,93 @@ package io.element.android.features.roomdetails.impl.members import androidx.compose.ui.tooling.preview.PreviewParameterProvider -import io.element.android.features.userlist.api.UserSearchResultState -import io.element.android.features.userlist.api.aUserListState import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.ui.components.aMatrixUser -import kotlinx.collections.immutable.ImmutableList +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.RoomMembershipState import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList internal class RoomMemberListStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( - aRoomMemberListState(allUsers = Async.Success(persistentListOf(aMatrixUser()))), - aRoomMemberListState(allUsers = Async.Loading()) + aRoomMemberListState( + roomMembers = Async.Success( + RoomMembers( + invited = persistentListOf(aVictor(), aWalter()), + joined = persistentListOf(anAlice(), aBob()), + ) + ) + ), + aRoomMemberListState(roomMembers = Async.Loading()), + aRoomMemberListState().copy(isSearchActive = false), + aRoomMemberListState().copy(isSearchActive = true), + aRoomMemberListState().copy(isSearchActive = true, searchQuery = "someone"), + aRoomMemberListState().copy( + isSearchActive = true, + searchQuery = "@someone:matrix.org", + searchResults = RoomMemberSearchResultState.Results( + RoomMembers( + invited = persistentListOf(aVictor()), + joined = persistentListOf(anAlice()), + ) + ), + ), + aRoomMemberListState().copy( + isSearchActive = true, + searchQuery = "something-with-no-results", + searchResults = RoomMemberSearchResultState.NoResults + ), ) } internal fun aRoomMemberListState( - searchResults: UserSearchResultState = UserSearchResultState.NotSearching, - allUsers: Async> = Async.Uninitialized, -) = - RoomMemberListState( - userListState = aUserListState().copy(searchResults = searchResults), - allUsers = allUsers, - ) + roomMembers: Async = Async.Uninitialized, + searchResults: RoomMemberSearchResultState = RoomMemberSearchResultState.NotSearching, +) = RoomMemberListState( + roomMembers = roomMembers, + searchQuery = "", + searchResults = searchResults, + isSearchActive = false, + eventSink = {} +) + +fun aRoomMember( + userId: UserId = UserId("@alice:server.org"), + displayName: String? = null, + avatarUrl: String? = null, + membership: RoomMembershipState = RoomMembershipState.JOIN, + isNameAmbiguous: Boolean = false, + powerLevel: Long = 0L, + normalizedPowerLevel: Long = 0L, + isIgnored: Boolean = false, +) = RoomMember( + userId = userId, + displayName = displayName, + avatarUrl = avatarUrl, + membership = membership, + isNameAmbiguous = isNameAmbiguous, + powerLevel = powerLevel, + normalizedPowerLevel = normalizedPowerLevel, + isIgnored = isIgnored, +) + +fun aRoomMemberList() = listOf( + anAlice(), + aBob(), + aRoomMember(UserId("@carol:server.org"), "Carol"), + aRoomMember(UserId("@david:server.org"), "David"), + aRoomMember(UserId("@eve:server.org"), "Eve"), + aRoomMember(UserId("@justin:server.org"), "Justin"), + aRoomMember(UserId("@mallory:server.org"), "Mallory"), + aRoomMember(UserId("@susie:server.org"), "Susie"), + aVictor(), + aWalter(), +) + +fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice") +fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob") + +fun aVictor() = aRoomMember(UserId("@victor:server.org"), "Victor", membership = RoomMembershipState.INVITE) + +fun aWalter() = aRoomMember(UserId("@walter:server.org"), "Walter", membership = RoomMembershipState.INVITE) diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt index 3a3ea3daa1..ab52b9774a 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListView.kt @@ -16,20 +16,31 @@ package io.element.android.features.roomdetails.impl.members +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.material.icons.filled.Search import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.SearchBarDefaults import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.alpha +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight @@ -39,37 +50,42 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.element.android.features.roomdetails.impl.R -import io.element.android.features.userlist.api.components.SearchSingleUserResultItem -import io.element.android.features.userlist.api.components.UserListView import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.isLoading import io.element.android.libraries.designsystem.ElementTextStyles +import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.theme.components.CenterAlignedTopAppBar import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.libraries.designsystem.theme.components.Icon +import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.SearchBar import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.user.MatrixUser +import io.element.android.libraries.matrix.ui.components.MatrixUserRow +import kotlinx.collections.immutable.ImmutableList +import io.element.android.libraries.ui.strings.R as StringR -@OptIn(ExperimentalMaterial3Api::class) @Composable fun RoomMemberListView( state: RoomMemberListState, + onBackPressed: () -> Unit, + onMemberSelected: (UserId) -> Unit, modifier: Modifier = Modifier, - onBackPressed: () -> Unit = {}, - onMemberSelected: (UserId) -> Unit = {}, ) { - fun onUserSelected(user: MatrixUser) { - onMemberSelected(user.userId) + fun onUserSelected(roomMember: RoomMember) { + onMemberSelected(roomMember.userId) } Scaffold( topBar = { - if (!state.userListState.isSearchActive) { + if (!state.isSearchActive) { RoomMemberListTopBar(onBackPressed = onBackPressed) } } @@ -80,33 +96,26 @@ fun RoomMemberListView( .padding(padding), verticalArrangement = Arrangement.spacedBy(16.dp), ) { - UserListView( - state = state.userListState, - onUserSelected = ::onUserSelected, - ) + Column { + RoomMemberSearchBar( + query = state.searchQuery, + state = state.searchResults, + active = state.isSearchActive, + placeHolderTitle = stringResource(StringR.string.common_search_for_someone), + onActiveChanged = { state.eventSink(RoomMemberListEvents.OnSearchActiveChanged(it)) }, + onTextChanged = { state.eventSink(RoomMemberListEvents.UpdateSearchQuery(it)) }, + onUserSelected = ::onUserSelected, + modifier = Modifier.fillMaxWidth() + ) + } - if (!state.userListState.isSearchActive) { - if (state.allUsers is Async.Success) { - LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { - item { - val memberCount = state.allUsers.state.count() - Text( - modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), - text = pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount), - style = ElementTextStyles.Regular.callout, - color = MaterialTheme.colorScheme.secondary, - textAlign = TextAlign.Start, - ) - } - items(state.allUsers.state) { matrixUser -> - SearchSingleUserResultItem( - modifier = Modifier.fillMaxWidth(), - matrixUser = matrixUser, - onClick = { onUserSelected(matrixUser) } - ) - } - } - } else if (state.allUsers.isLoading()) { + if (!state.isSearchActive) { + if (state.roomMembers is Async.Success) { + RoomMemberList( + roomMembers = state.roomMembers.state, + onUserSelected = ::onUserSelected, + ) + } else if (state.roomMembers.isLoading()) { Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CircularProgressIndicator() } @@ -116,9 +125,73 @@ fun RoomMemberListView( } } +@Composable +private fun RoomMemberList( + roomMembers: RoomMembers, + onUserSelected: (RoomMember) -> Unit, +) { + LazyColumn(modifier = Modifier.fillMaxWidth(), state = rememberLazyListState()) { + if (roomMembers.invited.isNotEmpty()) { + roomMemberListSection( + headerText = { stringResource(id = R.string.screen_room_member_list_pending_header_title) }, + members = roomMembers.invited, + onMemberSelected = { onUserSelected(it) } + ) + } + if (roomMembers.joined.isNotEmpty()) { + val memberCount = roomMembers.joined.count() + roomMemberListSection( + headerText = { pluralStringResource(id = R.plurals.screen_room_member_list_header_title, count = memberCount, memberCount) }, + members = roomMembers.joined, + onMemberSelected = { onUserSelected(it) } + ) + } + } +} + +private fun LazyListScope.roomMemberListSection( + headerText: @Composable () -> String, + members: ImmutableList, + onMemberSelected: (RoomMember) -> Unit, +) { + item { + Text( + modifier = Modifier.padding(horizontal = 16.dp, vertical = 12.dp), + text = headerText(), + style = ElementTextStyles.Regular.callout, + color = MaterialTheme.colorScheme.secondary, + textAlign = TextAlign.Start, + ) + } + items(members) { matrixUser -> + RoomMemberListItem( + modifier = Modifier.fillMaxWidth(), + roomMember = matrixUser, + onClick = { onMemberSelected(matrixUser) } + ) + } +} + +@Composable +private fun RoomMemberListItem( + roomMember: RoomMember, + modifier: Modifier = Modifier, + onClick: () -> Unit = {}, +) { + MatrixUserRow( + modifier = modifier.clickable(onClick = onClick), + matrixUser = MatrixUser( + userId = roomMember.userId, + displayName = roomMember.displayName, + avatarUrl = roomMember.avatarUrl + ), + avatarSize = AvatarSize.Custom(36.dp), + ) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable -fun RoomMemberListTopBar( +private fun RoomMemberListTopBar( modifier: Modifier = Modifier, onBackPressed: () -> Unit = {}, ) { @@ -135,6 +208,86 @@ fun RoomMemberListTopBar( ) } +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun RoomMemberSearchBar( + query: String, + state: RoomMemberSearchResultState, + active: Boolean, + placeHolderTitle: String, + onActiveChanged: (Boolean) -> Unit, + onTextChanged: (String) -> Unit, + onUserSelected: (RoomMember) -> Unit, + modifier: Modifier = Modifier, +) { + val focusManager = LocalFocusManager.current + + if (!active) { + onTextChanged("") + focusManager.clearFocus() + } + + SearchBar( + query = query, + onQueryChange = onTextChanged, + onSearch = { focusManager.clearFocus() }, + active = active, + onActiveChange = onActiveChanged, + modifier = modifier + .padding(horizontal = if (!active) 16.dp else 0.dp), + placeholder = { + Text( + text = placeHolderTitle, + modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) + ) + }, + leadingIcon = if (active) { + { BackButton(onClick = { onActiveChanged(false) }) } + } else { + null + }, + trailingIcon = when { + active && query.isNotEmpty() -> { + { + IconButton(onClick = { onTextChanged("") }) { + Icon(Icons.Default.Close, stringResource(StringR.string.action_clear)) + } + } + } + + !active -> { + { + Icon( + imageVector = Icons.Default.Search, + contentDescription = stringResource(StringR.string.action_search), + modifier = Modifier.alpha(0.4f), // FIXME align on Design system theme (removing alpha should be fine) + ) + } + } + + else -> null + }, + colors = if (!active) SearchBarDefaults.colors() else SearchBarDefaults.colors(containerColor = Color.Transparent), + content = { + if (state is RoomMemberSearchResultState.Results) { + RoomMemberList( + roomMembers = state.results, + onUserSelected = { onUserSelected(it) } + ) + } else if (state is RoomMemberSearchResultState.NoResults) { + Spacer(Modifier.size(80.dp)) + + Text( + text = stringResource(StringR.string.common_no_results), + textAlign = TextAlign.Center, + color = MaterialTheme.colorScheme.tertiary, + modifier = Modifier.fillMaxWidth() + ) + } + }, + ) +} + @Preview @Composable fun RoomMemberListLightPreview(@PreviewParameter(RoomMemberListStateProvider::class) state: RoomMemberListState) = @@ -147,5 +300,9 @@ fun RoomMemberListDarkPreview(@PreviewParameter(RoomMemberListStateProvider::cla @Composable private fun ContentToPreview(state: RoomMemberListState) { - RoomMemberListView(state) + RoomMemberListView( + state = state, + onBackPressed = {}, + onMemberSelected = {} + ) } 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 2cc6d0ec24..b3717f4244 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 @@ -24,6 +24,8 @@ import io.element.android.features.roomdetails.impl.LeaveRoomWarning import io.element.android.features.roomdetails.impl.RoomDetailsEvent import io.element.android.features.roomdetails.impl.RoomDetailsPresenter import io.element.android.features.roomdetails.impl.RoomDetailsType +import io.element.android.features.roomdetails.impl.members.aRoomMember +import io.element.android.features.roomdetails.impl.members.aRoomMemberList import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.libraries.architecture.Async import io.element.android.libraries.matrix.api.core.RoomId @@ -90,7 +92,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom() val roomMembers = listOf( aRoomMember(A_USER_ID), - aRoomMember(A_USER_ID_2), + aRoomMember(A_USER_ID_2, membership = RoomMembershipState.INVITE), ) val presenter = aRoomDetailsPresenter(room) moleculeFlow(RecompositionClock.Immediate) { @@ -112,7 +114,7 @@ class RoomDetailsPresenterTests { room.givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) //skipItems(1) val successState = awaitItem() - Truth.assertThat(successState.memberCount).isEqualTo(Async.Success(roomMembers.size)) + Truth.assertThat(successState.memberCount).isEqualTo(Async.Success(1)) cancelAndIgnoreRemainingEvents() } @@ -266,22 +268,3 @@ fun aMatrixRoom( isDirect = isDirect, ) -fun aRoomMember( - userId: UserId = A_USER_ID, - displayName: String? = null, - avatarUrl: String? = null, - membership: RoomMembershipState = RoomMembershipState.JOIN, - isNameAmbiguous: Boolean = false, - powerLevel: Long = 0L, - normalizedPowerLevel: Long = 0L, - isIgnored: Boolean = false, -) = RoomMember( - userId = userId, - displayName = displayName, - avatarUrl = avatarUrl, - membership = membership, - isNameAmbiguous = isNameAmbiguous, - powerLevel = powerLevel, - normalizedPowerLevel = normalizedPowerLevel, - isIgnored = isIgnored, -) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt index 7eb650c937..48cf660877 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt @@ -20,62 +20,111 @@ import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth +import io.element.android.features.roomdetails.aMatrixRoom +import io.element.android.features.roomdetails.impl.members.RoomMemberListDataSource +import io.element.android.features.roomdetails.impl.members.RoomMemberListEvents import io.element.android.features.roomdetails.impl.members.RoomMemberListPresenter -import io.element.android.features.userlist.api.SelectionMode -import io.element.android.features.userlist.api.UserListDataSource -import io.element.android.features.userlist.api.UserListDataStore -import io.element.android.features.userlist.api.UserListPresenter -import io.element.android.features.userlist.api.UserListPresenterArgs -import io.element.android.features.userlist.api.UserSearchResultState -import io.element.android.features.userlist.impl.DefaultUserListPresenter -import io.element.android.features.userlist.test.FakeUserListDataSource +import io.element.android.features.roomdetails.impl.members.RoomMemberSearchResultState +import io.element.android.features.roomdetails.impl.members.aRoomMemberList +import io.element.android.features.roomdetails.impl.members.aVictor +import io.element.android.features.roomdetails.impl.members.aWalter import io.element.android.libraries.architecture.Async -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.ui.components.aMatrixUser +import io.element.android.libraries.core.coroutine.CoroutineDispatchers +import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest -import okhttp3.internal.toImmutableList import org.junit.Test @ExperimentalCoroutinesApi class RoomMemberListPresenterTests { - private val testCoroutineDispatchers = testCoroutineDispatchers() - @Test - fun `present - search is done automatically on start, but is async`() = runTest { - val searchResult = listOf(aMatrixUser()) - val userListDataSource = FakeUserListDataSource().apply { - givenSearchResult(searchResult) - } - val userListDataStore = UserListDataStore() - val userListFactory = object : UserListPresenter.Factory { - override fun create( - args: UserListPresenterArgs, - userListDataSource: UserListDataSource, - userListDataStore: UserListDataStore, - ) = DefaultUserListPresenter(args, userListDataSource, userListDataStore) - } - val fakeRoom = FakeMatrixRoom() - val presenter = RoomMemberListPresenter( - userListPresenterFactory = userListFactory, - userListDataSource = userListDataSource, - userListDataStore = userListDataStore, - room = fakeRoom, - coroutineDispatchers = testCoroutineDispatchers - ) + fun `search is done automatically on start, but is async`() = runTest { + val presenter = createPresenter() moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() - Truth.assertThat(initialState.allUsers).isInstanceOf(Async.Loading::class.java) - Truth.assertThat(initialState.userListState.isSearchActive).isFalse() - Truth.assertThat(initialState.userListState.searchResults).isEqualTo(UserSearchResultState.NotSearching) - Truth.assertThat(initialState.userListState.selectionMode).isEqualTo(SelectionMode.Single) + Truth.assertThat(initialState.roomMembers).isInstanceOf(Async.Loading::class.java) + Truth.assertThat(initialState.searchQuery).isEmpty() + Truth.assertThat(initialState.searchResults).isEqualTo(RoomMemberSearchResultState.NotSearching) + Truth.assertThat(initialState.isSearchActive).isFalse() val loadedState = awaitItem() - Truth.assertThat((loadedState.allUsers as? Async.Success)?.state).isEqualTo(searchResult.toImmutableList()) + Truth.assertThat(loadedState.roomMembers).isInstanceOf(Async.Success::class.java) + Truth.assertThat((loadedState.roomMembers as Async.Success).state.invited).isEqualTo(listOf(aVictor(), aWalter())) + Truth.assertThat((loadedState.roomMembers as Async.Success).state.joined).isNotEmpty() + } + } + + @Test + fun `open search`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val loadedState = awaitItem() + + loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) + + val searchActiveState = awaitItem() + Truth.assertThat((searchActiveState.isSearchActive)).isTrue() + } + } + + @Test + fun `search for something which is not found`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val loadedState = awaitItem() + loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) + val searchActiveState = awaitItem() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("something")) + val searchQueryUpdatedState = awaitItem() + Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("something") + val searchSearchResultDelivered = awaitItem() + Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(RoomMemberSearchResultState.NoResults::class.java) + } + } + + @Test + fun `search for something which is found`() = runTest { + val presenter = createPresenter() + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val loadedState = awaitItem() + loadedState.eventSink(RoomMemberListEvents.OnSearchActiveChanged(true)) + val searchActiveState = awaitItem() + loadedState.eventSink(RoomMemberListEvents.UpdateSearchQuery("Alice")) + val searchQueryUpdatedState = awaitItem() + Truth.assertThat((searchQueryUpdatedState.searchQuery)).isEqualTo("Alice") + val searchSearchResultDelivered = awaitItem() + Truth.assertThat((searchSearchResultDelivered.searchResults)).isInstanceOf(RoomMemberSearchResultState.Results::class.java) + Truth.assertThat((searchSearchResultDelivered.searchResults as RoomMemberSearchResultState.Results).results.joined.first().displayName) + .isEqualTo("Alice") + } } } + +@ExperimentalCoroutinesApi +private fun createDataSource( + matrixRoom: MatrixRoom = aMatrixRoom().apply { + givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) + }, + coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() +) = RoomMemberListDataSource(matrixRoom, coroutineDispatchers) + +@ExperimentalCoroutinesApi +private fun createPresenter( + roomMemberListDataSource: RoomMemberListDataSource = createDataSource(), + coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers() +) = RoomMemberListPresenter(roomMemberListDataSource, coroutineDispatchers) 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 13eb28ca85..294b689ea9 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 @@ -22,7 +22,7 @@ import app.cash.turbine.test import com.google.common.truth.Truth import io.element.android.features.roomdetails.aMatrixClient import io.element.android.features.roomdetails.aMatrixRoom -import io.element.android.features.roomdetails.aRoomMember +import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt index d41b8c28fc..acfc822658 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/RoomListView.kt @@ -116,7 +116,7 @@ fun RoomListView( } } -@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class) +@OptIn(ExperimentalMaterial3Api::class) @Composable fun RoomListContent( state: RoomListState, diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt index 32bf30b082..f7fee35c75 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/components/RoomListTopBar.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.features.roomlist.impl.components import androidx.activity.compose.BackHandler diff --git a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt index c2baca9a6b..021959c3aa 100644 --- a/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt +++ b/features/roomlist/impl/src/main/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearch.kt @@ -20,7 +20,6 @@ import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn @@ -30,7 +29,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -60,6 +58,7 @@ import io.element.android.libraries.designsystem.modifiers.applyIf import io.element.android.libraries.designsystem.theme.components.Icon import io.element.android.libraries.designsystem.theme.components.IconButton import io.element.android.libraries.designsystem.theme.components.Scaffold +import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.designsystem.utils.copy import io.element.android.libraries.matrix.api.core.RoomId @@ -128,12 +127,14 @@ internal fun RoomListSearchResultContent( .focusRequester(focusRequester), value = filter, onValueChange = { state.eventSink(RoomListEvents.UpdateFilter(it)) }, - colors = TextFieldDefaults.textFieldColors( - containerColor = Color.Transparent, + colors = TextFieldDefaults.colors( + focusedContainerColor = Color.Transparent, + unfocusedContainerColor = Color.Transparent, + disabledContainerColor = Color.Transparent, focusedIndicatorColor = Color.Transparent, unfocusedIndicatorColor = Color.Transparent, + disabledIndicatorColor = Color.Transparent, errorIndicatorColor = Color.Transparent, - disabledIndicatorColor = Color.Transparent ), trailingIcon = { if (filter.isNotEmpty()) { diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt index 28b8219189..040a5b5b50 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/DefaultInviteStateDataSourceTest.kt @@ -27,11 +27,9 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.tests.testutils.testCoroutineDispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) internal class DefaultInviteStateDataSourceTest { @Test @@ -133,5 +131,4 @@ internal class DefaultInviteStateDataSourceTest { Truth.assertThat(awaitItem()).isEqualTo(InvitesState.NoInvites) } } - } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt index 9030b818bc..104c419f74 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTests.kt @@ -38,12 +38,11 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.room.FakeRoomSummaryDataSource import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class RoomListPresenterTests { +class RoomListPresenterTests { @Test fun `present - should start with no user and then load user with success`() = runTest { diff --git a/features/userlist/impl/src/test/kotlin/io/element/android/features/userlist/impl/DefaultUserListPresenterTests.kt b/features/userlist/impl/src/test/kotlin/io/element/android/features/userlist/impl/DefaultUserListPresenterTests.kt index cf9d9da642..b13881dc5b 100644 --- a/features/userlist/impl/src/test/kotlin/io/element/android/features/userlist/impl/DefaultUserListPresenterTests.kt +++ b/features/userlist/impl/src/test/kotlin/io/element/android/features/userlist/impl/DefaultUserListPresenterTests.kt @@ -16,7 +16,6 @@ package io.element.android.features.userlist.impl -import androidx.compose.foundation.lazy.LazyListState import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test @@ -30,14 +29,10 @@ import io.element.android.features.userlist.test.FakeUserListDataSource import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.user.MatrixUser import io.element.android.libraries.matrix.ui.components.aMatrixUser -import io.mockk.coJustRun -import io.mockk.mockkConstructor import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class DefaultUserListPresenterTests { private val userListDataSource = FakeUserListDataSource() @@ -136,9 +131,6 @@ class DefaultUserListPresenterTests { @Test fun `present - select a user`() = runTest { - mockkConstructor(LazyListState::class) - coJustRun { anyConstructed().scrollToItem(index = any()) } - val presenter = DefaultUserListPresenter( UserListPresenterArgs(selectionMode = SelectionMode.Single), userListDataSource, @@ -158,16 +150,15 @@ class DefaultUserListPresenterTests { assertThat(awaitItem().selectedUsers).containsExactly(userA) initialState.eventSink(UserListEvents.AddToSelection(userB)) - // the last added user should be presented first - assertThat(awaitItem().selectedUsers).containsExactly(userB, userA) + assertThat(awaitItem().selectedUsers).containsExactly(userA, userB) initialState.eventSink(UserListEvents.AddToSelection(userABis)) initialState.eventSink(UserListEvents.AddToSelection(userC)) // duplicated users should be ignored - assertThat(awaitItem().selectedUsers).containsExactly(userC, userB, userA) + assertThat(awaitItem().selectedUsers).containsExactly(userA, userB, userC) initialState.eventSink(UserListEvents.RemoveFromSelection(userB)) - assertThat(awaitItem().selectedUsers).containsExactly(userC, userA) + assertThat(awaitItem().selectedUsers).containsExactly(userA, userC) initialState.eventSink(UserListEvents.RemoveFromSelection(userA)) assertThat(awaitItem().selectedUsers).containsExactly(userC) initialState.eventSink(UserListEvents.RemoveFromSelection(userC)) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a6e923667e..cbd56b3ed6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,11 +19,11 @@ activity = "1.7.1" startup = "1.1.1" # Compose -compose_bom = "2023.04.01" +compose_bom = "2023.05.01" composecompiler = "1.4.7" # Coroutines -coroutines = "1.6.4" +coroutines = "1.7.1" # Accompanist accompanist = "0.30.1" @@ -43,7 +43,7 @@ stem = "2.3.0" sqldelight = "1.5.5" # DI -dagger = "2.46" +dagger = "2.46.1" anvil = "2.4.5" # quality @@ -129,7 +129,7 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } appyx_core = { module = "com.bumble.appyx:core", version.ref = "appyx" } molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref = "molecule" } timber = "com.jakewharton.timber:timber:5.0.1" -matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.12" +matrix_sdk = "org.matrix.rustcomponents:sdk-android:0.1.14" sqldelight-driver-android = { module = "com.squareup.sqldelight:android-driver", version.ref = "sqldelight" } sqldelight-driver-jvm = { module = "com.squareup.sqldelight:sqlite-driver", version.ref = "sqldelight" } sqldelight-coroutines = { module = "com.squareup.sqldelight:coroutines-extensions", version.ref = "sqldelight" } @@ -138,6 +138,7 @@ sqlite = "androidx.sqlite:sqlite:2.3.1" unifiedpush = "com.github.UnifiedPush:android-connector:2.1.1" gujun_span = "me.gujun.android:span:1.7" otaliastudios_transcoder = "com.otaliastudios:transcoder:0.10.5" +vanniktech_blurhash = "com.vanniktech:blurhash:0.1.0" # Di inject = "javax.inject:javax.inject:1" diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt index 137b15a893..80df69cbcc 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt @@ -37,6 +37,7 @@ fun File.safeDelete() { ) } -suspend fun Context.createTmpFile(baseDir: File = cacheDir): File = withContext(Dispatchers.IO) { - File.createTempFile(UUID.randomUUID().toString(), null, baseDir).apply { mkdirs() } +suspend fun Context.createTmpFile(baseDir: File = cacheDir, extension: String? = null): File = withContext(Dispatchers.IO) { + val suffix = extension?.let { ".$extension" } + File.createTempFile(UUID.randomUUID().toString(), suffix, baseDir).apply { mkdirs() } } diff --git a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt index 3e3afbfe0e..20e2a6d8ec 100644 --- a/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt +++ b/libraries/core/src/main/kotlin/io/element/android/libraries/core/mimetype/MimeTypes.kt @@ -32,6 +32,7 @@ object MimeTypes { const val Gif = "image/gif" const val Videos = "video/*" + const val Mp4 = "video/mp4" const val Audio = "audio/*" diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt index 531c18ed6b..03ba290829 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/preferences/PreferenceScreen.kt @@ -45,7 +45,6 @@ import io.element.android.libraries.designsystem.theme.components.Scaffold import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TopAppBar -@OptIn(ExperimentalMaterial3Api::class) @Composable fun PreferenceView( title: String, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CenterAlignedTopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CenterAlignedTopAppBar.kt index 5c2e741814..dab180e1c2 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CenterAlignedTopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/CenterAlignedTopAppBar.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.layout.RowScope diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt index 7f864bcb5c..d3cd2fee07 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/MediumTopAppBar.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.layout.RowScope @@ -57,6 +55,7 @@ fun MediumTopAppBar( internal fun MediumTopAppBarPreview() = ElementThemedPreview { ContentToPreview() } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ContentToPreview() { MediumTopAppBar(title = { Text(text = "Title") }) diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt index 95d603b9dd..2f40a90615 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/ModalBottomSheetLayout.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterialApi::class, ExperimentalMaterialApi::class) - package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.background @@ -107,6 +105,7 @@ internal fun ModalBottomSheetLayoutLightPreview() = internal fun ModalBottomSheetLayoutDarkPreview() = ElementPreviewDark { ContentToPreview() } +@OptIn(ExperimentalMaterialApi::class) @Composable private fun ContentToPreview() { ModalBottomSheetLayout( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt index 046dc5aa89..e0edeed050 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/OutlinedTextField.kt @@ -23,10 +23,9 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.TextFieldColors -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi @@ -49,7 +48,6 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.utils.allBooleans import io.element.android.libraries.designsystem.utils.asInt -@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class) @Composable fun OutlinedTextField( value: String, @@ -70,8 +68,8 @@ fun OutlinedTextField( singleLine: Boolean = false, maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - shape: Shape = TextFieldDefaults.outlinedShape, - colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors() + shape: Shape = OutlinedTextFieldDefaults.shape, + colors: TextFieldColors = OutlinedTextFieldDefaults.colors() ) { androidx.compose.material3.OutlinedTextField( value = value, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt index 99a69c0553..20e30e55b3 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/Scaffold.kt @@ -18,7 +18,6 @@ package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FabPosition import androidx.compose.material3.MaterialTheme import androidx.compose.material3.ScaffoldDefaults @@ -27,7 +26,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -@OptIn(ExperimentalMaterial3Api::class) @Composable fun Scaffold( modifier: Modifier = Modifier, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt index ca25501446..9a037cb7bb 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.interaction.MutableInteractionSource @@ -77,6 +75,7 @@ fun SearchBar( @Composable internal fun DockedSearchBarPreview() = ElementThemedPreview { ContentToPreview() } +@OptIn(ExperimentalMaterial3Api::class) @Composable private fun ContentToPreview() { SearchBar( diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt index a4f07a61fb..54fe50b8bf 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TextField.kt @@ -23,7 +23,6 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults @@ -50,7 +49,6 @@ import io.element.android.libraries.designsystem.preview.PreviewGroup import io.element.android.libraries.designsystem.utils.allBooleans import io.element.android.libraries.designsystem.utils.asInt -@OptIn(ExperimentalMaterial3Api::class) @Composable fun TextField( value: String, @@ -71,8 +69,8 @@ fun TextField( singleLine: Boolean = false, maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, - shape: Shape = TextFieldDefaults.filledShape, - colors: TextFieldColors = TextFieldDefaults.textFieldColors() + shape: Shape = TextFieldDefaults.shape, + colors: TextFieldColors = TextFieldDefaults.colors() ) { androidx.compose.material3.TextField( value = value, diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt index 602818f233..23848ef76d 100644 --- a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/TopAppBar.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.libraries.designsystem.theme.components import androidx.compose.foundation.layout.RowScope diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt index 6547f32448..6ed6e474b6 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/media/AudioInfo.kt @@ -18,5 +18,6 @@ package io.element.android.libraries.matrix.api.media data class AudioInfo( val duration: Long?, - val size: Long? + val size: Long?, + val mimeType: String?, ) 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 35451874aa..90b55a3a42 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 @@ -20,10 +20,15 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.media.AudioInfo +import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.timeline.MatrixTimeline import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import java.io.Closeable +import java.io.File interface MatrixRoom : Closeable { val sessionId: SessionId @@ -67,6 +72,14 @@ interface MatrixRoom : Closeable { suspend fun redactEvent(eventId: EventId, reason: String? = null): Result + suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result + + suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result + + suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result + + suspend fun sendFile(file: File, fileInfo: FileInfo): Result + suspend fun leave(): Result suspend fun acceptInvitation(): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt index 7c977f7569..3c9bd030b0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/RoomMember.kt @@ -17,7 +17,6 @@ package io.element.android.libraries.matrix.api.room import io.element.android.libraries.matrix.api.core.UserId -import io.element.android.libraries.matrix.api.user.MatrixUser data class RoomMember( val userId: UserId, @@ -30,12 +29,6 @@ data class RoomMember( val isIgnored: Boolean, ) -fun RoomMember.toMatrixUser() = MatrixUser( - userId = userId, - displayName = displayName, - avatarUrl = avatarUrl, -) - enum class RoomMembershipState { BAN, INVITE, JOIN, KNOCK, LEAVE } 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 0381869503..57dc8c2fc9 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 @@ -44,7 +44,6 @@ import io.element.android.libraries.matrix.impl.verification.RustSessionVerifica import io.element.android.libraries.sessionstorage.api.SessionStore import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.filter @@ -307,7 +306,6 @@ class RustMatrixClient constructor( } } - @ExperimentalCoroutinesApi override fun close() { slidingSyncUpdateJob?.cancel() stopSync() diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt index 1108dd1cc8..7c35c14fb7 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/AudioInfo.kt @@ -21,5 +21,12 @@ import org.matrix.rustcomponents.sdk.AudioInfo as RustAudioInfo fun RustAudioInfo.map(): AudioInfo = AudioInfo( duration = duration?.toLong(), - size = size?.toLong() + size = size?.toLong(), + mimeType = mimetype +) + +fun AudioInfo.map(): RustAudioInfo = RustAudioInfo( + duration = duration?.toULong(), + size = size?.toULong(), + mimetype = mimeType, ) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt index 98c96c4d9a..a13c48efc5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/FileInfo.kt @@ -27,3 +27,10 @@ fun RustFileInfo.map(): FileInfo = FileInfo( thumbnailInfo = thumbnailInfo?.map(), thumbnailUrl = thumbnailSource?.useUrl() ) + +fun FileInfo.map(): RustFileInfo = RustFileInfo( + mimetype = mimetype, + size = size?.toULong(), + thumbnailInfo = thumbnailInfo?.map(), + thumbnailSource = null +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt index 27ab6d656a..21130ab86e 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ImageInfo.kt @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.media import io.element.android.libraries.matrix.api.media.ImageInfo +import org.matrix.rustcomponents.sdk.MediaSource import org.matrix.rustcomponents.sdk.ImageInfo as RustImageInfo fun RustImageInfo.map(): ImageInfo = ImageInfo( @@ -28,3 +29,13 @@ fun RustImageInfo.map(): ImageInfo = ImageInfo( thumbnailUrl = thumbnailSource?.useUrl(), blurhash = blurhash ) + +fun ImageInfo.map(): RustImageInfo = RustImageInfo( + height = height?.toULong(), + width = width?.toULong(), + mimetype = mimetype, + size = size?.toULong(), + thumbnailInfo = thumbnailInfo?.map(), + thumbnailSource = null, + blurhash = blurhash, +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt index 3f10720132..c3940ac967 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/ThumbnailInfo.kt @@ -25,3 +25,10 @@ fun RustThumbnailInfo.map(): ThumbnailInfo = ThumbnailInfo( mimetype = mimetype, size = size?.toLong() ) + +fun ThumbnailInfo.map(): RustThumbnailInfo = RustThumbnailInfo( + height = height?.toULong(), + width = width?.toULong(), + mimetype = mimetype, + size = size?.toULong() +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt index 0db364f544..9d03e2be2f 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/media/VideoInfo.kt @@ -29,3 +29,14 @@ fun RustVideoInfo.map(): VideoInfo = VideoInfo( thumbnailUrl = thumbnailSource?.useUrl(), blurhash = blurhash ) + +fun VideoInfo.map(): RustVideoInfo = RustVideoInfo( + duration = duration?.toULong(), + height = height?.toULong(), + width = width?.toULong(), + mimetype = mimetype, + size = size?.toULong(), + thumbnailInfo = thumbnailInfo?.map(), + thumbnailSource = null, + blurhash = blurhash +) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt index 079b1e0a5a..4b121db9bf 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/notification/NotificationMapper.kt @@ -16,6 +16,7 @@ package io.element.android.libraries.matrix.impl.notification +import io.element.android.libraries.core.bool.orFalse import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId @@ -36,7 +37,7 @@ class NotificationMapper @Inject constructor() { senderDisplayName = it.senderDisplayName, roomAvatarUrl = it.roomAvatarUrl, isDirect = it.isDirect, - isEncrypted = it.isEncrypted, + isEncrypted = it.isEncrypted.orFalse(), isNoisy = it.isNoisy ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt index 2d2be4f36a..e79a8088aa 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RoomMemberMapper.kt @@ -24,17 +24,18 @@ import org.matrix.rustcomponents.sdk.RoomMember as RustRoomMember object RoomMemberMapper { - fun map(roomMember: RustRoomMember): RoomMember = + fun map(roomMember: RustRoomMember): RoomMember = roomMember.use { RoomMember( - UserId(roomMember.userId()), - roomMember.displayName(), - roomMember.avatarUrl(), - mapMembership(roomMember.membership()), - roomMember.isNameAmbiguous(), - roomMember.powerLevel(), - roomMember.normalizedPowerLevel(), - roomMember.isIgnored(), + UserId(it.userId()), + it.displayName(), + it.avatarUrl(), + mapMembership(it.membership()), + it.isNameAmbiguous(), + it.powerLevel(), + it.normalizedPowerLevel(), + it.isIgnored(), ) + } fun mapMembership(membershipState: RustMembershipState): RoomMembershipState = when (membershipState) { 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 062f24ab62..ae35449a8e 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 @@ -21,10 +21,15 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.media.AudioInfo +import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.room.roomMembers import io.element.android.libraries.matrix.api.timeline.MatrixTimeline +import io.element.android.libraries.matrix.impl.media.map import io.element.android.libraries.matrix.impl.timeline.RustMatrixTimeline import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -39,6 +44,7 @@ import org.matrix.rustcomponents.sdk.SlidingSyncRoom import org.matrix.rustcomponents.sdk.UpdateSummary import org.matrix.rustcomponents.sdk.genTransactionId import org.matrix.rustcomponents.sdk.messageEventContentFromMarkdown +import java.io.File class RustMatrixRoom( override val sessionId: SessionId, @@ -155,9 +161,10 @@ class RustMatrixRoom( override suspend fun sendMessage(message: String): Result = withContext(coroutineDispatchers.io) { val transactionId = genTransactionId() - val content = messageEventContentFromMarkdown(message) - runCatching { - innerRoom.send(content, transactionId) + messageEventContentFromMarkdown(message).use { content -> + runCatching { + innerRoom.send(content, transactionId) + } } } @@ -202,4 +209,28 @@ class RustMatrixRoom( } } + override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result { + return runCatching { + innerRoom.sendImage(file.path, thumbnailFile.path, imageInfo.map()) + } + } + + override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result { + return runCatching { + innerRoom.sendVideo(file.path, thumbnailFile.path, videoInfo.map()) + } + } + + override suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result { + return runCatching { + innerRoom.sendAudio(file.path, audioInfo.map()) + } + } + + override suspend fun sendFile(file: File, fileInfo: FileInfo): Result { + return runCatching { + innerRoom.sendFile(file.path, fileInfo.map()) + } + } + } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt index c908651e75..2574e96d3b 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/room/RustRoomSummaryDataSource.kt @@ -91,7 +91,6 @@ internal class RustRoomSummaryDataSource( coroutineScope.cancel() } - //@OptIn(FlowPreview::class) override fun roomSummaries(): StateFlow> { return roomSummaries } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt index 7151f3550b..5b4eaa9e36 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/MatrixTimelineItemMapper.kt @@ -27,12 +27,12 @@ class MatrixTimelineItemMapper( ) { fun map(timelineItem: TimelineItem): MatrixTimelineItem = timelineItem.use { - val asEvent = timelineItem.asEvent() + val asEvent = it.asEvent() if (asEvent != null) { val eventTimelineItem = eventTimelineItemMapper.map(asEvent) return MatrixTimelineItem.Event(eventTimelineItem) } - val asVirtual = timelineItem.asVirtual() + val asVirtual = it.asVirtual() if (asVirtual != null) { val virtualTimelineItem = virtualTimelineItemMapper.map(asVirtual) return MatrixTimelineItem.Virtual(virtualTimelineItem) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt index a000783e86..674e02a13d 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/timeline/item/event/EventMessageMapper.kt @@ -39,7 +39,7 @@ import org.matrix.rustcomponents.sdk.MessageFormat as RustMessageFormat class EventMessageMapper { fun map(message: Message): MessageContent = message.use { - val type = message.msgtype().use { type -> + val type = it.msgtype().use { type -> when (type) { is MessageType.Audio -> { AudioMessageType(type.content.body, type.content.source.useUrl(), type.content.info?.map()) @@ -68,9 +68,9 @@ class EventMessageMapper { } } MessageContent( - body = message.body(), - inReplyTo = message.inReplyTo()?.eventId?.let(::EventId), - isEdited = message.isEdited(), + body = it.body(), + inReplyTo = it.inReplyTo()?.eventId?.let(::EventId), + isEdited = it.isEdited(), type = type ) } 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 3ec412ae7d..726b448099 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 @@ -31,18 +31,18 @@ class EventTimelineItemMapper(private val contentMapper: TimelineEventContentMap fun map(eventTimelineItem: RustEventTimelineItem): EventTimelineItem = eventTimelineItem.use { EventTimelineItem( - uniqueIdentifier = eventTimelineItem.uniqueIdentifier(), - eventId = eventTimelineItem.eventId()?.let { EventId(it) }, - isEditable = eventTimelineItem.isEditable(), - isLocal = eventTimelineItem.isLocal(), - isOwn = eventTimelineItem.isOwn(), - isRemote = eventTimelineItem.isRemote(), - localSendState = eventTimelineItem.localSendState()?.map(), - reactions = eventTimelineItem.reactions().map(), - sender = UserId(eventTimelineItem.sender()), - senderProfile = eventTimelineItem.senderProfile().map(), - timestamp = eventTimelineItem.timestamp().toLong(), - content = contentMapper.map(eventTimelineItem.content()) + uniqueIdentifier = it.uniqueIdentifier(), + eventId = it.eventId()?.let { EventId(it) }, + isEditable = it.isEditable(), + isLocal = it.isLocal(), + isOwn = it.isOwn(), + isRemote = it.isRemote(), + localSendState = it.localSendState()?.map(), + reactions = it.reactions().map(), + sender = UserId(it.sender()), + senderProfile = it.senderProfile().map(), + timestamp = it.timestamp().toLong(), + content = contentMapper.map(it.content()) ) } } 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 51e84b441f..f776c52670 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 @@ -17,6 +17,7 @@ package io.element.android.libraries.matrix.impl.timeline.item.event import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseMessageLikeContent import io.element.android.libraries.matrix.api.timeline.item.event.FailedToParseStateContent import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange @@ -26,7 +27,6 @@ import io.element.android.libraries.matrix.api.timeline.item.event.RedactedConte import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent import io.element.android.libraries.matrix.api.timeline.item.event.StateContent import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent -import io.element.android.libraries.matrix.api.timeline.item.event.EventContent import io.element.android.libraries.matrix.api.timeline.item.event.UnableToDecryptContent import io.element.android.libraries.matrix.api.timeline.item.event.UnknownContent import io.element.android.libraries.matrix.impl.media.map @@ -39,7 +39,7 @@ import org.matrix.rustcomponents.sdk.OtherState as RustOtherState class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMapper = EventMessageMapper()) { fun map(content: TimelineItemContent): EventContent = content.use { - when (val kind = content.kind()) { + when (val kind = it.kind()) { is TimelineItemContentKind.FailedToParseMessageLike -> { FailedToParseMessageLikeContent( eventType = kind.eventType, @@ -54,7 +54,7 @@ class TimelineEventContentMapper(private val eventMessageMapper: EventMessageMap ) } TimelineItemContentKind.Message -> { - val message = content.asMessage() + val message = it.asMessage() if (message == null) { UnknownContent } else { 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 28d6525739..a790812137 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 @@ -20,6 +20,10 @@ import io.element.android.libraries.matrix.api.core.EventId import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.api.media.AudioInfo +import io.element.android.libraries.matrix.api.media.FileInfo +import io.element.android.libraries.matrix.api.media.ImageInfo +import io.element.android.libraries.matrix.api.media.VideoInfo import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState import io.element.android.libraries.matrix.api.timeline.MatrixTimeline @@ -30,6 +34,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.emptyFlow +import java.io.File class FakeMatrixRoom( override val sessionId: SessionId = A_SESSION_ID, @@ -54,6 +59,9 @@ class FakeMatrixRoom( private var updateMembersResult: Result = Result.success(Unit) private var acceptInviteResult = Result.success(Unit) private var rejectInviteResult = Result.success(Unit) + private var sendMediaResult = Result.success(Unit) + var sendMediaCount = 0 + private set var isInviteAccepted: Boolean = false private set @@ -128,6 +136,14 @@ class FakeMatrixRoom( return rejectInviteResult } + override suspend fun sendImage(file: File, thumbnailFile: File, imageInfo: ImageInfo): Result = sendMediaResult.also { sendMediaCount++ } + + override suspend fun sendVideo(file: File, thumbnailFile: File, videoInfo: VideoInfo): Result = sendMediaResult.also { sendMediaCount++ } + + override suspend fun sendAudio(file: File, audioInfo: AudioInfo): Result = sendMediaResult.also { sendMediaCount++ } + + override suspend fun sendFile(file: File, fileInfo: FileInfo): Result = sendMediaResult.also { sendMediaCount++ } + override fun close() = Unit fun givenLeaveRoomError(throwable: Throwable?) { @@ -165,4 +181,8 @@ class FakeMatrixRoom( fun givenUnIgnoreResult(result: Result) { unignoreResult = result } + + fun givenSendMediaResult(result: Result) { + sendMediaResult = result + } } diff --git a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt index 36eb8f5102..6696806e24 100644 --- a/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt +++ b/libraries/mediaupload/api/src/main/kotlin/io/element/android/libraries/mediaupload/api/MediaUploadInfo.kt @@ -24,8 +24,8 @@ import io.element.android.libraries.matrix.api.media.VideoInfo import java.io.File sealed interface MediaUploadInfo { - data class Image(val file: File, val info: ImageInfo, val thumbnailInfo: ThumbnailProcessingInfo?) : MediaUploadInfo - data class Video(val file: File, val info: VideoInfo, val thumbnailInfo: ThumbnailProcessingInfo?) : MediaUploadInfo + data class Image(val file: File, val info: ImageInfo, val thumbnailInfo: ThumbnailProcessingInfo) : MediaUploadInfo + data class Video(val file: File, val info: VideoInfo, val thumbnailInfo: ThumbnailProcessingInfo) : MediaUploadInfo data class Audio(val file: File, val info: AudioInfo) : MediaUploadInfo data class AnyFile(val file: File, val info: FileInfo) : MediaUploadInfo } @@ -33,4 +33,5 @@ sealed interface MediaUploadInfo { data class ThumbnailProcessingInfo( val file: File, val info: ThumbnailInfo, + val blurhash: String, ) diff --git a/libraries/mediaupload/impl/build.gradle.kts b/libraries/mediaupload/impl/build.gradle.kts index c451d81c7e..a23ab14b74 100644 --- a/libraries/mediaupload/impl/build.gradle.kts +++ b/libraries/mediaupload/impl/build.gradle.kts @@ -41,6 +41,7 @@ android { implementation(libs.androidx.exifinterface) implementation(libs.coroutines.core) implementation(libs.otaliastudios.transcoder) + implementation(libs.vanniktech.blurhash) testImplementation(libs.test.junit) testImplementation(libs.coroutines.test) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt index 45c09a2a47..96d5e2ea63 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/ImageCompressor.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.mediaupload import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactory +import com.vanniktech.blurhash.BlurHash import io.element.android.libraries.androidutils.bitmap.calculateInSampleSize import io.element.android.libraries.androidutils.bitmap.resizeToMax import io.element.android.libraries.androidutils.bitmap.rotateToMetadataOrientation @@ -48,14 +49,21 @@ class ImageCompressor @Inject constructor( ): Result = withContext(Dispatchers.IO) { runCatching { val compressedBitmap = compressToBitmap(inputStream, resizeMode).getOrThrow() + val blurhash = BlurHash.encode(compressedBitmap, 3, 3) // Encode bitmap to the destination temporary file - val tmpFile = context.createTmpFile() + val tmpFile = context.createTmpFile(extension = "jpeg") tmpFile.outputStream().use { compressedBitmap.compress(format, desiredQuality, it) } - ImageCompressionResult(tmpFile, compressedBitmap.width, compressedBitmap.height, tmpFile.length()) + ImageCompressionResult( + file = tmpFile, + width = compressedBitmap.width, + height = compressedBitmap.height, + size = tmpFile.length(), + blurhash = blurhash + ) } } @@ -108,6 +116,7 @@ data class ImageCompressionResult( val width: Int, val height: Int, val size: Long, + val blurhash: String, ) sealed interface ResizeMode { diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/MediaPreProcessorImpl.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/MediaPreProcessorImpl.kt index 3d51cb24f1..c6dd7eb841 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/MediaPreProcessorImpl.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/MediaPreProcessorImpl.kt @@ -26,9 +26,7 @@ import io.element.android.libraries.androidutils.file.createTmpFile import io.element.android.libraries.androidutils.media.runAndRelease import io.element.android.libraries.core.data.tryOrNull import io.element.android.libraries.core.mimetype.MimeTypes -import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeAudio import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeImage -import io.element.android.libraries.core.mimetype.MimeTypes.isMimeTypeVideo import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.ApplicationContext import io.element.android.libraries.matrix.api.media.AudioInfo @@ -41,7 +39,6 @@ import io.element.android.libraries.mediaupload.api.MediaType import io.element.android.libraries.mediaupload.api.MediaUploadInfo import io.element.android.libraries.mediaupload.api.ThumbnailProcessingInfo import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.onEach @@ -93,19 +90,23 @@ class MediaPreProcessorImpl @Inject constructor( deleteOriginal: Boolean, ): Result = runCatching { // Camera returns an 'octet-stream' mimetype, so it needs to be overridden - val originalMimeType = contentResolver.getType(uri) - val mimeType = when (mediaType) { - MediaType.Image -> MimeTypes.Images - MediaType.Video -> MimeTypes.Videos - MediaType.Audio -> MimeTypes.Audio - else -> originalMimeType + val mimeType = contentResolver.getType(uri) + val mimeTypeOrDefault = if (mimeType == MimeTypes.OctetStream) { + when(mediaType) { + MediaType.Image -> MimeTypes.Jpeg + MediaType.Video -> MimeTypes.Mp4 + MediaType.Audio -> MimeTypes.Ogg + else -> mimeType + } + } else { + mimeType } val compressBeforeSending = mediaType in sequenceOf(MediaType.Image, MediaType.Video) val result = if (compressBeforeSending && mimeType != MimeTypes.Gif) { - when { - mimeType.isMimeTypeImage() -> processImage(uri) - mimeType.isMimeTypeVideo() -> processVideo(uri, mimeType) - mimeType.isMimeTypeAudio() -> processAudio(uri) + when(mediaType) { + MediaType.Image -> processImage(uri) + MediaType.Video -> processVideo(uri, mimeTypeOrDefault) + MediaType.Audio -> processAudio(uri, mimeTypeOrDefault) else -> error("Cannot compress file of type: $mimeType") } } else { @@ -115,7 +116,7 @@ class MediaPreProcessorImpl @Inject constructor( removeSensitiveImageMetadata(file) } val info = FileInfo( - mimetype = originalMimeType, + mimetype = mimeType, size = file.length(), thumbnailInfo = null, thumbnailUrl = null, @@ -141,7 +142,7 @@ class MediaPreProcessorImpl @Inject constructor( removeSensitiveImageMetadata(compressedFileResult.file) val thumbnailResult = compressedFileResult.file.inputStream().use { generateImageThumbnail(it) } - val processingResult = compressedFileResult.toImageInfo(MimeTypes.Jpeg, thumbnailResult?.file?.path, thumbnailResult?.info) + val processingResult = compressedFileResult.toImageInfo(MimeTypes.Jpeg, thumbnailResult.file.path, thumbnailResult.info) return MediaUploadInfo.Image(compressedFileResult.file, processingResult, thumbnailResult) } @@ -155,32 +156,33 @@ class MediaPreProcessorImpl @Inject constructor( .first() .file - val videoProcessingInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo?.file?.path, thumbnailInfo?.info) + val videoProcessingInfo = extractVideoMetadata(resultFile, mimeType, thumbnailInfo.file.path, thumbnailInfo) return MediaUploadInfo.Video(resultFile, videoProcessingInfo, thumbnailInfo) } - private suspend fun processAudio(uri: Uri): MediaUploadInfo { + private suspend fun processAudio(uri: Uri, mimeType: String?): MediaUploadInfo { val file = copyToTmpFile(uri) return MediaMetadataRetriever().runAndRelease { setDataSource(context, Uri.fromFile(file)) val info = AudioInfo( duration = extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)?.toLong() ?: 0L, - size = file.length() + size = file.length(), + mimeType = mimeType, ) MediaUploadInfo.Audio(file, info) } } - private suspend fun generateImageThumbnail(inputStream: InputStream): ThumbnailProcessingInfo? { + private suspend fun generateImageThumbnail(inputStream: InputStream): ThumbnailProcessingInfo { val thumbnailResult = imageCompressor .compressToTmpFile( inputStream = inputStream, resizeMode = ResizeMode.Strict(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT), - ).getOrNull() + ).getOrThrow() - return thumbnailResult?.toThumbnailProcessingInfo(MimeTypes.Jpeg) + return thumbnailResult.toThumbnailProcessingInfo(MimeTypes.Jpeg) } private fun removeSensitiveImageMetadata(file: File) { @@ -203,7 +205,7 @@ class MediaPreProcessorImpl @Inject constructor( } } - private fun extractVideoMetadata(file: File, mimeType: String?, thumbnailUrl: String?, thumbnailInfo: ThumbnailInfo?): VideoInfo = + private fun extractVideoMetadata(file: File, mimeType: String?, thumbnailUrl: String?, thumbnailInfo: ThumbnailProcessingInfo?): VideoInfo = MediaMetadataRetriever().runAndRelease { setDataSource(context, Uri.fromFile(file)) @@ -213,16 +215,16 @@ class MediaPreProcessorImpl @Inject constructor( height = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)?.toLong() ?: 0L, mimetype = mimeType, size = file.length(), - thumbnailInfo = thumbnailInfo, + thumbnailInfo = thumbnailInfo?.info, thumbnailUrl = thumbnailUrl, - blurhash = null, + blurhash = thumbnailInfo?.blurhash, ) } - private suspend fun extractVideoThumbnail(uri: Uri): ThumbnailProcessingInfo? = + private suspend fun extractVideoThumbnail(uri: Uri): ThumbnailProcessingInfo = MediaMetadataRetriever().runAndRelease { setDataSource(context, uri) - val bitmap = getFrameAtTime(VIDEO_THUMB_FRAME) ?: return@runAndRelease null + val bitmap = requireNotNull(getFrameAtTime(VIDEO_THUMB_FRAME)) val inputStream = ByteArrayOutputStream().use { bitmap.compress(Bitmap.CompressFormat.JPEG, 80, it) ByteArrayInputStream(it.toByteArray()) @@ -249,7 +251,7 @@ fun ImageCompressionResult.toImageInfo(mimeType: String, thumbnailUrl: String?, size = size, thumbnailInfo = thumbnailInfo, thumbnailUrl = thumbnailUrl, - blurhash = null, + blurhash = blurhash, ) fun ImageCompressionResult.toThumbnailProcessingInfo(mimeType: String) = ThumbnailProcessingInfo( @@ -260,4 +262,5 @@ fun ImageCompressionResult.toThumbnailProcessingInfo(mimeType: String) = Thumbna mimetype = mimeType, size = size, ), + blurhash = blurhash, ) diff --git a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt index ea0dbf28fd..490e286353 100644 --- a/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt +++ b/libraries/mediaupload/impl/src/main/kotlin/io/element/android/libraries/mediaupload/VideoCompressor.kt @@ -32,7 +32,7 @@ class VideoCompressor @Inject constructor( ) { fun compress(uri: Uri) = callbackFlow { - val tmpFile = context.createTmpFile() + val tmpFile = context.createTmpFile(extension = "mp4") val future = Transcoder.into(tmpFile.path) .addDataSource(context, uri) .setListener(object : TranscoderListener { diff --git a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt index 10e6edf3f5..24b174426c 100644 --- a/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt +++ b/libraries/permissions/impl/src/test/kotlin/io/element/android/libraries/permissions/impl/DefaultPermissionsPresenterTest.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class, ExperimentalPermissionsApi::class) +@file:OptIn(ExperimentalPermissionsApi::class) package io.element.android.libraries.permissions.impl @@ -25,7 +25,6 @@ import com.google.accompanist.permissions.ExperimentalPermissionsApi import com.google.accompanist.permissions.PermissionStatus import com.google.common.truth.Truth.assertThat import io.element.android.libraries.permissions.api.PermissionsEvents -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt index 36b908cb0d..9992480436 100644 --- a/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt +++ b/libraries/permissions/noop/src/test/kotlin/io/element/android/libraries/permissions/noop/NoopPermissionsPresenterTest.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.libraries.permissions.noop import app.cash.molecule.RecompositionClock import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt index 4f5316497d..48e4daa40c 100644 --- a/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt +++ b/libraries/pushstore/impl/src/test/kotlin/io/element/android/libraries/pushstore/impl/clientsecret/PushClientSecretImplTest.kt @@ -14,13 +14,10 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.libraries.pushstore.impl.clientsecret import com.google.common.truth.Truth.assertThat import io.element.android.libraries.matrix.api.core.SessionId -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Test diff --git a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt index 0260604f6e..28b9dfba50 100644 --- a/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt +++ b/libraries/session-storage/impl/src/test/kotlin/io/element/android/libraries/sessionstorage/impl/DatabaseSessionStoreTests.kt @@ -20,12 +20,10 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import com.squareup.sqldelight.sqlite.driver.JdbcSqliteDriver import io.element.android.libraries.matrix.session.SessionData -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class DatabaseSessionStoreTests { private lateinit var database: SessionDatabase diff --git a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt index 885a830b96..962cb2c18d 100644 --- a/plugins/src/main/kotlin/extension/DependencyHandleScope.kt +++ b/plugins/src/main/kotlin/extension/DependencyHandleScope.kt @@ -43,9 +43,7 @@ fun DependencyHandlerScope.composeDependencies(libs: LibrariesForLibs) { androidTestImplementation(composeBom) implementation("androidx.compose.ui:ui") implementation("androidx.compose.material:material") - // Override BOM version, SearchBar is not available in the actual version - // do not use latest version because of clashes on androidx lifecycle dependency - implementation("androidx.compose.material3:material3:1.1.0-alpha04") + implementation("androidx.compose.material3:material3") implementation("androidx.compose.material:material-icons-extended") implementation("androidx.compose.ui:ui-tooling-preview") implementation(libs.androidx.activity.compose) diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt index 1accae8464..ef776c613c 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/MainActivity.kt @@ -14,15 +14,12 @@ * limitations under the License. */ -@file:OptIn(ExperimentalMaterial3Api::class) - package io.element.android.samples.minimal import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index df1f052e79..aab7d5ca5f 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -33,17 +33,16 @@ import io.element.android.libraries.dateformatter.impl.LocalDateTimeProvider import io.element.android.libraries.designsystem.utils.SnackbarDispatcher import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import kotlinx.datetime.Clock import kotlinx.datetime.TimeZone import java.util.Locale -import java.util.concurrent.Executors class RoomListScreen( context: Context, private val matrixClient: MatrixClient, + private val coroutineDispatchers: CoroutineDispatchers = Singleton.coroutineDispatchers, ) { private val clock = Clock.System private val locale = Locale.getDefault() @@ -58,28 +57,24 @@ class RoomListScreen( sessionVerificationService = sessionVerificationService, networkMonitor = NetworkMonitorImpl(context), snackbarDispatcher = SnackbarDispatcher(), - inviteStateDataSource = DefaultInviteStateDataSource( - matrixClient, - DefaultSeenInvitesStore(context), - CoroutineDispatchers( - io = Dispatchers.IO, - computation = Dispatchers.Default, - main = Dispatchers.Main, - diffUpdateDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() - ) - ) + inviteStateDataSource = DefaultInviteStateDataSource(matrixClient, DefaultSeenInvitesStore(context), coroutineDispatchers) ) @Composable fun Content(modifier: Modifier = Modifier) { fun onRoomClicked(roomId: RoomId) { - val room = matrixClient.getRoom(roomId)!! - val timeline = room.timeline() Singleton.appScope.launch { - timeline.apply { - initialize() - paginateBackwards(20, 50) - dispose() + withContext(coroutineDispatchers.io) { + matrixClient.getRoom(roomId)!!.use { room -> + val timeline = room.timeline() + + timeline.apply { + // TODO This doesn't work reliably as initialize is asynchronous, and the timeline can't be used until it's finished + initialize() + paginateBackwards(20, 50) + dispose() + } + } } } } diff --git a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt index 1ccd4c5b7a..d6000dc1d8 100644 --- a/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt +++ b/services/appnavstate/impl/src/test/kotlin/io/element/android/services/appnavstate/impl/DefaultAppNavigationStateServiceTest.kt @@ -14,8 +14,6 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.services.appnavstate.impl import com.google.common.truth.Truth.assertThat @@ -28,11 +26,11 @@ import io.element.android.services.appnavstate.test.A_ROOM_OWNER import io.element.android.services.appnavstate.test.A_SESSION_OWNER import io.element.android.services.appnavstate.test.A_SPACE_OWNER import io.element.android.services.appnavstate.test.A_THREAD_OWNER -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.junit.Assert.assertThrows import org.junit.Test + class DefaultAppNavigationStateServiceTest { @Test diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt index 1309a14cb1..e155eabd73 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/TestCoroutineDispatchers.kt @@ -14,12 +14,9 @@ * limitations under the License. */ -@file:OptIn(ExperimentalCoroutinesApi::class) - package io.element.android.tests.testutils import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.TestCoroutineScheduler import kotlinx.coroutines.test.TestDispatcher import kotlinx.coroutines.test.UnconfinedTestDispatcher diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png index 2b77d948e4..7daf56c098 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f92e4287ae8db895d18452840a29240989597dea560640244b8188ee61e8c266 -size 30730 +oid sha256:57c89db393e87207bb9628244c435ee1541df327814d1ef7f296b80df8b8100c +size 30821 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png index c36b73a560..55b5f262b6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.components_null_DefaultGroup_LabelledTextFieldLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c66806646645afee1654b50818c539863e60129eba7fcd608e9801bd34d0fe30 -size 30028 +oid sha256:89bcb8fd5b3f68fbfc1568cd5e7d5c7bbabd3ca05836fbb1d8e75fbc32379317 +size 30222 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 699b8973c1..160def016f 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:756d835de7820c96019995ab47d34c2663330c4ddc9ca124eac266ea9eeef0ab -size 64397 +oid sha256:24ad0b5edc27ddddca573b37f82579715d880793c0dc880248d2b572dd6d6a40 +size 64379 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 62e855dab1..c6a054a2d3 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:df7631ad85dc6b3fbdabfc820b11b4e12a6128202e3faa2d105ba22793fe8c22 -size 103428 +oid sha256:b0c9d4eaaaf6a691f43d25e0855ee056b69fa090ad8c53b26732c8f2532e2138 +size 103384 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index a9a3720ece..26129e266a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a03aa11c787804adc5b2947359421b90e6abcdc7e840983a0276a6e02da59a2f -size 58526 +oid sha256:4023481d2a045d7e0d824db81b28ee47c4bcdc568d98537b3573d554e0b76e32 +size 58445 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 4045dae649..af4d0b2046 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.configureroom_null_DefaultGroup_ConfigureRoomViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3c2731ea21ca5b001aadc29ecbb15a2b263b0c042b7344cb10149174ff88ec0a -size 96835 +oid sha256:8f953d0df467ad4cd1ae24291e9593945b945712506f093bdcae3da975f8ca91 +size 96800 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 7aa5b9b9ba..f2249286f5 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fac7314c1dee8d4570922b984b0749f24ec8984e5e602892e2e4ed925d143b6 -size 41145 +oid sha256:c9e1058bff99d3193f3e69d7635a0446f76295ecdd47a16b03e39690a7a4852f +size 41232 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index bcae624eb5..5332196c0c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dff29249aabb7818151e55dbc9a1290bc09cf0e638e3ad2842791904b14368ca -size 44381 +oid sha256:42e2f813350feb63441492b67653fd22edb311d4e7068d32a42a537c9e78e94f +size 44632 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index ba0061da9b..eae8a2c125 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5450116320f6d75befbc784a31a8ad51c868936794e75e65bf24e6e1b69f1e23 -size 44421 +oid sha256:29c3ac70cd3f7a66d2be73996fe93825f850bb5a73312e9a0f2434a5e7003035 +size 44631 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 1bea49ba74..3f6519d314 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4363256d328a0ab2cc3966b42827c7a7319bee962710a0bac542f6494799ff71 -size 48526 +oid sha256:494167e5d7f96bf16b16f5b7c53b304dacf4015b31ce0cd51bfe9e05802664df +size 48831 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png index 6740abc12a..7f8690a106 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:afa9ab67471727f4c396d1919fd1187ec28a40b8cb9b03c2b35400c5e32f4eda -size 43776 +oid sha256:746b009cb527d5ed6be3c8b30bac9465c42970bd9766e259aee2957aaeb96da7 +size 44007 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png index 17f36f745e..d6ebbc9165 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b724d1b93a04117fc3cc405920fd22530e004cc9ba9c39f88ca1a2a2a4229c4 -size 43743 +oid sha256:cc677f36b2262eabc091023be5a373aaa51d47237fc6777d9b4950f16efa6ea2 +size 43964 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index d97cb348db..0827e7d026 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:28c23058fdd9b24a7079251a2ab1ae2f85dafe0c1d4f01be3ffeca91984e0805 -size 37703 +oid sha256:ad6c6bdc7d4e4c405231c2c8944613ff151eb309e00821ac7eda107e853ed456 +size 37891 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 2520176bfd..e005e5910d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7779d6bf28eca6e72f9f1bcbd8b27881d0df0e594041269367a1f909e941cec9 -size 41368 +oid sha256:4cfd887060df1a1b7e12e3f47010e813678fec822595d90b19c51af99b42ab16 +size 41714 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index c48eee4f48..32208a1dde 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:af77d0276f38c78524201c49484548e29d1c99168e68e11874c49f0db0803df2 -size 41392 +oid sha256:ede9377d3fbceae95a10e02cb1c033f9bbd53901137ef88ca52fdd4041737c70 +size 41695 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png index d5c33e67c3..72944ac735 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:27d2d879493e8ed1cc17dcdbb384504126fad63eb7e53a069f530457c35b94b3 -size 47609 +oid sha256:6a976609ec6f5ce83388242db7a43ff6c062f84011ce7d2263e5ff8d23c64a96 +size 47912 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png index 488e1403d4..2b16953c1c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:66e9060ad93cec0e1bf8e284ac959a713a8201a33ff931aeb61aaf29ab7d9640 -size 40335 +oid sha256:e0a065c5a4c0af748524b0600e1b5233023cdff0eaae8a2039649323e8a19fe3 +size 40635 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png index 1cc8419b21..bb4d824ee8 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.changeserver_null_DefaultGroup_ChangeServerViewLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7f7ecb805b73afe15db56853fec77d92f28fc9f8080dc7f1b2bb9f99cc262719 -size 40317 +oid sha256:dfa33ed990249e2d3e0097521bc51a3028bf51537736839a1301b54f99ad473b +size 40598 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 558ba024ca..1277ac6fa1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1501fd1372e15cd6f7693c4c854afcfd5ade3eb7a972f8cc76aea10de8416a20 -size 33382 +oid sha256:2bd0026073c5d0b7bb916bff9a034aca6393d005e299298f089f2f64d541f166 +size 33301 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png index 8341e4aa97..9919e584a6 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:762a1d43dc3bc9edfa47f081b8385614f7075b5cecdd3eb3cb6b4959baf6811c -size 33445 +oid sha256:cbf81d780ebde2c089327df2b1397a6b879b9729dacefa78304e0d16e0a4dc11 +size 33318 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png index a0dbfd3863..a73c22ff1a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7559f64cb74c33ef441dd15d557607061b72ffcdd3dce7ec7f8a663d928fd681 -size 29585 +oid sha256:4777def89f3b8717c8f9cba0023d33d8e244b3041270d283da28b813fd8f52ad +size 29606 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png index 558ba024ca..1277ac6fa1 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1501fd1372e15cd6f7693c4c854afcfd5ade3eb7a972f8cc76aea10de8416a20 -size 33382 +oid sha256:2bd0026073c5d0b7bb916bff9a034aca6393d005e299298f089f2f64d541f166 +size 33301 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png index d713681419..0ec9d0351d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68ef9478a9bbf361a7ace91b9214c6fca449a23b2756f0e0692e634f25c98181 -size 32385 +oid sha256:3eaf06ed9c04ad6b2d9314e7305b4c090cc5d78e6621628eed5c16a9e2738c0d +size 32196 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png index 7140bc573f..bd0731900e 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2fafe08ac635bf3f0e62753c832ead92de297f0826ec735efe1a1f90481cb421 -size 32417 +oid sha256:227596a612d3e87d4dd55ef43068875b1d50e334056b48e956e2708e5ed10645 +size 32220 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png index a7010b4602..36576c6d42 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7954d3f7064666b287ce6ab05fb6814f0e4570db2db6a44437291f0f90d8e87f -size 29062 +oid sha256:beeb25a16dd5662be9b633b761fae145b91515859f87bda2399323c38cc0e561 +size 28995 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png index d713681419..0ec9d0351d 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.login.impl.root_null_DefaultGroup_LoginRootScreenLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:68ef9478a9bbf361a7ace91b9214c6fca449a23b2756f0e0692e634f25c98181 -size 32385 +oid sha256:3eaf06ed9c04ad6b2d9314e7305b4c090cc5d78e6621628eed5c16a9e2738c0d +size 32196 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png index a1f391b1b1..cd34f90d2b 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d38eddd629e8c46993e616ed5bab2689fa5f24bb023ae43afca1a41ef5381ea5 -size 59843 +oid sha256:847c42b0aec8b12708f2a429bbbe2a86ca2b149b5963dd16135f3b34d9d0c073 +size 60153 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png index 2968e30033..07451a0330 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9630d9b5e3d26b677eaa8c0496d76d73f95d28a487ef0ef845d3a89747d6a8cb -size 179668 +oid sha256:628f1f00dca9d15faabd8288a3c54b1b8581a380cf14edf364f0dee9ecc187d5 +size 180117 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png index 2d89c7181f..87ba73daee 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dcff656fe8f2b1233dc55e5e4bc80800aa8ed511bb328b178f8944c83d4199a2 -size 59284 +oid sha256:43974ffd4bec4c2d8e07a6d4798f00db1aec316a11633c6d2eb5c6608deb694d +size 59586 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_0,NEXUS_5,1.0,en].png index 3dba4f5dbc..a86c5980de 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:60ee09b1052414f00af4d80dcf63c738d7dc937f5d58a3a043840cacc3618317 -size 58781 +oid sha256:18597d5e200b7079201cfa169ac3f9266c089b7dadde691c750192d2e22c1dea +size 59035 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png index 3292b422ee..8146c241cb 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2f8ad5f69dbcda0284f731a01851fc73dc93ba9b1fad9e778b23f0155bfc7107 -size 178346 +oid sha256:827bccb0fdd3106fb24a95d665b4da2cfc15de48e8f508ae809c9f75d6d1bbf0 +size 178915 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_2,NEXUS_5,1.0,en].png index 4470d22005..7aa17c8c18 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_2,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.rageshake.impl.bugreport_null_DefaultGroup_BugReportViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f01532b48a428362db2f963861a0ec5731ee3b1f13869a526facce3a85f82e8f -size 56677 +oid sha256:6bfb40e55460e4c11fdf0f020982b63e2b726a329902ceaa57522bafa328789d +size 56987 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_0,NEXUS_5,1.0,en].png index 27f36f9248..f96c3b07b9 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6893108aabbf06f43af90f17b6b1a8a521312559f407647ce08db06ecb9a8f84 -size 22033 +oid sha256:29fa45284bef52648e02629a440b335595c7d4a4d04d0da5c4acbe9ae457bad6 +size 46918 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..d03094bab0 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:49e7533e6afba903219942193e0cc5cfbe2f67ba3b57516f77c259e7c42e8e3f +size 11813 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..f13a65f8e2 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:89ea65099fb4981bbeb24e49afb7400b0e8da79e3b783e198519ba1e970404a8 +size 8317 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..74a7f599cd --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a8ba207cf61c56b64c6855d04c8a30def0b3daf325adf57edd45b40981ed745 +size 7733 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..31ca39b498 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92c4537ed8794f4db08b4edec74a0de41847c4fd8c4fe03b7112bc63124b0a61 +size 29326 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..0f9a5a79cc --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListDarkPreview_0_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d5b879b74654fdad0638f432a71adfc9186fbe00dafd5d963a3affb33ec8c5c8 +size 12841 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_0,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_0,NEXUS_5,1.0,en].png index b493c070d2..c6b9c1b3ab 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_0,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_0,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3e7b5bd916d4d3067b5013400b4ac864fab560e96fcb75ec895684021a00b8ba -size 21808 +oid sha256:3d40819700e3cbe9ca8ae1e5886de036a6dc05a7da6dd366490cc79c27ae3e8d +size 45653 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_2,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..3d261cb40b --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9c3abebbe9e55706af5f4d1089e4308b0e975258a9d6a0e1793a4208b36c9c93 +size 11754 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_3,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_3,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..eb23dcec96 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_3,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b8de2f28cf9918a7eddcc1311c34ba0376242a4708724b57689df000b7480524 +size 8197 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_4,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_4,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e2e4a33e67 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_4,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:687133e4729ea42382ac0b76af6ceaf8b20cbc65923412fb97b077d2475c70f7 +size 7514 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_5,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_5,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..4275020d4c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_5,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1f663a70c43b9f2e66b651f17169b63859fe7c87c21312b61ed61a9136eabe5f +size 27989 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_6,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_6,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..787c71d87e --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.roomdetails.impl.members_null_DefaultGroup_RoomMemberListLightPreview_0_null_6,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:534cba0c13aa52b3557ab8df854c521dd13b82e5a1550d73abcef12925e389da +size 11878 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsDarkPreview_0_null,NEXUS_5,1.0,en].png index a1f82c98d8..a75c5f4399 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:02be8bb5887ed87aadc9758466cd4f3c622255fd32bccd347cedb70f509efa73 -size 49879 +oid sha256:916228ae8244eb2dd1602d6e3030890138d92c0775e1c8d8c11fcea810b32e6a +size 50153 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsPreview_0_null,NEXUS_5,1.0,en].png index 29d860ddc9..2fe01fc5ff 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_OutlinedTextFieldsPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:38fdce46be1fd5acf8c8f9e23e59a1cae5f458daa191d7ba9fde6c929b380823 -size 45128 +oid sha256:914aecd8cef405ffd0c8d33b65d718ecad50060e95cb4b866de914a79c5ce6aa +size 45342 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png index 80f1a795af..c802637d6c 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:505c2f4449cf297eec253c4a027b268e7f774bebd65dce94b651578b4659cb1f -size 55908 +oid sha256:52de83a9b21ff774b53db05f287fd76dcae248e6f509f8ff9873d1a252313dc2 +size 55177 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldLightPreview_0_null,NEXUS_5,1.0,en].png b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldLightPreview_0_null,NEXUS_5,1.0,en].png index 70831eed9a..afe53b155a 100644 --- a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldLightPreview_0_null,NEXUS_5,1.0,en].png +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.theme.components_null_TextFields_TextFieldLightPreview_0_null,NEXUS_5,1.0,en].png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f44ac6df2ab5199c0243c2b01e503912f2272657223abf1de22b359b2df2dfd -size 55733 +oid sha256:12283d22402cc6bb118e8ea5c2ef6c294f46e8c0f5d594ddfb374931e2717069 +size 55224