Merge pull request #1520 from vector-im/feature/bma/sessionDb
Improve session db and improve deleted session behavior
This commit is contained in:
commit
88ca37984f
43 changed files with 968 additions and 49 deletions
28
features/signedout/api/build.gradle.kts
Normal file
28
features/signedout/api/build.gradle.kts
Normal file
|
|
@ -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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.signedout.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* 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.signedout.api
|
||||
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
interface SignedOutEntryPoint : FeatureEntryPoint {
|
||||
|
||||
data class Params(
|
||||
val sessionId: SessionId,
|
||||
)
|
||||
|
||||
fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder
|
||||
|
||||
interface NodeBuilder {
|
||||
fun params(params: Params): NodeBuilder
|
||||
fun build(): Node
|
||||
}
|
||||
}
|
||||
|
||||
53
features/signedout/impl/build.gradle.kts
Normal file
53
features/signedout/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
alias(libs.plugins.anvil)
|
||||
alias(libs.plugins.ksp)
|
||||
id("kotlin-parcelize")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.signedout.impl"
|
||||
}
|
||||
|
||||
anvil {
|
||||
generateDaggerFactories.set(true)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.anvilannotations)
|
||||
anvil(projects.anvilcodegen)
|
||||
api(projects.features.signedout.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixui)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.sessionStorage.implMemory)
|
||||
testImplementation(projects.tests.testutils)
|
||||
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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.signedout.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.signedout.api.SignedOutEntryPoint
|
||||
import io.element.android.libraries.architecture.createNode
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultSignedOutEntryPoint @Inject constructor() : SignedOutEntryPoint {
|
||||
|
||||
override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): SignedOutEntryPoint.NodeBuilder {
|
||||
val plugins = ArrayList<Plugin>()
|
||||
|
||||
return object : SignedOutEntryPoint.NodeBuilder {
|
||||
|
||||
override fun params(params: SignedOutEntryPoint.Params): SignedOutEntryPoint.NodeBuilder {
|
||||
plugins += SignedOutNode.Inputs(params.sessionId)
|
||||
return this
|
||||
}
|
||||
|
||||
override fun build(): Node {
|
||||
return parentNode.createNode<SignedOutNode>(buildContext, plugins)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
sealed interface SignedOutEvents {
|
||||
data object SignInAgain : SignedOutEvents
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.bumble.appyx.core.modality.BuildContext
|
||||
import com.bumble.appyx.core.node.Node
|
||||
import com.bumble.appyx.core.plugin.Plugin
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.anvilannotations.ContributesNode
|
||||
import io.element.android.libraries.architecture.NodeInputs
|
||||
import io.element.android.libraries.architecture.inputs
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
|
||||
@ContributesNode(AppScope::class)
|
||||
class SignedOutNode @AssistedInject constructor(
|
||||
@Assisted buildContext: BuildContext,
|
||||
@Assisted plugins: List<Plugin>,
|
||||
presenterFactory: SignedOutPresenter.Factory,
|
||||
) : Node(buildContext, plugins = plugins) {
|
||||
|
||||
data class Inputs(
|
||||
val sessionId: SessionId,
|
||||
) : NodeInputs
|
||||
|
||||
private val inputs: Inputs = inputs()
|
||||
private val presenter = presenterFactory.create(inputs.sessionId.value)
|
||||
|
||||
@Composable
|
||||
override fun View(modifier: Modifier) {
|
||||
val state = presenter.present()
|
||||
SignedOutView(
|
||||
state = state,
|
||||
modifier = modifier
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.sessionstorage.api.SessionStore
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class SignedOutPresenter @AssistedInject constructor(
|
||||
@Assisted private val sessionId: String, /* Cannot inject SessionId */
|
||||
private val sessionStore: SessionStore,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : Presenter<SignedOutState> {
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(sessionId: String): SignedOutPresenter
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): SignedOutState {
|
||||
val sessions by sessionStore.sessionsFlow().collectAsState(initial = emptyList())
|
||||
val signedOutSession by remember {
|
||||
derivedStateOf { sessions.firstOrNull { it.userId == sessionId } }
|
||||
}
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
||||
fun handleEvents(event: SignedOutEvents) {
|
||||
when (event) {
|
||||
SignedOutEvents.SignInAgain -> coroutineScope.launch {
|
||||
sessionStore.removeSession(sessionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return SignedOutState(
|
||||
appName = buildMeta.applicationName,
|
||||
signedOutSession = signedOutSession,
|
||||
eventSink = ::handleEvents
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class SignedOutState(
|
||||
val appName: String,
|
||||
val signedOutSession: SessionData?,
|
||||
val eventSink: (SignedOutEvents) -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
import io.element.android.libraries.sessionstorage.api.LoginType
|
||||
import io.element.android.libraries.sessionstorage.api.SessionData
|
||||
|
||||
open class SignedOutStateProvider : PreviewParameterProvider<SignedOutState> {
|
||||
override val values: Sequence<SignedOutState>
|
||||
get() = sequenceOf(
|
||||
aSignedOutState(),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
fun aSignedOutState() = SignedOutState(
|
||||
appName = "AppName",
|
||||
signedOutSession = aSessionData(),
|
||||
eventSink = {},
|
||||
)
|
||||
|
||||
fun aSessionData(
|
||||
sessionId: SessionId = SessionId("@alice:server.org"),
|
||||
isTokenValid: Boolean = false,
|
||||
): SessionData {
|
||||
return SessionData(
|
||||
userId = sessionId.value,
|
||||
deviceId = "aDeviceId",
|
||||
accessToken = "anAccessToken",
|
||||
refreshToken = "aRefreshToken",
|
||||
homeserverUrl = "aHomeserverUrl",
|
||||
oidcData = null,
|
||||
slidingSyncProxy = null,
|
||||
loginTimestamp = null,
|
||||
isTokenValid = isTokenValid,
|
||||
loginType = LoginType.UNKNOWN,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.systemBarsPadding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.BiasAlignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.ButtonColumnMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.IconTitleSubtitleMolecule
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListItem
|
||||
import io.element.android.libraries.designsystem.atomic.organisms.InfoListOrganism
|
||||
import io.element.android.libraries.designsystem.atomic.pages.HeaderFooterPage
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
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.Icon
|
||||
import io.element.android.libraries.designsystem.theme.temporaryColorBgSpecial
|
||||
import io.element.android.libraries.theme.ElementTheme
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
fun SignedOutView(
|
||||
state: SignedOutState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
BackHandler(onBack = { state.eventSink(SignedOutEvents.SignInAgain) })
|
||||
HeaderFooterPage(
|
||||
modifier = modifier
|
||||
.fillMaxSize()
|
||||
.systemBarsPadding()
|
||||
.imePadding(),
|
||||
header = { SignedOutHeader(state) },
|
||||
content = { SignedOutContent() },
|
||||
footer = {
|
||||
SignedOutFooter(
|
||||
onSignInAgain = { state.eventSink(SignedOutEvents.SignInAgain) },
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignedOutHeader(state: SignedOutState) {
|
||||
IconTitleSubtitleMolecule(
|
||||
modifier = Modifier.padding(top = 60.dp, bottom = 12.dp),
|
||||
title = stringResource(id = R.string.screen_signed_out_title),
|
||||
subTitle = stringResource(id = R.string.screen_signed_out_subtitle, state.appName),
|
||||
iconImageVector = Icons.Filled.AccountCircle,
|
||||
iconTint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignedOutContent(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Box(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
contentAlignment = BiasAlignment(
|
||||
horizontalBias = 0f,
|
||||
verticalBias = -0.4f
|
||||
)
|
||||
) {
|
||||
InfoListOrganism(
|
||||
items = persistentListOf(
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_1),
|
||||
iconComposable = { Icon(R.drawable.ic_lock_outline) },
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_2),
|
||||
iconComposable = { Icon(R.drawable.ic_devices) },
|
||||
),
|
||||
InfoListItem(
|
||||
message = stringResource(id = R.string.screen_signed_out_reason_3),
|
||||
iconComposable = { Icon(R.drawable.ic_do_disturb_alt) },
|
||||
),
|
||||
),
|
||||
textStyle = ElementTheme.typography.fontBodyMdMedium,
|
||||
iconTint = ElementTheme.colors.textPrimary,
|
||||
backgroundColor = ElementTheme.colors.temporaryColorBgSpecial
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Icon(
|
||||
@DrawableRes iconResourceId: Int,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Icon(
|
||||
modifier = modifier
|
||||
.size(20.dp),
|
||||
resourceId = iconResourceId,
|
||||
contentDescription = null,
|
||||
tint = ElementTheme.colors.iconSecondary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SignedOutFooter(
|
||||
modifier: Modifier = Modifier,
|
||||
onSignInAgain: () -> Unit,
|
||||
) {
|
||||
ButtonColumnMolecule(
|
||||
modifier = modifier,
|
||||
) {
|
||||
Button(
|
||||
text = stringResource(id = CommonStrings.action_sign_in_again),
|
||||
onClick = onSignInAgain,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@PreviewsDayNight
|
||||
@Composable
|
||||
internal fun SignedOutViewPreview(
|
||||
@PreviewParameter(SignedOutStateProvider::class) state: SignedOutState,
|
||||
) = ElementPreview {
|
||||
SignedOutView(
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
29
features/signedout/impl/src/main/res/drawable/ic_devices.xml
Normal file
29
features/signedout/impl/src/main/res/drawable/ic_devices.xml
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M3.333,5.833C3.333,5.375 3.708,5 4.167,5H17.5C17.958,5 18.333,4.625 18.333,4.167C18.333,3.708 17.958,3.333 17.5,3.333H3.333C2.417,3.333 1.667,4.083 1.667,5V14.167H1.25C0.558,14.167 0,14.725 0,15.417C0,16.108 0.558,16.667 1.25,16.667H11.667V14.167H3.333V5.833ZM19.167,6.667H14.167C13.708,6.667 13.333,7.042 13.333,7.5V15.833C13.333,16.292 13.708,16.667 14.167,16.667H19.167C19.625,16.667 20,16.292 20,15.833V7.5C20,7.042 19.625,6.667 19.167,6.667ZM18.333,14.167H15V8.333H18.333V14.167Z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M10,1.667C5.417,1.667 1.666,5.417 1.666,10C1.666,14.583 5.417,18.333 10,18.333C14.583,18.333 18.333,14.583 18.333,10C18.333,5.417 14.583,1.667 10,1.667ZM3.333,10C3.333,6.333 6.333,3.333 10,3.333C11.5,3.333 12.917,3.833 14.083,4.75L4.75,14.083C3.833,12.917 3.333,11.5 3.333,10ZM10,16.667C8.5,16.667 7.083,16.167 5.917,15.25L15.25,5.917C16.167,7.083 16.667,8.5 16.667,10C16.667,13.667 13.667,16.667 10,16.667Z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</group>
|
||||
</vector>
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
<!--
|
||||
~ 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.
|
||||
-->
|
||||
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="20dp"
|
||||
android:height="20dp"
|
||||
android:viewportWidth="20"
|
||||
android:viewportHeight="20">
|
||||
<group>
|
||||
<clip-path
|
||||
android:pathData="M0,0h20v20h-20z"/>
|
||||
<path
|
||||
android:pathData="M15,6.667H14.167V5C14.167,2.7 12.3,0.833 10,0.833C7.7,0.833 5.833,2.7 5.833,5V6.667H5C4.083,6.667 3.333,7.417 3.333,8.333V16.667C3.333,17.583 4.083,18.333 5,18.333H15C15.917,18.333 16.667,17.583 16.667,16.667V8.333C16.667,7.417 15.917,6.667 15,6.667ZM7.5,5C7.5,3.617 8.617,2.5 10,2.5C11.384,2.5 12.5,3.617 12.5,5V6.667H7.5V5ZM14.167,16.667H5.833C5.375,16.667 5,16.292 5,15.833V9.167C5,8.708 5.375,8.333 5.833,8.333H14.167C14.625,8.333 15,8.708 15,9.167V15.833C15,16.292 14.625,16.667 14.167,16.667ZM10,14.167C10.917,14.167 11.667,13.417 11.667,12.5C11.667,11.583 10.917,10.833 10,10.833C9.083,10.833 8.333,11.583 8.333,12.5C8.333,13.417 9.083,14.167 10,14.167Z"
|
||||
android:fillColor="@android:color/white"/>
|
||||
</group>
|
||||
</vector>
|
||||
8
features/signedout/impl/src/main/res/values/localazy.xml
Normal file
8
features/signedout/impl/src/main/res/values/localazy.xml
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<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_2">"You have deleted the session from another session"</string>
|
||||
<string name="screen_signed_out_reason_3">"Your server’s administrator has invalidated 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_title">"You’re signed out"</string>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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.signedout.impl
|
||||
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.matrix.api.core.SessionId
|
||||
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.impl.memory.InMemorySessionStore
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class SignedOutPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val appName = "AppName"
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val aSessionData = aSessionData()
|
||||
val sessionStore = InMemorySessionStore().apply {
|
||||
storeData(aSessionData)
|
||||
}
|
||||
val presenter = createSignedOutPresenter(sessionStore = sessionStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.appName).isEqualTo(appName)
|
||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - sign in again`() = runTest {
|
||||
val aSessionData = aSessionData()
|
||||
val sessionStore = InMemorySessionStore().apply {
|
||||
storeData(aSessionData)
|
||||
}
|
||||
val presenter = createSignedOutPresenter(sessionStore = sessionStore)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.signedOutSession).isEqualTo(aSessionData)
|
||||
assertThat(sessionStore.getAllSessions()).isNotEmpty()
|
||||
initialState.eventSink(SignedOutEvents.SignInAgain)
|
||||
assertThat(awaitItem().signedOutSession).isNull()
|
||||
assertThat(sessionStore.getAllSessions()).isEmpty()
|
||||
}
|
||||
}
|
||||
|
||||
private fun createSignedOutPresenter(
|
||||
sessionId: SessionId = A_SESSION_ID,
|
||||
sessionStore: SessionStore = InMemorySessionStore(),
|
||||
): SignedOutPresenter {
|
||||
return SignedOutPresenter(
|
||||
sessionId = sessionId.value,
|
||||
sessionStore = sessionStore,
|
||||
buildMeta = aBuildMeta(applicationName = appName),
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue