diff --git a/changelog.d/6871.feature b/changelog.d/6871.feature new file mode 100644 index 0000000000..313be1a602 --- /dev/null +++ b/changelog.d/6871.feature @@ -0,0 +1 @@ +Improves Developer Mode Debug Button UX and adds it to New App Layout diff --git a/changelog.d/6877.wip b/changelog.d/6877.wip new file mode 100644 index 0000000000..d1e1c7c82d --- /dev/null +++ b/changelog.d/6877.wip @@ -0,0 +1 @@ +[New Layout] Adds back navigation through spaces diff --git a/vector/src/main/java/im/vector/app/SpaceStateHandler.kt b/vector/src/main/java/im/vector/app/SpaceStateHandler.kt index d9f002be37..64b6fc4e8b 100644 --- a/vector/src/main/java/im/vector/app/SpaceStateHandler.kt +++ b/vector/src/main/java/im/vector/app/SpaceStateHandler.kt @@ -51,11 +51,13 @@ interface SpaceStateHandler : DefaultLifecycleObserver { ) /** - * Gets the current backstack of spaces (via their id). + * Gets the Space ID of the space on top of the backstack. * - * null may be an entry in the ArrayDeque to indicate the root space (All Chats) + * May return null to indicate the All Chats space. */ - fun getSpaceBackstack(): ArrayDeque<String?> + fun popSpaceBackstack(): String? + + fun getSpaceBackstack(): List<String?> /** * Gets a flow of the selected space for clients to react immediately to space changes. diff --git a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt index c6a4b2c5f0..a665b619c0 100644 --- a/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt +++ b/vector/src/main/java/im/vector/app/SpaceStateHandlerImpl.kt @@ -23,6 +23,7 @@ import im.vector.app.core.utils.BehaviorDataSource import im.vector.app.features.analytics.AnalyticsTracker import im.vector.app.features.analytics.plan.UserProperties import im.vector.app.features.session.coroutineScope +import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -52,13 +53,13 @@ class SpaceStateHandlerImpl @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, private val activeSessionHolder: ActiveSessionHolder, - private val analyticsTracker: AnalyticsTracker + private val analyticsTracker: AnalyticsTracker, + private val vectorPreferences: VectorPreferences, ) : SpaceStateHandler { private val coroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main) private val selectedSpaceDataSource = BehaviorDataSource<Option<RoomSummary>>(Option.empty()) private val selectedSpaceFlow = selectedSpaceDataSource.stream() - private val spaceBackstack = ArrayDeque<String?>() override fun getCurrentSpace(): RoomSummary? { return selectedSpaceDataSource.currentValue?.orNull()?.let { spaceSummary -> @@ -73,26 +74,26 @@ class SpaceStateHandlerImpl @Inject constructor( isForwardNavigation: Boolean, ) { val activeSession = session ?: activeSessionHolder.getSafeActiveSession() ?: return - val currentSpace = selectedSpaceDataSource.currentValue?.orNull() - val spaceSummary = spaceId?.let { activeSession.getRoomSummary(spaceId) } - val sameSpaceSelected = currentSpace != null && spaceId == currentSpace.roomId + val spaceToLeave = selectedSpaceDataSource.currentValue?.orNull() + val spaceToSet = spaceId?.let { activeSession.getRoomSummary(spaceId) } + val sameSpaceSelected = spaceId == spaceToLeave?.roomId if (sameSpaceSelected) { return } if (isForwardNavigation) { - spaceBackstack.addLast(currentSpace?.roomId) + addToBackstack(spaceToLeave, spaceToSet) } if (persistNow) { - uiStateRepository.storeSelectedSpace(spaceSummary?.roomId, activeSession.sessionId) + uiStateRepository.storeSelectedSpace(spaceToSet?.roomId, activeSession.sessionId) } - if (spaceSummary == null) { + if (spaceToSet == null) { selectedSpaceDataSource.post(Option.empty()) } else { - selectedSpaceDataSource.post(Option.just(spaceSummary)) + selectedSpaceDataSource.post(Option.just(spaceToSet)) } if (spaceId != null) { @@ -104,6 +105,17 @@ class SpaceStateHandlerImpl @Inject constructor( } } + private fun addToBackstack(spaceToLeave: RoomSummary?, spaceToSet: RoomSummary?) { + // Only add to the backstack if the space to set is not All Chats, else clear the backstack + if (spaceToSet != null) { + val currentPersistedBackstack = vectorPreferences.getSpaceBackstack().toMutableList() + currentPersistedBackstack.add(spaceToLeave?.roomId) + vectorPreferences.setSpaceBackstack(currentPersistedBackstack) + } else { + vectorPreferences.setSpaceBackstack(emptyList()) + } + } + private fun observeActiveSession() { sessionDataSource.stream() .distinctUntilChanged() @@ -127,7 +139,15 @@ class SpaceStateHandlerImpl @Inject constructor( }.launchIn(session.coroutineScope) } - override fun getSpaceBackstack() = spaceBackstack + override fun popSpaceBackstack(): String? { + vectorPreferences.getSpaceBackstack().toMutableList().apply { + val poppedSpaceId = removeLast() + vectorPreferences.setSpaceBackstack(this) + return poppedSpaceId + } + } + + override fun getSpaceBackstack() = vectorPreferences.getSpaceBackstack() override fun getSelectedSpaceFlow() = selectedSpaceFlow diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt index 553b45ad81..2a8390c93c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivity.kt @@ -41,6 +41,7 @@ import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.extensions.hideKeyboard import im.vector.app.core.extensions.registerStartForActivityResult import im.vector.app.core.extensions.replaceFragment +import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.validateBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorMenuProvider @@ -56,6 +57,8 @@ import im.vector.app.features.analytics.plan.MobileScreen import im.vector.app.features.analytics.plan.ViewRoom import im.vector.app.features.crypto.recover.SetupMode import im.vector.app.features.disclaimer.showDisclaimerDialog +import im.vector.app.features.home.room.list.actions.RoomListSharedAction +import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.home.room.list.home.layout.HomeLayoutSettingBottomDialogFragment import im.vector.app.features.matrixto.MatrixToBottomSheet import im.vector.app.features.matrixto.OriginOfMatrixTo @@ -110,6 +113,7 @@ class HomeActivity : VectorMenuProvider { private lateinit var sharedActionViewModel: HomeSharedActionViewModel + private lateinit var roomListSharedActionViewModel: RoomListSharedActionViewModel private val homeActivityViewModel: HomeActivityViewModel by viewModel() @@ -136,6 +140,8 @@ class HomeActivity : @Inject lateinit var fcmHelper: FcmHelper @Inject lateinit var nightlyProxy: NightlyProxy + private var isNewAppLayoutEnabled: Boolean = false // delete once old app layout is removed + private val createSpaceResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { val spaceId = SpaceCreationActivity.getCreatedSpaceId(activityResult.data) @@ -155,8 +161,9 @@ class HomeActivity : navigator.switchToSpace( context = this, spaceId = spaceId, - postSwitchOption + postSwitchOption, ) + roomListSharedActionViewModel.post(RoomListSharedAction.CloseBottomSheet) } } } @@ -193,6 +200,7 @@ class HomeActivity : override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + isNewAppLayoutEnabled = vectorFeatures.isNewAppLayoutEnabled() analyticsScreenName = MobileScreen.ScreenName.Home supportFragmentManager.registerFragmentLifecycleCallbacks(fragmentLifecycleCallbacks, false) unifiedPushHelper.register(this) { @@ -205,6 +213,7 @@ class HomeActivity : } } sharedActionViewModel = viewModelProvider[HomeSharedActionViewModel::class.java] + roomListSharedActionViewModel = viewModelProvider[RoomListSharedActionViewModel::class.java] views.drawerLayout.addDrawerListener(drawerListener) if (isFirstCreation()) { if (vectorFeatures.isNewAppLayoutEnabled()) { @@ -561,6 +570,14 @@ class HomeActivity : // Check nightly nightlyProxy.onHomeResumed() + + checkNewAppLayoutFlagChange() + } + + private fun checkNewAppLayoutFlagChange() { + if (buildMeta.isDebug && vectorFeatures.isNewAppLayoutEnabled() != isNewAppLayoutEnabled) { + restart() + } } override fun getMenuRes() = if (vectorFeatures.isNewAppLayoutEnabled()) R.menu.menu_new_home else R.menu.menu_home diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt index cdc16f1f33..990cca489c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailFragment.kt @@ -186,7 +186,7 @@ class HomeDetailFragment : } private fun navigateBack() { - val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull() + val previousSpaceId = spaceStateHandler.popSpaceBackstack() val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull() setCurrentSpace(previousSpaceId ?: parentSpaceId) } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt index 011a7c4537..106fbc7281 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDrawerFragment.kt @@ -116,10 +116,14 @@ class HomeDrawerFragment : } // Debug menu - views.homeDrawerHeaderDebugView.isVisible = buildMeta.isDebug && vectorPreferences.developerMode() views.homeDrawerHeaderDebugView.debouncedClicks { sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) navigator.openDebug(requireActivity()) } } + + override fun onResume() { + super.onResume() + views.homeDrawerHeaderDebugView.isVisible = buildMeta.isDebug && vectorPreferences.developerMode() + } } diff --git a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt index 4b14fecae9..e3aebe8594 100644 --- a/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/NewHomeDetailFragment.kt @@ -23,10 +23,12 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState +import com.google.android.material.appbar.AppBarLayout import dagger.hilt.android.AndroidEntryPoint import im.vector.app.R import im.vector.app.SpaceStateHandler @@ -35,6 +37,7 @@ import im.vector.app.core.platform.OnBackPressed import im.vector.app.core.platform.VectorBaseActivity import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.platform.VectorMenuProvider +import im.vector.app.core.resources.BuildMeta import im.vector.app.core.resources.ColorProvider import im.vector.app.core.ui.views.CurrentCallsView import im.vector.app.core.ui.views.CurrentCallsViewPresenter @@ -74,6 +77,7 @@ class NewHomeDetailFragment : @Inject lateinit var vectorPreferences: VectorPreferences @Inject lateinit var spaceStateHandler: SpaceStateHandler @Inject lateinit var session: Session + @Inject lateinit var buildMeta: BuildMeta private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() @@ -127,6 +131,7 @@ class NewHomeDetailFragment : setupToolbar() setupKeysBackupBanner() setupActiveCallView() + setupDebugButton() childFragmentManager.commitTransaction { add(R.id.roomListContainer, HomeRoomListFragment::class.java, null, HOME_ROOM_LIST_FRAGMENT_TAG) @@ -169,12 +174,6 @@ class NewHomeDetailFragment : } } - private fun navigateBack() { - val previousSpaceId = spaceStateHandler.getSpaceBackstack().removeLastOrNull() - val parentSpaceId = spaceStateHandler.getCurrentSpace()?.flattenParentIds?.lastOrNull() - setCurrentSpace(previousSpaceId ?: parentSpaceId) - } - private fun setCurrentSpace(spaceId: String?) { spaceStateHandler.setCurrentSpace(spaceId, isForwardNavigation = false) sharedActionViewModel.post(HomeActivitySharedAction.OnCloseSpace) @@ -189,6 +188,7 @@ class NewHomeDetailFragment : super.onResume() callManager.checkForProtocolsSupportIfNeeded() refreshSpaceState() + refreshDebugButtonState() } private fun refreshSpaceState() { @@ -298,6 +298,21 @@ class NewHomeDetailFragment : } } + private fun setupDebugButton() { + views.debugButton.debouncedClicks { + sharedActionViewModel.post(HomeActivitySharedAction.CloseDrawer) + navigator.openDebug(requireActivity()) + } + + views.appBarLayout.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> + views.debugButton.isVisible = verticalOffset == 0 + }) + } + + private fun refreshDebugButtonState() { + views.debugButton.isVisible = buildMeta.isDebug && vectorPreferences.developerMode() + } + /* ========================================================================================== * KeysBackupBanner Listener * ========================================================================================== */ @@ -337,13 +352,16 @@ class NewHomeDetailFragment : } } - override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.getCurrentSpace() != null) { - navigateBack() - true - } else { + override fun onBackPressed(toolbarButton: Boolean) = if (spaceStateHandler.isRoot()) { false + } else { + val lastSpace = spaceStateHandler.popSpaceBackstack() + spaceStateHandler.setCurrentSpace(lastSpace, isForwardNavigation = false) + true } + private fun SpaceStateHandler.isRoot() = getSpaceBackstack().isEmpty() + companion object { private const val HOME_ROOM_LIST_FRAGMENT_TAG = "TAG_HOME_ROOM_LIST" } diff --git a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt index 4eec5c75a1..3b56867a20 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/DefaultNavigator.kt @@ -176,13 +176,25 @@ class DefaultNavigator @Inject constructor( startActivity(context, intent, buildTask) } - override fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: Navigator.PostSwitchSpaceAction) { + override fun switchToSpace( + context: Context, + spaceId: String, + postSwitchSpaceAction: Navigator.PostSwitchSpaceAction, + ) { if (sessionHolder.getSafeActiveSession()?.getRoomSummary(spaceId) == null) { fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) return } spaceStateHandler.setCurrentSpace(spaceId) - when (postSwitchSpaceAction) { + handlePostSwitchAction(context, spaceId, postSwitchSpaceAction) + } + + private fun handlePostSwitchAction( + context: Context, + spaceId: String, + action: Navigator.PostSwitchSpaceAction, + ) { + when (action) { Navigator.PostSwitchSpaceAction.None -> { // go back to home if we are showing room details? // This is a bit ugly, but the navigator is supposed to know about the activity stack @@ -198,9 +210,9 @@ class DefaultNavigator @Inject constructor( } is Navigator.PostSwitchSpaceAction.OpenDefaultRoom -> { val args = TimelineArgs( - postSwitchSpaceAction.roomId, + action.roomId, eventId = null, - openShareSpaceForId = spaceId.takeIf { postSwitchSpaceAction.showShareSheet } + openShareSpaceForId = spaceId.takeIf { action.showShareSheet } ) val intent = RoomDetailActivity.newIntent(context, args, false) startActivity(context, intent, false) diff --git a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt index 8e01b3ed50..5d86456ba5 100644 --- a/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt +++ b/vector/src/main/java/im/vector/app/features/navigation/Navigator.kt @@ -68,7 +68,11 @@ interface Navigator { data class OpenDefaultRoom(val roomId: String, val showShareSheet: Boolean) : PostSwitchSpaceAction() } - fun switchToSpace(context: Context, spaceId: String, postSwitchSpaceAction: PostSwitchSpaceAction) + fun switchToSpace( + context: Context, + spaceId: String, + postSwitchSpaceAction: PostSwitchSpaceAction, + ) fun openSpacePreview(context: Context, spaceId: String) diff --git a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt index fc6586eeab..cefbe64d9d 100755 --- a/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt +++ b/vector/src/main/java/im/vector/app/features/settings/VectorPreferences.kt @@ -78,6 +78,7 @@ class VectorPreferences @Inject constructor( const val SETTINGS_ALLOW_INTEGRATIONS_KEY = "SETTINGS_ALLOW_INTEGRATIONS_KEY" const val SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY = "SETTINGS_INTEGRATION_MANAGER_UI_URL_KEY" const val SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY = "SETTINGS_SECURE_MESSAGE_RECOVERY_PREFERENCE_KEY" + const val SETTINGS_PERSISTED_SPACE_BACKSTACK = "SETTINGS_PERSISTED_SPACE_BACKSTACK" const val SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT = "SETTINGS_CRYPTOGRAPHY_HS_ADMIN_DISABLED_E2E_DEFAULT" // const val SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY = "SETTINGS_SECURE_BACKUP_RESET_PREFERENCE_KEY" @@ -1126,6 +1127,25 @@ class VectorPreferences @Inject constructor( .apply() } + /** + * Sets the space backstack that is used for up navigation. + * This needs to be persisted because navigating up through spaces should work across sessions. + * + * Only the IDs of the spaces are stored. + */ + fun setSpaceBackstack(spaceBackstack: List<String?>) { + val spaceIdsJoined = spaceBackstack.takeIf { it.isNotEmpty() }?.joinToString(",") + defaultPrefs.edit().putString(SETTINGS_PERSISTED_SPACE_BACKSTACK, spaceIdsJoined).apply() + } + + /** + * Gets the space backstack used for up navigation. + */ + fun getSpaceBackstack(): List<String?> { + val spaceIdsJoined = defaultPrefs.getString(SETTINGS_PERSISTED_SPACE_BACKSTACK, null) + return spaceIdsJoined?.takeIf { it.isNotEmpty() }?.split(",").orEmpty() + } + fun showLiveSenderInfo(): Boolean { return defaultPrefs.getBoolean(SETTINGS_TIMELINE_SHOW_LIVE_SENDER_INFO, getDefault(R.bool.settings_timeline_show_live_sender_info_default)) } diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt index 2b45db2e4e..80421659dd 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/NewSpaceSummaryController.kt @@ -63,10 +63,6 @@ class NewSpaceSummaryController @Inject constructor( homeCount: RoomAggregateNotificationCount, expandedStates: Map<String, Boolean>, ) { - newSpaceListHeaderItem { - id("space_list_header") - } - addHomeItem(selectedSpace == null, homeCount) addSpaces(spaceSummaries, selectedSpace, rootSpaces, expandedStates) addCreateItem() diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt index 9048026771..c7f8e0d411 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewModel.kt @@ -65,7 +65,7 @@ class SpaceListViewModel @AssistedInject constructor( private val session: Session, private val vectorPreferences: VectorPreferences, private val autoAcceptInvites: AutoAcceptInvites, - private val analyticsTracker: AnalyticsTracker + private val analyticsTracker: AnalyticsTracker, ) : VectorViewModel<SpaceListViewState, SpaceListAction, SpaceListViewEvents>(initialState) { @AssistedFactory @@ -88,9 +88,7 @@ class SpaceListViewModel @AssistedInject constructor( spaceStateHandler.getSelectedSpaceFlow() .distinctUntilChanged() .setOnEach { selectedSpaceOption -> - copy( - selectedSpace = selectedSpaceOption.orNull() - ) + copy(selectedSpace = selectedSpaceOption.orNull()) } // XXX there should be a way to refactor this and share it diff --git a/vector/src/main/res/layout/fragment_new_home_detail.xml b/vector/src/main/res/layout/fragment_new_home_detail.xml index 85ff076364..57ba666be7 100644 --- a/vector/src/main/res/layout/fragment_new_home_detail.xml +++ b/vector/src/main/res/layout/fragment_new_home_detail.xml @@ -74,6 +74,19 @@ </com.google.android.material.appbar.MaterialToolbar> + <ImageView + android:id="@+id/debug_button" + style="@style/VectorDebug" + android:layout_width="48dp" + android:layout_height="48dp" + android:layout_gravity="bottom|end" + android:layout_marginStart="12dp" + android:importantForAccessibility="no" + android:scaleType="center" + android:src="@drawable/ic_settings_x" + app:tint="?colorPrimary" + tools:ignore="MissingPrefix" /> + </com.google.android.material.appbar.CollapsingToolbarLayout> </com.google.android.material.appbar.AppBarLayout> diff --git a/vector/src/main/res/layout/item_new_space_list_header.xml b/vector/src/main/res/layout/item_new_space_list_header.xml deleted file mode 100644 index 2c52304249..0000000000 --- a/vector/src/main/res/layout/item_new_space_list_header.xml +++ /dev/null @@ -1,16 +0,0 @@ -<?xml version="1.0" encoding="utf-8"?> -<TextView xmlns:android="http://schemas.android.com/apk/res/android" - xmlns:tools="http://schemas.android.com/tools" - style="@style/TextAppearance.Vector.Body.Medium" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:background="@drawable/bg_space_item" - android:ellipsize="middle" - android:orientation="vertical" - android:padding="16dp" - android:singleLine="true" - android:text="@string/change_space" - android:textAllCaps="true" - android:textColor="?vctr_content_tertiary" - android:textSize="14sp" - tools:viewBindingIgnore="true" /> diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 4e6e81b0f0..4d39dcae87 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -139,7 +139,6 @@ <string name="all_chats">All Chats</string> <string name="start_chat">Start Chat</string> <string name="create_room">Create Room</string> - <string name="change_space">Change Space</string> <string name="explore_rooms">Explore Rooms</string> <string name="a11y_expand_space_children">Expand space children</string> <string name="a11y_collapse_space_children">Collapse space children</string> diff --git a/vector/src/test/java/im/vector/app/SpaceStateHandlerImplTest.kt b/vector/src/test/java/im/vector/app/SpaceStateHandlerImplTest.kt index 36d372cfac..3a01054db2 100644 --- a/vector/src/test/java/im/vector/app/SpaceStateHandlerImplTest.kt +++ b/vector/src/test/java/im/vector/app/SpaceStateHandlerImplTest.kt @@ -21,11 +21,13 @@ import im.vector.app.test.fakes.FakeActiveSessionHolder import im.vector.app.test.fakes.FakeAnalyticsTracker import im.vector.app.test.fakes.FakeSession import im.vector.app.test.fakes.FakeUiStateRepository +import im.vector.app.test.fakes.FakeVectorPreferences import im.vector.app.test.fixtures.RoomSummaryFixture.aRoomSummary import kotlinx.coroutines.flow.first import kotlinx.coroutines.test.runTest import org.amshove.kluent.shouldBe import org.amshove.kluent.shouldBeEqualTo +import org.junit.Before import org.junit.Test internal class SpaceStateHandlerImplTest { @@ -38,14 +40,21 @@ internal class SpaceStateHandlerImplTest { private val uiStateRepository = FakeUiStateRepository() private val activeSessionHolder = FakeActiveSessionHolder(session) private val analyticsTracker = FakeAnalyticsTracker() + private val vectorPreferences = FakeVectorPreferences() private val spaceStateHandler = SpaceStateHandlerImpl( sessionDataSource.instance, uiStateRepository, activeSessionHolder.instance, analyticsTracker, + vectorPreferences.instance, ) + @Before + fun setup() { + vectorPreferences.givenSpaceBackstack(emptyList()) + } + @Test fun `given selected space doesn't exist, when getCurrentSpace, then return null`() { val currentSpace = spaceStateHandler.getCurrentSpace() @@ -77,33 +86,33 @@ internal class SpaceStateHandlerImplTest { } @Test - fun `given is forward navigation and no current space, when setCurrentSpace, then null added to backstack`() { + fun `given not in space and is forward navigation, when setCurrentSpace, then null added to backstack`() { spaceStateHandler.setCurrentSpace(spaceId, session, isForwardNavigation = true) - val backstack = spaceStateHandler.getSpaceBackstack() - - backstack.size shouldBe 1 - backstack.first() shouldBe null + vectorPreferences.verifySetSpaceBackstack(listOf(null)) } @Test - fun `given is forward navigation and is in space, when setCurrentSpace, then previous space added to backstack`() { + fun `given in space and is forward navigation, when setCurrentSpace, then previous space added to backstack`() { spaceStateHandler.setCurrentSpace(spaceId, session, isForwardNavigation = true) spaceStateHandler.setCurrentSpace("secondSpaceId", session, isForwardNavigation = true) - val backstack = spaceStateHandler.getSpaceBackstack() - - backstack.size shouldBe 2 - backstack shouldBeEqualTo listOf(null, spaceId) + vectorPreferences.verifySetSpaceBackstack(listOf(spaceId)) } @Test fun `given is not forward navigation, when setCurrentSpace, then previous space not added to backstack`() { spaceStateHandler.setCurrentSpace(spaceId, session, isForwardNavigation = false) - val backstack = spaceStateHandler.getSpaceBackstack() + vectorPreferences.verifySetSpaceBackstack(listOf(spaceId), inverse = true) + } - backstack.size shouldBe 0 + @Test + fun `given navigating to all chats, when setCurrentSpace, then backstack cleared`() { + spaceStateHandler.setCurrentSpace(spaceId, session) + spaceStateHandler.setCurrentSpace(null, session) + + vectorPreferences.verifySetSpaceBackstack(emptyList()) } @Test diff --git a/vector/src/test/java/im/vector/app/features/navigation/DefaultNavigatorTest.kt b/vector/src/test/java/im/vector/app/features/navigation/DefaultNavigatorTest.kt new file mode 100644 index 0000000000..bdf1dbd4e6 --- /dev/null +++ b/vector/src/test/java/im/vector/app/features/navigation/DefaultNavigatorTest.kt @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.navigation + +import im.vector.app.test.fakes.FakeActiveSessionHolder +import im.vector.app.test.fakes.FakeAnalyticsTracker +import im.vector.app.test.fakes.FakeContext +import im.vector.app.test.fakes.FakeDebugNavigator +import im.vector.app.test.fakes.FakeSpaceStateHandler +import im.vector.app.test.fakes.FakeSupportedVerificationMethodsProvider +import im.vector.app.test.fakes.FakeVectorFeatures +import im.vector.app.test.fakes.FakeVectorPreferences +import im.vector.app.test.fakes.FakeWidgetArgsBuilder +import im.vector.app.test.fixtures.RoomSummaryFixture.aRoomSummary +import org.junit.Test + +internal class DefaultNavigatorTest { + + private val sessionHolder = FakeActiveSessionHolder() + private val vectorPreferences = FakeVectorPreferences() + private val widgetArgsBuilder = FakeWidgetArgsBuilder() + private val spaceStateHandler = FakeSpaceStateHandler() + private val supportedVerificationMethodsProvider = FakeSupportedVerificationMethodsProvider() + private val features = FakeVectorFeatures() + private val analyticsTracker = FakeAnalyticsTracker() + private val debugNavigator = FakeDebugNavigator() + + private val navigator = DefaultNavigator( + sessionHolder.instance, + vectorPreferences.instance, + widgetArgsBuilder.instance, + spaceStateHandler, + supportedVerificationMethodsProvider.instance, + features, + analyticsTracker, + debugNavigator, + ) + + /** + * The below test is by no means all that we want to test in [DefaultNavigator]. + * Please add relevant tests as you make changes to or related to other functions in the class. + */ + + @Test + fun `when switchToSpace, then current space set`() { + val spaceId = "space-id" + val spaceSummary = aRoomSummary(spaceId) + sessionHolder.fakeSession.fakeRoomService.getRoomSummaryReturns(spaceSummary) + + navigator.switchToSpace(FakeContext().instance, spaceId, Navigator.PostSwitchSpaceAction.None) + + spaceStateHandler.verifySetCurrentSpace(spaceId) + } +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeDebugNavigator.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeDebugNavigator.kt new file mode 100644 index 0000000000..42074093b3 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeDebugNavigator.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.core.debug.DebugNavigator +import io.mockk.mockk + +class FakeDebugNavigator : DebugNavigator by mockk() diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt index b09256f747..506e96ba11 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeRoomService.kt @@ -16,12 +16,18 @@ package im.vector.app.test.fakes +import io.mockk.every import io.mockk.mockk import org.matrix.android.sdk.api.session.room.RoomService +import org.matrix.android.sdk.api.session.room.model.RoomSummary class FakeRoomService( private val fakeRoom: FakeRoom = FakeRoom() ) : RoomService by mockk() { override fun getRoom(roomId: String) = fakeRoom + + fun getRoomSummaryReturns(roomSummary: RoomSummary?) { + every { getRoomSummary(any()) } returns roomSummary + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt index 18af88ba0f..ee016ecae3 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSession.kt @@ -37,7 +37,7 @@ class FakeSession( val fakeProfileService: FakeProfileService = FakeProfileService(), val fakeHomeServerCapabilitiesService: FakeHomeServerCapabilitiesService = FakeHomeServerCapabilitiesService(), val fakeSharedSecretStorageService: FakeSharedSecretStorageService = FakeSharedSecretStorageService(), - private val fakeRoomService: FakeRoomService = FakeRoomService(), + val fakeRoomService: FakeRoomService = FakeRoomService(), private val fakeEventService: FakeEventService = FakeEventService(), ) : Session by mockk(relaxed = true) { diff --git a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSpaceStateHandler.kt similarity index 57% rename from vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt rename to vector/src/test/java/im/vector/app/test/fakes/FakeSpaceStateHandler.kt index 8fc53f07d4..f95b968c91 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/NewSpaceListHeaderItem.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSpaceStateHandler.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 New Vector Ltd + * Copyright (c) 2022 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,14 +14,15 @@ * limitations under the License. */ -package im.vector.app.features.spaces +package im.vector.app.test.fakes -import com.airbnb.epoxy.EpoxyModelClass -import im.vector.app.R -import im.vector.app.core.epoxy.VectorEpoxyHolder -import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.SpaceStateHandler +import io.mockk.mockk +import io.mockk.verify -@EpoxyModelClass -abstract class NewSpaceListHeaderItem : VectorEpoxyModel<NewSpaceListHeaderItem.Holder>(R.layout.item_new_space_list_header) { - class Holder : VectorEpoxyHolder() +class FakeSpaceStateHandler : SpaceStateHandler by mockk(relaxUnitFun = true) { + + fun verifySetCurrentSpace(spaceId: String) { + verify { setCurrentSpace(spaceId) } + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeSupportedVerificationMethodsProvider.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeSupportedVerificationMethodsProvider.kt new file mode 100644 index 0000000000..ecb7c3289b --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeSupportedVerificationMethodsProvider.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.crypto.verification.SupportedVerificationMethodsProvider +import io.mockk.mockk + +class FakeSupportedVerificationMethodsProvider { + + val instance = mockk<SupportedVerificationMethodsProvider>() +} diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt index eb8f9ac413..bc761d9016 100644 --- a/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeVectorPreferences.kt @@ -19,12 +19,21 @@ package im.vector.app.test.fakes import im.vector.app.features.settings.VectorPreferences import io.mockk.every import io.mockk.mockk +import io.mockk.verify class FakeVectorPreferences { - val instance = mockk<VectorPreferences>() + val instance = mockk<VectorPreferences>(relaxUnitFun = true) fun givenUseCompleteNotificationFormat(value: Boolean) { every { instance.useCompleteNotificationFormat() } returns value } + + fun givenSpaceBackstack(value: List<String?>) { + every { instance.getSpaceBackstack() } returns value + } + + fun verifySetSpaceBackstack(value: List<String?>, inverse: Boolean = false) { + verify(inverse = inverse) { instance.setSpaceBackstack(value) } + } } diff --git a/vector/src/test/java/im/vector/app/test/fakes/FakeWidgetArgsBuilder.kt b/vector/src/test/java/im/vector/app/test/fakes/FakeWidgetArgsBuilder.kt new file mode 100644 index 0000000000..3a810b2c23 --- /dev/null +++ b/vector/src/test/java/im/vector/app/test/fakes/FakeWidgetArgsBuilder.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.test.fakes + +import im.vector.app.features.widgets.WidgetArgsBuilder +import io.mockk.mockk + +class FakeWidgetArgsBuilder { + + val instance = mockk<WidgetArgsBuilder>() +}