AwaitRoom : create loading state with placeholders

This commit is contained in:
ganfra 2023-07-05 12:42:01 +02:00
parent 03b2cbb06f
commit 8667784271
6 changed files with 79 additions and 23 deletions

View file

@ -14,14 +14,27 @@
* limitations under the License. * limitations under the License.
*/ */
@file:OptIn(ExperimentalMaterial3Api::class)
package io.element.android.appnav package io.element.android.appnav
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.bumble.appyx.core.composable.Children import com.bumble.appyx.core.composable.Children
import com.bumble.appyx.core.modality.BuildContext import com.bumble.appyx.core.modality.BuildContext
@ -30,6 +43,7 @@ import com.bumble.appyx.core.node.node
import com.bumble.appyx.core.plugin.Plugin import com.bumble.appyx.core.plugin.Plugin
import com.bumble.appyx.core.plugin.plugins import com.bumble.appyx.core.plugin.plugins
import com.bumble.appyx.navmodel.backstack.BackStack import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.operation.newRoot
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import io.element.android.anvilannotations.ContributesNode import io.element.android.anvilannotations.ContributesNode
@ -37,10 +51,17 @@ import io.element.android.libraries.architecture.BackstackNode
import io.element.android.libraries.architecture.NodeInputs import io.element.android.libraries.architecture.NodeInputs
import io.element.android.libraries.architecture.createNode import io.element.android.libraries.architecture.createNode
import io.element.android.libraries.architecture.inputs import io.element.android.libraries.architecture.inputs
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
import io.element.android.libraries.designsystem.theme.components.Scaffold
import io.element.android.libraries.designsystem.theme.components.TopAppBar
import io.element.android.libraries.designsystem.theme.placeholderBackground
import io.element.android.libraries.di.SessionScope import io.element.android.libraries.di.SessionScope
import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.theme.ElementTheme
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.asFlow import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -86,9 +107,9 @@ class AwaitRoomNode @AssistedInject constructor(
init { init {
roomStateFlow.onEach { room -> roomStateFlow.onEach { room ->
if (room == null) { if (room == null) {
backstack.safeRoot(NavTarget.Loading) backstack.newRoot(NavTarget.Loading)
} else { } else {
backstack.safeRoot(NavTarget.Loaded) backstack.newRoot(NavTarget.Loaded)
} }
}.launchIn(lifecycleScope) }.launchIn(lifecycleScope)
} }
@ -100,22 +121,57 @@ class AwaitRoomNode @AssistedInject constructor(
val roomFlowNodeCallback = plugins<RoomFlowNode.Callback>() val roomFlowNodeCallback = plugins<RoomFlowNode.Callback>()
val room = roomStateFlow.value val room = roomStateFlow.value
if (room == null) { if (room == null) {
loadingNode(buildContext) loadingNode(buildContext, this::navigateUp)
} else { } else {
val inputs = RoomFlowNode.Inputs(room, initialElement = inputs.initialElement) val inputs = RoomFlowNode.Inputs(room, initialElement = inputs.initialElement)
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback + nodeLifecycleCallbacks) createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback + nodeLifecycleCallbacks)
} }
} }
NavTarget.Loading -> { NavTarget.Loading -> {
loadingNode(buildContext) loadingNode(buildContext, this::navigateUp)
} }
} }
} }
private fun loadingNode(buildContext: BuildContext) = node(buildContext) { private fun loadingNode(buildContext: BuildContext, onBackPressed: () -> Unit) = node(buildContext) { modifier ->
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) { Scaffold(
CircularProgressIndicator() modifier = modifier,
} contentWindowInsets = WindowInsets.systemBars,
topBar = {
TopAppBar(
modifier = Modifier,
navigationIcon = {
BackButton(onClick = onBackPressed)
},
title = {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Box(
modifier = Modifier
.size(AvatarSize.TimelineRoom.dp)
.align(Alignment.CenterVertically)
.background(color = ElementTheme.colors.placeholderBackground, shape = CircleShape)
)
Spacer(modifier = Modifier.width(8.dp))
PlaceholderAtom(width = 20.dp, height = 7.dp)
Spacer(modifier = Modifier.width(7.dp))
PlaceholderAtom(width = 45.dp, height = 7.dp)
}
},
)
},
content = { padding ->
Box(
modifier = Modifier
.fillMaxSize()
.padding(padding), contentAlignment = Alignment.Center
) {
CircularProgressIndicator()
}
},
)
} }
@Composable @Composable

View file

@ -267,14 +267,14 @@ class LoggedInFlowNode @AssistedInject constructor(
.build() .build()
} }
is NavTarget.Room -> { is NavTarget.Room -> {
val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>() val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>()
val callback = object : RoomFlowNode.Callback { val callback = object : RoomFlowNode.Callback {
override fun onForwardedToSingleRoom(roomId: RoomId) { override fun onForwardedToSingleRoom(roomId: RoomId) {
coroutineScope.launch { attachRoom(roomId) } coroutineScope.launch { attachRoom(roomId) }
}
} }
val inputs = AwaitRoomNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement) }
createNode<AwaitRoomNode>(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks) val inputs = AwaitRoomNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement)
createNode<AwaitRoomNode>(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks)
} }
NavTarget.Settings -> { NavTarget.Settings -> {
val callback = object : PreferencesEntryPoint.Callback { val callback = object : PreferencesEntryPoint.Callback {

View file

@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.roomListPlaceholder import io.element.android.libraries.designsystem.theme.placeholderBackground
import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.ElementTheme
/** /**
@ -56,7 +56,7 @@ internal fun RoomSummaryPlaceholderRow(
modifier = Modifier modifier = Modifier
.size(AvatarSize.RoomListItem.dp) .size(AvatarSize.RoomListItem.dp)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
.background(color = ElementTheme.colors.roomListPlaceholder, shape = CircleShape) .background(color = ElementTheme.colors.placeholderBackground, shape = CircleShape)
) )
Column( Column(
modifier = Modifier modifier = Modifier

View file

@ -29,7 +29,7 @@ import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.roomListPlaceholder import io.element.android.libraries.designsystem.theme.placeholderBackground
import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.ElementTheme
@Composable @Composable
@ -37,7 +37,7 @@ fun PlaceholderAtom(
width: Dp, width: Dp,
height: Dp, height: Dp,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
color: Color = ElementTheme.colors.roomListPlaceholder, color: Color = ElementTheme.colors.placeholderBackground,
) { ) {
Box( Box(
modifier = modifier modifier = modifier

View file

@ -42,7 +42,7 @@ fun MaterialTheme.roomListRoomMessageDate() = colorScheme.secondary
val SemanticColors.unreadIndicator val SemanticColors.unreadIndicator
get() = iconAccentTertiary get() = iconAccentTertiary
val SemanticColors.roomListPlaceholder val SemanticColors.placeholderBackground
get() = bgSubtleSecondary get() = bgSubtleSecondary
// This color is not present in Semantic color, so put hard-coded value for now // This color is not present in Semantic color, so put hard-coded value for now
@ -83,7 +83,7 @@ private fun ContentToPreview() {
"roomListRoomMessage" to MaterialTheme.roomListRoomMessage(), "roomListRoomMessage" to MaterialTheme.roomListRoomMessage(),
"roomListRoomMessageDate" to MaterialTheme.roomListRoomMessageDate(), "roomListRoomMessageDate" to MaterialTheme.roomListRoomMessageDate(),
"unreadIndicator" to ElementTheme.colors.unreadIndicator, "unreadIndicator" to ElementTheme.colors.unreadIndicator,
"roomListPlaceholder" to ElementTheme.colors.roomListPlaceholder, "placeholderBackground" to ElementTheme.colors.placeholderBackground,
"messageFromMeBackground" to ElementTheme.colors.messageFromMeBackground, "messageFromMeBackground" to ElementTheme.colors.messageFromMeBackground,
"messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground, "messageFromOtherBackground" to ElementTheme.colors.messageFromOtherBackground,
) )

View file

@ -36,7 +36,7 @@ import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
import io.element.android.libraries.designsystem.components.avatar.AvatarSize import io.element.android.libraries.designsystem.components.avatar.AvatarSize
import io.element.android.libraries.designsystem.preview.ElementPreviewDark import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.theme.roomListPlaceholder import io.element.android.libraries.designsystem.theme.placeholderBackground
import io.element.android.libraries.theme.ElementTheme import io.element.android.libraries.theme.ElementTheme
@Composable @Composable
@ -53,7 +53,7 @@ fun MatrixUserHeaderPlaceholder(
modifier = Modifier modifier = Modifier
.padding(vertical = 12.dp) .padding(vertical = 12.dp)
.size(AvatarSize.UserPreference.dp) .size(AvatarSize.UserPreference.dp)
.background(color = ElementTheme.colors.roomListPlaceholder, shape = CircleShape) .background(color = ElementTheme.colors.placeholderBackground, shape = CircleShape)
) )
Spacer(modifier = Modifier.width(16.dp)) Spacer(modifier = Modifier.width(16.dp))
Column( Column(