diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/classic/ElementClassicConnection.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/classic/ElementClassicConnection.kt index 3c4dd4de3c..dfddd1d496 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/classic/ElementClassicConnection.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/classic/ElementClassicConnection.kt @@ -43,7 +43,6 @@ interface ElementClassicConnection { fun start() fun stop() fun requestSession() - fun requestAvatar(userId: UserId) val stateFlow: StateFlow } @@ -174,7 +173,7 @@ class DefaultElementClassicConnection( } } - override fun requestAvatar(userId: UserId) { + private fun requestAvatar(userId: UserId) { Timber.tag(loggerTag.value).d("requestAvatar()") coroutineScope.launch { val finalMessenger = messenger @@ -225,6 +224,11 @@ class DefaultElementClassicConnection( coroutineScope.launch { val updatedState = ensureHomeserverIsSupported(state) emitState(updatedState) + val userId = (updatedState as? ElementClassicConnectionState.ElementClassicReady)?.elementClassicSession?.userId + if (userId != null) { + // Step 2, request the avatar + requestAvatar(userId) + } } } @@ -241,11 +245,15 @@ class DefaultElementClassicConnection( ) } else { val avatar = BundleCompat.getParcelable(data, KEY_USER_AVATAR_PARCELABLE, Bitmap::class.java) - val updatedState = currentState.copy( - avatar = avatar, - ) - coroutineScope.launch { - emitState(updatedState) + // If the avatar is identical to the current one, do not emit a new state to avoid unnecessary recompositions + // and blink on the avatar image + if (avatar == null || !avatar.sameAs(currentState.avatar)) { + val updatedState = currentState.copy( + avatar = avatar, + ) + coroutineScope.launch { + emitState(updatedState) + } } } } else { @@ -343,6 +351,10 @@ class DefaultElementClassicConnection( append(doesContainBackupKey) } ) + // Ensure avatar is not lost when refreshing the data + val currentAvatar = (stateFlow.value as? ElementClassicConnectionState.ElementClassicReady) + ?.takeIf { it.elementClassicSession.userId == userId } + ?.avatar ElementClassicConnectionState.ElementClassicReady( elementClassicSession = ElementClassicSession( userId = userId, @@ -352,7 +364,7 @@ class DefaultElementClassicConnection( doesContainBackupKey = doesContainBackupKey, ), displayName = displayName, - avatar = null, + avatar = currentAvatar, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt index 8d79453318..f2ff998652 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNode.kt @@ -11,6 +11,7 @@ import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope +import com.bumble.appyx.core.lifecycle.subscribe import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.plugin.Plugin @@ -75,6 +76,11 @@ class ClassicFlowNode( override fun onBuilt() { super.onBuilt() observeElementClassicConnection() + lifecycle.subscribe( + onResume = { + classicFlowNodeHelper.onResume() + }, + ) } private fun observeElementClassicConnection() { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNodeHelper.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNodeHelper.kt index cae4f834d0..a5bc74c5e4 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNodeHelper.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/ClassicFlowNodeHelper.kt @@ -26,6 +26,10 @@ class ClassicFlowNodeHelper( private val elementClassicConnection: ElementClassicConnection, private val sessionStore: SessionStore, ) { + fun onResume() { + elementClassicConnection.requestSession() + } + @OptIn(ExperimentalCoroutinesApi::class) fun navigationEventFlow(): Flow { return elementClassicConnection.stateFlow diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicEvent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicEvent.kt index e3c6ed782d..6ba9b2142a 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicEvent.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicEvent.kt @@ -8,7 +8,6 @@ package io.element.android.features.login.impl.screens.classic.loginwithclassic sealed interface LoginWithClassicEvent { - data object RefreshData : LoginWithClassicEvent data object Submit : LoginWithClassicEvent data object ClearError : LoginWithClassicEvent } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenter.kt index 6494ee741e..90a528c3ae 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenter.kt @@ -56,13 +56,6 @@ class LoginWithClassicPresenter( fun handleEvent(event: LoginWithClassicEvent) { when (event) { - LoginWithClassicEvent.RefreshData -> { - // Request the avatar if not known yet - val currentState = elementClassicConnection.stateFlow.value - if ((currentState as? ElementClassicConnectionState.ElementClassicReady)?.avatar == null) { - elementClassicConnection.requestAvatar(userId) - } - } LoginWithClassicEvent.Submit -> { val currentState = elementClassicConnection.stateFlow.value if (currentState is ElementClassicConnectionState.ElementClassicReady) { diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt index aeb61946ff..6b5c48f1ec 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicView.kt @@ -33,8 +33,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.compose.LifecycleEventEffect import io.element.android.compound.theme.ElementTheme import io.element.android.features.login.impl.R import io.element.android.features.login.impl.login.LoginModeView @@ -67,10 +65,6 @@ fun LoginWithClassicView( onCreateAccountContinue: (url: String) -> Unit, modifier: Modifier = Modifier, ) { - LifecycleEventEffect(Lifecycle.Event.ON_RESUME) { - state.eventSink(LoginWithClassicEvent.RefreshData) - } - val isLoading by remember(state.loginMode) { derivedStateOf { state.loginMode is AsyncData.Loading diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupEvent.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupEvent.kt deleted file mode 100644 index a8b86ec1bf..0000000000 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupEvent.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * 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. - */ - -package io.element.android.features.login.impl.screens.classic.missingkeybackup - -sealed interface MissingKeyBackupEvent { - data object OnResume : MissingKeyBackupEvent -} diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenter.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenter.kt index 7b8ea7e633..593c50dcb5 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenter.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenter.kt @@ -8,38 +8,18 @@ package io.element.android.features.login.impl.screens.classic.missingkeybackup import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import dev.zacsweers.metro.Inject -import io.element.android.features.login.impl.classic.ElementClassicConnection import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.meta.BuildMeta @Inject class MissingKeyBackupPresenter( private val buildMeta: BuildMeta, - private val elementClassicConnection: ElementClassicConnection, ) : Presenter { @Composable override fun present(): MissingKeyBackupState { - var resumeCounter by remember { mutableIntStateOf(0) } - fun handleEvent(event: MissingKeyBackupEvent) { - when (event) { - MissingKeyBackupEvent.OnResume -> { - resumeCounter++ - if (resumeCounter > 1) { - // The user has returned to this screen, we can assume they have gone to the backup flow and are now back here - elementClassicConnection.requestSession() - } - } - } - } - return MissingKeyBackupState( appName = buildMeta.applicationName, - eventSink = ::handleEvent, ) } } diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupState.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupState.kt index 78d3d81c72..31eaf015a0 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupState.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupState.kt @@ -9,5 +9,4 @@ package io.element.android.features.login.impl.screens.classic.missingkeybackup data class MissingKeyBackupState( val appName: String, - val eventSink: (MissingKeyBackupEvent) -> Unit ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupStateProvider.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupStateProvider.kt index 85d1042985..2c6a09b3ed 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupStateProvider.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupStateProvider.kt @@ -19,8 +19,6 @@ open class MissingKeyBackupStateProvider : PreviewParameterProvider Unit = {}, ) = MissingKeyBackupState( appName = appName, - eventSink = eventSink ) diff --git a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupView.kt b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupView.kt index 67865ffcb8..c4c9c5f286 100644 --- a/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupView.kt +++ b/features/login/impl/src/main/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupView.kt @@ -16,7 +16,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.unit.dp -import androidx.lifecycle.Lifecycle import io.element.android.compound.tokens.generated.CompoundIcons import io.element.android.features.login.impl.R import io.element.android.libraries.designsystem.atomic.organisms.NumberedListOrganism @@ -25,7 +24,6 @@ import io.element.android.libraries.designsystem.components.BigIcon 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.utils.OnLifecycleEvent import kotlinx.collections.immutable.persistentListOf @Composable @@ -35,11 +33,6 @@ fun MissingKeyBackupView( onOpenClassicClick: () -> Unit, modifier: Modifier = Modifier, ) { - OnLifecycleEvent { _, event -> - if (event == Lifecycle.Event.ON_RESUME) { - state.eventSink.invoke(MissingKeyBackupEvent.OnResume) - } - } FlowStepPage( modifier = modifier, onBackClick = onBackClick, diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/classic/FakeElementClassicConnection.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/classic/FakeElementClassicConnection.kt index 6b601543ce..227aa514b3 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/classic/FakeElementClassicConnection.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/classic/FakeElementClassicConnection.kt @@ -7,7 +7,6 @@ package io.element.android.features.login.impl.classic -import io.element.android.libraries.matrix.api.core.UserId import io.element.android.tests.testutils.lambda.lambdaError import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -17,13 +16,11 @@ class FakeElementClassicConnection( private val startResult: () -> Unit = { lambdaError() }, private val stopResult: () -> Unit = { lambdaError() }, private val requestSessionResult: () -> Unit = { lambdaError() }, - private val requestAvatarResult: (UserId) -> Unit = { lambdaError() }, initialState: ElementClassicConnectionState = ElementClassicConnectionState.Idle ) : ElementClassicConnection { override fun start() = startResult() override fun stop() = stopResult() override fun requestSession() = requestSessionResult() - override fun requestAvatar(userId: UserId) = requestAvatarResult(userId) private val mutableStateFlow = MutableStateFlow(initialState) override val stateFlow: StateFlow = mutableStateFlow.asStateFlow() suspend fun emitState(state: ElementClassicConnectionState) { diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenterTest.kt index 3bd2dd0cf8..6b2a4fb0e1 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/loginwithclassic/LoginWithClassicPresenterTest.kt @@ -57,36 +57,6 @@ class LoginWithClassicPresenterTest { } } - @Test - fun `present - refresh data invokes the expected methods`() = runTest { - val requestAvatarResult = lambdaRecorder { } - val elementClassicConnection = FakeElementClassicConnection( - startResult = {}, - requestAvatarResult = requestAvatarResult, - ) - val presenter = createPresenter( - elementClassicConnection = elementClassicConnection, - ) - presenter.test { - skipItems(1) - elementClassicConnection.emitState( - anElementClassicReady( - elementClassicSession = anElementClassicSession( - userId = A_USER_ID, - secrets = A_SECRET, - roomKeysVersion = ROOM_KEYS_VERSION, - ), - displayName = A_USER_NAME, - ) - ) - val readyState = awaitItem() - assertThat(readyState.userId).isEqualTo(A_USER_ID) - assertThat(readyState.displayName).isEqualTo(A_USER_NAME) - readyState.eventSink(LoginWithClassicEvent.RefreshData) - requestAvatarResult.assertions().isCalledOnce() - } - } - @Test fun `present - start login with correct state - user can login`() = runTest { val authenticationService = FakeMatrixAuthenticationService( diff --git a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenterTest.kt b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenterTest.kt index 2a4af8bf5a..447b0ba77b 100644 --- a/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenterTest.kt +++ b/features/login/impl/src/test/kotlin/io/element/android/features/login/impl/screens/classic/missingkeybackup/MissingKeyBackupPresenterTest.kt @@ -8,12 +8,9 @@ package io.element.android.features.login.impl.screens.classic.missingkeybackup import com.google.common.truth.Truth.assertThat -import io.element.android.features.login.impl.classic.ElementClassicConnection -import io.element.android.features.login.impl.classic.FakeElementClassicConnection import io.element.android.libraries.core.meta.BuildMeta import io.element.android.libraries.matrix.test.AN_APPLICATION_NAME import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.test import kotlinx.coroutines.test.runTest import org.junit.Test @@ -27,29 +24,10 @@ class MissingKeyBackupPresenterTest { assertThat(initialState.appName).isEqualTo(AN_APPLICATION_NAME) } } - - @Test - fun `present - when the screen is resumed twice, the start over method is called`() = runTest { - val requestSessionResult = lambdaRecorder { } - val presenter = createPresenter( - elementClassicConnection = FakeElementClassicConnection( - requestSessionResult = requestSessionResult, - ), - ) - presenter.test { - val initialState = awaitItem() - initialState.eventSink(MissingKeyBackupEvent.OnResume) - expectNoEvents() - initialState.eventSink(MissingKeyBackupEvent.OnResume) - requestSessionResult.assertions().isCalledOnce() - } - } } private fun createPresenter( buildMeta: BuildMeta = aBuildMeta(applicationName = AN_APPLICATION_NAME), - elementClassicConnection: ElementClassicConnection = FakeElementClassicConnection(), ) = MissingKeyBackupPresenter( buildMeta = buildMeta, - elementClassicConnection = elementClassicConnection, )