Skip to content

Commit ddaf112

Browse files
authored
Merge pull request #6598 from vector-im/task/eric/space-switching-unit-tests
Space Switching Refactoring and Unit Tests
2 parents 5e19838 + 4f5ad81 commit ddaf112

22 files changed

+395
-108
lines changed

changelog.d/6598.misc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Refactors SpaceStateHandler (previously AppStateHandler) and adds unit tests for it
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright (c) 2022 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 im.vector.app
18+
19+
import androidx.lifecycle.DefaultLifecycleObserver
20+
import arrow.core.Option
21+
import kotlinx.coroutines.flow.Flow
22+
import org.matrix.android.sdk.api.session.Session
23+
import org.matrix.android.sdk.api.session.room.model.RoomSummary
24+
25+
/**
26+
* Gets info about the current space the user has navigated to, any space backstack they may have
27+
* and handles switching to different spaces
28+
*/
29+
interface SpaceStateHandler : DefaultLifecycleObserver {
30+
31+
/**
32+
* Gets the current space the current user has navigated to
33+
*
34+
* @return null if the user is not in
35+
*/
36+
fun getCurrentSpace(): RoomSummary?
37+
38+
/**
39+
* Sets the new space the current user is navigating to
40+
*
41+
* @param spaceId the id of the space being navigated to
42+
* @param session the current active session
43+
* @param persistNow if true, the current space will immediately be persisted in shared prefs
44+
* @param isForwardNavigation whether this navigation is a forward action to properly handle backstack
45+
*/
46+
fun setCurrentSpace(
47+
spaceId: String?,
48+
session: Session? = null,
49+
persistNow: Boolean = false,
50+
isForwardNavigation: Boolean = true,
51+
)
52+
53+
/**
54+
* Gets the current backstack of spaces (via their id)
55+
*
56+
* null may be an entry in the ArrayDeque to indicate the root space (All Chats)
57+
*/
58+
fun getSpaceBackstack(): ArrayDeque<String?>
59+
60+
/**
61+
* Gets a flow of the selected space for clients to react immediately to space changes
62+
*/
63+
fun getSelectedSpaceFlow(): Flow<Option<RoomSummary>>
64+
65+
/**
66+
* Gets the id of the active space, or null if there is none
67+
*/
68+
fun getSafeActiveSpaceId(): String?
69+
}

vector/src/main/java/im/vector/app/AppStateHandler.kt renamed to vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt

+27-20
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package im.vector.app
1818

19-
import androidx.lifecycle.DefaultLifecycleObserver
2019
import androidx.lifecycle.LifecycleOwner
2120
import arrow.core.Option
2221
import im.vector.app.core.di.ActiveSessionHolder
@@ -46,54 +45,60 @@ import javax.inject.Singleton
4645

4746
/**
4847
* This class handles the global app state.
49-
* It requires to be added to ProcessLifecycleOwner.get().lifecycle
48+
* It is required that this class is added as an observer to ProcessLifecycleOwner.get().lifecycle in [VectorApplication]
5049
*/
51-
// TODO Keep this class for now, will maybe be used fro Space
5250
@Singleton
53-
class AppStateHandler @Inject constructor(
51+
class SpaceStateHandlerImpl @Inject constructor(
5452
private val sessionDataSource: ActiveSessionDataSource,
5553
private val uiStateRepository: UiStateRepository,
5654
private val activeSessionHolder: ActiveSessionHolder,
5755
private val analyticsTracker: AnalyticsTracker
58-
) : DefaultLifecycleObserver {
56+
) : SpaceStateHandler {
5957

6058
private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
6159
private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomSummary>>(Option.empty())
62-
63-
val selectedSpaceFlow = selectedSpaceDataSource.stream()
64-
60+
private val selectedSpaceFlow = selectedSpaceDataSource.stream()
6561
private val spaceBackstack = ArrayDeque<String?>()
6662

67-
fun getCurrentSpace(): RoomSummary? {
63+
override fun getCurrentSpace(): RoomSummary? {
6864
return selectedSpaceDataSource.currentValue?.orNull()?.let { spaceSummary ->
6965
activeSessionHolder.getSafeActiveSession()?.roomService()?.getRoomSummary(spaceSummary.roomId)
7066
}
7167
}
7268

73-
fun setCurrentSpace(spaceId: String?, session: Session? = null, persistNow: Boolean = false, isForwardNavigation: Boolean = true) {
69+
override fun setCurrentSpace(
70+
spaceId: String?,
71+
session: Session?,
72+
persistNow: Boolean,
73+
isForwardNavigation: Boolean,
74+
) {
75+
val activeSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
7476
val currentSpace = selectedSpaceDataSource.currentValue?.orNull()
75-
val uSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return
76-
if (currentSpace != null && spaceId == currentSpace.roomId) return
77-
val spaceSum = spaceId?.let { uSession.getRoomSummary(spaceId) }
77+
val spaceSummary = spaceId?.let { activeSession.getRoomSummary(spaceId) }
78+
val sameSpaceSelected = currentSpace != null && spaceId == currentSpace.roomId
79+
80+
if (sameSpaceSelected) {
81+
return
82+
}
7883

7984
if (isForwardNavigation) {
8085
spaceBackstack.addLast(currentSpace?.roomId)
8186
}
8287

8388
if (persistNow) {
84-
uiStateRepository.storeSelectedSpace(spaceSum?.roomId, uSession.sessionId)
89+
uiStateRepository.storeSelectedSpace(spaceSummary?.roomId, activeSession.sessionId)
8590
}
8691

87-
if (spaceSum == null) {
92+
if (spaceSummary == null) {
8893
selectedSpaceDataSource.post(Option.empty())
8994
} else {
90-
selectedSpaceDataSource.post(Option.just(spaceSum))
95+
selectedSpaceDataSource.post(Option.just(spaceSummary))
9196
}
9297

9398
if (spaceId != null) {
94-
uSession.coroutineScope.launch(Dispatchers.IO) {
99+
activeSession.coroutineScope.launch(Dispatchers.IO) {
95100
tryOrNull {
96-
uSession.getRoom(spaceId)?.membershipService()?.loadRoomMembersIfNeeded()
101+
activeSession.getRoom(spaceId)?.membershipService()?.loadRoomMembersIfNeeded()
97102
}
98103
}
99104
}
@@ -122,9 +127,11 @@ class AppStateHandler @Inject constructor(
122127
}.launchIn(session.coroutineScope)
123128
}
124129

125-
fun getSpaceBackstack() = spaceBackstack
130+
override fun getSpaceBackstack() = spaceBackstack
131+
132+
override fun getSelectedSpaceFlow() = selectedSpaceFlow
126133

127-
fun safeActiveSpaceId(): String? {
134+
override fun getSafeActiveSpaceId(): String? {
128135
return selectedSpaceDataSource.currentValue?.orNull()?.roomId
129136
}
130137

vector/src/main/java/im/vector/app/VectorApplication.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ class VectorApplication :
8888
@Inject lateinit var vectorPreferences: VectorPreferences
8989
@Inject lateinit var versionProvider: VersionProvider
9090
@Inject lateinit var notificationUtils: NotificationUtils
91-
@Inject lateinit var appStateHandler: AppStateHandler
91+
@Inject lateinit var spaceStateHandler: SpaceStateHandler
9292
@Inject lateinit var popupAlertManager: PopupAlertManager
9393
@Inject lateinit var pinLocker: PinLocker
9494
@Inject lateinit var callManager: WebRtcCallManager
@@ -177,7 +177,7 @@ class VectorApplication :
177177
fcmHelper.onEnterBackground(activeSessionHolder)
178178
}
179179
})
180-
ProcessLifecycleOwner.get().lifecycle.addObserver(appStateHandler)
180+
ProcessLifecycleOwner.get().lifecycle.addObserver(spaceStateHandler)
181181
ProcessLifecycleOwner.get().lifecycle.addObserver(pinLocker)
182182
ProcessLifecycleOwner.get().lifecycle.addObserver(callManager)
183183
// This should be done as early as possible

vector/src/main/java/im/vector/app/core/di/SingletonModule.kt

+5
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ import dagger.hilt.components.SingletonComponent
3131
import im.vector.app.BuildConfig
3232
import im.vector.app.EmojiCompatWrapper
3333
import im.vector.app.EmojiSpanify
34+
import im.vector.app.SpaceStateHandler
35+
import im.vector.app.SpaceStateHandlerImpl
3436
import im.vector.app.config.analyticsConfig
3537
import im.vector.app.core.dispatchers.CoroutineDispatchers
3638
import im.vector.app.core.error.DefaultErrorFormatter
@@ -108,6 +110,9 @@ abstract class VectorBindModule {
108110

109111
@Binds
110112
abstract fun bindSystemSettingsProvide(provider: AndroidSystemSettingsProvider): SystemSettingsProvider
113+
114+
@Binds
115+
abstract fun bindSpaceStateHandler(spaceStateHandlerImpl: SpaceStateHandlerImpl): SpaceStateHandler
111116
}
112117

113118
@InstallIn(SingletonComponent::class)

vector/src/main/java/im/vector/app/features/home/HomeActivity.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ import com.airbnb.mvrx.Mavericks
3535
import com.airbnb.mvrx.viewModel
3636
import com.google.android.material.dialog.MaterialAlertDialogBuilder
3737
import dagger.hilt.android.AndroidEntryPoint
38-
import im.vector.app.AppStateHandler
3938
import im.vector.app.R
39+
import im.vector.app.SpaceStateHandler
4040
import im.vector.app.core.di.ActiveSessionHolder
4141
import im.vector.app.core.extensions.hideKeyboard
4242
import im.vector.app.core.extensions.registerStartForActivityResult
@@ -130,7 +130,7 @@ class HomeActivity :
130130
@Inject lateinit var permalinkHandler: PermalinkHandler
131131
@Inject lateinit var avatarRenderer: AvatarRenderer
132132
@Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter
133-
@Inject lateinit var appStateHandler: AppStateHandler
133+
@Inject lateinit var spaceStateHandler: SpaceStateHandler
134134
@Inject lateinit var unifiedPushHelper: UnifiedPushHelper
135135
@Inject lateinit var fcmHelper: FcmHelper
136136
@Inject lateinit var nightlyProxy: NightlyProxy

vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ import com.airbnb.mvrx.activityViewModel
2828
import com.airbnb.mvrx.fragmentViewModel
2929
import com.airbnb.mvrx.withState
3030
import com.google.android.material.badge.BadgeDrawable
31-
import im.vector.app.AppStateHandler
3231
import im.vector.app.R
32+
import im.vector.app.SpaceStateHandler
3333
import im.vector.app.core.extensions.commitTransaction
3434
import im.vector.app.core.extensions.toMvRxBundle
3535
import im.vector.app.core.platform.OnBackPressed
@@ -68,7 +68,7 @@ class HomeDetailFragment @Inject constructor(
6868
private val alertManager: PopupAlertManager,
6969
private val callManager: WebRtcCallManager,
7070
private val vectorPreferences: VectorPreferences,
71-
private val appStateHandler: AppStateHandler,
71+
private val spaceStateHandler: SpaceStateHandler,
7272
private val vectorFeatures: VectorFeatures,
7373
) : VectorBaseFragment<FragmentHomeDetailBinding>(),
7474
KeysBackupBanner.Delegate,
@@ -186,13 +186,13 @@ class HomeDetailFragment @Inject constructor(
186186
}
187187

188188
private fun navigateBack() {
189-
val previousSpaceId = appStateHandler.getSpaceBackstack().removeLastOrNull()
190-
val parentSpaceId = appStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
189+
val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull()
190+
val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull()
191191
setCurrentSpace(previousSpaceId ?: parentSpaceId)
192192
}
193193

194194
private fun setCurrentSpace(spaceId: String?) {
195-
appStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
195+
spaceStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false)
196196
sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace)
197197
}
198198

@@ -215,7 +215,7 @@ class HomeDetailFragment @Inject constructor(
215215
}
216216

217217
private fun refreshSpaceState() {
218-
appStateHandler.getCurrentSpace()?.let {
218+
spaceStateHandler.getCurrentSpace()?.let {
219219
onSpaceChange(it)
220220
}
221221
}
@@ -473,7 +473,7 @@ class HomeDetailFragment @Inject constructor(
473473
return this
474474
}
475475

476-
override fun onBackPressed(toolbarButton: Boolean) = if (appStateHandler.getCurrentSpace() != null) {
476+
override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.getCurrentSpace() != null) {
477477
navigateBack()
478478
true
479479
} else {

vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt

+5-5
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import com.airbnb.mvrx.ViewModelContext
2222
import dagger.assisted.Assisted
2323
import dagger.assisted.AssistedFactory
2424
import dagger.assisted.AssistedInject
25-
import im.vector.app.AppStateHandler
25+
import im.vector.app.SpaceStateHandler
2626
import im.vector.app.core.di.MavericksAssistedViewModelFactory
2727
import im.vector.app.core.di.hiltMavericksViewModelFactory
2828
import im.vector.app.core.extensions.singletonEntryPoint
@@ -68,7 +68,7 @@ class HomeDetailViewModel @AssistedInject constructor(
6868
private val vectorDataStore: VectorDataStore,
6969
private val callManager: WebRtcCallManager,
7070
private val directRoomHelper: DirectRoomHelper,
71-
private val appStateHandler: AppStateHandler,
71+
private val spaceStateHandler: SpaceStateHandler,
7272
private val autoAcceptInvites: AutoAcceptInvites,
7373
private val vectorOverrides: VectorOverrides
7474
) : VectorViewModel<HomeDetailViewState, HomeDetailAction, HomeDetailViewEvents>(initialState),
@@ -206,7 +206,7 @@ class HomeDetailViewModel @AssistedInject constructor(
206206
}
207207

208208
private fun observeRoomGroupingMethod() {
209-
appStateHandler.selectedSpaceFlow
209+
spaceStateHandler.getSelectedSpaceFlow()
210210
.setOnEach {
211211
copy(
212212
selectedSpace = it.orNull()
@@ -215,7 +215,7 @@ class HomeDetailViewModel @AssistedInject constructor(
215215
}
216216

217217
private fun observeRoomSummaries() {
218-
appStateHandler.selectedSpaceFlow.distinctUntilChanged().flatMapLatest {
218+
spaceStateHandler.getSelectedSpaceFlow().distinctUntilChanged().flatMapLatest {
219219
// we use it as a trigger to all changes in room, but do not really load
220220
// the actual models
221221
session.roomService().getPagedRoomSummariesLive(
@@ -227,7 +227,7 @@ class HomeDetailViewModel @AssistedInject constructor(
227227
}
228228
.throttleFirst(300)
229229
.onEach {
230-
val activeSpaceRoomId = appStateHandler.getCurrentSpace()?.roomId
230+
val activeSpaceRoomId = spaceStateHandler.getCurrentSpace()?.roomId
231231
var dmInvites = 0
232232
var roomsInvite = 0
233233
if (autoAcceptInvites.showInvites()) {

vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt

+6-6
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import com.airbnb.mvrx.MavericksViewModelFactory
2222
import dagger.assisted.Assisted
2323
import dagger.assisted.AssistedFactory
2424
import dagger.assisted.AssistedInject
25-
import im.vector.app.AppStateHandler
25+
import im.vector.app.SpaceStateHandler
2626
import im.vector.app.core.di.MavericksAssistedViewModelFactory
2727
import im.vector.app.core.di.hiltMavericksViewModelFactory
2828
import im.vector.app.core.platform.EmptyAction
@@ -58,7 +58,7 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
5858
@Assisted initialState: UnreadMessagesState,
5959
session: Session,
6060
private val vectorPreferences: VectorPreferences,
61-
appStateHandler: AppStateHandler,
61+
spaceStateHandler: SpaceStateHandler,
6262
private val autoAcceptInvites: AutoAcceptInvites
6363
) :
6464
VectorViewModel<UnreadMessagesState, EmptyAction, EmptyViewEvents>(initialState) {
@@ -109,8 +109,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
109109
}
110110

111111
combine(
112-
appStateHandler.selectedSpaceFlow.distinctUntilChanged(),
113-
appStateHandler.selectedSpaceFlow.flatMapLatest {
112+
spaceStateHandler.getSelectedSpaceFlow().distinctUntilChanged(),
113+
spaceStateHandler.getSelectedSpaceFlow().flatMapLatest {
114114
roomService.getPagedRoomSummariesLive(
115115
roomSummaryQueryParams {
116116
this.memberships = Membership.activeMemberships()
@@ -162,10 +162,10 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(
162162
CountInfo(
163163
homeCount = counts,
164164
otherCount = RoomAggregateNotificationCount(
165-
notificationCount = rootCounts.fold(0, { acc, rs -> acc + rs.notificationCount }) +
165+
notificationCount = rootCounts.fold(0) { acc, rs -> acc + rs.notificationCount } +
166166
(counts.notificationCount.takeIf { selectedSpace != null } ?: 0) +
167167
spaceInviteCount,
168-
highlightCount = rootCounts.fold(0, { acc, rs -> acc + rs.highlightCount }) +
168+
highlightCount = rootCounts.fold(0) { acc, rs -> acc + rs.highlightCount } +
169169
(counts.highlightCount.takeIf { selectedSpace != null } ?: 0) +
170170
spaceInviteCount
171171
)

0 commit comments

Comments
 (0)