Skip to content

Commit 0f08e80

Browse files
committed
Merge branch 'release/0.4.10' into main
2 parents cac0586 + 282c345 commit 0f08e80

File tree

339 files changed

+4336
-2241
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

339 files changed

+4336
-2241
lines changed

.github/workflows/gradle-wrapper-validation.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ jobs:
1212
# No concurrency required, this is a prerequisite to other actions and should run every time.
1313
steps:
1414
- uses: actions/checkout@v4
15-
- uses: gradle/wrapper-validation-action@v2
15+
- uses: gradle/wrapper-validation-action@v3

CHANGES.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
Changes in Element X v0.4.10 (2024-04-17)
2+
=========================================
3+
4+
Matrix Rust SDK 0.2.14
5+
6+
Features ✨
7+
----------
8+
- Rework room navigation to handle unknown room and prepare work on permalink. ([#2695](https://github.com/element-hq/element-x-android/issues/2695))
9+
10+
Other changes
11+
-------------
12+
- Encrypt new session data with a passphrase ([#2703](https://github.com/element-hq/element-x-android/issues/2703))
13+
- Use sdk API to build permalinks ([#2708](https://github.com/element-hq/element-x-android/issues/2708))
14+
- Parse permalink using parseMatrixEntityFrom from the SDK ([#2709](https://github.com/element-hq/element-x-android/issues/2709))
15+
- Fix compile for forks that use the `noop` analytics module ([#2698](https://github.com/element-hq/element-x-android/issues/2698))
16+
17+
118
Changes in Element X v0.4.9 (2024-04-12)
219
========================================
320

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

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,20 @@ import dagger.assisted.AssistedInject
4242
import io.element.android.anvilannotations.ContributesNode
4343
import io.element.android.appnav.loggedin.LoggedInNode
4444
import io.element.android.appnav.room.RoomFlowNode
45-
import io.element.android.appnav.room.RoomLoadedFlowNode
45+
import io.element.android.appnav.room.RoomNavigationTarget
46+
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
4647
import io.element.android.features.createroom.api.CreateRoomEntryPoint
4748
import io.element.android.features.ftue.api.FtueEntryPoint
4849
import io.element.android.features.ftue.api.state.FtueService
4950
import io.element.android.features.ftue.api.state.FtueState
50-
import io.element.android.features.invitelist.api.InviteListEntryPoint
51+
import io.element.android.features.invite.api.InviteListEntryPoint
5152
import io.element.android.features.lockscreen.api.LockScreenEntryPoint
5253
import io.element.android.features.lockscreen.api.LockScreenLockState
5354
import io.element.android.features.lockscreen.api.LockScreenService
5455
import io.element.android.features.networkmonitor.api.NetworkMonitor
5556
import io.element.android.features.networkmonitor.api.NetworkStatus
5657
import io.element.android.features.preferences.api.PreferencesEntryPoint
58+
import io.element.android.features.roomdirectory.api.RoomDescription
5759
import io.element.android.features.roomdirectory.api.RoomDirectoryEntryPoint
5860
import io.element.android.features.roomlist.api.RoomListEntryPoint
5961
import io.element.android.features.securebackup.api.SecureBackupEntryPoint
@@ -82,6 +84,7 @@ import kotlinx.coroutines.launch
8284
import kotlinx.coroutines.withContext
8385
import kotlinx.parcelize.Parcelize
8486
import timber.log.Timber
87+
import java.util.Optional
8588

8689
@ContributesNode(SessionScope::class)
8790
class LoggedInFlowNode @AssistedInject constructor(
@@ -213,7 +216,8 @@ class LoggedInFlowNode @AssistedInject constructor(
213216
@Parcelize
214217
data class Room(
215218
val roomId: RoomId,
216-
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages
219+
val roomDescription: RoomDescription? = null,
220+
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages
217221
) : NavTarget
218222

219223
@Parcelize
@@ -273,7 +277,7 @@ class LoggedInFlowNode @AssistedInject constructor(
273277
}
274278

275279
override fun onRoomSettingsClicked(roomId: RoomId) {
276-
backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomDetails))
280+
backstack.push(NavTarget.Room(roomId, initialElement = RoomNavigationTarget.Details))
277281
}
278282

279283
override fun onReportBugClicked() {
@@ -290,7 +294,7 @@ class LoggedInFlowNode @AssistedInject constructor(
290294
.build()
291295
}
292296
is NavTarget.Room -> {
293-
val callback = object : RoomLoadedFlowNode.Callback {
297+
val callback = object : JoinedRoomLoadedFlowNode.Callback {
294298
override fun onOpenRoom(roomId: RoomId) {
295299
backstack.push(NavTarget.Room(roomId))
296300
}
@@ -303,7 +307,11 @@ class LoggedInFlowNode @AssistedInject constructor(
303307
backstack.push(NavTarget.Settings(PreferencesEntryPoint.InitialTarget.NotificationSettings))
304308
}
305309
}
306-
val inputs = RoomFlowNode.Inputs(roomId = navTarget.roomId, initialElement = navTarget.initialElement)
310+
val inputs = RoomFlowNode.Inputs(
311+
roomId = navTarget.roomId,
312+
roomDescription = Optional.ofNullable(navTarget.roomDescription),
313+
initialElement = navTarget.initialElement
314+
)
307315
createNode<RoomFlowNode>(buildContext, plugins = listOf(inputs, callback))
308316
}
309317
is NavTarget.Settings -> {
@@ -317,7 +325,7 @@ class LoggedInFlowNode @AssistedInject constructor(
317325
}
318326

319327
override fun onOpenRoomNotificationSettings(roomId: RoomId) {
320-
backstack.push(NavTarget.Room(roomId, initialElement = RoomLoadedFlowNode.NavTarget.RoomNotificationSettings))
328+
backstack.push(NavTarget.Room(roomId, initialElement = RoomNavigationTarget.NotificationSettings))
321329
}
322330
}
323331
val inputs = PreferencesEntryPoint.Params(navTarget.initialElement)
@@ -349,6 +357,10 @@ class LoggedInFlowNode @AssistedInject constructor(
349357
backstack.pop()
350358
}
351359

360+
override fun onInviteClicked(roomId: RoomId) {
361+
backstack.push(NavTarget.Room(roomId))
362+
}
363+
352364
override fun onInviteAccepted(roomId: RoomId) {
353365
backstack.push(NavTarget.Room(roomId))
354366
}
@@ -370,8 +382,12 @@ class LoggedInFlowNode @AssistedInject constructor(
370382
NavTarget.RoomDirectorySearch -> {
371383
roomDirectoryEntryPoint.nodeBuilder(this, buildContext)
372384
.callback(object : RoomDirectoryEntryPoint.Callback {
373-
override fun onOpenRoom(roomId: RoomId) {
374-
coroutineScope.launch { attachRoom(roomId) }
385+
override fun onRoomJoined(roomId: RoomId) {
386+
backstack.push(NavTarget.Room(roomId))
387+
}
388+
389+
override fun onResultClicked(roomDescription: RoomDescription) {
390+
backstack.push(NavTarget.Room(roomDescription.roomId, roomDescription))
375391
}
376392
})
377393
.build()
Lines changed: 62 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2023 New Vector Ltd
2+
* Copyright (c) 2024 New Vector Ltd
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -14,15 +14,13 @@
1414
* limitations under the License.
1515
*/
1616

17-
@file:OptIn(ExperimentalMaterial3Api::class)
18-
1917
package io.element.android.appnav.room
2018

2119
import android.os.Parcelable
22-
import androidx.compose.material3.ExperimentalMaterial3Api
20+
import androidx.compose.foundation.layout.Box
21+
import androidx.compose.foundation.layout.fillMaxSize
2322
import androidx.compose.runtime.Composable
24-
import androidx.compose.runtime.collectAsState
25-
import androidx.compose.runtime.getValue
23+
import androidx.compose.ui.Alignment
2624
import androidx.compose.ui.Modifier
2725
import androidx.lifecycle.lifecycleScope
2826
import com.bumble.appyx.core.modality.BuildContext
@@ -36,102 +34,109 @@ import com.bumble.appyx.navmodel.backstack.operation.newRoot
3634
import dagger.assisted.Assisted
3735
import dagger.assisted.AssistedInject
3836
import io.element.android.anvilannotations.ContributesNode
39-
import io.element.android.features.networkmonitor.api.NetworkMonitor
40-
import io.element.android.features.networkmonitor.api.NetworkStatus
37+
import io.element.android.appnav.room.joined.JoinedRoomFlowNode
38+
import io.element.android.appnav.room.joined.JoinedRoomLoadedFlowNode
39+
import io.element.android.features.joinroom.api.JoinRoomEntryPoint
40+
import io.element.android.features.roomdirectory.api.RoomDescription
4141
import io.element.android.libraries.architecture.BackstackView
4242
import io.element.android.libraries.architecture.BaseFlowNode
4343
import io.element.android.libraries.architecture.NodeInputs
4444
import io.element.android.libraries.architecture.createNode
4545
import io.element.android.libraries.architecture.inputs
46+
import io.element.android.libraries.designsystem.theme.components.CircularProgressIndicator
4647
import io.element.android.libraries.di.SessionScope
48+
import io.element.android.libraries.matrix.api.MatrixClient
4749
import io.element.android.libraries.matrix.api.core.RoomId
48-
import kotlinx.coroutines.flow.distinctUntilChanged
50+
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
51+
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
52+
import kotlinx.coroutines.flow.filter
4953
import kotlinx.coroutines.flow.launchIn
50-
import kotlinx.coroutines.flow.map
5154
import kotlinx.coroutines.flow.onEach
5255
import kotlinx.parcelize.Parcelize
56+
import timber.log.Timber
57+
import java.util.Optional
58+
import kotlin.jvm.optionals.getOrNull
5359

5460
@ContributesNode(SessionScope::class)
5561
class RoomFlowNode @AssistedInject constructor(
5662
@Assisted val buildContext: BuildContext,
5763
@Assisted plugins: List<Plugin>,
58-
loadingRoomStateFlowFactory: LoadingRoomStateFlowFactory,
59-
private val networkMonitor: NetworkMonitor,
60-
) :
61-
BaseFlowNode<RoomFlowNode.NavTarget>(
62-
backstack = BackStack(
63-
initialElement = NavTarget.Loading,
64-
savedStateMap = buildContext.savedStateMap,
65-
),
66-
buildContext = buildContext,
67-
plugins = plugins
68-
) {
64+
private val client: MatrixClient,
65+
private val roomMembershipObserver: RoomMembershipObserver,
66+
private val joinRoomEntryPoint: JoinRoomEntryPoint,
67+
) : BaseFlowNode<RoomFlowNode.NavTarget>(
68+
backstack = BackStack(
69+
initialElement = NavTarget.Loading,
70+
savedStateMap = buildContext.savedStateMap,
71+
),
72+
buildContext = buildContext,
73+
plugins = plugins
74+
) {
6975
data class Inputs(
7076
val roomId: RoomId,
71-
val initialElement: RoomLoadedFlowNode.NavTarget = RoomLoadedFlowNode.NavTarget.Messages,
77+
val roomDescription: Optional<RoomDescription>,
78+
val initialElement: RoomNavigationTarget = RoomNavigationTarget.Messages,
7279
) : NodeInputs
7380

7481
private val inputs: Inputs = inputs()
75-
private val loadingRoomStateStateFlow = loadingRoomStateFlowFactory.create(lifecycleScope, inputs.roomId)
7682

7783
sealed interface NavTarget : Parcelable {
7884
@Parcelize
7985
data object Loading : NavTarget
8086

8187
@Parcelize
82-
data object Loaded : NavTarget
88+
data object JoinRoom : NavTarget
89+
90+
@Parcelize
91+
data object JoinedRoom : NavTarget
8392
}
8493

8594
override fun onBuilt() {
8695
super.onBuilt()
87-
loadingRoomStateStateFlow
88-
.map {
89-
it is LoadingRoomState.Loaded
96+
client.getRoomInfoFlow(
97+
inputs.roomId
98+
).onEach { roomInfo ->
99+
Timber.d("Room membership: ${roomInfo.map { it.currentUserMembership }}")
100+
if (roomInfo.getOrNull()?.currentUserMembership == CurrentUserMembership.JOINED) {
101+
backstack.newRoot(NavTarget.JoinedRoom)
102+
} else {
103+
backstack.newRoot(NavTarget.JoinRoom)
90104
}
91-
.distinctUntilChanged()
92-
.onEach { isLoaded ->
93-
if (isLoaded) {
94-
backstack.newRoot(NavTarget.Loaded)
95-
} else {
96-
backstack.newRoot(NavTarget.Loading)
97-
}
105+
}
106+
.launchIn(lifecycleScope)
107+
108+
// When leaving the room from this session only, navigate up.
109+
roomMembershipObserver.updates
110+
.filter { update -> update.roomId == inputs.roomId && !update.isUserInRoom }
111+
.onEach {
112+
navigateUp()
98113
}
99114
.launchIn(lifecycleScope)
100115
}
101116

102117
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
103118
return when (navTarget) {
104-
NavTarget.Loaded -> {
105-
val roomFlowNodeCallback = plugins<RoomLoadedFlowNode.Callback>()
106-
val awaitRoomState = loadingRoomStateStateFlow.value
107-
if (awaitRoomState is LoadingRoomState.Loaded) {
108-
val inputs = RoomLoadedFlowNode.Inputs(awaitRoomState.room, initialElement = inputs.initialElement)
109-
createNode<RoomLoadedFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
110-
} else {
111-
loadingNode(buildContext, this::navigateUp)
112-
}
119+
NavTarget.Loading -> loadingNode(buildContext)
120+
NavTarget.JoinRoom -> {
121+
val inputs = JoinRoomEntryPoint.Inputs(inputs.roomId, roomDescription = inputs.roomDescription)
122+
joinRoomEntryPoint.createNode(this, buildContext, inputs)
113123
}
114-
NavTarget.Loading -> {
115-
loadingNode(buildContext, this::navigateUp)
124+
NavTarget.JoinedRoom -> {
125+
val roomFlowNodeCallback = plugins<JoinedRoomLoadedFlowNode.Callback>()
126+
val inputs = JoinedRoomFlowNode.Inputs(inputs.roomId, initialElement = inputs.initialElement)
127+
createNode<JoinedRoomFlowNode>(buildContext, plugins = listOf(inputs) + roomFlowNodeCallback)
116128
}
117129
}
118130
}
119131

120-
private fun loadingNode(buildContext: BuildContext, onBackClicked: () -> Unit) = node(buildContext) { modifier ->
121-
val loadingRoomState by loadingRoomStateStateFlow.collectAsState()
122-
val networkStatus by networkMonitor.connectivity.collectAsState()
123-
LoadingRoomNodeView(
124-
state = loadingRoomState,
125-
hasNetworkConnection = networkStatus == NetworkStatus.Online,
126-
modifier = modifier,
127-
onBackClicked = onBackClicked
128-
)
132+
private fun loadingNode(buildContext: BuildContext) = node(buildContext) {
133+
Box(modifier = it.fillMaxSize(), contentAlignment = Alignment.Center) {
134+
CircularProgressIndicator()
135+
}
129136
}
130137

131138
@Composable
132139
override fun View(modifier: Modifier) {
133-
BackstackView(
134-
transitionHandler = JumpToEndTransitionHandler(),
135-
)
140+
BackstackView(transitionHandler = JumpToEndTransitionHandler())
136141
}
137142
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright (c) 2024 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+
enum class RoomNavigationTarget {
20+
Messages,
21+
Details,
22+
NotificationSettings,
23+
}

0 commit comments

Comments
 (0)