diff --git a/vector/src/main/java/de/spiritcroc/viewpager/ViewPager2.kt b/vector/src/main/java/de/spiritcroc/viewpager/ViewPager2.kt new file mode 100644 index 0000000000..8ea7a61ae4 --- /dev/null +++ b/vector/src/main/java/de/spiritcroc/viewpager/ViewPager2.kt @@ -0,0 +1,22 @@ +package de.spiritcroc.viewpager + +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import timber.log.Timber +import java.lang.Exception + +// Mainly taken from https://stackoverflow.com/a/63455547 +fun ViewPager2.reduceDragSensitivity(factor: Int) { + try { + val recyclerViewField = ViewPager2::class.java.getDeclaredField("mRecyclerView") + recyclerViewField.isAccessible = true + val recyclerView = recyclerViewField.get(this) as RecyclerView + + val touchSlopField = RecyclerView::class.java.getDeclaredField("mTouchSlop") + touchSlopField.isAccessible = true + val touchSlop = touchSlopField.get(recyclerView) as Int + touchSlopField.set(recyclerView, touchSlop * factor) + } catch (e: Exception) { + Timber.e("Cannot reduce viewpager drag sensitivity: $e") + } +} 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 ec16ab85c6..c64cc19a87 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 @@ -24,14 +24,16 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.fragment.app.Fragment +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 import com.airbnb.mvrx.activityViewModel import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.badge.BadgeDrawable +import de.spiritcroc.viewpager.reduceDragSensitivity import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.RoomGroupingMethod -import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.restart import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.platform.ToolbarConfigurable @@ -48,6 +50,7 @@ import im.vector.app.features.call.dialpad.DialPadFragment import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.home.room.list.RoomListFragment import im.vector.app.features.home.room.list.RoomListParams +import im.vector.app.features.home.room.list.RoomListSectionBuilderSpace.Companion.SPACE_ID_FOLLOW_APP import im.vector.app.features.home.room.list.UnreadCounterBadgeView import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert @@ -60,6 +63,7 @@ import im.vector.app.features.workers.signout.ServerBackupStatusViewModel import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.internal.crypto.model.rest.DeviceInfo +import timber.log.Timber import javax.inject.Inject class HomeDetailFragment @Inject constructor( @@ -73,6 +77,10 @@ class HomeDetailFragment @Inject constructor( KeysBackupBanner.Delegate, CurrentCallsView.Callback { + companion object { + const val DEBUG_VIEW_PAGER = true + } + private val viewModel: HomeDetailViewModel by fragmentViewModel() private val unknownDeviceDetectorSharedViewModel: UnknownDeviceDetectorSharedViewModel by activityViewModel() private val unreadMessagesSharedViewModel: UnreadMessagesSharedViewModel by activityViewModel() @@ -93,6 +101,12 @@ class HomeDetailFragment @Inject constructor( } } + private var initialPageSelected = false + private var pagerSpaces: List? = null + private var pagerTab: HomeTab? = null + private var pagerPagingEnabled: Boolean = false + private val pendingSpaceIds = mutableListOf() + override fun getMenuRes() = R.menu.room_list override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -131,6 +145,29 @@ class HomeDetailFragment @Inject constructor( checkNotificationTabStatus() + // Reduce sensitivity of viewpager to avoid scrolling horizontally by accident too easily + views.roomListContainerPager.reduceDragSensitivity(4) + + // space pager: update appStateHandler's current page to update rest of the UI accordingly + views.roomListContainerPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + if (DEBUG_VIEW_PAGER) Timber.i("Home pager: selected page $position $initialPageSelected") + super.onPageSelected(position) + if (!initialPageSelected) { + // Do not apply before we have restored the previous value + if (position == 0) { + return + } else { + // User has swiped, store it anyways + initialPageSelected = true + } + } + val selectedId = getSpaceIdForPageIndex(position) + pendingSpaceIds.add(selectedId) + appStateHandler.setCurrentSpace(selectedId) + } + }) + viewModel.onEach(HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> when (roomGroupingMethod) { is RoomGroupingMethod.ByLegacyGroup -> { @@ -188,6 +225,10 @@ class HomeDetailFragment @Inject constructor( ) } + viewModel.onEach(HomeDetailViewState::roomGroupingMethod, HomeDetailViewState::rootSpacesOrdered, HomeDetailViewState::currentTab) { roomGroupingMethod, rootSpacesOrdered, currentTab -> + setupViewPager(roomGroupingMethod, rootSpacesOrdered, currentTab) + } + sharedCallActionViewModel .liveKnownCalls .observe(viewLifecycleOwner) { @@ -438,12 +479,13 @@ class HomeDetailFragment @Inject constructor( private fun updateUIForTab(tab: HomeTab) { views.bottomNavigationView.menu.findItem(tab.toMenuId()).isChecked = true views.groupToolbarTitleView.setText(tab.titleRes) - updateSelectedFragment(tab) + //updateSelectedFragment(tab) invalidateOptionsMenu() } private fun HomeTab.toFragmentTag() = "FRAGMENT_TAG_$this" + /* private fun updateSelectedFragment(tab: HomeTab) { val fragmentTag = tab.toFragmentTag() val fragmentToShow = childFragmentManager.findFragmentByTag(fragmentTag) @@ -471,6 +513,120 @@ class HomeDetailFragment @Inject constructor( } } } + */ + + private fun setupViewPager(roomGroupingMethod: RoomGroupingMethod, spaces: List?, tab: HomeTab) { + val oldAdapter = views.roomListContainerPager.adapter as? FragmentStateAdapter + val pagingAllowed = vectorPreferences.enableSpacePager() + if (DEBUG_VIEW_PAGER) Timber.i("Home pager: setup, old adapter: $oldAdapter") + val unsafeSpaces = spaces?.map { it.roomId } ?: listOf() + val selectedSpaceId = (roomGroupingMethod as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId + val selectedIndex = getPageIndexForSpaceId(selectedSpaceId, unsafeSpaces) + val pagingEnabled = pagingAllowed && roomGroupingMethod is RoomGroupingMethod.BySpace && unsafeSpaces.size > 0 && selectedIndex != null + val safeSpaces = if (pagingEnabled) unsafeSpaces else listOf() + // Check if we need to recreate the adapter for a new tab + if (oldAdapter != null) { + val changed = pagerTab != tab || pagerSpaces != safeSpaces || pagerPagingEnabled != pagingEnabled + if (DEBUG_VIEW_PAGER) Timber.i("Home pager: has changed: $changed (${pagerTab != tab} ${pagerSpaces != safeSpaces} ${pagerPagingEnabled != pagingEnabled} $selectedIndex ${views.roomListContainerPager.currentItem}) | $safeSpaces") + if (!changed) { + if (pagingEnabled) { + // No need to re-setup pager, just check for selected page + // Discard state changes that we created ourselves by swiping on the pager + while (pendingSpaceIds.size > 0) { + val pendingSpaceId = pendingSpaceIds.removeAt(0) + if (pendingSpaceId == selectedSpaceId) { + return + } + } + if (selectedIndex != null) { + if (selectedIndex != views.roomListContainerPager.currentItem) { + // post() mitigates a case where we could end up in an endless loop circling around the same few spaces + views.roomListContainerPager.post { + views.roomListContainerPager.currentItem = selectedIndex + } + } + return + } + } else { + // Nothing to change + return + } + } else { + // Clean up old fragments + val transaction = childFragmentManager.beginTransaction() + childFragmentManager.fragments + .forEach { + transaction.detach(it) + } + transaction.commit() + } + } + pagerSpaces = safeSpaces + pagerTab = tab + pagerPagingEnabled = pagingEnabled + initialPageSelected = false + pendingSpaceIds.clear() + + views.roomListContainerPager.offscreenPageLimit = 2 + + val adapter = object: FragmentStateAdapter(this@HomeDetailFragment) { + override fun getItemCount(): Int { + if (!pagingEnabled) { + return 1 + } + return when (tab) { + is HomeTab.DialPad -> 1 + else -> safeSpaces.size + 1 + } + } + + override fun createFragment(position: Int): Fragment { + if (DEBUG_VIEW_PAGER) Timber.i("Home pager: create fragment for position $position") + return when (tab) { + is HomeTab.DialPad -> createDialPadFragment() + is HomeTab.RoomList -> { + val params = if (pagingEnabled) { + RoomListParams(tab.displayMode, getSpaceIdForPageIndex(position)).toMvRxBundle() + } else { + RoomListParams(tab.displayMode, SPACE_ID_FOLLOW_APP).toMvRxBundle() + } + childFragmentManager.fragmentFactory.instantiate(activity!!.classLoader, RoomListFragment::class.java.name).apply { + arguments = params + } + } + } + } + } + + views.roomListContainerPager.adapter = adapter + if (pagingEnabled) { + views.roomListContainerPager.post { + try { + if (DEBUG_VIEW_PAGER) Timber.i("Home pager: set initial page $selectedIndex") + views.roomListContainerPager.setCurrentItem(selectedIndex ?: 0, false) + initialPageSelected = true + } catch (e: Exception) { + Timber.e("Home pager: Could not set initial page after creating adapter: $e") + } + } + } + } + + private fun getPageIndexForSpaceId(spaceId: String?, spaces: List? = pagerSpaces): Int? { + if (spaceId == null) { + return 0 + } + val indexInList = spaces?.indexOf(spaceId) + return when (indexInList) { + null, -1 -> null + else -> indexInList + 1 + } + } + + private fun getSpaceIdForPageIndex(position: Int, spaces: List? = pagerSpaces): String? { + val safeSpaces = spaces ?: return null + return if (position == 0) null else safeSpaces[position-1] + } private fun createDialPadFragment(): Fragment { val fragment = childFragmentManager.fragmentFactory.instantiate(vectorBaseActivity.classLoader, DialPadFragment::class.java.name) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index 5260330475..06ee2273dc 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -38,19 +38,26 @@ import im.vector.app.features.invite.showInvites import im.vector.app.features.settings.VectorDataStore import im.vector.app.features.ui.UiStateRepository import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterIsInstance import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter +import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.query.RoomCategoryFilter import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.events.model.toModel import org.matrix.android.sdk.api.session.initsync.SyncStatusService import org.matrix.android.sdk.api.session.room.RoomSortOrder +import org.matrix.android.sdk.api.session.room.accountdata.RoomAccountDataTypes import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.session.space.model.SpaceOrderContent +import org.matrix.android.sdk.api.session.space.model.TopLevelSpaceComparator import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.flow.flow import timber.log.Timber @@ -92,6 +99,7 @@ class HomeDetailViewModel @AssistedInject constructor( observeRoomSummaries() updatePstnSupportFlag() observeDataStore() + observeRootSpaces() callManager.addProtocolsCheckerListener(this) session.flow().liveUser(session.myUserId).execute { copy( @@ -276,4 +284,46 @@ class HomeDetailViewModel @AssistedInject constructor( } .launchIn(viewModelScope) } + + // Taken from SpaceListViewModel.observeSpaceSummaries() + private fun observeRootSpaces() { + val spaceSummaryQueryParams = roomSummaryQueryParams { + memberships = listOf(Membership.JOIN, Membership.INVITE) + displayName = QueryStringValue.IsNotEmpty + excludeType = listOf(/**RoomType.MESSAGING,$*/ + null) + } + + val flowSession = session.flow() + + combine( + flowSession + .liveUser(session.myUserId) + .map { + it.getOrNull() + }, + flowSession + .liveSpaceSummaries(spaceSummaryQueryParams), + session + .accountDataService() + .getLiveRoomAccountDataEvents(setOf(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER)) + .asFlow() + ) { _, communityGroups, _ -> + communityGroups + } + .execute { //async -> + val rootSpaces = session.spaceService().getRootSpaceSummaries() + val orders = rootSpaces.map { + it.roomId to session.getRoom(it.roomId) + ?.getAccountDataEvent(RoomAccountDataTypes.EVENT_TYPE_SPACE_ORDER) + ?.content.toModel() + ?.safeOrder() + }.toMap() + copy( + //asyncSpaces = async, + rootSpacesOrdered = rootSpaces.sortedWith(TopLevelSpaceComparator(orders)), + //spaceOrderInfo = orders + ) + } + } } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt index d1357a6b27..614f52de7a 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewState.kt @@ -32,6 +32,7 @@ data class HomeDetailViewState( val myMatrixItem: MatrixItem? = null, val asyncRooms: Async> = Uninitialized, val currentTab: HomeTab = HomeTab.RoomList(RoomListDisplayMode.ALL), + val rootSpacesOrdered: List? = null, val notificationCountCatchup: Int = 0, val notificationHighlightCatchup: Boolean = false, val notificationCountPeople: Int = 0, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt index 6283fee1ba..2dcba563b1 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListFragment.kt @@ -47,6 +47,7 @@ import im.vector.app.core.resources.UserPreferencesProvider import im.vector.app.databinding.FragmentRoomListBinding import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.home.room.filtered.FilteredRoomFooterItem +import im.vector.app.features.home.room.list.RoomListSectionBuilderSpace.Companion.SPACE_ID_FOLLOW_APP import im.vector.app.features.home.room.list.actions.RoomListActionsArgs import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet import im.vector.app.features.home.room.list.actions.RoomListQuickActionsSharedAction @@ -66,7 +67,8 @@ import javax.inject.Inject @Parcelize data class RoomListParams( - val displayMode: RoomListDisplayMode + val displayMode: RoomListDisplayMode, + val explicitSpaceId: String? = SPACE_ID_FOLLOW_APP ) : Parcelable class RoomListFragment @Inject constructor( @@ -559,6 +561,9 @@ class RoomListFragment @Inject constructor( } override fun onSwitchSpace(spaceId: String?) { + if (roomListParams.explicitSpaceId == SPACE_ID_FOLLOW_APP) { + return + } if (spaceId != expandStatusSpaceId) { persistExpandStatus() expandStatusSpaceId = spaceId diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt index c98f613c40..db97723413 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilder.kt @@ -19,5 +19,5 @@ package im.vector.app.features.home.room.list import im.vector.app.features.home.RoomListDisplayMode interface RoomListSectionBuilder { - fun buildSections(mode: RoomListDisplayMode): List + fun buildSections(mode: RoomListDisplayMode, explicitSpaceId: String?): List } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt index 0a093c3a5a..9f4e69b93b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderGroup.kt @@ -47,7 +47,7 @@ class RoomListSectionBuilderGroup( private val onUpdatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { - override fun buildSections(mode: RoomListDisplayMode): List { + override fun buildSections(mode: RoomListDisplayMode, explicitSpaceId: String?): List { val activeGroupAwareQueries = mutableListOf() val sections = mutableListOf() val actualGroupId = appStateHandler.safeActiveGroupId() diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt index 23a01b5845..ea15778624 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListSectionBuilderSpace.kt @@ -49,6 +49,7 @@ import org.matrix.android.sdk.api.session.room.UpdatableLivePageResult import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount import timber.log.Timber +import kotlin.math.exp class RoomListSectionBuilderSpace( private val session: Session, @@ -61,6 +62,10 @@ class RoomListSectionBuilderSpace( private val onlyOrphansInHome: Boolean = false ) : RoomListSectionBuilder { + companion object { + const val SPACE_ID_FOLLOW_APP = "de.spiritcroc.riotx.SPACE_ID_FOLLOW_APP" + } + private val pagedListConfig = PagedList.Config.Builder() .setPageSize(10) .setInitialLoadSizeHint(20) @@ -68,20 +73,20 @@ class RoomListSectionBuilderSpace( .setPrefetchDistance(10) .build() - override fun buildSections(mode: RoomListDisplayMode): List { + override fun buildSections(mode: RoomListDisplayMode, explicitSpaceId: String?): List { val sections = mutableListOf() val activeSpaceAwareQueries = mutableListOf() when (mode) { RoomListDisplayMode.PEOPLE -> { // 4 sections Invites / Fav / Dms / Low Priority - buildDmSections(sections, activeSpaceAwareQueries) + buildDmSections(sections, activeSpaceAwareQueries, explicitSpaceId) } RoomListDisplayMode.ROOMS -> { // 6 sections invites / Fav / Rooms / Low Priority / Server notice / Suggested rooms - buildRoomsSections(sections, activeSpaceAwareQueries) + buildRoomsSections(sections, activeSpaceAwareQueries, explicitSpaceId) } RoomListDisplayMode.ALL -> { - buildUnifiedSections(sections, activeSpaceAwareQueries) + buildUnifiedSections(sections, activeSpaceAwareQueries, explicitSpaceId) } RoomListDisplayMode.FILTERED -> { // Used when searching for rooms @@ -106,6 +111,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.invitations_header, notifyOfLocalEcho = true, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -123,6 +129,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.bottom_action_rooms, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -135,24 +142,31 @@ class RoomListSectionBuilderSpace( } } - appStateHandler.selectedRoomGroupingObservable - .distinctUntilChanged() - .onEach { groupingMethod -> - val selectedSpace = groupingMethod.orNull()?.space() - activeSpaceAwareQueries.onEach { updater -> - updater.updateForSpaceId(selectedSpace?.roomId) - } - }.launchIn(viewModelScope) + if (explicitSpaceId == SPACE_ID_FOLLOW_APP) { + appStateHandler.selectedRoomGroupingObservable + .distinctUntilChanged() + .onEach { groupingMethod -> + val selectedSpace = groupingMethod.orNull()?.space() + activeSpaceAwareQueries.onEach { updater -> + updater.updateForSpaceId(selectedSpace?.roomId) + } + }.launchIn(viewModelScope) + } else { + activeSpaceAwareQueries.onEach { updater -> + updater.updateForSpaceId(explicitSpaceId) + } + } return sections } - private fun buildUnifiedSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { + private fun buildUnifiedSections(sections: MutableList, activeSpaceAwareQueries: MutableList, explicitSpaceId: String?) { addSection( sections = sections, activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.invitations_header, notifyOfLocalEcho = true, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, countRoomAsNotif = true ) { @@ -164,7 +178,8 @@ class RoomListSectionBuilderSpace( activeSpaceAwareQueries, R.string.bottom_action_favourites, false, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + explicitSpaceId = explicitSpaceId, ) { it.memberships = listOf(Membership.JOIN) it.roomTagQueryFilter = RoomTagQueryFilter(true, null, null) @@ -175,6 +190,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.normal_priority_header, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -190,6 +206,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.low_priority_header, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -205,6 +222,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.system_alerts_header, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -215,17 +233,19 @@ class RoomListSectionBuilderSpace( it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true) } - addSuggestedRoomsSection(sections) + addSuggestedRoomsSection(sections, explicitSpaceId) } private fun buildRoomsSections(sections: MutableList, - activeSpaceAwareQueries: MutableList) { + activeSpaceAwareQueries: MutableList, + explicitSpaceId: String?) { if (autoAcceptInvites.showInvites()) { addSection( sections = sections, activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.invitations_header, notifyOfLocalEcho = true, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, countRoomAsNotif = true ) { @@ -239,7 +259,8 @@ class RoomListSectionBuilderSpace( activeSpaceAwareQueries, R.string.bottom_action_favourites, false, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + explicitSpaceId = explicitSpaceId, ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS @@ -251,6 +272,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.bottom_action_rooms, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -267,6 +289,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.low_priority_header, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -283,6 +306,7 @@ class RoomListSectionBuilderSpace( activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.system_alerts_header, notifyOfLocalEcho = false, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = if (onlyOrphansInHome) { RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL } else { @@ -294,12 +318,13 @@ class RoomListSectionBuilderSpace( it.roomTagQueryFilter = RoomTagQueryFilter(null, null, true) } - addSuggestedRoomsSection(sections) + addSuggestedRoomsSection(sections, explicitSpaceId) } - private fun addSuggestedRoomsSection(sections: MutableList) { + private fun addSuggestedRoomsSection(sections: MutableList, + explicitSpaceId: String?) { // add suggested rooms - val suggestedRoomsFlow = // MutableLiveData>() + val suggestedRoomsFlow = if (explicitSpaceId == SPACE_ID_FOLLOW_APP) { // MutableLiveData>() appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() .flatMapLatest { groupingMethod -> @@ -321,6 +346,24 @@ class RoomListSectionBuilderSpace( }.asFlow() } } + } else { + if (explicitSpaceId == null) { + flowOf(emptyList()) + } else { + liveData(context = viewModelScope.coroutineContext + Dispatchers.IO) { + val spaceSum = tryOrNull { + session.spaceService() + .querySpaceChildren(explicitSpaceId, suggestedOnly = true, null, null) + } + val value = spaceSum?.children.orEmpty().distinctBy { it.childRoomId } + // i need to check if it's already joined. + val filtered = value.filter { + session.getRoomSummary(it.childRoomId)?.membership?.isActive() != true + } + emit(filtered) + }.asFlow() + } + } val liveSuggestedRooms = MutableLiveData() combine( @@ -345,13 +388,15 @@ class RoomListSectionBuilderSpace( } private fun buildDmSections(sections: MutableList, - activeSpaceAwareQueries: MutableList) { + activeSpaceAwareQueries: MutableList, + explicitSpaceId: String?) { if (autoAcceptInvites.showInvites()) { addSection( sections = sections, activeSpaceUpdaters = activeSpaceAwareQueries, nameRes = R.string.invitations_header, notifyOfLocalEcho = true, + explicitSpaceId = explicitSpaceId, spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, countRoomAsNotif = true ) { @@ -365,7 +410,8 @@ class RoomListSectionBuilderSpace( activeSpaceAwareQueries, R.string.bottom_action_favourites, false, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + explicitSpaceId = explicitSpaceId ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -377,7 +423,8 @@ class RoomListSectionBuilderSpace( activeSpaceAwareQueries, R.string.bottom_action_people_x, false, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + explicitSpaceId = explicitSpaceId ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -389,7 +436,8 @@ class RoomListSectionBuilderSpace( activeSpaceAwareQueries, R.string.low_priority_header, false, - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + explicitSpaceId = explicitSpaceId ) { it.memberships = listOf(Membership.JOIN) it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM @@ -398,19 +446,28 @@ class RoomListSectionBuilderSpace( } + private fun AppStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId: String?): String? { + return if (explicitSpaceId == SPACE_ID_FOLLOW_APP) { + safeActiveSpaceId() + } else { + explicitSpaceId + } + } + private fun addSection(sections: MutableList, activeSpaceUpdaters: MutableList, @StringRes nameRes: Int, notifyOfLocalEcho: Boolean = false, spaceFilterStrategy: RoomListViewModel.SpaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.NONE, countRoomAsNotif: Boolean = false, + explicitSpaceId: String?, query: (RoomSummaryQueryParams.Builder) -> Unit) { withQueryParams( { query.invoke(it) }, { roomQueryParams -> val name = stringProvider.getString(nameRes) session.getFilteredPagedRoomSummariesLive( - roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()), + roomQueryParams.process(spaceFilterStrategy, appStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId)), pagedListConfig ).also { when (spaceFilterStrategy) { @@ -461,7 +518,7 @@ class RoomListSectionBuilderSpace( RoomAggregateNotificationCount(it.size, it.size, 0) } else { session.getNotificationCountForRooms( - roomQueryParams.process(spaceFilterStrategy, appStateHandler.safeActiveSpaceId()) + roomQueryParams.process(spaceFilterStrategy, appStateHandler.explicitOrSafeActiveSpaceId(explicitSpaceId)) ) } ) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index 405fe0d5fa..d89bffa604 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -146,7 +146,7 @@ class RoomListViewModel @AssistedInject constructor( } val sections: List by lazy { - roomListSectionBuilder.buildSections(initialState.displayMode) + roomListSectionBuilder.buildSections(initialState.displayMode, initialState.explicitSpaceId) } override fun handle(action: RoomListAction) { diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt index 46ff6c242b..4f8f79b49e 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewState.kt @@ -30,8 +30,10 @@ data class RoomListViewState( val roomMembershipChanges: Map = emptyMap(), val asyncSuggestedRooms: Async> = Uninitialized, val currentUserName: String? = null, - val currentRoomGrouping: Async = Uninitialized + val currentRoomGrouping: Async = Uninitialized, + // In comparison to currentRoomGrouping, the explicit space id fixes a filter method that should not change afterwards + val explicitSpaceId: String? = null ) : MavericksState { - constructor(args: RoomListParams) : this(displayMode = args.displayMode) + constructor(args: RoomListParams) : this(displayMode = args.displayMode, explicitSpaceId = args.explicitSpaceId) } 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 a668525fcb..31d0d745d8 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 @@ -214,6 +214,7 @@ class VectorPreferences @Inject constructor(private val context: Context): Stati const val SETTINGS_FORCE_ALLOW_BACKGROUND_SYNC = "SETTINGS_FORCE_ALLOW_BACKGROUND_SYNC" private const val SETTINGS_JUMP_TO_BOTTOM_ON_SEND = "SETTINGS_JUMP_TO_BOTTOM_ON_SEND" private const val SETTINGS_SPACE_MEMBERS_IN_SPACE_ROOMS = "SETTINGS_SPACE_MEMBERS_IN_SPACE_ROOMS" + private const val SETTINGS_ENABLE_SPACE_PAGER = "SETTINGS_ENABLE_SPACE_PAGER" private const val DID_ASK_TO_ENABLE_SESSION_PUSH = "DID_ASK_TO_ENABLE_SESSION_PUSH" private const val DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE = "DID_PROMOTE_NEW_RESTRICTED_JOIN_RULE" @@ -1043,18 +1044,26 @@ class VectorPreferences @Inject constructor(private val context: Context): Stati return Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP && defaultPrefs.getBoolean(SETTINGS_VOICE_MESSAGE, true) } + // SC addition fun jumpToBottomOnSend(): Boolean { return defaultPrefs.getBoolean(SETTINGS_JUMP_TO_BOTTOM_ON_SEND, true) } + // SC addition fun forceUseCustomUpGateway(): Boolean { return defaultPrefs.getBoolean(SETTINGS_UNIFIED_PUSH_FORCE_CUSTOM_GATEWAY, false) } + // SC addition fun forceAllowBackgroundSync(): Boolean { return defaultPrefs.getBoolean(SETTINGS_FORCE_ALLOW_BACKGROUND_SYNC, false) } + // SC addition + fun enableSpacePager(): Boolean { + return defaultPrefs.getBoolean(SETTINGS_ENABLE_SPACE_PAGER, false) + } + /** * I likely do more fresh installs of the app than anyone else, so a shortcut to change some of the default settings to * my preferred values can safe me some time @@ -1077,6 +1086,7 @@ class VectorPreferences @Inject constructor(private val context: Context): Stati .putBoolean(SETTINGS_USE_RAGE_SHAKE_KEY, true) .putBoolean(SETTINGS_UNIFIED_PUSH_FORCE_CUSTOM_GATEWAY, true) .putBoolean(SETTINGS_AGGREGATE_UNREAD_COUNTS, false) + .putBoolean(SETTINGS_ENABLE_SPACE_PAGER, true) .apply() } diff --git a/vector/src/main/res/layout/fragment_home_detail.xml b/vector/src/main/res/layout/fragment_home_detail.xml index 79e8a95b9d..5e4d55cae9 100644 --- a/vector/src/main/res/layout/fragment_home_detail.xml +++ b/vector/src/main/res/layout/fragment_home_detail.xml @@ -137,12 +137,21 @@ app:layout_constraintTop_toBottomOf="@id/syncStateView" tools:visibility="visible" /> + + + Show people in spaces If disabled, you can still add Direct Messages to Personal Spaces. If enabled, you\'ll automatically see everyone who is a member of the Space. + Swipe chat list to switch space + Allow to switch between root spaces by swiping horizontally in the chat list + diff --git a/vector/src/main/res/xml/vector_settings_labs.xml b/vector/src/main/res/xml/vector_settings_labs.xml index 700e00a879..1ee519a8a4 100644 --- a/vector/src/main/res/xml/vector_settings_labs.xml +++ b/vector/src/main/res/xml/vector_settings_labs.xml @@ -74,6 +74,12 @@ android:key="SETTINGS_LABS_UNREAD_NOTIFICATIONS_AS_TAB" android:title="@string/labs_show_unread_notifications_as_tab" /> + +