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 4254d66664..a0bcf3b489 100644 --- a/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt +++ b/appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt @@ -96,6 +96,7 @@ import io.element.android.libraries.matrix.api.verification.SessionVerificationS import io.element.android.libraries.matrix.api.verification.VerificationRequest import io.element.android.libraries.preferences.api.store.AppPreferencesStore import io.element.android.libraries.push.api.notifications.conversations.NotificationConversationService +import io.element.android.libraries.ui.common.nodes.EmptyState import io.element.android.libraries.ui.common.nodes.emptyNode import io.element.android.services.analytics.api.AnalyticsLongRunningTransaction import io.element.android.services.analytics.api.AnalyticsService @@ -327,7 +328,7 @@ class LoggedInFlowNode( return when (navTarget) { // Rendered while the FtueState is Unknown. Render a loading state rather than nothing at all, // since there is no guarantee that this state will be left. - NavTarget.Placeholder -> emptyNode(buildContext, delayBeforeShowingContent = 500.milliseconds) + NavTarget.Placeholder -> emptyNode(buildContext, state = EmptyState(delayBeforeShowingContent = 500.milliseconds)) NavTarget.LoggedInPermanent -> { val callback = object : LoggedInNode.Callback { override fun navigateToNotificationTroubleshoot() { 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 b3d96a88e5..461fc4ea80 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 @@ -32,6 +32,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.EmptyState import io.element.android.libraries.ui.common.nodes.emptyNode import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.launchIn @@ -88,7 +89,7 @@ class FtueFlowNode( // Rendered while waiting for the initial state (see FtueStep.WaitingForInitialState). // This can last for a while, or forever if the sync service never manages to run, so // render a loading state rather than nothing at all. - emptyNode(buildContext, delayBeforeShowingContent = 500.milliseconds) + emptyNode(buildContext, EmptyState(delayBeforeShowingContent = 500.milliseconds)) } is NavTarget.SessionVerification -> { val callback = object : FtueSessionVerificationFlowNode.Callback { 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 5e8131f23d..1e7e1b8d21 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 @@ -17,16 +17,14 @@ import kotlin.time.Duration * Ref: https://www.figma.com/design/0MMNu7cTOzLOlWb7ctTkv3/Element-X?node-id=1518-85323 * * @param buildContext the [BuildContext] of the Node. - * @param delayBeforeShowingContent when finite, a loading indicator fades in after this delay. Use it for - * placeholders which can stay on screen for a long time, or forever, else the application looks frozen. - * Defaults to [Duration.INFINITE], i.e. the Node stays empty. + * @param state the [EmptyState] of the Node. Default value will render an empty node forever. */ fun emptyNode( buildContext: BuildContext, - delayBeforeShowingContent: Duration = Duration.INFINITE, + state: EmptyState = EmptyState(Duration.INFINITE), ): Node = node(buildContext) { modifier -> EmptyView( - delayBeforeShowingContent = delayBeforeShowingContent, + state = state, modifier = modifier, ) } diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyState.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyState.kt new file mode 100644 index 0000000000..0930523881 --- /dev/null +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyState.kt @@ -0,0 +1,18 @@ +/* + * 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.libraries.ui.common.nodes + +import kotlin.time.Duration + +/** + * @property delayBeforeShowingContent when finite, a loading indicator fades in after this delay. Use it for + * placeholders which can stay on screen for a long time, or forever, else the application looks frozen. + */ +data class EmptyState( + val delayBeforeShowingContent: Duration, +) diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/DurationPreviewParam.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyStatePreviewParam.kt similarity index 68% rename from libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/DurationPreviewParam.kt rename to libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyStatePreviewParam.kt index 4cf621c51c..c0f1705276 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/DurationPreviewParam.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyStatePreviewParam.kt @@ -11,10 +11,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds -open class DurationPreviewParam : PreviewParameterProvider { - override val values: Sequence +open class EmptyStatePreviewParam : PreviewParameterProvider { + override val values: Sequence get() = sequenceOf( - Duration.INFINITE, - 1.seconds, + EmptyState(Duration.INFINITE), + EmptyState(1.seconds), ) } diff --git a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyView.kt b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyView.kt index f9979790f2..77ff6555a5 100644 --- a/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyView.kt +++ b/libraries/ui-common/src/main/kotlin/io/element/android/libraries/ui/common/nodes/EmptyView.kt @@ -26,11 +26,10 @@ import io.element.android.libraries.designsystem.theme.components.CircularProgre import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.utils.DelayedVisibility import io.element.android.libraries.ui.strings.CommonStrings -import kotlin.time.Duration @Composable fun EmptyView( - delayBeforeShowingContent: Duration, + state: EmptyState, modifier: Modifier = Modifier, ) = Box( modifier = modifier @@ -38,8 +37,8 @@ fun EmptyView( .background(ElementTheme.colors.bgCanvasDefault), contentAlignment = Alignment.Center, ) { - if (delayBeforeShowingContent.isFinite()) { - DelayedVisibility(duration = delayBeforeShowingContent) { + if (state.delayBeforeShowingContent.isFinite()) { + DelayedVisibility(duration = state.delayBeforeShowingContent) { LoadingContent() } } @@ -62,7 +61,7 @@ private fun LoadingContent() = Column( @PreviewsDayNight @Composable internal fun EmptyViewPreview( - @PreviewParameter(DurationPreviewParam::class) duration: Duration + @PreviewParameter(EmptyStatePreviewParam::class) state: EmptyState ) = ElementPreview { - EmptyView(delayBeforeShowingContent = duration) + EmptyView(state = state) } diff --git a/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Day_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Day_0_en.png rename to tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_0_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Day_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Day_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Day_1_en.png diff --git a/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Night_0_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_0_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Night_0_en.png rename to 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_EmptyViewPreview-KLykuaI_Night_1_en.png b/tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_1_en.png similarity index 100% rename from tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyViewPreview-KLykuaI_Night_1_en.png rename to tests/uitests/src/test/snapshots/images/libraries.ui.common.nodes_EmptyView_Night_1_en.png