mirror of
https://github.com/Novaloop-AG/element-x-android.git
synced 2026-11-04 19:10:43 +00:00
Merge pull request #7822 from element-hq/feature/bma/signedOutScreen
Update signed out screen
This commit is contained in:
@@ -16,6 +16,12 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "io.element.android.features.signedout.impl"
|
namespace = "io.element.android.features.signedout.impl"
|
||||||
|
|
||||||
|
testOptions {
|
||||||
|
unitTests {
|
||||||
|
isIncludeAndroidResources = true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setupDependencyInjection()
|
setupDependencyInjection()
|
||||||
@@ -30,7 +36,7 @@ dependencies {
|
|||||||
implementation(projects.libraries.sessionStorage.api)
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
|
|
||||||
testCommonDependencies(libs)
|
testCommonDependencies(libs, true)
|
||||||
testImplementation(projects.libraries.matrix.test)
|
testImplementation(projects.libraries.matrix.test)
|
||||||
testImplementation(projects.libraries.sessionStorage.test)
|
testImplementation(projects.libraries.sessionStorage.test)
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -9,5 +9,5 @@
|
|||||||
package io.element.android.features.signedout.impl
|
package io.element.android.features.signedout.impl
|
||||||
|
|
||||||
sealed interface SignedOutEvent {
|
sealed interface SignedOutEvent {
|
||||||
data object SignInAgain : SignedOutEvent
|
data object Submit : SignedOutEvent
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-9
@@ -17,17 +17,18 @@ import dev.zacsweers.metro.Assisted
|
|||||||
import dev.zacsweers.metro.AssistedFactory
|
import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.core.meta.BuildMeta
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.mapNotNull
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class SignedOutPresenter(
|
class SignedOutPresenter(
|
||||||
@Assisted private val sessionId: SessionId,
|
@Assisted private val sessionId: SessionId,
|
||||||
private val sessionStore: SessionStore,
|
private val sessionStore: SessionStore,
|
||||||
private val buildMeta: BuildMeta,
|
|
||||||
) : Presenter<SignedOutState> {
|
) : Presenter<SignedOutState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
fun interface Factory {
|
fun interface Factory {
|
||||||
@@ -37,24 +38,31 @@ class SignedOutPresenter(
|
|||||||
@Composable
|
@Composable
|
||||||
override fun present(): SignedOutState {
|
override fun present(): SignedOutState {
|
||||||
val signedOutSession by remember {
|
val signedOutSession by remember {
|
||||||
sessionStore.sessionsFlow().map { sessions ->
|
sessionStore.sessionsFlow()
|
||||||
sessions.firstOrNull { it.userId == sessionId.value }
|
// Ignore the removal of the session, to keep rendering the account data until the screen is closed
|
||||||
}
|
.mapNotNull { sessions -> sessions.firstOrNull { it.userId == sessionId.value } }
|
||||||
}.collectAsState(initial = null)
|
.map { it.toMatrixUser() }
|
||||||
|
}.collectAsState(initial = MatrixUser(userId = sessionId))
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
|
|
||||||
fun handleEvent(event: SignedOutEvent) {
|
fun handleEvent(event: SignedOutEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
SignedOutEvent.SignInAgain -> coroutineScope.launch {
|
SignedOutEvent.Submit -> coroutineScope.launch {
|
||||||
sessionStore.removeSession(sessionId.value)
|
sessionStore.removeSession(sessionId.value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SignedOutState(
|
return SignedOutState(
|
||||||
appName = buildMeta.applicationName,
|
signedOutMatrixUser = signedOutSession,
|
||||||
signedOutSession = signedOutSession,
|
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun SessionData.toMatrixUser() = MatrixUser(
|
||||||
|
userId = sessionId,
|
||||||
|
displayName = userDisplayName,
|
||||||
|
avatarUrl = userAvatarUrl,
|
||||||
|
avatarThumbnail = userAvatarData,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-3
@@ -8,10 +8,9 @@
|
|||||||
|
|
||||||
package io.element.android.features.signedout.impl
|
package io.element.android.features.signedout.impl
|
||||||
|
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
||||||
data class SignedOutState(
|
data class SignedOutState(
|
||||||
val appName: String,
|
val signedOutMatrixUser: MatrixUser,
|
||||||
val signedOutSession: SessionData?,
|
|
||||||
val eventSink: (SignedOutEvent) -> Unit,
|
val eventSink: (SignedOutEvent) -> Unit,
|
||||||
)
|
)
|
||||||
|
|||||||
+9
-32
@@ -9,44 +9,21 @@
|
|||||||
package io.element.android.features.signedout.impl
|
package io.element.android.features.signedout.impl
|
||||||
|
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
|
||||||
open class SignedOutStatePreviewParam : PreviewParameterProvider<SignedOutState> {
|
open class SignedOutStatePreviewParam : PreviewParameterProvider<SignedOutState> {
|
||||||
override val values: Sequence<SignedOutState>
|
override val values: Sequence<SignedOutState>
|
||||||
get() = sequenceOf(
|
get() = sequenceOf(
|
||||||
aSignedOutState(),
|
aSignedOutState(),
|
||||||
// Add other states here
|
aSignedOutState(signedOutSession = aMatrixUser(id = "@alice:server.org", displayName = null)),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun aSignedOutState() = SignedOutState(
|
internal fun aSignedOutState(
|
||||||
appName = "AppName",
|
signedOutSession: MatrixUser = aMatrixUser(id = "@alice:server.org", displayName = "Alice"),
|
||||||
signedOutSession = aSessionData(),
|
eventSink: (SignedOutEvent) -> Unit = {},
|
||||||
eventSink = {},
|
) = SignedOutState(
|
||||||
|
signedOutMatrixUser = signedOutSession,
|
||||||
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun aSessionData(
|
|
||||||
sessionId: String = "@alice:server.org",
|
|
||||||
isTokenValid: Boolean = false,
|
|
||||||
): SessionData {
|
|
||||||
return SessionData(
|
|
||||||
userId = sessionId,
|
|
||||||
deviceId = "aDeviceId",
|
|
||||||
accessToken = "anAccessToken",
|
|
||||||
refreshToken = "aRefreshToken",
|
|
||||||
homeserverUrl = "aHomeserverUrl",
|
|
||||||
oAuthData = null,
|
|
||||||
loginTimestamp = null,
|
|
||||||
isTokenValid = isTokenValid,
|
|
||||||
loginType = LoginType.UNKNOWN,
|
|
||||||
passphrase = null,
|
|
||||||
sessionPath = "/a/path/to/a/session",
|
|
||||||
cachePath = "/a/path/to/a/cache",
|
|
||||||
position = 0,
|
|
||||||
lastUsageIndex = 0,
|
|
||||||
userDisplayName = null,
|
|
||||||
userAvatarUrl = null,
|
|
||||||
userAvatarData = null,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|||||||
+39
-21
@@ -9,14 +9,14 @@
|
|||||||
package io.element.android.features.signedout.impl
|
package io.element.android.features.signedout.impl
|
||||||
|
|
||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.imePadding
|
import androidx.compose.foundation.layout.imePadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.systemBarsPadding
|
import androidx.compose.foundation.layout.systemBarsPadding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.BiasAlignment
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
@@ -31,58 +31,76 @@ import io.element.android.libraries.designsystem.components.visuallist.VisualLis
|
|||||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||||
import io.element.android.libraries.designsystem.theme.components.Button
|
import io.element.android.libraries.designsystem.theme.components.Button
|
||||||
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCard
|
||||||
|
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardMode
|
||||||
|
import io.element.android.libraries.matrix.ui.components.accountinfo.AccountInfoCardState
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://www.figma.com/design/kEAcfun9iSpszeUDvdKZ6b/ER-351--Multi-account-in-EX?node-id=856-6104
|
||||||
|
*/
|
||||||
@Composable
|
@Composable
|
||||||
fun SignedOutView(
|
fun SignedOutView(
|
||||||
state: SignedOutState,
|
state: SignedOutState,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BackHandler(onBack = { state.eventSink(SignedOutEvent.SignInAgain) })
|
BackHandler(onBack = { state.eventSink(SignedOutEvent.Submit) })
|
||||||
HeaderFooterPage(
|
HeaderFooterPage(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.systemBarsPadding()
|
.systemBarsPadding()
|
||||||
.imePadding(),
|
.imePadding(),
|
||||||
header = { SignedOutHeader(state) },
|
isScrollable = true,
|
||||||
content = { SignedOutContent() },
|
header = { SignedOutHeader() },
|
||||||
|
content = { SignedOutContent(state.signedOutMatrixUser) },
|
||||||
footer = {
|
footer = {
|
||||||
SignedOutFooter(
|
SignedOutFooter(
|
||||||
onSignInAgain = { state.eventSink(SignedOutEvent.SignInAgain) },
|
onSubmit = { state.eventSink(SignedOutEvent.Submit) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SignedOutHeader(state: SignedOutState) {
|
private fun SignedOutHeader() {
|
||||||
IconTitleSubtitleMolecule(
|
IconTitleSubtitleMolecule(
|
||||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
||||||
title = stringResource(id = R.string.screen_signed_out_title),
|
title = stringResource(id = R.string.screen_signed_out_title),
|
||||||
subTitle = stringResource(id = R.string.screen_signed_out_subtitle, state.appName),
|
subTitle = stringResource(id = R.string.screen_signed_out_subtitle_v2),
|
||||||
iconStyle = BigIcon.Style.Default(CompoundIcons.UserProfileSolid()),
|
iconStyle = BigIcon.Style.Default(
|
||||||
|
CompoundIcons.Devices(),
|
||||||
|
usePrimaryTint = true,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SignedOutContent() {
|
private fun SignedOutContent(
|
||||||
Box(
|
signedOutMatrixUser: MatrixUser,
|
||||||
modifier = Modifier.fillMaxSize(),
|
) {
|
||||||
contentAlignment = BiasAlignment(
|
Column(
|
||||||
horizontalBias = 0f,
|
modifier = Modifier
|
||||||
verticalBias = -0.4f
|
.fillMaxSize()
|
||||||
)
|
.padding(top = 8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(24.dp),
|
||||||
) {
|
) {
|
||||||
|
AccountInfoCard(
|
||||||
|
AccountInfoCardState(
|
||||||
|
matrixUser = signedOutMatrixUser,
|
||||||
|
mode = AccountInfoCardMode.Simple,
|
||||||
|
)
|
||||||
|
)
|
||||||
VisualList(
|
VisualList(
|
||||||
items = persistentListOf(
|
items = persistentListOf(
|
||||||
VisualListItemData(
|
VisualListItemData(
|
||||||
message = stringResource(id = R.string.screen_signed_out_reason_1),
|
message = stringResource(id = R.string.screen_signed_out_reason_1),
|
||||||
iconVector = CompoundIcons.Lock(),
|
iconVector = CompoundIcons.UserProfile(),
|
||||||
),
|
),
|
||||||
VisualListItemData(
|
VisualListItemData(
|
||||||
message = stringResource(id = R.string.screen_signed_out_reason_2),
|
message = stringResource(id = R.string.screen_signed_out_reason_2),
|
||||||
iconVector = CompoundIcons.Devices(),
|
iconVector = CompoundIcons.Lock(),
|
||||||
),
|
),
|
||||||
VisualListItemData(
|
VisualListItemData(
|
||||||
message = stringResource(id = R.string.screen_signed_out_reason_3),
|
message = stringResource(id = R.string.screen_signed_out_reason_3),
|
||||||
@@ -95,12 +113,12 @@ private fun SignedOutContent() {
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SignedOutFooter(
|
private fun SignedOutFooter(
|
||||||
onSignInAgain: () -> Unit,
|
onSubmit: () -> Unit,
|
||||||
) {
|
) {
|
||||||
ButtonColumnMolecule {
|
ButtonColumnMolecule {
|
||||||
Button(
|
Button(
|
||||||
text = stringResource(id = CommonStrings.action_sign_in_again),
|
text = stringResource(id = CommonStrings.action_ok),
|
||||||
onClick = onSignInAgain,
|
onClick = onSubmit,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="screen_signed_out_reason_1">"You’ve changed your password on another session"</string>
|
<string name="screen_signed_out_reason_1">"You removed this device from your account"</string>
|
||||||
<string name="screen_signed_out_reason_2">"You have deleted the session from another session"</string>
|
<string name="screen_signed_out_reason_2">"You’ve changed your password on another device"</string>
|
||||||
<string name="screen_signed_out_reason_3">"Your server’s administrator has invalidated your access"</string>
|
<string name="screen_signed_out_reason_3">"Your server\'s session expired or the administrator has blocked your access"</string>
|
||||||
<string name="screen_signed_out_subtitle">"You might have been signed out for one of the reasons listed below. Please sign in again to continue using %s."</string>
|
<string name="screen_signed_out_subtitle">"You might have been signed out for one of the reasons listed below. Please sign in again to continue using %s."</string>
|
||||||
<string name="screen_signed_out_title">"You’re signed out"</string>
|
<string name="screen_signed_out_subtitle_v2">"This device was removed for one of the reasons listed below. Please sign in again to connect this device to your account."</string>
|
||||||
|
<string name="screen_signed_out_title">"Device removed"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
+28
-11
@@ -13,13 +13,14 @@ import app.cash.molecule.moleculeFlow
|
|||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import io.element.android.libraries.matrix.test.AN_APPLICATION_NAME
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
|
||||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
import io.element.android.libraries.sessionstorage.test.InMemorySessionStore
|
||||||
import io.element.android.libraries.sessionstorage.test.aSessionData
|
import io.element.android.libraries.sessionstorage.test.aSessionData
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runCurrent
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -30,7 +31,12 @@ class SignedOutPresenterTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - initial state`() = runTest {
|
fun `present - initial state`() = runTest {
|
||||||
val aSessionData = aSessionData()
|
val aSessionData = aSessionData(
|
||||||
|
sessionId = A_SESSION_ID.value,
|
||||||
|
userDisplayName = "Alice",
|
||||||
|
userAvatarUrl = "avatarUrl",
|
||||||
|
userAvatarData = "avatarData",
|
||||||
|
)
|
||||||
val sessionStore = InMemorySessionStore(
|
val sessionStore = InMemorySessionStore(
|
||||||
initialList = listOf(aSessionData)
|
initialList = listOf(aSessionData)
|
||||||
)
|
)
|
||||||
@@ -38,16 +44,26 @@ class SignedOutPresenterTest {
|
|||||||
moleculeFlow(RecompositionMode.Immediate) {
|
moleculeFlow(RecompositionMode.Immediate) {
|
||||||
presenter.present()
|
presenter.present()
|
||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.appName).isEqualTo(AN_APPLICATION_NAME)
|
assertThat(initialState.signedOutMatrixUser).isEqualTo(MatrixUser(userId = A_SESSION_ID))
|
||||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
assertThat(awaitItem().signedOutMatrixUser).isEqualTo(
|
||||||
|
MatrixUser(
|
||||||
|
userId = A_SESSION_ID,
|
||||||
|
displayName = "Alice",
|
||||||
|
avatarUrl = "avatarUrl",
|
||||||
|
avatarThumbnail = "avatarData",
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `present - sign in again`() = runTest {
|
fun `present - sign in again`() = runTest {
|
||||||
val aSessionData = aSessionData()
|
val aSessionData = aSessionData(
|
||||||
|
sessionId = A_SESSION_ID.value,
|
||||||
|
userDisplayName = "Alice",
|
||||||
|
)
|
||||||
val sessionStore = InMemorySessionStore(
|
val sessionStore = InMemorySessionStore(
|
||||||
initialList = listOf(aSessionData)
|
initialList = listOf(aSessionData)
|
||||||
)
|
)
|
||||||
@@ -57,13 +73,15 @@ class SignedOutPresenterTest {
|
|||||||
}.test {
|
}.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
assertThat(initialState.signedOutMatrixUser).isEqualTo(MatrixUser(userId = A_SESSION_ID, displayName = "Alice"))
|
||||||
assertThat(sessionStore.getAllSessions()).isNotEmpty()
|
assertThat(sessionStore.getAllSessions()).isNotEmpty()
|
||||||
assertThat(sessionStore.numberOfSessions()).isEqualTo(1)
|
assertThat(sessionStore.numberOfSessions()).isEqualTo(1)
|
||||||
initialState.eventSink(SignedOutEvent.SignInAgain)
|
initialState.eventSink(SignedOutEvent.Submit)
|
||||||
assertThat(awaitItem().signedOutSession).isNull()
|
runCurrent()
|
||||||
assertThat(sessionStore.getAllSessions()).isEmpty()
|
assertThat(sessionStore.getAllSessions()).isEmpty()
|
||||||
assertThat(sessionStore.numberOfSessions()).isEqualTo(0)
|
assertThat(sessionStore.numberOfSessions()).isEqualTo(0)
|
||||||
|
// The removal of the session must not change the rendered data
|
||||||
|
expectNoEvents()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,6 +93,5 @@ internal fun createSignedOutPresenter(
|
|||||||
return SignedOutPresenter(
|
return SignedOutPresenter(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
sessionStore = sessionStore,
|
sessionStore = sessionStore,
|
||||||
buildMeta = aBuildMeta(applicationName = AN_APPLICATION_NAME),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+86
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.signedout.impl
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.assertIsDisplayed
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
|
import io.element.android.libraries.matrix.ui.components.aMatrixUser
|
||||||
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.clickOn
|
||||||
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
|
import io.element.android.tests.testutils.robolectric.RobolectricTest
|
||||||
|
import org.junit.Test
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
|
class SignedOutViewTest : RobolectricTest() {
|
||||||
|
@Test
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
fun `the signed out user is displayed`() = runAndroidComposeUiTest {
|
||||||
|
setSignedOutView(
|
||||||
|
aSignedOutState(
|
||||||
|
signedOutSession = aMatrixUser(id = "@alice:server.org", displayName = "Alice"),
|
||||||
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onNodeWithText("Alice").assertIsDisplayed()
|
||||||
|
onNodeWithText("@alice:server.org").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Config(qualifiers = "h1024dp")
|
||||||
|
fun `the signed out user id is displayed when there is no display name`() = runAndroidComposeUiTest {
|
||||||
|
setSignedOutView(
|
||||||
|
aSignedOutState(
|
||||||
|
signedOutSession = aMatrixUser(id = "@alice:server.org", displayName = null),
|
||||||
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
onNodeWithText("@alice:server.org").assertIsDisplayed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on OK sends a Submit event`() = runAndroidComposeUiTest {
|
||||||
|
val eventsRecorder = EventsRecorder<SignedOutEvent>()
|
||||||
|
setSignedOutView(
|
||||||
|
aSignedOutState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
clickOn(CommonStrings.action_ok)
|
||||||
|
eventsRecorder.assertSingle(SignedOutEvent.Submit)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `pressing back sends a Submit event`() = runAndroidComposeUiTest {
|
||||||
|
val eventsRecorder = EventsRecorder<SignedOutEvent>()
|
||||||
|
setSignedOutView(
|
||||||
|
aSignedOutState(
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
pressBackKey()
|
||||||
|
eventsRecorder.assertSingle(SignedOutEvent.Submit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AndroidComposeUiTest<ComponentActivity>.setSignedOutView(
|
||||||
|
state: SignedOutState,
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
SignedOutView(
|
||||||
|
state = state,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
Reference in New Issue
Block a user