Skip to content

Commit f32a0ca

Browse files
authored
Merge pull request #794 from vector-im/feature/fga/waiting_ss_room
Feature/fga/waiting ss room
2 parents 3b6385f + 10c2859 commit f32a0ca

File tree

34 files changed

+592
-145
lines changed

34 files changed

+592
-145
lines changed

app/src/main/kotlin/io/element/android/x/MainNode.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import com.bumble.appyx.core.node.Node
2828
import com.bumble.appyx.core.node.ParentNode
2929
import com.bumble.appyx.core.plugin.Plugin
3030
import io.element.android.appnav.LoggedInFlowNode
31-
import io.element.android.appnav.RoomFlowNode
31+
import io.element.android.appnav.room.RoomLoadedFlowNode
3232
import io.element.android.appnav.RootFlowNode
3333
import io.element.android.libraries.architecture.bindings
3434
import io.element.android.libraries.architecture.createNode
@@ -67,7 +67,7 @@ class MainNode(
6767
}
6868
}
6969

70-
private val roomFlowNodeCallback = object : RoomFlowNode.LifecycleCallback {
70+
private val roomFlowNodeCallback = object : RoomLoadedFlowNode.LifecycleCallback {
7171
override fun onFlowCreated(identifier: String, room: MatrixRoom) {
7272
val component = bindings<RoomComponent.ParentBindings>().roomComponentBuilder().room(room).build()
7373
mainDaggerComponentOwner.addComponent(identifier, component)

appnav/src/main/kotlin/io/element/android/appnav/LoggedInFlowNode.kt

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@ import dagger.assisted.Assisted
4242
import dagger.assisted.AssistedInject
4343
import io.element.android.anvilannotations.ContributesNode
4444
import io.element.android.appnav.loggedin.LoggedInNode
45+
import io.element.android.appnav.room.RoomFlowNode
46+
import io.element.android.appnav.room.RoomLoadedFlowNode
4547
import io.element.android.features.analytics.api.AnalyticsEntryPoint
4648
import io.element.android.features.createroom.api.CreateRoomEntryPoint
4749
import io.element.android.features.invitelist.api.InviteListEntryPoint
@@ -56,7 +58,6 @@ import io.element.android.libraries.architecture.animation.rememberDefaultTransi
5658
import io.element.android.libraries.architecture.bindings
5759
import io.element.android.libraries.architecture.createNode
5860
import io.element.android.libraries.architecture.inputs
59-
import io.element.android.libraries.designsystem.theme.components.Text
6061
import io.element.android.libraries.designsystem.utils.SnackbarDispatcher
6162
import io.element.android.libraries.di.AppScope
6263
import io.element.android.libraries.matrix.api.MatrixClient
@@ -138,6 +139,7 @@ class LoggedInFlowNode @AssistedInject constructor(
138139
observeAnalyticsState()
139140
lifecycle.subscribe(
140141
onCreate = {
142+
syncService.startSync()
141143
plugins<LifecycleCallback>().forEach { it.onFlowCreated(id, inputs.matrixClient) }
142144
val imageLoaderFactory = bindings<MatrixUIBindings>().loggedInImageLoaderFactory()
143145
Coil.setImageLoader(imageLoaderFactory)
@@ -194,7 +196,7 @@ class LoggedInFlowNode @AssistedInject constructor(
194196
@Parcelize
195197
data class Room(
196198
val roomId: RoomId,
197-
val initialElement: RoomFlowNode.NavTarget = RoomFlowNode.NavTarget.Messages
199+
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages
198200
) : NavTarget
199201

200202
@Parcelize
@@ -241,7 +243,7 @@ class LoggedInFlowNode @AssistedInject constructor(
241243
}
242244

243245
override fun onRoomSettingsClicked(roomId: RoomId) {
244-
backstack.push(NavTarget.Room(roomId, initialElement = RoomFlowNode.NavTarget.RoomDetails))
246+
backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomDetails))
245247
}
246248

247249
override fun onReportBugClicked() {
@@ -254,24 +256,14 @@ class LoggedInFlowNode @AssistedInject constructor(
254256
.build()
255257
}
256258
is NavTarget.Room -> {
257-
val room = inputs.matrixClient.getRoom(roomId = navTarget.roomId)
258-
if (room == null) {
259-
// TODO CREATE UNKNOWN ROOM NODE
260-
node(buildContext) {
261-
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
262-
Text(text = "Unknown room with id = ${navTarget.roomId}")
263-
}
264-
}
265-
} else {
266-
val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>()
267-
val callback = object : RoomFlowNode.Callback {
268-
override fun onForwardedToSingleRoom(roomId: RoomId) {
269-
coroutineScope.launch { attachRoom(roomId) }
270-
}
259+
val nodeLifecycleCallbacks = plugins<NodeLifecycleCallback>()
260+
val callback = object : RoomLoadedFlowNode.Callback {
261+
override fun onForwardedToSingleRoom(roomId: RoomId) {
262+
coroutineScope.launch { attachRoom(roomId) }
271263
}
272-
val inputs = RoomFlowNode.Inputs(room, initialElement = navTarget.initialElement)
273-
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks)
274264
}
265+
val inputs = RoomFlowNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement)
266+
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback) + nodeLifecycleCallbacks)
275267
}
276268
NavTarget.Settings -> {
277269
val callback = object : PreferencesEntryPoint.Callback {
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.appnav.room
18+
19+
import androidx.compose.foundation.background
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.Column
22+
import androidx.compose.foundation.layout.Row
23+
import androidx.compose.foundation.layout.Spacer
24+
import androidx.compose.foundation.layout.WindowInsets
25+
import androidx.compose.foundation.layout.fillMaxSize
26+
import androidx.compose.foundation.layout.padding
27+
import androidx.compose.foundation.layout.size
28+
import androidx.compose.foundation.layout.systemBars
29+
import androidx.compose.foundation.layout.width
30+
import androidx.compose.foundation.shape.CircleShape
31+
import androidx.compose.material3.ExperimentalMaterial3Api
32+
import androidx.compose.runtime.Composable
33+
import androidx.compose.ui.Alignment
34+
import androidx.compose.ui.Modifier
35+
import androidx.compose.ui.res.stringResource
36+
import androidx.compose.ui.tooling.preview.Preview
37+
import androidx.compose.ui.tooling.preview.PreviewParameter
38+
import androidx.compose.ui.unit.dp
39+
import androidx.compose.ui.unit.sp
40+
import io.element.android.features.networkmonitor.api.ui.ConnectivityIndicatorView
41+
import io.element.android.libraries.designsystem.atomic.atoms.PlaceholderAtom
42+
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
43+
import io.element.android.libraries.designsystem.components.button.BackButton
44+
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
45+
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
46+
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
47+
import io.element.android.libraries.designsystem.theme.components.Scaffold
48+
import io.element.android.libraries.designsystem.theme.components.Text
49+
import io.element.android.libraries.designsystem.theme.components.TopAppBar
50+
import io.element.android.libraries.designsystem.theme.placeholderBackground
51+
import io.element.android.libraries.theme.ElementTheme
52+
import io.element.android.libraries.ui.strings.CommonStrings
53+
54+
@Composable
55+
fun LoadingRoomNodeView(
56+
state: LoadingRoomState,
57+
hasNetworkConnection: Boolean,
58+
onBackClicked: () -> Unit,
59+
modifier: Modifier = Modifier,
60+
) {
61+
Scaffold(
62+
modifier = modifier,
63+
contentWindowInsets = WindowInsets.systemBars,
64+
topBar = {
65+
Column {
66+
ConnectivityIndicatorView(isOnline = hasNetworkConnection)
67+
LoadingRoomTopBar(onBackClicked)
68+
}
69+
},
70+
content = { padding ->
71+
Box(
72+
modifier = Modifier
73+
.fillMaxSize()
74+
.padding(padding)
75+
.padding(16.dp), contentAlignment = Alignment.Center
76+
) {
77+
if (state is LoadingRoomState.Error) {
78+
Text(
79+
text = stringResource(id = CommonStrings.error_unknown),
80+
color = ElementTheme.colors.textSecondary,
81+
fontSize = 14.sp,
82+
)
83+
} else {
84+
CircularProgressIndicator()
85+
}
86+
}
87+
},
88+
)
89+
}
90+
91+
@OptIn(ExperimentalMaterial3Api::class)
92+
@Composable
93+
private fun LoadingRoomTopBar(onBackClicked: () -> Unit) {
94+
TopAppBar(
95+
modifier = Modifier,
96+
navigationIcon = {
97+
BackButton(onClick = onBackClicked)
98+
},
99+
title = {
100+
Row(
101+
verticalAlignment = Alignment.CenterVertically
102+
) {
103+
Box(
104+
modifier = Modifier
105+
.size(AvatarSize.TimelineRoom.dp)
106+
.align(Alignment.CenterVertically)
107+
.background(color = ElementTheme.colors.placeholderBackground, shape = CircleShape)
108+
)
109+
Spacer(modifier = Modifier.width(8.dp))
110+
PlaceholderAtom(width = 20.dp, height = 7.dp)
111+
Spacer(modifier = Modifier.width(7.dp))
112+
PlaceholderAtom(width = 45.dp, height = 7.dp)
113+
}
114+
},
115+
)
116+
}
117+
118+
@Preview
119+
@Composable
120+
fun LoadingRoomNodeViewLightPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
121+
ElementPreviewLight { ContentToPreview(state) }
122+
123+
@Preview
124+
@Composable
125+
fun LoadingRoomNodeViewDarkPreview(@PreviewParameter(LoadingRoomStateProvider::class) state: LoadingRoomState) =
126+
ElementPreviewDark { ContentToPreview(state) }
127+
128+
@Composable
129+
private fun ContentToPreview(state: LoadingRoomState) {
130+
LoadingRoomNodeView(
131+
state = state,
132+
onBackClicked = {},
133+
hasNetworkConnection = false
134+
)
135+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright (c) 2023 New Vector Ltd
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package io.element.android.appnav.room
18+
19+
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
20+
import io.element.android.libraries.di.SessionScope
21+
import io.element.android.libraries.di.SingleIn
22+
import io.element.android.libraries.matrix.api.MatrixClient
23+
import io.element.android.libraries.matrix.api.core.RoomId
24+
import io.element.android.libraries.matrix.api.room.MatrixRoom
25+
import kotlinx.coroutines.CoroutineScope
26+
import kotlinx.coroutines.flow.Flow
27+
import kotlinx.coroutines.flow.SharingStarted
28+
import kotlinx.coroutines.flow.StateFlow
29+
import kotlinx.coroutines.flow.asFlow
30+
import kotlinx.coroutines.flow.map
31+
import kotlinx.coroutines.flow.stateIn
32+
import javax.inject.Inject
33+
34+
sealed interface LoadingRoomState {
35+
object Loading : LoadingRoomState
36+
object Error : LoadingRoomState
37+
data class Loaded(val room: MatrixRoom) : LoadingRoomState
38+
}
39+
40+
open class LoadingRoomStateProvider : PreviewParameterProvider<LoadingRoomState> {
41+
override val values: Sequence<LoadingRoomState>
42+
get() = sequenceOf(
43+
LoadingRoomState.Loading,
44+
LoadingRoomState.Error
45+
)
46+
}
47+
48+
@SingleIn(SessionScope::class)
49+
class LoadingRoomStateFlowFactory @Inject constructor(private val matrixClient: MatrixClient) {
50+
51+
fun create(lifecycleScope: CoroutineScope, roomId: RoomId): StateFlow<LoadingRoomState> =
52+
getRoomFlow(roomId)
53+
.map { room ->
54+
if (room != null) {
55+
LoadingRoomState.Loaded(room)
56+
} else {
57+
LoadingRoomState.Error
58+
}
59+
}
60+
.stateIn(lifecycleScope, SharingStarted.Eagerly, LoadingRoomState.Loading)
61+
62+
private fun getRoomFlow(roomId: RoomId): Flow<MatrixRoom?> = suspend {
63+
matrixClient.getRoom(roomId = roomId)
64+
}
65+
.asFlow()
66+
}

0 commit comments

Comments
 (0)