Benoit Marty
11 months ago
committed by
GitHub
43 changed files with 968 additions and 49 deletions
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
Improve deleted session behavior. |
@ -0,0 +1,28 @@
@@ -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 @@
@@ -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 |
||||
} |
||||
} |
||||
|
@ -0,0 +1,53 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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, |
||||
) |
||||
} |
@ -0,0 +1,29 @@
@@ -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 @@
@@ -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 @@
@@ -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> |
@ -0,0 +1,8 @@
@@ -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 @@
@@ -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), |
||||
) |
||||
} |
||||
} |
@ -0,0 +1,25 @@
@@ -0,0 +1,25 @@
|
||||
/* |
||||
* 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.sessionstorage.api |
||||
|
||||
sealed interface LoggedInState { |
||||
data object NotLoggedIn : LoggedInState |
||||
data class LoggedIn( |
||||
val sessionId: String, |
||||
val isTokenValid: Boolean, |
||||
) : LoggedInState |
||||
} |
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
/* |
||||
* 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.sessionstorage.api |
||||
|
||||
// Imported from Element Android, to be able to migrate from EA to EXA. |
||||
enum class LoginType { |
||||
PASSWORD, |
||||
OIDC, |
||||
SSO, |
||||
UNSUPPORTED, |
||||
CUSTOM, |
||||
DIRECT, |
||||
UNKNOWN, |
||||
QR; |
||||
|
||||
companion object { |
||||
|
||||
fun fromName(name: String) = when (name) { |
||||
PASSWORD.name -> PASSWORD |
||||
OIDC.name -> OIDC |
||||
SSO.name -> SSO |
||||
UNSUPPORTED.name -> UNSUPPORTED |
||||
CUSTOM.name -> CUSTOM |
||||
DIRECT.name -> DIRECT |
||||
QR.name -> QR |
||||
else -> UNKNOWN |
||||
} |
||||
} |
||||
} |
Binary file not shown.
Binary file not shown.
@ -0,0 +1,2 @@
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE SessionData ADD COLUMN isTokenValid INTEGER NOT NULL DEFAULT 1; |
||||
ALTER TABLE SessionData ADD COLUMN loginType TEXT; |
Binary file not shown.
Loading…
Reference in new issue