From 9b6df78bac3bbec8805b604934fe9d9015bc4ca8 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Sep 2025 14:32:50 +0200 Subject: [PATCH 01/11] Add Konsist test to check Metro annotation. --- .../android/tests/konsist/KonsistDiTest.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt diff --git a/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt new file mode 100644 index 0000000000..093cfd4488 --- /dev/null +++ b/tests/konsist/src/test/kotlin/io/element/android/tests/konsist/KonsistDiTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2025 New Vector 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.tests.konsist + +import com.lemonappdev.konsist.api.Konsist +import com.lemonappdev.konsist.api.ext.list.withAnnotationOf +import com.lemonappdev.konsist.api.ext.list.withParameter +import com.lemonappdev.konsist.api.verify.assertTrue +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.Inject +import org.junit.Test + +class KonsistDiTest { + @Test + fun `class annotated with @Inject should not have constructors with @Assisted parameter`() { + Konsist + .scopeFromProject() + .classes() + .withAnnotationOf(Inject::class) + .assertTrue( + additionalMessage = "Class with @Assisted parameter in constructor should be annotated with @AssistedInject and not @Inject" + ) { classDeclaration -> + classDeclaration.constructors + .withParameter { parameterDeclaration -> + parameterDeclaration.hasAnnotationOf(Assisted::class) + } + .isEmpty() + } + } +} From 598d4047b0337ee84df6492acff13b66115feefa Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Sep 2025 14:34:05 +0200 Subject: [PATCH 02/11] Use AssistedInject instead of Inject. --- .../main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 3 +-- .../io/element/android/features/ftue/impl/FtueFlowNode.kt | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index a15c87ed77..95e9a24ed8 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -39,7 +39,6 @@ import com.bumble.appyx.navmodel.backstack.operation.singleTop import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject -import dev.zacsweers.metro.Inject import im.vector.app.features.analytics.plan.JoinedRoom import io.element.android.annotations.ContributesNode import io.element.android.appnav.loggedin.LoggedInNode @@ -550,7 +549,7 @@ class LoggedInFlowNode( } @ContributesNode(AppScope::class) -@Inject +@AssistedInject class PlaceholderNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index b625b07f12..a34d0efa9f 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -23,7 +23,6 @@ import com.bumble.appyx.navmodel.backstack.operation.replace import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject -import dev.zacsweers.metro.Inject import io.element.android.annotations.ContributesNode import io.element.android.features.analytics.api.AnalyticsEntryPoint import io.element.android.features.ftue.impl.notifications.NotificationsOptInNode @@ -149,7 +148,7 @@ class FtueFlowNode( } @ContributesNode(AppScope::class) -@Inject +@AssistedInject class PlaceholderNode( @Assisted buildContext: BuildContext, @Assisted plugins: List, From 733ac57928695019d2ff2f30fd062756cd5f2702 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Fri, 26 Sep 2025 14:48:49 +0200 Subject: [PATCH 03/11] Create a LoadingNode to reduce code duplication. --- .../android/appnav/LoggedInFlowNode.kt | 11 ++---- .../io/element/android/appnav/RootFlowNode.kt | 21 ++++------- .../features/ftue/impl/FtueFlowNode.kt | 22 ++---------- .../settings/LockScreenSettingsFlowNode.kt | 10 +++--- libraries/architecture/build.gradle.kts | 1 + .../architecture/appyx/LoadingNode.kt | 36 +++++++++++++++++++ 6 files changed, 53 insertions(+), 48 deletions(-) create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 95e9a24ed8..2b087f06f7 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -36,7 +36,6 @@ import com.bumble.appyx.navmodel.backstack.operation.pop import com.bumble.appyx.navmodel.backstack.operation.push import com.bumble.appyx.navmodel.backstack.operation.replace import com.bumble.appyx.navmodel.backstack.operation.singleTop -import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import im.vector.app.features.analytics.plan.JoinedRoom @@ -65,6 +64,7 @@ import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForNavTargetAttached import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -281,7 +281,7 @@ class LoggedInFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Placeholder -> createNode(buildContext) + NavTarget.Placeholder -> createNode(buildContext) NavTarget.LoggedInPermanent -> { val callback = object : LoggedInNode.Callback { override fun navigateToNotificationTroubleshoot() { @@ -548,13 +548,6 @@ class LoggedInFlowNode( } } -@ContributesNode(AppScope::class) -@AssistedInject -class PlaceholderNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, -) : Node(buildContext, plugins = plugins) - @Parcelize private class AttachRoomOperation( val roomTarget: LoggedInFlowNode.NavTarget.Room, diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index d71fbf2eef..538c084f71 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -11,15 +11,11 @@ import android.content.Intent import android.os.Parcelable import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node -import com.bumble.appyx.core.node.node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.state.MutableSavedStateMap import com.bumble.appyx.navmodel.backstack.BackStack @@ -46,12 +42,12 @@ import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForChildAttached import io.element.android.libraries.core.uri.ensureProtocol import io.element.android.libraries.deeplink.api.DeeplinkData -import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.matrix.api.core.SessionId @@ -219,9 +215,10 @@ import timber.log.Timber override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { is NavTarget.LoggedInFlow -> { - val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) ?: return splashNode(buildContext).also { - Timber.w("Couldn't find any session, go through SplashScreen") - } + val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) + ?: return createNode(buildContext).also { + Timber.w("Couldn't find any session, go through SplashScreen") + } val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) val callback = object : LoggedInAppScopeFlowNode.Callback { override fun onOpenBugReport() { @@ -252,7 +249,7 @@ import timber.log.Timber ) ).build() } - NavTarget.SplashScreen -> splashNode(buildContext) + NavTarget.SplashScreen -> createNode(buildContext) NavTarget.BugReport -> { val callback = object : BugReportEntryPoint.Callback { override fun onDone() { @@ -289,12 +286,6 @@ import timber.log.Timber } } - private fun splashNode(buildContext: BuildContext) = node(buildContext) { - Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } - suspend fun handleIntent(intent: Intent) { val resolvedIntent = intentResolver.resolve(intent) ?: return when (resolvedIntent) { diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index a34d0efa9f..5e3fd6ac5c 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -8,10 +8,7 @@ package io.element.android.features.ftue.impl import android.os.Parcelable -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.lifecycle.lifecycleScope import com.bumble.appyx.core.modality.BuildContext @@ -20,7 +17,6 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot import com.bumble.appyx.navmodel.backstack.operation.replace -import dev.zacsweers.metro.AppScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode @@ -33,8 +29,8 @@ import io.element.android.features.ftue.impl.state.InternalFtueState import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode -import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.di.SessionScope import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn @@ -87,7 +83,7 @@ class FtueFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Placeholder -> { - createNode(buildContext) + createNode(buildContext) } is NavTarget.SessionVerification -> { val callback = object : FtueSessionVerificationFlowNode.Callback { @@ -146,17 +142,3 @@ class FtueFlowNode( BackstackView() } } - -@ContributesNode(AppScope::class) -@AssistedInject -class PlaceholderNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, -) : Node(buildContext, plugins = plugins) { - @Composable - override fun View(modifier: Modifier) { - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } - } -} diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index cfde6db84f..ece73c7800 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -14,7 +14,6 @@ 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.node.node import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.newRoot @@ -28,6 +27,7 @@ import io.element.android.features.lockscreen.impl.setup.pin.SetupPinNode import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode +import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope import kotlinx.coroutines.flow.first @@ -42,7 +42,7 @@ class LockScreenSettingsFlowNode( private val pinCodeManager: PinCodeManager, ) : BaseFlowNode( backstack = BackStack( - initialElement = NavTarget.Unknown, + initialElement = NavTarget.Loading, savedStateMap = buildContext.savedStateMap, ), buildContext = buildContext, @@ -50,7 +50,7 @@ class LockScreenSettingsFlowNode( ) { sealed interface NavTarget : Parcelable { @Parcelize - data object Unknown : NavTarget + data object Loading : NavTarget @Parcelize data object Unlock : NavTarget @@ -94,6 +94,9 @@ class LockScreenSettingsFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { + NavTarget.Loading -> { + createNode(buildContext) + } NavTarget.Unlock -> { val callback = object : PinUnlockNode.Callback { override fun onUnlock() { @@ -113,7 +116,6 @@ class LockScreenSettingsFlowNode( } createNode(buildContext, plugins = listOf(callback)) } - NavTarget.Unknown -> node(buildContext) { } } } diff --git a/libraries/architecture/build.gradle.kts b/libraries/architecture/build.gradle.kts index 55b79abca0..b5cf38f5a2 100644 --- a/libraries/architecture/build.gradle.kts +++ b/libraries/architecture/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { api(libs.appyx.core) api(libs.androidx.lifecycle.runtime) api(libs.molecule.runtime) + implementation(projects.libraries.designsystem) testCommonDependencies(libs) } diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt new file mode 100644 index 0000000000..c1a0bd9de8 --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2025 New Vector 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.libraries.architecture.appyx + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +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 dev.zacsweers.metro.AppScope +import dev.zacsweers.metro.Assisted +import dev.zacsweers.metro.AssistedInject +import io.element.android.annotations.ContributesNode +import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator + +@ContributesNode(AppScope::class) +@AssistedInject +class LoadingNode( + @Assisted buildContext: BuildContext, + @Assisted plugins: List, +) : Node(buildContext, plugins = plugins) { + @Composable + override fun View(modifier: Modifier) { + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() + } + } +} From 71b61a13a684e7cb7a3e482b0157c62de0f75602 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 29 Sep 2025 10:06:31 +0200 Subject: [PATCH 04/11] Fix compilation issue. --- appnav/build.gradle.kts | 1 + .../android/appnav/LoggedInFlowNode.kt | 2 +- .../io/element/android/appnav/RootFlowNode.kt | 2 +- features/ftue/impl/build.gradle.kts | 1 + .../features/ftue/impl/FtueFlowNode.kt | 2 +- features/lockscreen/impl/build.gradle.kts | 1 + .../settings/LockScreenSettingsFlowNode.kt | 2 +- libraries/architecture/build.gradle.kts | 1 - libraries/ui-common/build.gradle.kts | 24 +++++++++++++++++++ .../libraries/ui/common/nodes}/LoadingNode.kt | 2 +- 10 files changed, 32 insertions(+), 6 deletions(-) create mode 100644 libraries/ui-common/build.gradle.kts rename libraries/{architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx => ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes}/LoadingNode.kt (95%) diff --git a/appnav/build.gradle.kts b/appnav/build.gradle.kts index b200672f94..7f487a1e63 100644 --- a/appnav/build.gradle.kts +++ b/appnav/build.gradle.kts @@ -38,6 +38,7 @@ dependencies { implementation(projects.libraries.pushproviders.api) implementation(projects.libraries.designsystem) implementation(projects.libraries.matrixui) + implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiStrings) implementation(projects.features.login.api) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index 2b087f06f7..f2ac04e0e6 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -64,7 +64,6 @@ import io.element.android.features.userprofile.api.UserProfileEntryPoint import io.element.android.features.verifysession.api.IncomingVerificationEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForNavTargetAttached import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher @@ -81,6 +80,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService +import io.element.android.libraries.ui.common.nodes.LoadingNode import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 538c084f71..8f96a29806 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -42,7 +42,6 @@ import io.element.android.features.signedout.api.SignedOutEntryPoint import io.element.android.libraries.accountselect.api.AccountSelectEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.appyx.rememberDelegateTransitionHandler import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.waitForChildAttached @@ -57,6 +56,7 @@ import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore +import io.element.android.libraries.ui.common.nodes.LoadingNode import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/features/ftue/impl/build.gradle.kts b/features/ftue/impl/build.gradle.kts index 8f940ed29a..d7d61f6d8d 100644 --- a/features/ftue/impl/build.gradle.kts +++ b/features/ftue/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.preferences.api) + implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiStrings) implementation(projects.libraries.testtags) implementation(projects.features.analytics.api) diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 5e3fd6ac5c..d1bfa86a58 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -29,9 +29,9 @@ import io.element.android.features.ftue.impl.state.InternalFtueState import io.element.android.features.lockscreen.api.LockScreenEntryPoint import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.ui.common.nodes.LoadingNode import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach diff --git a/features/lockscreen/impl/build.gradle.kts b/features/lockscreen/impl/build.gradle.kts index 011d4919e7..998b763804 100644 --- a/features/lockscreen/impl/build.gradle.kts +++ b/features/lockscreen/impl/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { implementation(projects.libraries.testtags) implementation(projects.libraries.uiUtils) implementation(projects.features.logout.api) + implementation(projects.libraries.uiCommon) implementation(projects.libraries.uiStrings) implementation(projects.libraries.sessionStorage.api) implementation(projects.services.appnavstate.api) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index ece73c7800..71fc2cb502 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -27,9 +27,9 @@ import io.element.android.features.lockscreen.impl.setup.pin.SetupPinNode import io.element.android.features.lockscreen.impl.unlock.PinUnlockNode import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode -import io.element.android.libraries.architecture.appyx.LoadingNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.ui.common.nodes.LoadingNode import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize diff --git a/libraries/architecture/build.gradle.kts b/libraries/architecture/build.gradle.kts index b5cf38f5a2..55b79abca0 100644 --- a/libraries/architecture/build.gradle.kts +++ b/libraries/architecture/build.gradle.kts @@ -25,7 +25,6 @@ dependencies { api(libs.appyx.core) api(libs.androidx.lifecycle.runtime) api(libs.molecule.runtime) - implementation(projects.libraries.designsystem) testCommonDependencies(libs) } diff --git a/libraries/ui-common/build.gradle.kts b/libraries/ui-common/build.gradle.kts new file mode 100644 index 0000000000..b8fbc2047e --- /dev/null +++ b/libraries/ui-common/build.gradle.kts @@ -0,0 +1,24 @@ +import extension.setupDependencyInjection + +/* + * Copyright 2025 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial + * Please see LICENSE files in the repository root for full details. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.libraries.ui.common" +} + +setupDependencyInjection() + +dependencies { + implementation(projects.libraries.architecture) + implementation(projects.libraries.designsystem) + implementation(projects.libraries.di) +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt similarity index 95% rename from libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt rename to libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt index c1a0bd9de8..cd078fbb0e 100644 --- a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/appyx/LoadingNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt @@ -5,7 +5,7 @@ * Please see LICENSE files in the repository root for full details. */ -package io.element.android.libraries.architecture.appyx +package io.element.android.libraries.ui.common.nodes import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize From 6be09fedc78047209c557ee1a5af3b65c67ede65 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 29 Sep 2025 10:44:23 +0200 Subject: [PATCH 05/11] Fix issue after rebase. --- .../io/element/android/features/space/impl/SpaceFlowNode.kt | 4 ++-- .../android/features/space/impl/leave/LeaveSpacePresenter.kt | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt index 3fec810cb2..3515e07a77 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/SpaceFlowNode.kt @@ -19,7 +19,7 @@ import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.operation.push import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.AssistedInject import io.element.android.annotations.ContributesNode import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.features.space.impl.leave.LeaveSpaceNode @@ -33,7 +33,7 @@ import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.parcelize.Parcelize @ContributesNode(SessionScope::class) -@Inject +@AssistedInject class SpaceFlowNode( @Assisted val buildContext: BuildContext, @Assisted plugins: List, diff --git a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt index c7be10d956..9da27e244d 100644 --- a/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt +++ b/features/space/impl/src/main/kotlin/io/element/android/features/space/impl/leave/LeaveSpacePresenter.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import dev.zacsweers.metro.Assisted import dev.zacsweers.metro.AssistedFactory -import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.AssistedInject import io.element.android.features.space.api.SpaceEntryPoint import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.AsyncData @@ -35,7 +35,7 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch import kotlin.jvm.optionals.getOrNull -@Inject +@AssistedInject class LeaveSpacePresenter( @Assisted private val inputs: SpaceEntryPoint.Inputs, matrixClient: MatrixClient, From 1cd75af66677a68ad403f2a2bda20ad345d0fb07 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Sep 2025 11:50:17 +0200 Subject: [PATCH 06/11] No need of DI for the LoadingNode. --- .../android/appnav/LoggedInFlowNode.kt | 4 ++-- .../io/element/android/appnav/RootFlowNode.kt | 6 ++--- .../features/ftue/impl/FtueFlowNode.kt | 4 ++-- .../settings/LockScreenSettingsFlowNode.kt | 4 ++-- libraries/ui-common/build.gradle.kts | 7 +----- .../libraries/ui/common/nodes/LoadingNode.kt | 22 ++++--------------- 6 files changed, 14 insertions(+), 33 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index f2ac04e0e6..ddc7789302 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -80,7 +80,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService -import io.element.android.libraries.ui.common.nodes.LoadingNode +import io.element.android.libraries.ui.common.nodes.loadingNode import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -281,7 +281,7 @@ class LoggedInFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Placeholder -> createNode(buildContext) + NavTarget.Placeholder -> loadingNode(buildContext) NavTarget.LoggedInPermanent -> { val callback = object : LoggedInNode.Callback { override fun navigateToNotificationTroubleshoot() { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 8f96a29806..dbd6e146f0 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore -import io.element.android.libraries.ui.common.nodes.LoadingNode +import io.element.android.libraries.ui.common.nodes.loadingNode import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -216,7 +216,7 @@ import timber.log.Timber return when (navTarget) { is NavTarget.LoggedInFlow -> { val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) - ?: return createNode(buildContext).also { + ?: return loadingNode(buildContext).also { Timber.w("Couldn't find any session, go through SplashScreen") } val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) @@ -249,7 +249,7 @@ import timber.log.Timber ) ).build() } - NavTarget.SplashScreen -> createNode(buildContext) + NavTarget.SplashScreen -> loadingNode(buildContext) NavTarget.BugReport -> { val callback = object : BugReportEntryPoint.Callback { override fun onDone() { diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index d1bfa86a58..3cc0de7438 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -31,7 +31,7 @@ import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.ui.common.nodes.LoadingNode +import io.element.android.libraries.ui.common.nodes.loadingNode import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -83,7 +83,7 @@ class FtueFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Placeholder -> { - createNode(buildContext) + loadingNode(buildContext) } is NavTarget.SessionVerification -> { val callback = object : FtueSessionVerificationFlowNode.Callback { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index 71fc2cb502..4f335a3b97 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -29,7 +29,7 @@ import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.ui.common.nodes.LoadingNode +import io.element.android.libraries.ui.common.nodes.loadingNode import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -95,7 +95,7 @@ class LockScreenSettingsFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Loading -> { - createNode(buildContext) + loadingNode(buildContext) } NavTarget.Unlock -> { val callback = object : PinUnlockNode.Callback { diff --git a/libraries/ui-common/build.gradle.kts b/libraries/ui-common/build.gradle.kts index b8fbc2047e..55ef7eaf49 100644 --- a/libraries/ui-common/build.gradle.kts +++ b/libraries/ui-common/build.gradle.kts @@ -1,5 +1,3 @@ -import extension.setupDependencyInjection - /* * Copyright 2025 New Vector Ltd. * @@ -15,10 +13,7 @@ android { namespace = "io.element.android.libraries.ui.common" } -setupDependencyInjection() - dependencies { - implementation(projects.libraries.architecture) + implementation(libs.appyx.core) implementation(projects.libraries.designsystem) - implementation(projects.libraries.di) } diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt index cd078fbb0e..e9a33049d7 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt @@ -9,28 +9,14 @@ package io.element.android.libraries.ui.common.nodes import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment -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 dev.zacsweers.metro.AppScope -import dev.zacsweers.metro.Assisted -import dev.zacsweers.metro.AssistedInject -import io.element.android.annotations.ContributesNode +import com.bumble.appyx.core.node.node import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator -@ContributesNode(AppScope::class) -@AssistedInject -class LoadingNode( - @Assisted buildContext: BuildContext, - @Assisted plugins: List, -) : Node(buildContext, plugins = plugins) { - @Composable - override fun View(modifier: Modifier) { - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() - } +fun loadingNode(buildContext: BuildContext): Node = node(buildContext) { modifier -> + Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator() } } From bfb51e188e3e33d2bdd2f087ce3059291d364775 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Sep 2025 11:54:43 +0200 Subject: [PATCH 07/11] loadingNode: hide ProgressIndicator in some cases. --- .../kotlin/io/element/android/appnav/LoggedInFlowNode.kt | 2 +- .../kotlin/io/element/android/appnav/RootFlowNode.kt | 2 +- .../impl/settings/LockScreenSettingsFlowNode.kt | 2 +- .../android/libraries/ui/common/nodes/LoadingNode.kt | 9 +++++++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index ddc7789302..a0622a0766 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -281,7 +281,7 @@ class LoggedInFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Placeholder -> loadingNode(buildContext) + NavTarget.Placeholder -> loadingNode(buildContext, showProgressIndicator = false) NavTarget.LoggedInPermanent -> { val callback = object : LoggedInNode.Callback { override fun navigateToNotificationTroubleshoot() { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index dbd6e146f0..57aed62911 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -216,7 +216,7 @@ import timber.log.Timber return when (navTarget) { is NavTarget.LoggedInFlow -> { val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) - ?: return loadingNode(buildContext).also { + ?: return loadingNode(buildContext, showProgressIndicator = false).also { Timber.w("Couldn't find any session, go through SplashScreen") } val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index 4f335a3b97..2dbfcddfbc 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -95,7 +95,7 @@ class LockScreenSettingsFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Loading -> { - loadingNode(buildContext) + loadingNode(buildContext, showProgressIndicator = false) } NavTarget.Unlock -> { val callback = object : PinUnlockNode.Callback { diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt index e9a33049d7..abc22068af 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt @@ -15,8 +15,13 @@ import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.node import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator -fun loadingNode(buildContext: BuildContext): Node = node(buildContext) { modifier -> +fun loadingNode( + buildContext: BuildContext, + showProgressIndicator: Boolean = true, +): Node = node(buildContext) { modifier -> Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - CircularProgressIndicator() + if (showProgressIndicator) { + CircularProgressIndicator() + } } } From f5df8dcad9ac5b6b3a2109adb4da2ead6c1995d5 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Sep 2025 12:15:25 +0200 Subject: [PATCH 08/11] Remove Progress from splashscreen to follow design requirements. --- .../android/appnav/LoggedInFlowNode.kt | 4 ++-- .../io/element/android/appnav/RootFlowNode.kt | 6 +++--- .../features/ftue/impl/FtueFlowNode.kt | 4 ++-- .../settings/LockScreenSettingsFlowNode.kt | 4 ++-- .../nodes/{LoadingNode.kt => EmptyNode.kt} | 20 ++++++++++--------- 5 files changed, 20 insertions(+), 18 deletions(-) rename libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/{LoadingNode.kt => EmptyNode.kt} (60%) diff --git a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt index a0622a0766..66df9a6dd5 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -80,7 +80,7 @@ import io.element.android.libraries.matrix.api.permalink.PermalinkData import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService -import io.element.android.libraries.ui.common.nodes.loadingNode +import io.element.android.libraries.ui.common.nodes.emptyNode import io.element.android.services.appnavstate.api.AppNavigationStateService import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.first @@ -281,7 +281,7 @@ class LoggedInFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { - NavTarget.Placeholder -> loadingNode(buildContext, showProgressIndicator = false) + NavTarget.Placeholder -> emptyNode(buildContext) NavTarget.LoggedInPermanent -> { val callback = object : LoggedInNode.Callback { override fun navigateToNotificationTroubleshoot() { diff --git a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt index 57aed62911..93df005c76 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/RootFlowNode.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.oidc.api.OidcAction import io.element.android.libraries.oidc.api.OidcActionFlow import io.element.android.libraries.sessionstorage.api.LoggedInState import io.element.android.libraries.sessionstorage.api.SessionStore -import io.element.android.libraries.ui.common.nodes.loadingNode +import io.element.android.libraries.ui.common.nodes.emptyNode import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -216,7 +216,7 @@ import timber.log.Timber return when (navTarget) { is NavTarget.LoggedInFlow -> { val matrixClient = matrixSessionCache.getOrNull(navTarget.sessionId) - ?: return loadingNode(buildContext, showProgressIndicator = false).also { + ?: return emptyNode(buildContext).also { Timber.w("Couldn't find any session, go through SplashScreen") } val inputs = LoggedInAppScopeFlowNode.Inputs(matrixClient) @@ -249,7 +249,7 @@ import timber.log.Timber ) ).build() } - NavTarget.SplashScreen -> loadingNode(buildContext) + NavTarget.SplashScreen -> emptyNode(buildContext) NavTarget.BugReport -> { val callback = object : BugReportEntryPoint.Callback { override fun onDone() { diff --git a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt index 3cc0de7438..6552a3b360 100644 --- a/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt +++ b/features/ftue/impl/src/main/kotlin/io/element/android/features/ftue/impl/FtueFlowNode.kt @@ -31,7 +31,7 @@ import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.ui.common.nodes.loadingNode +import io.element.android.libraries.ui.common.nodes.emptyNode import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -83,7 +83,7 @@ class FtueFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Placeholder -> { - loadingNode(buildContext) + emptyNode(buildContext) } is NavTarget.SessionVerification -> { val callback = object : FtueSessionVerificationFlowNode.Callback { diff --git a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt index 2dbfcddfbc..01aeaabe89 100644 --- a/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt +++ b/features/lockscreen/impl/src/main/kotlin/io/element/android/features/lockscreen/impl/settings/LockScreenSettingsFlowNode.kt @@ -29,7 +29,7 @@ import io.element.android.libraries.architecture.BackstackView import io.element.android.libraries.architecture.BaseFlowNode import io.element.android.libraries.architecture.createNode import io.element.android.libraries.di.SessionScope -import io.element.android.libraries.ui.common.nodes.loadingNode +import io.element.android.libraries.ui.common.nodes.emptyNode import kotlinx.coroutines.flow.first import kotlinx.coroutines.launch import kotlinx.parcelize.Parcelize @@ -95,7 +95,7 @@ class LockScreenSettingsFlowNode( override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node { return when (navTarget) { NavTarget.Loading -> { - loadingNode(buildContext, showProgressIndicator = false) + emptyNode(buildContext) } NavTarget.Unlock -> { val callback = object : PinUnlockNode.Callback { diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt similarity index 60% rename from libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt rename to libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt index abc22068af..6f64cf90fb 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/LoadingNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt @@ -7,21 +7,23 @@ package io.element.android.libraries.ui.common.nodes +import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.ui.Alignment import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.node.Node import com.bumble.appyx.core.node.node -import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator +import io.element.android.compound.theme.ElementTheme -fun loadingNode( +/** + * Ref: https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=1518-85323 + */ +fun emptyNode( buildContext: BuildContext, - showProgressIndicator: Boolean = true, ): Node = node(buildContext) { modifier -> - Box(modifier = modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - if (showProgressIndicator) { - CircularProgressIndicator() - } - } + Box( + modifier = modifier + .fillMaxSize() + .background(ElementTheme.colors.bgCanvasDefault), + ) } From 03e92bac25254bcd5acd134699fe60a7bd09b94c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Sep 2025 12:19:21 +0200 Subject: [PATCH 09/11] Add Preview --- .../libraries/ui/common/nodes/EmptyNode.kt | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt index 6f64cf90fb..58daff05f4 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt @@ -10,10 +10,14 @@ package io.element.android.libraries.ui.common.nodes import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +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.node.node import io.element.android.compound.theme.ElementTheme +import io.element.android.libraries.designsystem.preview.ElementPreview +import io.element.android.libraries.designsystem.preview.PreviewsDayNight /** * Ref: https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=1518-85323 @@ -21,9 +25,18 @@ import io.element.android.compound.theme.ElementTheme fun emptyNode( buildContext: BuildContext, ): Node = node(buildContext) { modifier -> - Box( - modifier = modifier - .fillMaxSize() - .background(ElementTheme.colors.bgCanvasDefault), - ) + EmptyView(modifier) +} + +@Composable +private fun EmptyView(modifier: Modifier) = Box( + modifier = modifier + .fillMaxSize() + .background(ElementTheme.colors.bgCanvasDefault), +) + +@PreviewsDayNight +@Composable +internal fun EmptyViewPreview() = ElementPreview { + EmptyView(Modifier) } From 16cf3b1a9c150fb4bc7001f3632b5e6da88e099f Mon Sep 17 00:00:00 2001 From: ElementBot Date: Tue, 30 Sep 2025 10:33:23 +0000 Subject: [PATCH 10/11] Update screenshots --- .../images/libraries.ui.common.nodes_EmptyView_Day_0_en.png | 3 +++ .../images/libraries.ui.common.nodes_EmptyView_Night_0_en.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png create mode 100644 tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png new file mode 100644 index 0000000000..1b6fb4bab8 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:96a867cb12498cbdc97957bee07855dfaa13602baddaf933aff2b666ef4c7650 +size 3642 diff --git a/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_0_en.png new file mode 100644 index 0000000000..d6fd8eeb70 --- /dev/null +++ b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_0_en.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:5bb36ccd718f3fec5b04f1bc812dc7718b5ea7fa4619c8b031466297a8d016fd +size 3659 From 519a42dcff88b6a24a17cb207357e5dc5c037e4d Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 30 Sep 2025 13:09:44 +0200 Subject: [PATCH 11/11] Add default value --- .../io/element/android/libraries/ui/common/nodes/EmptyNode.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt index 58daff05f4..ab622b4b9d 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyNode.kt @@ -29,7 +29,9 @@ fun emptyNode( } @Composable -private fun EmptyView(modifier: Modifier) = Box( +private fun EmptyView( + modifier: Modifier = Modifier, +) = Box( modifier = modifier .fillMaxSize() .background(ElementTheme.colors.bgCanvasDefault),