diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 1028c6f16d..82389a8884 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -32,6 +32,7 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push +import com.bumble.appyx.navmodel.backstack.operation.replace import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode @@ -191,7 +192,16 @@ class LoggedInFlowNode @AssistedInject constructor( .build() } NavTarget.CreateRoom -> { - createRoomEntryPoint.createNode(this, buildContext) + val callback = object : CreateRoomEntryPoint.Callback { + override fun onOpenRoom(roomId: RoomId) { + backstack.replace(NavTarget.Room(roomId)) + } + } + + createRoomEntryPoint + .nodeBuilder(this, buildContext) + .callback(callback) + .build() } NavTarget.VerifySession -> { verifySessionEntryPoint.createNode(this, buildContext) diff --git a/build.gradle.kts b/build.gradle.kts index d228aa2acb..0da25dc1bd 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -229,7 +229,7 @@ koverMerged { target = kotlinx.kover.api.VerificationTarget.CLASS overrideClassFilter { includes += "*Presenter" - excludes += "*TemplatePresenter" + excludes += "*Fake*Presenter" } bound { minValue = 90 diff --git a/changelog.d/96.feature b/changelog.d/96.feature new file mode 100644 index 0000000000..7a1c8d21b8 --- /dev/null +++ b/changelog.d/96.feature @@ -0,0 +1 @@ +[Create and join rooms] Show or create direct message room diff --git a/features/createroom/api/build.gradle.kts b/features/createroom/api/build.gradle.kts index b3fceedc27..cafcf7c4b5 100644 --- a/features/createroom/api/build.gradle.kts +++ b/features/createroom/api/build.gradle.kts @@ -24,4 +24,5 @@ android { dependencies { implementation(projects.libraries.architecture) + implementation(projects.libraries.matrix.api) } diff --git a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt index 049c101806..73f5110daa 100644 --- a/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt +++ b/features/createroom/api/src/main/kotlin/io/element/android/features/createroom/api/CreateRoomEntryPoint.kt @@ -16,6 +16,21 @@ package io.element.android.features.createroom.api -import io.element.android.libraries.architecture.SimpleFeatureEntryPoint +import com.bumble.appyx.core.modality.BuildContext +import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin +import io.element.android.libraries.architecture.FeatureEntryPoint +import io.element.android.libraries.matrix.api.core.RoomId -interface CreateRoomEntryPoint : SimpleFeatureEntryPoint +interface CreateRoomEntryPoint : FeatureEntryPoint { + + fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder + interface NodeBuilder { + fun callback(callback: Callback): NodeBuilder + fun build(): Node + } + + interface Callback : Plugin { + fun onOpenRoom(roomId: RoomId) + } +} diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt index 13c3ff52be..22851904c4 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/CreateRoomFlowNode.kt @@ -23,17 +23,20 @@ import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin +import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode +import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.features.createroom.impl.addpeople.AddPeopleNode import io.element.android.features.createroom.impl.root.CreateRoomRootNode import io.element.android.libraries.architecture.BackstackNode import io.element.android.libraries.architecture.animation.rememberDefaultTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) @@ -64,6 +67,10 @@ class CreateRoomFlowNode @AssistedInject constructor( override fun onCreateNewRoom() { backstack.push(NavTarget.NewRoom) } + + override fun onOpenRoom(roomId: RoomId) { + plugins().forEach { it.onOpenRoom(roomId) } + } } createNode(buildContext, plugins = listOf(callback)) } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt index 214ed3a9ec..34e514be3e 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultCreateRoomEntryPoint.kt @@ -18,6 +18,7 @@ package io.element.android.features.createroom.impl import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node +import com.bumble.appyx.core.plugin.Plugin import com.squareup.anvil.annotations.ContributesBinding import io.element.android.features.createroom.api.CreateRoomEntryPoint import io.element.android.libraries.architecture.createNode @@ -26,7 +27,21 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultCreateRoomEntryPoint @Inject constructor() : CreateRoomEntryPoint { - override fun createNode(parentNode: Node, buildContext: BuildContext): Node { - return parentNode.createNode(buildContext) + + override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): CreateRoomEntryPoint.NodeBuilder { + + val plugins = ArrayList() + + return object : CreateRoomEntryPoint.NodeBuilder { + + override fun callback(callback: CreateRoomEntryPoint.Callback): CreateRoomEntryPoint.NodeBuilder { + plugins += callback + return this + } + + override fun build(): Node { + return parentNode.createNode(buildContext, plugins) + } + } } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt index 5d2f0f684c..87f35e4b1b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootEvents.kt @@ -19,6 +19,7 @@ package io.element.android.features.createroom.impl.root import io.element.android.libraries.matrix.ui.model.MatrixUser sealed interface CreateRoomRootEvents { - data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents object InvitePeople : CreateRoomRootEvents + data class StartDM(val matrixUser: MatrixUser) : CreateRoomRootEvents + object CancelStartDM : CreateRoomRootEvents } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt index dadc16efc4..3a714a0eb3 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootNode.kt @@ -16,7 +16,6 @@ package io.element.android.features.createroom.impl.root -import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.bumble.appyx.core.modality.BuildContext @@ -27,7 +26,7 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedInject import io.element.android.anvilannotations.ContributesNode import io.element.android.libraries.di.SessionScope -import kotlinx.parcelize.Parcelize +import io.element.android.libraries.matrix.api.core.RoomId @ContributesNode(SessionScope::class) class CreateRoomRootNode @AssistedInject constructor( @@ -38,15 +37,17 @@ class CreateRoomRootNode @AssistedInject constructor( interface Callback : Plugin { fun onCreateNewRoom() + fun onOpenRoom(roomId: RoomId) } - private fun onCreateNewRoom() { - plugins().forEach { it.onCreateNewRoom() } - } + private val callback = object : Callback { + override fun onCreateNewRoom() { + plugins().forEach { it.onCreateNewRoom() } + } - sealed interface NavTarget : Parcelable { - @Parcelize - object Root : NavTarget + override fun onOpenRoom(roomId: RoomId) { + plugins().forEach { it.onOpenRoom(roomId) } + } } @Composable @@ -56,7 +57,8 @@ class CreateRoomRootNode @AssistedInject constructor( state = state, modifier = modifier, onClosePressed = this::navigateUp, - onNewRoomClicked = this::onCreateNewRoom, + onNewRoomClicked = callback::onCreateNewRoom, + onOpenDM = callback::onOpenRoom, ) } } 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 1250031710..89932b0cdb 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 @@ -17,19 +17,29 @@ package io.element.android.features.createroom.impl.root import androidx.compose.runtime.Composable -import io.element.android.features.userlist.api.SelectionMode +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import io.element.android.features.userlist.api.MatrixUserDataSource +import io.element.android.features.userlist.api.SelectionMode import io.element.android.features.userlist.api.UserListPresenter import io.element.android.features.userlist.api.UserListPresenterArgs +import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter +import io.element.android.libraries.architecture.execute +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.ui.model.MatrixUser -import timber.log.Timber +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch import javax.inject.Inject import javax.inject.Named class CreateRoomRootPresenter @Inject constructor( private val presenterFactory: UserListPresenter.Factory, @Named("AllUsers") private val matrixUserDataSource: MatrixUserDataSource, + private val matrixClient: MatrixClient, ) : Presenter { private val presenter by lazy { @@ -43,20 +53,37 @@ class CreateRoomRootPresenter @Inject constructor( override fun present(): CreateRoomRootState { val userListState = presenter.present() + val localCoroutineScope = rememberCoroutineScope() + val startDmAction: MutableState> = remember { mutableStateOf(Async.Uninitialized) } + + fun startDm(matrixUser: MatrixUser) { + startDmAction.value = Async.Uninitialized + val existingDM = matrixClient.findDM(matrixUser.id) + if (existingDM == null) { + localCoroutineScope.createDM(matrixUser, startDmAction) + } else { + startDmAction.value = Async.Success(existingDM.roomId) + } + } + fun handleEvents(event: CreateRoomRootEvents) { when (event) { - is CreateRoomRootEvents.StartDM -> handleStartDM(event.matrixUser) + is CreateRoomRootEvents.StartDM -> startDm(event.matrixUser) + CreateRoomRootEvents.CancelStartDM -> startDmAction.value = Async.Uninitialized CreateRoomRootEvents.InvitePeople -> Unit // Todo Handle invite people action } } return CreateRoomRootState( userListState = userListState, + startDmAction = startDmAction.value, eventSink = ::handleEvents, ) } - private fun handleStartDM(matrixUser: MatrixUser) { - Timber.d("handleStartDM: $matrixUser") // Todo handle start DM action + private fun CoroutineScope.createDM(user: MatrixUser, startDmAction: MutableState>) = launch { + suspend { + matrixClient.createDM(user.id).getOrThrow() + }.execute(startDmAction) } } diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt index d5d75fcfae..d8e7533b0b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootState.kt @@ -17,8 +17,11 @@ package io.element.android.features.createroom.impl.root import io.element.android.features.userlist.api.UserListState +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId data class CreateRoomRootState( val userListState: UserListState, + val startDmAction: Async, val eventSink: (CreateRoomRootEvents) -> Unit, ) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt index d7c18085dc..72ed4d0d0b 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootStateProvider.kt @@ -18,15 +18,41 @@ package io.element.android.features.createroom.impl.root import androidx.compose.ui.tooling.preview.PreviewParameterProvider import io.element.android.features.userlist.api.aUserListState +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.ui.components.aMatrixUser +import kotlinx.collections.immutable.persistentListOf open class CreateRoomRootStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aCreateRoomRootState(), + aCreateRoomRootState().copy( + startDmAction = Async.Loading(), + userListState = aMatrixUser().let { + aUserListState().copy( + searchQuery = it.id.value, + searchResults = persistentListOf(it), + selectedUsers = persistentListOf(it), + isSearchActive = true, + ) + } + ), + aCreateRoomRootState().copy( + startDmAction = Async.Failure(Throwable()), + userListState = aMatrixUser().let { + aUserListState().copy( + searchQuery = it.id.value, + searchResults = persistentListOf(it), + selectedUsers = persistentListOf(it), + isSearchActive = true, + ) + } + ), ) } fun aCreateRoomRootState() = CreateRoomRootState( eventSink = {}, - userListState = aUserListState(), + startDmAction = Async.Uninitialized, + userListState = aUserListState(), ) 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 8a4bd5af2d..e488645b78 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 @@ -16,6 +16,7 @@ package io.element.android.features.createroom.impl.root +import android.widget.Toast import androidx.annotation.DrawableRes import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -28,17 +29,22 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import io.element.android.features.userlist.api.UserListView import io.element.android.features.createroom.impl.R +import io.element.android.features.userlist.api.UserListView +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.designsystem.components.ProgressDialog +import io.element.android.libraries.designsystem.components.dialogs.RetryDialog 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 @@ -46,6 +52,7 @@ 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.Text +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 @@ -56,7 +63,14 @@ fun CreateRoomRootView( modifier: Modifier = Modifier, onClosePressed: () -> Unit = {}, onNewRoomClicked: () -> Unit = {}, + onOpenDM: (RoomId) -> Unit = {}, ) { + if (state.startDmAction is Async.Success) { + LaunchedEffect(state.startDmAction) { + onOpenDM(state.startDmAction.state) + } + } + Scaffold( modifier = modifier.fillMaxWidth(), topBar = { @@ -69,10 +83,16 @@ fun CreateRoomRootView( modifier = Modifier.padding(paddingValues), verticalArrangement = Arrangement.spacedBy(8.dp), ) { + val context = LocalContext.current UserListView( modifier = Modifier.fillMaxWidth(), state = state.userListState, - onUserSelected = { state.eventSink.invoke(CreateRoomRootEvents.StartDM(it)) }, + onUserSelected = { + // Fixme disabled DM creation since it can break the account data which is not correctly synced + // uncomment to enable it again or move behind a feature flag + Toast.makeText(context, "Create DM feature is disabled.", Toast.LENGTH_SHORT).show() +// state.eventSink(CreateRoomRootEvents.StartDM(it)) + }, ) if (!state.userListState.isSearchActive) { @@ -83,6 +103,25 @@ fun CreateRoomRootView( } } } + + when (state.startDmAction) { + is Async.Loading -> { + ProgressDialog(text = stringResource(id = StringR.string.common_creating_room)) + } + is Async.Failure -> { + RetryDialog( + content = stringResource(id = StringR.string.screen_start_chat_error_starting_chat), + onDismiss = { state.eventSink(CreateRoomRootEvents.CancelStartDM) }, + onRetry = { + state.userListState.selectedUsers.firstOrNull() + ?.let { state.eventSink(CreateRoomRootEvents.StartDM(it)) } + // Cancel start DM if there is no more selected user (should not happen) + ?: state.eventSink(CreateRoomRootEvents.CancelStartDM) + }, + ) + } + else -> Unit + } } @OptIn(ExperimentalMaterial3Api::class) 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 b9e0c32d7d..086b5edf30 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 @@ -22,10 +22,8 @@ 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.userlist.api.MatrixUserDataSource -import io.element.android.features.userlist.api.UserListPresenterArgs -import io.element.android.features.userlist.impl.DefaultUserListPresenter import io.element.android.features.userlist.test.FakeMatrixUserDataSource +import io.element.android.features.userlist.test.FakeUserListPresenterFactory import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -37,11 +35,7 @@ class AddPeoplePresenterTests { @Before fun setup() { - val userListFactory = object : DefaultUserListPresenter.DefaultUserListFactory { - override fun create(args: UserListPresenterArgs, dataSource: MatrixUserDataSource) = DefaultUserListPresenter(args, dataSource) - } - val dataSource = FakeMatrixUserDataSource() - presenter = AddPeoplePresenter(userListFactory, dataSource) + presenter = AddPeoplePresenter(FakeUserListPresenterFactory(), FakeMatrixUserDataSource()) } @Test 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 f90856deda..cf399fdbd3 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 @@ -22,12 +22,18 @@ 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.userlist.api.MatrixUserDataSource -import io.element.android.features.userlist.api.UserListPresenterArgs -import io.element.android.features.userlist.impl.DefaultUserListPresenter +import io.element.android.features.userlist.api.aUserListState import io.element.android.features.userlist.test.FakeMatrixUserDataSource +import io.element.android.features.userlist.test.FakeUserListPresenter +import io.element.android.features.userlist.test.FakeUserListPresenterFactory +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId +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 io.element.android.libraries.matrix.ui.model.MatrixUser +import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Before @@ -37,14 +43,15 @@ class CreateRoomRootPresenterTests { private lateinit var userListDataSource: FakeMatrixUserDataSource private lateinit var presenter: CreateRoomRootPresenter + private lateinit var fakeUserListPresenter: FakeUserListPresenter + private lateinit var fakeMatrixClient: FakeMatrixClient @Before fun setup() { - val userListPresenter = object : DefaultUserListPresenter.DefaultUserListFactory { - override fun create(args: UserListPresenterArgs, dataSource: MatrixUserDataSource) = DefaultUserListPresenter(args, dataSource) - } + fakeUserListPresenter = FakeUserListPresenter() + fakeMatrixClient = FakeMatrixClient() userListDataSource = FakeMatrixUserDataSource() - presenter = CreateRoomRootPresenter(userListPresenter, userListDataSource) + presenter = CreateRoomRootPresenter(FakeUserListPresenterFactory(fakeUserListPresenter), userListDataSource, fakeMatrixClient) } @Test @@ -68,13 +75,82 @@ class CreateRoomRootPresenterTests { } @Test - fun `present - trigger start DM action`() = runTest { + fun `present - trigger create DM action`() = runTest { moleculeFlow(RecompositionClock.Immediate) { presenter.present() }.test { val initialState = awaitItem() val matrixUser = MatrixUser(UserId("@name:matrix.org")) + val createDmResult = Result.success(RoomId("!createDmResult")) + + fakeMatrixClient.givenFindDmResult(null) + fakeMatrixClient.givenCreateDmResult(createDmResult) + initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + val stateAfterStartDM = awaitItem() + assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) + assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) + } + } + + @Test + fun `present - trigger retrieve DM action`() = runTest { + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val matrixUser = MatrixUser(UserId("@name:matrix.org")) + val fakeDmResult = FakeMatrixRoom(RoomId("!fakeDmResult")) + + fakeMatrixClient.givenFindDmResult(fakeDmResult) + + initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + val stateAfterStartDM = awaitItem() + assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) + assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId) + } + } + + @Test + fun `present - trigger retry create DM action`() = runTest { + moleculeFlow(RecompositionClock.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + val matrixUser = MatrixUser(UserId("@name:matrix.org")) + val createDmResult = Result.success(RoomId("!createDmResult")) + fakeUserListPresenter.givenState(aUserListState().copy(selectedUsers = persistentListOf(matrixUser))) + + fakeMatrixClient.givenFindDmResult(null) + fakeMatrixClient.givenCreateDmError(A_THROWABLE) + fakeMatrixClient.givenCreateDmResult(createDmResult) + + // Failure + initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + val stateAfterStartDM = awaitItem() + assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java) + + // Cancel + stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM) + val stateAfterCancel = awaitItem() + assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java) + + // Failure + stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + val stateAfterSecondAttempt = awaitItem() + assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java) + + // Retry with success + fakeMatrixClient.givenCreateDmError(null) + stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + assertThat(awaitItem().startDmAction).isInstanceOf(Async.Uninitialized::class.java) + assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) + val stateAfterRetryStartDM = awaitItem() + assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java) + assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) } } } diff --git a/features/userlist/test/build.gradle.kts b/features/userlist/test/build.gradle.kts index 56ac66c154..98ecc7a3b4 100644 --- a/features/userlist/test/build.gradle.kts +++ b/features/userlist/test/build.gradle.kts @@ -14,10 +14,8 @@ * limitations under the License. */ -// TODO: Remove once https://youtrack.jetbrains.com/issue/KTIJ-19369 is fixed -@Suppress("DSL_SCOPE_VIOLATION") plugins { - id("io.element.android-library") + id("io.element.android-compose-library") } android { @@ -25,6 +23,7 @@ android { } dependencies { + implementation(projects.libraries.architecture) implementation(projects.libraries.matrixui) implementation(projects.libraries.matrix.api) api(projects.features.userlist.api) diff --git a/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenter.kt b/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenter.kt new file mode 100644 index 0000000000..54aef4a6f7 --- /dev/null +++ b/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenter.kt @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.userlist.test + +import androidx.compose.runtime.Composable +import io.element.android.features.userlist.api.UserListPresenter +import io.element.android.features.userlist.api.UserListState +import io.element.android.features.userlist.api.aUserListState + +class FakeUserListPresenter : UserListPresenter { + + private var state = aUserListState() + + fun givenState(state: UserListState) { + this.state = state + } + + @Composable + override fun present(): UserListState { + return state + } +} diff --git a/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenterFactory.kt b/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenterFactory.kt new file mode 100644 index 0000000000..37d50c303c --- /dev/null +++ b/features/userlist/test/src/main/kotlin/io/element/android/features/userlist/test/FakeUserListPresenterFactory.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.userlist.test + +import io.element.android.features.userlist.api.MatrixUserDataSource +import io.element.android.features.userlist.api.UserListPresenter +import io.element.android.features.userlist.api.UserListPresenterArgs + +class FakeUserListPresenterFactory( + private val fakeUserListPresenter: FakeUserListPresenter = FakeUserListPresenter() +) : UserListPresenter.Factory { + + override fun create(args: UserListPresenterArgs, matrixUserDataSource: MatrixUserDataSource): UserListPresenter = fakeUserListPresenter +} diff --git a/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt new file mode 100644 index 0000000000..ebfa8effc8 --- /dev/null +++ b/libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/components/dialogs/RetryDialog.kt @@ -0,0 +1,97 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.designsystem.components.dialogs + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.AlertDialogDefaults +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp +import io.element.android.libraries.designsystem.preview.ElementPreviewDark +import io.element.android.libraries.designsystem.preview.ElementPreviewLight +import io.element.android.libraries.designsystem.theme.components.Text +import io.element.android.libraries.ui.strings.R as StringR + +@Composable +fun RetryDialog( + content: String, + modifier: Modifier = Modifier, + title: String = RetryDialogDefaults.title, + retryText: String = RetryDialogDefaults.retryText, + dismissText: String = RetryDialogDefaults.dismissText, + onRetry: () -> Unit = {}, + onDismiss: () -> Unit = {}, + shape: Shape = AlertDialogDefaults.shape, + containerColor: Color = AlertDialogDefaults.containerColor, + iconContentColor: Color = AlertDialogDefaults.iconContentColor, + titleContentColor: Color = AlertDialogDefaults.titleContentColor, + textContentColor: Color = AlertDialogDefaults.textContentColor, + tonalElevation: Dp = AlertDialogDefaults.TonalElevation, +) { + AlertDialog( + modifier = modifier, + onDismissRequest = onDismiss, + title = { + Text(title) + }, + text = { + Text(content) + }, + confirmButton = { + TextButton(onClick = onRetry) { + Text(retryText) + } + }, + dismissButton = { + TextButton(onClick = onDismiss) { + Text(dismissText) + } + }, + shape = shape, + containerColor = containerColor, + iconContentColor = iconContentColor, + titleContentColor = titleContentColor, + textContentColor = textContentColor, + tonalElevation = tonalElevation, + ) +} + +object RetryDialogDefaults { + val title: String @Composable get() = stringResource(id = StringR.string.dialog_title_error) + val retryText: String @Composable get() = stringResource(id = StringR.string.action_retry) + val dismissText: String @Composable get() = stringResource(id = StringR.string.action_cancel) +} + +@Preview +@Composable +internal fun RetryDialogLightPreview() = ElementPreviewLight { ContentToPreview() } + +@Preview +@Composable +internal fun RetryDialogDarkPreview() = ElementPreviewDark { ContentToPreview() } + +@Composable +private fun ContentToPreview() { + RetryDialog( + content = "Content", + ) +} diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index 8a991771a5..abb105bfd0 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -18,6 +18,7 @@ package io.element.android.libraries.matrix.api 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.MediaResolver import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -29,6 +30,8 @@ interface MatrixClient : Closeable { val sessionId: SessionId val roomSummaryDataSource: RoomSummaryDataSource fun getRoom(roomId: RoomId): MatrixRoom? + suspend fun createDM(userId: UserId): Result + fun findDM(userId: UserId): MatrixRoom? fun startSync() fun stopSync() fun mediaResolver(): MediaResolver 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 2fe34bfa55..5a33f1ba06 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 @@ -38,7 +38,10 @@ import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.withContext import org.matrix.rustcomponents.sdk.Client import org.matrix.rustcomponents.sdk.ClientDelegate +import org.matrix.rustcomponents.sdk.CreateRoomParameters import org.matrix.rustcomponents.sdk.RequiredState +import org.matrix.rustcomponents.sdk.RoomPreset +import org.matrix.rustcomponents.sdk.RoomVisibility import org.matrix.rustcomponents.sdk.SlidingSyncListBuilder import org.matrix.rustcomponents.sdk.SlidingSyncMode import org.matrix.rustcomponents.sdk.SlidingSyncRequestListFilters @@ -158,6 +161,30 @@ class RustMatrixClient constructor( ) } + override fun findDM(userId: UserId): MatrixRoom? { + val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) } + return roomId?.let { getRoom(it) } + } + + override suspend fun createDM(userId: UserId): Result = + withContext(dispatchers.io) { + runCatching { + val roomId = client.createRoom( + CreateRoomParameters( + name = null, + topic = null, + isEncrypted = true, + isDirect = true, + visibility = RoomVisibility.PRIVATE, + preset = RoomPreset.TRUSTED_PRIVATE_CHAT, + invite = listOf(userId.value), + avatar = null, + ) + ) + RoomId(roomId) + } + } + override fun mediaResolver(): MediaResolver = mediaResolver override fun sessionVerificationService(): SessionVerificationService = verificationService diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 998c7cdea4..7bebea48b5 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -19,6 +19,7 @@ package io.element.android.libraries.matrix.test import io.element.android.libraries.matrix.api.MatrixClient 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.MediaResolver import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.RoomMembershipObserver @@ -38,12 +39,25 @@ class FakeMatrixClient( private val sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService() ) : MatrixClient { + private var createDmResult: Result = Result.success(A_ROOM_ID) + private var createDmFailure: Throwable? = null + private var findDmResult: MatrixRoom? = FakeMatrixRoom() private var logoutFailure: Throwable? = null override fun getRoom(roomId: RoomId): MatrixRoom? { return FakeMatrixRoom(roomId) } + override suspend fun createDM(userId: UserId): Result { + delay(100) + createDmFailure?.let { throw it } + return createDmResult + } + + override fun findDM(userId: UserId): MatrixRoom? { + return findDmResult + } + override fun startSync() = Unit override fun stopSync() = Unit @@ -52,10 +66,6 @@ class FakeMatrixClient( return FakeMediaResolver() } - fun givenLogoutError(failure: Throwable) { - logoutFailure = failure - } - override suspend fun logout() { delay(100) logoutFailure?.let { throw it } @@ -86,4 +96,22 @@ class FakeMatrixClient( override fun roomMembershipObserver(): RoomMembershipObserver { return RoomMembershipObserver(A_SESSION_ID) } + + // Mocks + + fun givenLogoutError(failure: Throwable?) { + logoutFailure = failure + } + + fun givenCreateDmResult(result: Result) { + createDmResult = result + } + + fun givenCreateDmError(failure: Throwable?) { + createDmFailure = failure + } + + fun givenFindDmResult(result: MatrixRoom?) { + findDmResult = result + } } diff --git a/libraries/rustsdk/build.gradle b/libraries/rustsdk/build.gradle deleted file mode 100644 index bfafe67f28..0000000000 --- a/libraries/rustsdk/build.gradle +++ /dev/null @@ -1,2 +0,0 @@ -configurations.maybeCreate("default") -artifacts.add("default", file('matrix-rust-sdk.aar')) \ No newline at end of file diff --git a/libraries/rustsdk/build.gradle.kts b/libraries/rustsdk/build.gradle.kts new file mode 100644 index 0000000000..56fd094897 --- /dev/null +++ b/libraries/rustsdk/build.gradle.kts @@ -0,0 +1,2 @@ +configurations.maybeCreate("default") +artifacts.add("default", file("matrix-rust-sdk.aar")) diff --git a/settings.gradle.kts b/settings.gradle.kts index 944b17ab52..e44a2cb8fa 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -41,7 +41,6 @@ include(":appnav") include(":tests:uitests") include(":anvilannotations") include(":anvilcodegen") -include(":libraries:rustsdk") include(":samples:minimal") diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_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.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..1e92cf978d --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8f29da5d5aeb65659b065b7bd6afe276f83e020545a027780d2391308d1a4076 +size 20750 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_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.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..74acb423d6 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewDarkPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7b91a2d05b975d568116615c286568f376ebead49e25ff17f5aae8b75be0e1f +size 28876 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_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.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..23c4de3194 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_1,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ae3e8c4e952b97628d026dfe78781aef894d6c2e742ac6ae1f1a2c0170df159e +size 20382 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_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.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_2,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..bb7d1036e4 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.features.createroom.impl.root_null_DefaultGroup_CreateRoomRootViewLightPreview_0_null_2,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:44a35f28b3a59cc28937fee16eda26c26ef7b7622f929218e40f7537e096b2e8 +size 28120 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.dialogs_null_DefaultGroup_RetryDialogDarkPreview_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.components.dialogs_null_DefaultGroup_RetryDialogDarkPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..62dcdf219c --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.dialogs_null_DefaultGroup_RetryDialogDarkPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:596dee7cf5300dc5c2c6b314bf537619466f377b9946114e675c630e2e330976 +size 12059 diff --git a/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.dialogs_null_DefaultGroup_RetryDialogLightPreview_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.components.dialogs_null_DefaultGroup_RetryDialogLightPreview_0_null,NEXUS_5,1.0,en].png new file mode 100644 index 0000000000..e1de911d46 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/io.element.android.tests.uitests_ScreenshotTest_preview_tests[io.element.android.libraries.designsystem.components.dialogs_null_DefaultGroup_RetryDialogLightPreview_0_null,NEXUS_5,1.0,en].png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5f791b1c552138b0d3184b936a51982cfc3e23c0b7de2c9ef5c31559d9279245 +size 12113