diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index 1f090c89ab..40bbcebbfd 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -19,20 +19,29 @@ package im.vector.app import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent -import arrow.core.Option import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.utils.BehaviorDataSource -import im.vector.app.features.spaces.ALL_COMMUNITIES_GROUP_ID +import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.ui.UiStateRepository import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton +sealed class RoomGroupingMethod { + data class ByLegacyGroup(val groupSummary: GroupSummary?) : RoomGroupingMethod() + data class BySpace(val spaceSummary: RoomSummary?) : RoomGroupingMethod() +} + +fun RoomGroupingMethod.space() = (this as? RoomGroupingMethod.BySpace)?.spaceSummary +fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary + /** * This class handles the global app state. * It requires to be added to ProcessLifecycleOwner.get().lifecycle @@ -42,48 +51,83 @@ import javax.inject.Singleton class AppStateHandler @Inject constructor( sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + vectorPreferences: VectorPreferences ) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() - private val selectedSpaceDataSource = BehaviorDataSource>(Option.empty()) + private val selectedSpaceDataSource = BehaviorDataSource( + // TODO get that from latest persisted? + if (vectorPreferences.labSpaces()) + RoomGroupingMethod.BySpace(null) + else RoomGroupingMethod.ByLegacyGroup(null) + ) - val selectedSpaceObservable = selectedSpaceDataSource.observe() + val selectedRoomGroupingObservable = selectedSpaceDataSource.observe() - fun setCurrentSpace(space: RoomSummary?) { - if (space == selectedSpaceDataSource.currentValue?.orNull()) return - selectedSpaceDataSource.post(space?.let { Option.just(it) } ?: Option.empty()) - if (space != null && space.roomId != ALL_COMMUNITIES_GROUP_ID) { + fun getCurrentRoomGroupingMethod(): RoomGroupingMethod = selectedSpaceDataSource.currentValue ?: RoomGroupingMethod.BySpace(null) + + fun setCurrentSpace(spaceId: String?, session: Session? = null) { + val uSession = session ?: activeSessionHolder.getSafeActiveSession() + if (selectedSpaceDataSource.currentValue is RoomGroupingMethod.BySpace + && spaceId == selectedSpaceDataSource.currentValue?.space()?.roomId) return + val spaceSum = spaceId?.let { uSession?.getRoomSummary(spaceId) } + selectedSpaceDataSource.post(RoomGroupingMethod.BySpace(spaceSum)) + if (spaceId != null) { GlobalScope.launch { tryOrNull { - activeSessionHolder.getSafeActiveSession()?.getRoom(space.roomId)?.loadRoomMembersIfNeeded() + uSession?.getRoom(spaceId)?.loadRoomMembersIfNeeded() + } + } + } + } + + fun setCurrentGroup(groupId: String?, session: Session? = null) { + val uSession = session ?: activeSessionHolder.getSafeActiveSession() + if (selectedSpaceDataSource.currentValue is RoomGroupingMethod.ByLegacyGroup + && groupId == selectedSpaceDataSource.currentValue?.group()?.groupId) return + val activeGroup = groupId?.let { uSession?.getGroupSummary(groupId) } + selectedSpaceDataSource.post(RoomGroupingMethod.ByLegacyGroup(activeGroup)) + if (groupId != null) { + GlobalScope.launch { + tryOrNull { + uSession?.getGroup(groupId)?.fetchGroupData() } } } } init { - // restore current space from ui state - sessionDataSource.currentValue?.orNull()?.let { session -> - uiStateRepository.getSelectedSpace(session.sessionId)?.let { selectedSpaceId -> - session.getRoomSummary(selectedSpaceId)?.let { - setCurrentSpace(it) - } - } + + sessionDataSource.observe() + .distinctUntilChanged() + .subscribe { + it.orNull()?.let { session -> + Timber.w("VAL: Latest method is space? ${uiStateRepository.isGroupingMethodSpace(session.sessionId)}") + if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { + uiStateRepository.getSelectedSpace(session.sessionId)?.let { selectedSpaceId -> + Timber.w("VAL: Latest selected space: $selectedSpaceId") + setCurrentSpace(selectedSpaceId, session) + } + } else { + uiStateRepository.getSelectedGroup(session.sessionId)?.let { selectedGroupId -> + setCurrentGroup(selectedGroupId, session) + } + } + } + }.also { + compositeDisposable.add(it) } + // restore current space from ui state } fun safeActiveSpaceId(): String? { - return selectedSpaceDataSource.currentValue?.orNull()?.roomId?.takeIf { - MatrixPatterns.isRoomId(it) - } + return (selectedSpaceDataSource.currentValue as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId } - fun safeActiveSpace(): RoomSummary? { - return selectedSpaceDataSource.currentValue?.orNull()?.takeIf { - MatrixPatterns.isRoomId(it.roomId) - } + fun safeActiveGroupId(): String? { + return (selectedSpaceDataSource.currentValue as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId } @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) @@ -93,5 +137,19 @@ class AppStateHandler @Inject constructor( @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun entersBackground() { compositeDisposable.clear() + Timber.w("VAL: entersBackground session: ${ activeSessionHolder.getSafeActiveSession()?.myUserId}") + val session = activeSessionHolder.getSafeActiveSession() ?: return + when (val currentMethod = selectedSpaceDataSource.currentValue ?: RoomGroupingMethod.BySpace(null)) { + is RoomGroupingMethod.BySpace -> { + uiStateRepository.storeGroupingMethod(true, session) + Timber.w("VAL: Store selected space: ${currentMethod.spaceSummary?.roomId}") + uiStateRepository.storeSelectedSpace(currentMethod.spaceSummary?.roomId, session) + } + is RoomGroupingMethod.ByLegacyGroup -> { + uiStateRepository.storeGroupingMethod(false, session) + Timber.w("VAL: Store group space: ${currentMethod.groupSummary?.groupId}") + uiStateRepository.storeSelectedGroup(currentMethod.groupSummary?.groupId, session) + } + } } } diff --git a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt index 3329c9d372..3d15bdd123 100644 --- a/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/FragmentModule.kt @@ -51,7 +51,6 @@ import im.vector.app.features.devtools.RoomDevToolSendFormFragment import im.vector.app.features.devtools.RoomDevToolStateEventListFragment import im.vector.app.features.discovery.DiscoverySettingsFragment import im.vector.app.features.discovery.change.SetIdentityServerFragment -import im.vector.app.features.grouplist.GroupListFragment import im.vector.app.features.home.HomeDetailFragment import im.vector.app.features.home.HomeDrawerFragment import im.vector.app.features.home.LoadingFragment @@ -150,11 +149,6 @@ interface FragmentModule { @FragmentKey(LocalePickerFragment::class) fun bindLocalePickerFragment(fragment: LocalePickerFragment): Fragment - @Binds - @IntoMap - @FragmentKey(GroupListFragment::class) - fun bindGroupListFragment(fragment: GroupListFragment): Fragment - @Binds @IntoMap @FragmentKey(SpaceListFragment::class) diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index c8d5f6dd20..e5a47e872c 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -35,7 +35,6 @@ import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.configuration.VectorConfiguration import im.vector.app.features.crypto.keysrequest.KeyRequestHandler import im.vector.app.features.crypto.verification.IncomingVerificationRequestHandler -import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.CurrentSpaceSuggestedRoomListDataSource import im.vector.app.features.home.room.detail.RoomDetailPendingActionStore @@ -115,8 +114,6 @@ interface VectorComponent { fun errorFormatter(): ErrorFormatter - fun selectedGroupStore(): SelectedGroupDataSource - fun appStateHandler(): AppStateHandler fun currentSpaceSuggestedRoomListDataSource(): CurrentSpaceSuggestedRoomListDataSource diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt deleted file mode 100644 index 4d974b8ce8..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListAction.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import im.vector.app.core.platform.VectorViewModelAction -import org.matrix.android.sdk.api.session.group.model.GroupSummary - -sealed class GroupListAction : VectorViewModelAction { - data class SelectGroup(val groupSummary: GroupSummary) : GroupListAction() -} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt deleted file mode 100644 index 297e33c583..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListFragment.kt +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import com.airbnb.mvrx.Incomplete -import com.airbnb.mvrx.Success -import com.airbnb.mvrx.fragmentViewModel -import com.airbnb.mvrx.withState -import im.vector.app.core.extensions.cleanup -import im.vector.app.core.extensions.configureWith -import im.vector.app.core.extensions.exhaustive -import im.vector.app.core.platform.StateView -import im.vector.app.core.platform.VectorBaseFragment -import im.vector.app.databinding.FragmentGroupListBinding -import im.vector.app.features.home.HomeActivitySharedAction -import im.vector.app.features.home.HomeSharedActionViewModel - -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import javax.inject.Inject - -class GroupListFragment @Inject constructor( - val groupListViewModelFactory: GroupListViewModel.Factory, - private val groupController: GroupSummaryController -) : VectorBaseFragment(), - GroupSummaryController.Callback { - - private lateinit var sharedActionViewModel: HomeSharedActionViewModel - private val viewModel: GroupListViewModel by fragmentViewModel() - - override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentGroupListBinding { - return FragmentGroupListBinding.inflate(inflater, container, false) - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) - groupController.callback = this - views.stateView.contentView = views.groupListView - views.groupListView.configureWith(groupController) - viewModel.observeViewEvents { - when (it) { - is GroupListViewEvents.OpenGroupSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) - }.exhaustive - } - } - - override fun onDestroyView() { - groupController.callback = null - views.groupListView.cleanup() - super.onDestroyView() - } - - override fun invalidate() = withState(viewModel) { state -> - when (state.asyncGroups) { - is Incomplete -> views.stateView.state = StateView.State.Loading - is Success -> views.stateView.state = StateView.State.Content - } - groupController.update(state) - } - - override fun onGroupSelected(groupSummary: GroupSummary) { - viewModel.handle(GroupListAction.SelectGroup(groupSummary)) - } -} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt deleted file mode 100644 index 0f6f77783d..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewModel.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import androidx.lifecycle.viewModelScope -import arrow.core.Option -import com.airbnb.mvrx.FragmentViewModelContext -import com.airbnb.mvrx.MvRxViewModelFactory -import com.airbnb.mvrx.ViewModelContext -import dagger.assisted.Assisted -import dagger.assisted.AssistedInject -import dagger.assisted.AssistedFactory -import im.vector.app.R -import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import io.reactivex.Observable -import kotlinx.coroutines.launch -import org.matrix.android.sdk.api.extensions.tryOrNull -import org.matrix.android.sdk.api.query.QueryStringValue -import org.matrix.android.sdk.api.session.Session -import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.session.room.model.Membership -import org.matrix.android.sdk.rx.rx - -const val ALL_COMMUNITIES_GROUP_ID = "+ALL_COMMUNITIES_GROUP_ID" - -class GroupListViewModel @AssistedInject constructor(@Assisted initialState: GroupListViewState, - private val selectedGroupStore: SelectedGroupDataSource, - private val session: Session, - private val stringProvider: StringProvider -) : VectorViewModel(initialState) { - - @AssistedFactory - interface Factory { - fun create(initialState: GroupListViewState): GroupListViewModel - } - - companion object : MvRxViewModelFactory { - - @JvmStatic - override fun create(viewModelContext: ViewModelContext, state: GroupListViewState): GroupListViewModel? { - val groupListFragment: GroupListFragment = (viewModelContext as FragmentViewModelContext).fragment() - return groupListFragment.groupListViewModelFactory.create(state) - } - } - - private var currentGroupId = "" - - init { - observeGroupSummaries() - observeSelectionState() - } - - private fun observeSelectionState() { - selectSubscribe(GroupListViewState::selectedGroup) { groupSummary -> - if (groupSummary != null) { - // We only want to open group if the updated selectedGroup is a different one. - if (currentGroupId != groupSummary.groupId) { - currentGroupId = groupSummary.groupId - _viewEvents.post(GroupListViewEvents.OpenGroupSummary) - } - val optionGroup = Option.just(groupSummary) - selectedGroupStore.post(optionGroup) - } else { - // If selected group is null we force to default. It can happens when leaving the selected group. - setState { - copy(selectedGroup = this.asyncGroups()?.find { it.groupId == ALL_COMMUNITIES_GROUP_ID }) - } - } - } - } - - override fun handle(action: GroupListAction) { - when (action) { - is GroupListAction.SelectGroup -> handleSelectGroup(action) - } - } - - // PRIVATE METHODS ***************************************************************************** - - private fun handleSelectGroup(action: GroupListAction.SelectGroup) = withState { state -> - if (state.selectedGroup?.groupId != action.groupSummary.groupId) { - // We take care of refreshing group data when selecting to be sure we get all the rooms and users - tryOrNull { - viewModelScope.launch { - session.getGroup(action.groupSummary.groupId)?.fetchGroupData() - } - } - setState { copy(selectedGroup = action.groupSummary) } - } - } - - private fun observeGroupSummaries() { - val groupSummariesQueryParams = groupSummaryQueryParams { - memberships = listOf(Membership.JOIN) - displayName = QueryStringValue.IsNotEmpty - } - Observable.combineLatest, List>( - session - .rx() - .liveUser(session.myUserId) - .map { optionalUser -> - GroupSummary( - groupId = ALL_COMMUNITIES_GROUP_ID, - membership = Membership.JOIN, - displayName = stringProvider.getString(R.string.group_all_communities), - avatarUrl = optionalUser.getOrNull()?.avatarUrl ?: "") - }, - session - .rx() - .liveGroupSummaries(groupSummariesQueryParams), - { allCommunityGroup, communityGroups -> - listOf(allCommunityGroup) + communityGroups - } - ) - .execute { async -> - val currentSelectedGroupId = selectedGroup?.groupId - val newSelectedGroup = if (currentSelectedGroupId != null) { - async()?.find { it.groupId == currentSelectedGroupId } - } else { - async()?.firstOrNull() - } - copy(asyncGroups = async, selectedGroup = newSelectedGroup) - } - } -} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt deleted file mode 100644 index 4abcff2f67..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewState.kt +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import com.airbnb.mvrx.Async -import com.airbnb.mvrx.MvRxState -import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.api.session.group.model.GroupSummary - -data class GroupListViewState( - val asyncGroups: Async> = Uninitialized, - val selectedGroup: GroupSummary? = null -) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupSummaryController.kt b/vector/src/main/java/im/vector/app/features/grouplist/GroupSummaryController.kt deleted file mode 100644 index 03272c2729..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupSummaryController.kt +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import com.airbnb.epoxy.EpoxyController -import im.vector.app.features.home.AvatarRenderer -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import org.matrix.android.sdk.api.util.toMatrixItem -import javax.inject.Inject - -class GroupSummaryController @Inject constructor(private val avatarRenderer: AvatarRenderer) : EpoxyController() { - - var callback: Callback? = null - private var viewState: GroupListViewState? = null - - init { - requestModelBuild() - } - - fun update(viewState: GroupListViewState) { - this.viewState = viewState - requestModelBuild() - } - - override fun buildModels() { - val nonNullViewState = viewState ?: return - buildGroupModels(nonNullViewState.asyncGroups(), nonNullViewState.selectedGroup) - } - - private fun buildGroupModels(summaries: List?, selected: GroupSummary?) { - if (summaries.isNullOrEmpty()) { - return - } - summaries.forEach { groupSummary -> - val isSelected = groupSummary.groupId == selected?.groupId - groupSummaryItem { - avatarRenderer(avatarRenderer) - id(groupSummary.groupId) - matrixItem(groupSummary.toMatrixItem()) - selected(isSelected) - listener { callback?.onGroupSelected(groupSummary) } - } - } - } - - interface Callback { - fun onGroupSelected(groupSummary: GroupSummary) - } -} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/SelectedGroupDataSource.kt b/vector/src/main/java/im/vector/app/features/grouplist/SelectedGroupDataSource.kt deleted file mode 100644 index 5a172e2636..0000000000 --- a/vector/src/main/java/im/vector/app/features/grouplist/SelectedGroupDataSource.kt +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright 2019 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.grouplist - -import arrow.core.Option -import im.vector.app.core.utils.BehaviorDataSource -import org.matrix.android.sdk.api.session.group.model.GroupSummary -import javax.inject.Inject -import javax.inject.Singleton - -@Singleton -class SelectedGroupDataSource @Inject constructor() : BehaviorDataSource>(Option.empty()) 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 24ad6c8b85..5a7f2e2f8f 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 @@ -32,6 +32,7 @@ import androidx.core.view.isVisible import androidx.drawerlayout.widget.DrawerLayout import com.airbnb.mvrx.MvRx import com.airbnb.mvrx.viewModel +import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.di.ScreenComponent @@ -108,6 +109,7 @@ class HomeActivity : @Inject lateinit var permalinkHandler: PermalinkHandler @Inject lateinit var avatarRenderer: AvatarRenderer @Inject lateinit var initSyncStepFormatter: InitSyncStepFormatter + @Inject lateinit var appStateHandler: AppStateHandler private val createSpaceResultLauncher = registerStartForActivityResult { activityResult -> if (activityResult.resultCode == Activity.RESULT_OK) { @@ -152,10 +154,15 @@ class HomeActivity : sharedActionViewModel = viewModelProvider.get(HomeSharedActionViewModel::class.java) views.drawerLayout.addDrawerListener(drawerListener) if (isFirstCreation()) { - replaceFragment(R.id.homeDetailFragmentContainer, LoadingFragment::class.java) + replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java) replaceFragment(R.id.homeDrawerFragmentContainer, HomeDrawerFragment::class.java) } +// appStateHandler.selectedRoomGroupingObservable.subscribe { +// if (supportFragmentManager.getFragment()) +// replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) +// }.disposeOnDestroy() + sharedActionViewModel .observe() .subscribe { sharedAction -> @@ -164,13 +171,17 @@ class HomeActivity : is HomeActivitySharedAction.CloseDrawer -> views.drawerLayout.closeDrawer(GravityCompat.START) is HomeActivitySharedAction.OpenGroup -> { views.drawerLayout.closeDrawer(GravityCompat.START) + // Temporary + // When switching from space to group or group to space, we need to reload the fragment + // To be removed when dropping legacy groups + if (sharedAction.clearFragment) { + replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) + } else { + // nop + } // we might want to delay that to avoid having the drawer animation lagging // would be probably better to let the drawer do that? in the on closed callback? - views.coordinatorLayout.postDelayed({ - replaceFragment(R.id.homeDetailFragmentContainer, HomeDetailFragment::class.java, allowStateLoss = true) - }, 200) - Unit } is HomeActivitySharedAction.OpenSpacePreview -> { startActivity(SpacePreviewActivity.newIntent(this, sharedAction.spaceId)) diff --git a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt index e87ce3476f..db0a9ba9eb 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeActivitySharedAction.kt @@ -24,7 +24,7 @@ import im.vector.app.core.platform.VectorSharedAction sealed class HomeActivitySharedAction : VectorSharedAction { object OpenDrawer : HomeActivitySharedAction() object CloseDrawer : HomeActivitySharedAction() - object OpenGroup : HomeActivitySharedAction() + data class OpenGroup(val clearFragment: Boolean) : HomeActivitySharedAction() object AddSpace : HomeActivitySharedAction() data class OpenSpacePreview(val spaceId: String) : HomeActivitySharedAction() data class ShowSpaceSettings(val spaceId: String) : HomeActivitySharedAction() 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 e7831c72cc..7b989b9bf8 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 @@ -29,6 +29,7 @@ import com.airbnb.mvrx.fragmentViewModel import com.airbnb.mvrx.withState import com.google.android.material.badge.BadgeDrawable import im.vector.app.R +import im.vector.app.RoomGroupingMethod import im.vector.app.core.extensions.commitTransaction import im.vector.app.core.extensions.toMvRxBundle import im.vector.app.core.platform.ToolbarConfigurable @@ -48,7 +49,6 @@ import im.vector.app.features.popup.PopupAlertManager import im.vector.app.features.popup.VerificationVectorAlert import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity.Companion.EXTRA_DIRECT_ACCESS_SECURITY_PRIVACY_MANAGE_SESSIONS -import im.vector.app.features.spaces.ALL_COMMUNITIES_GROUP_ID import im.vector.app.features.themes.ThemeUtils import im.vector.app.features.workers.signout.BannerState import im.vector.app.features.workers.signout.ServerBackupStatusViewModel @@ -125,15 +125,14 @@ class HomeDetailFragment @Inject constructor( views.bottomNavigationView.selectedItemId = it.displayMode.toMenuId() } - viewModel.selectSubscribe(this, HomeDetailViewState::groupSummary) { groupSummary -> - if (!vectorPreferences.labSpaces()) { - onGroupChange(groupSummary.orNull()) - } - } - - viewModel.selectSubscribe(this, HomeDetailViewState::spaceSummary) { spaceSummary -> - if (vectorPreferences.labSpaces()) { - onSpaceChange(spaceSummary.orNull()) + viewModel.selectSubscribe(this, HomeDetailViewState::roomGroupingMethod) { roomGroupingMethod -> + when (roomGroupingMethod) { + is RoomGroupingMethod.ByLegacyGroup -> { + onGroupChange(roomGroupingMethod.groupSummary) + } + is RoomGroupingMethod.BySpace -> { + onSpaceChange(roomGroupingMethod.spaceSummary) + } } } @@ -257,8 +256,7 @@ class HomeDetailFragment @Inject constructor( } private fun onGroupChange(groupSummary: GroupSummary?) { - groupSummary ?: return - if (groupSummary.groupId == ALL_COMMUNITIES_GROUP_ID) { + if (groupSummary == null) { views.groupToolbarSpaceTitleView.isVisible = false } else { views.groupToolbarSpaceTitleView.isVisible = true @@ -267,8 +265,7 @@ class HomeDetailFragment @Inject constructor( } private fun onSpaceChange(spaceSummary: RoomSummary?) { - spaceSummary ?: return - if (spaceSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { + if (spaceSummary == null) { views.groupToolbarSpaceTitleView.isVisible = false } else { views.groupToolbarSpaceTitleView.isVisible = true @@ -310,11 +307,14 @@ class HomeDetailFragment @Inject constructor( views.homeToolbarContent.debouncedClicks { withState(viewModel) { - if (vectorPreferences.labSpaces()) { - val currentSpace = it.spaceSummary.orNull() - ?.takeIf { it.roomId != ALL_COMMUNITIES_GROUP_ID } - if (vectorPreferences.labSpaces() && currentSpace != null) { - sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(currentSpace.roomId)) + when (it.roomGroupingMethod) { + is RoomGroupingMethod.ByLegacyGroup -> { + // nothing do far + } + is RoomGroupingMethod.BySpace -> { + it.roomGroupingMethod.spaceSummary?.let { + sharedActionViewModel.post(HomeActivitySharedAction.ShowSpaceSettings(it.roomId)) + } } } } 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 1d57b26015..b497532a9b 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 @@ -24,12 +24,12 @@ import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler +import im.vector.app.RoomGroupingMethod import im.vector.app.core.di.HasScreenInjector import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel -import im.vector.app.core.resources.StringProvider -import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.ui.UiStateRepository +import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.matrix.android.sdk.api.query.ActiveSpaceFilter @@ -51,9 +51,7 @@ import java.util.concurrent.TimeUnit class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: HomeDetailViewState, private val session: Session, private val uiStateRepository: UiStateRepository, - private val selectedGroupStore: SelectedGroupDataSource, - private val appStateHandler: AppStateHandler, - private val stringProvider: StringProvider) + private val appStateHandler: AppStateHandler) : VectorViewModel(initialState) { @AssistedFactory @@ -79,8 +77,7 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho init { observeSyncState() - observeSelectedGroupStore() - observeSelectedSpaceStore() + observeRoomGroupingMethod() observeRoomSummaries() session.rx().liveUser(session.myUserId).execute { @@ -138,29 +135,22 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho .disposeOnClear() } - private fun observeSelectedGroupStore() { - selectedGroupStore - .observe() + private fun observeRoomGroupingMethod() { + appStateHandler.selectedRoomGroupingObservable .subscribe { - setState { - copy(groupSummary = it) - } - } - .disposeOnClear() - } - - private fun observeSelectedSpaceStore() { - appStateHandler.selectedSpaceObservable - .subscribe { - setState { - copy(spaceSummary = it) - } + setState { + copy( + roomGroupingMethod = it + ) + } } .disposeOnClear() } private fun observeRoomSummaries() { - appStateHandler.selectedSpaceObservable.distinctUntilChanged().switchMap { + appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged().switchMap { + // we use it as a trigger to all changes in room, but do not really load + // the actual models session.getPagedRoomSummariesLive( roomSummaryQueryParams { memberships = Membership.activeMemberships() @@ -168,50 +158,55 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho sortOrder = RoomSortOrder.NONE ).asObservable() } - -// .asObservable() + .observeOn(Schedulers.computation()) .throttleFirst(300, TimeUnit.MILLISECONDS) .subscribe { - val activeSpace = appStateHandler.safeActiveSpaceId() - val dmInvites = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - } - ).size + when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { + is RoomGroupingMethod.ByLegacyGroup -> { + // TODO!! + } + is RoomGroupingMethod.BySpace -> { + val dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ).size - val roomsInvite = session.getRoomSummaries( - roomSummaryQueryParams { - memberships = listOf(Membership.INVITE) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - } - ).size + val roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + ).size - val dmRooms = session.getNotificationCountForRooms( - roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_DM - } - ) + val dmRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + ) - val otherRooms = session.getNotificationCountForRooms( - roomSummaryQueryParams { - memberships = listOf(Membership.JOIN) - roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS - activeSpaceId = ActiveSpaceFilter.ActiveSpace(activeSpace) - } - ) + val otherRooms = session.getNotificationCountForRooms( + roomSummaryQueryParams { + memberships = listOf(Membership.JOIN) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + activeSpaceId = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) + } + ) - setState { - copy( - notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites, - notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight, - notificationCountPeople = dmRooms.totalCount + dmInvites, - notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0, - notificationCountRooms = otherRooms.totalCount + roomsInvite, - notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0, - hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0 - ) + setState { + copy( + notificationCountCatchup = dmRooms.totalCount + otherRooms.totalCount + roomsInvite + dmInvites, + notificationHighlightCatchup = dmRooms.isHighlight || otherRooms.isHighlight, + notificationCountPeople = dmRooms.totalCount + dmInvites, + notificationHighlightPeople = dmRooms.isHighlight || dmInvites > 0, + notificationCountRooms = otherRooms.totalCount + roomsInvite, + notificationHighlightRooms = otherRooms.isHighlight || roomsInvite > 0, + hasUnreadMessages = dmRooms.totalCount + otherRooms.totalCount > 0 + ) + } + } } } .disposeOnClear() 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 637182d052..5aa9612a7a 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 @@ -16,18 +16,16 @@ package im.vector.app.features.home -import arrow.core.Option import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized -import org.matrix.android.sdk.api.session.group.model.GroupSummary +import im.vector.app.RoomGroupingMethod import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.sync.SyncState import org.matrix.android.sdk.api.util.MatrixItem data class HomeDetailViewState( - val groupSummary: Option = Option.empty(), - val spaceSummary: Option = Option.empty(), + val roomGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null), val myMatrixItem: MatrixItem? = null, val asyncRooms: Async> = Uninitialized, val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE, 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 91811ffb13..e935b71604 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 @@ -30,7 +30,7 @@ import im.vector.app.core.extensions.replaceChildFragment import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.core.utils.startSharePlainTextIntent import im.vector.app.databinding.FragmentHomeDrawerBinding -import im.vector.app.features.grouplist.GroupListFragment +// import im.vector.app.features.grouplist.GroupListFragment import im.vector.app.features.settings.VectorPreferences import im.vector.app.features.settings.VectorSettingsActivity import im.vector.app.features.spaces.SpaceListFragment @@ -59,11 +59,11 @@ class HomeDrawerFragment @Inject constructor( sharedActionViewModel = activityViewModelProvider.get(HomeSharedActionViewModel::class.java) if (savedInstanceState == null) { - if (vectorPreferences.labSpaces()) { +// if (vectorPreferences.labSpaces()) { replaceChildFragment(R.id.homeDrawerGroupListContainer, SpaceListFragment::class.java) - } else { - replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java) - } +// } else { +// replaceChildFragment(R.id.homeDrawerGroupListContainer, GroupListFragment::class.java) +// } } session.getUserLive(session.myUserId).observeK(viewLifecycleOwner) { optionalUser -> val user = optionalUser?.getOrNull() diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 85440c6582..fd5b0d12d7 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -107,8 +107,8 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia }.disposeOnClear() Observable.combineLatest( - appStateHandler.selectedSpaceObservable.distinctUntilChanged(), - appStateHandler.selectedSpaceObservable.switchMap { + appStateHandler.selectedRoomGroupingObservable.distinctUntilChanged(), + appStateHandler.selectedRoomGroupingObservable.switchMap { session.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = Membership.activeMemberships() diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index c7d18f1da7..3d91aaedd4 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -179,14 +179,14 @@ class RoomDetailViewModel @AssistedInject constructor( observePowerLevel() updateShowDialerOptionState() room.getRoomSummaryLive() - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { tryOrNull { room.markAsRead(ReadService.MarkAsReadParams.READ_RECEIPT) } } // Inform the SDK that the room is displayed - viewModelScope.launch { + viewModelScope.launch(Dispatchers.IO) { try { session.onRoomDisplayed(initialState.roomId) - } catch (_: Exception) { + } catch (_: Throwable) { } } callManager.addPstnSupportListener(this) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt index 7c994666a9..6287ef58e0 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt @@ -17,10 +17,10 @@ package im.vector.app.features.home.room.list import androidx.annotation.StringRes +import im.vector.app.AppStateHandler import im.vector.app.R +import im.vector.app.RoomGroupingMethod import im.vector.app.core.resources.StringProvider -import im.vector.app.features.grouplist.ALL_COMMUNITIES_GROUP_ID -import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.home.RoomListDisplayMode import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers @@ -37,25 +37,24 @@ class GroupRoomListSectionBuilder( val session: Session, val stringProvider: StringProvider, val viewModelScope: CoroutineScope, - val selectedGroupDataSource: SelectedGroupDataSource, + val appStateHandler: AppStateHandler, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { override fun buildSections(mode: RoomListDisplayMode): List { - val activeSpaceAwareQueries = mutableListOf() + val activeGroupAwareQueries = mutableListOf() val sections = mutableListOf() - val actualGroupId = selectedGroupDataSource.currentValue?.orNull() - ?.groupId?.takeIf { it != ALL_COMMUNITIES_GROUP_ID } + val actualGroupId = appStateHandler.safeActiveGroupId() when (mode) { RoomListDisplayMode.PEOPLE -> { // 3 sections Invites / Fav / Dms - buildPeopleSections(sections, activeSpaceAwareQueries, actualGroupId) + buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId) } RoomListDisplayMode.ROOMS -> { // 5 sections invites / Fav / Rooms / Low Priority / Server notice - buildRoomsSections(sections, activeSpaceAwareQueries, actualGroupId) + buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId) } RoomListDisplayMode.FILTERED -> { // Used when searching for rooms @@ -76,7 +75,7 @@ class GroupRoomListSectionBuilder( RoomListDisplayMode.NOTIFICATIONS -> { addSection( sections, - activeSpaceAwareQueries, + activeGroupAwareQueries, R.string.invitations_header, true ) { @@ -87,7 +86,7 @@ class GroupRoomListSectionBuilder( addSection( sections, - activeSpaceAwareQueries, + activeGroupAwareQueries, R.string.bottom_action_rooms, false ) { @@ -98,11 +97,11 @@ class GroupRoomListSectionBuilder( } } - selectedGroupDataSource.observe() + appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { activeSpaceOption -> - val selectedGroupId = activeSpaceOption.orNull()?.groupId?.takeIf { it != ALL_COMMUNITIES_GROUP_ID } - activeSpaceAwareQueries.onEach { updater -> + .subscribe { groupingMethod -> + val selectedGroupId = (groupingMethod as? RoomGroupingMethod.ByLegacyGroup)?.groupSummary?.groupId + activeGroupAwareQueries.onEach { updater -> updater.updateQuery { query -> query.copy(activeGroupId = selectedGroupId) } 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 7f213336f8..00079e5a7b 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 @@ -26,10 +26,10 @@ import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import im.vector.app.AppStateHandler +import im.vector.app.RoomGroupingMethod import im.vector.app.core.extensions.exhaustive import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider -import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -49,7 +49,6 @@ class RoomListViewModel @Inject constructor( private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, - private val selectedGroupDataSource: SelectedGroupDataSource, private val vectorPreferences: VectorPreferences ) : VectorViewModel(initialState) { @@ -72,15 +71,14 @@ class RoomListViewModel @Inject constructor( } init { + Timber.w("VAL: RoomListViewModel INIT") observeMembershipChanges() - appStateHandler.selectedSpaceObservable - .distinctUntilChanged() - .map { it.orNull() } + appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() .execute { copy( - currentSpace = it + currentRoomGrouping = it ) } @@ -113,7 +111,7 @@ class RoomListViewModel @Inject constructor( } val sections: List by lazy { - if (vectorPreferences.labSpaces()) { + if (appStateHandler.getCurrentRoomGroupingMethod() is RoomGroupingMethod.BySpace) { SpaceRoomListSectionBuilder( session, stringProvider, @@ -132,7 +130,7 @@ class RoomListViewModel @Inject constructor( session, stringProvider, viewModelScope, - selectedGroupDataSource, + appStateHandler, { it.disposeOnClear() }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index 0a142304a0..a30c175f41 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -18,7 +18,6 @@ package im.vector.app.features.home.room.list import im.vector.app.AppStateHandler import im.vector.app.core.resources.StringProvider -import im.vector.app.features.grouplist.SelectedGroupDataSource import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -27,8 +26,7 @@ import javax.inject.Provider class RoomListViewModelFactory @Inject constructor(private val session: Provider, private val appStateHandler: AppStateHandler, private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences, - private val selectedGroupDataSource: SelectedGroupDataSource) + private val vectorPreferences: VectorPreferences) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { @@ -37,7 +35,6 @@ class RoomListViewModelFactory @Inject constructor(private val session: Provider session.get(), stringProvider, appStateHandler, - selectedGroupDataSource, vectorPreferences ) } 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 d18574d75b..68a8b9e515 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 @@ -19,9 +19,9 @@ package im.vector.app.features.home.room.list import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.app.RoomGroupingMethod import im.vector.app.features.home.RoomListDisplayMode import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState -import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo data class RoomListViewState( @@ -30,7 +30,7 @@ data class RoomListViewState( val roomMembershipChanges: Map = emptyMap(), val asyncSuggestedRooms: Async> = Uninitialized, val currentUserName: String? = null, - val currentSpace: Async = Uninitialized + val currentRoomGrouping: Async = Uninitialized ) : MvRxState { constructor(args: RoomListParams) : this(displayMode = args.displayMode) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index 353cf5732d..42f73caa74 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -26,13 +26,13 @@ import im.vector.app.AppStateHandler import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.Disposable import io.reactivex.rxkotlin.Observables import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers -import org.matrix.android.sdk.api.MatrixPatterns import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.RoomCategoryFilter @@ -110,12 +110,12 @@ class SpaceRoomListSectionBuilder( } } - appStateHandler.selectedSpaceObservable + appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .subscribe { activeSpaceOption -> - val selectedSpace = activeSpaceOption.orNull() + .subscribe { groupingMethod -> + val selectedSpace = groupingMethod.space() activeSpaceAwareQueries.onEach { updater -> - updater.updateForSpaceId(selectedSpace?.roomId?.takeIf { MatrixPatterns.isRoomId(it) }) + updater.updateForSpaceId(selectedSpace?.roomId) } }.also { onDisposable.invoke(it) @@ -185,10 +185,10 @@ class SpaceRoomListSectionBuilder( // add suggested rooms val suggestedRoomsObservable = // MutableLiveData>() - appStateHandler.selectedSpaceObservable + appStateHandler.selectedRoomGroupingObservable .distinctUntilChanged() - .switchMap { activeSpaceOption -> - val selectedSpace = activeSpaceOption.orNull() + .switchMap { groupingMethod -> + val selectedSpace = groupingMethod.space() if (selectedSpace == null) { Observable.just(emptyList()) } else { 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 c033cc40fa..73d8325bca 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 @@ -31,6 +31,7 @@ import androidx.core.util.Pair import androidx.core.view.ViewCompat import im.vector.app.AppStateHandler import im.vector.app.R +import im.vector.app.RoomGroupingMethod import im.vector.app.core.di.ActiveSessionHolder import im.vector.app.core.error.fatalError import im.vector.app.core.platform.VectorBaseActivity @@ -76,13 +77,13 @@ import im.vector.app.features.spaces.SpacePreviewActivity import im.vector.app.features.terms.ReviewTermsActivity import im.vector.app.features.widgets.WidgetActivity import im.vector.app.features.widgets.WidgetArgsBuilder +import im.vector.app.space import org.matrix.android.sdk.api.session.crypto.verification.IncomingSasVerificationTransaction import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom import org.matrix.android.sdk.api.session.room.model.thirdparty.RoomDirectoryData import org.matrix.android.sdk.api.session.terms.TermsService import org.matrix.android.sdk.api.session.widgets.model.Widget import org.matrix.android.sdk.api.session.widgets.model.WidgetType -import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -106,17 +107,11 @@ class DefaultNavigator @Inject constructor( } override fun switchToSpace(context: Context, spaceId: String, roomId: String?, openShareSheet: Boolean) { - if (sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId) == null) { + if (sessionHolder.getSafeActiveSession()?.getRoomSummary(spaceId) == null) { fatalError("Trying to open an unknown space $spaceId", vectorPreferences.failFast()) return } - - sessionHolder.getSafeActiveSession()?.spaceService()?.getSpace(spaceId)?.spaceSummary()?.let { - Timber.d("## Nav: Switching to space $spaceId / ${it.name}") - appStateHandler.setCurrentSpace(it) - } ?: kotlin.run { - Timber.d("## Nav: Failed to switch to space $spaceId") - } + appStateHandler.setCurrentSpace(spaceId) if (roomId != null) { val args = RoomDetailArgs(roomId, eventId = null, openShareSpaceForId = spaceId.takeIf { openShareSheet }) val intent = RoomDetailActivity.newIntent(context, args) @@ -251,15 +246,22 @@ class DefaultNavigator @Inject constructor( } override fun openRoomDirectory(context: Context, initialFilter: String) { - val selectedSpace = appStateHandler.safeActiveSpace()?.let { - sessionHolder.getSafeActiveSession()?.getRoomSummary(it.roomId) - } - if (selectedSpace == null) { - val intent = RoomDirectoryActivity.getIntent(context, initialFilter) - context.startActivity(intent) - } else { - SpaceExploreActivity.newIntent(context, selectedSpace.roomId).let { - context.startActivity(it) + when (val groupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { + is RoomGroupingMethod.ByLegacyGroup -> { + // TODO should open list of rooms of this group + val intent = RoomDirectoryActivity.getIntent(context, initialFilter) + context.startActivity(intent) + } + is RoomGroupingMethod.BySpace -> { + val selectedSpace = groupingMethod.space() + if (selectedSpace == null) { + val intent = RoomDirectoryActivity.getIntent(context, initialFilter) + context.startActivity(intent) + } else { + SpaceExploreActivity.newIntent(context, selectedSpace.roomId).let { + context.startActivity(it) + } + } } } } @@ -275,29 +277,36 @@ class DefaultNavigator @Inject constructor( } override fun openInviteUsersToRoom(context: Context, roomId: String) { - val selectedSpace = appStateHandler.safeActiveSpace() - if (vectorPreferences.labSpaces() && selectedSpace != null) { - // let user decides if he does it from space or room - (context as? AppCompatActivity)?.supportFragmentManager?.let { fm -> - InviteRoomSpaceChooserBottomSheet.newInstance( - selectedSpace.roomId, - roomId, - object : InviteRoomSpaceChooserBottomSheet.InteractionListener { - override fun inviteToSpace(spaceId: String) { - val intent = InviteUsersToRoomActivity.getIntent(context, spaceId) - context.startActivity(intent) - } - - override fun inviteToRoom(roomId: String) { - val intent = InviteUsersToRoomActivity.getIntent(context, roomId) - context.startActivity(intent) - } - } - ).show(fm, InviteRoomSpaceChooserBottomSheet::class.java.name) + when (val currentGroupingMethod = appStateHandler.getCurrentRoomGroupingMethod()) { + is RoomGroupingMethod.ByLegacyGroup -> { + val intent = InviteUsersToRoomActivity.getIntent(context, roomId) + context.startActivity(intent) + } + is RoomGroupingMethod.BySpace -> { + if (currentGroupingMethod.spaceSummary != null) { + // let user decides if he does it from space or room + (context as? AppCompatActivity)?.supportFragmentManager?.let { fm -> + InviteRoomSpaceChooserBottomSheet.newInstance( + currentGroupingMethod.spaceSummary.roomId, + roomId, + object : InviteRoomSpaceChooserBottomSheet.InteractionListener { + override fun inviteToSpace(spaceId: String) { + val intent = InviteUsersToRoomActivity.getIntent(context, spaceId) + context.startActivity(intent) + } + + override fun inviteToRoom(roomId: String) { + val intent = InviteUsersToRoomActivity.getIntent(context, roomId) + context.startActivity(intent) + } + } + ).show(fm, InviteRoomSpaceChooserBottomSheet::class.java.name) + } + } else { + val intent = InviteUsersToRoomActivity.getIntent(context, roomId) + context.startActivity(intent) + } } - } else { - val intent = InviteUsersToRoomActivity.getIntent(context, roomId) - context.startActivity(intent) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceAddItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceAddItem.kt new file mode 100644 index 0000000000..703ba73790 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceAddItem.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2021 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.spaces + +import com.airbnb.epoxy.EpoxyAttribute +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.core.utils.DebouncedClickListener + +@EpoxyModelClass(layout = R.layout.item_space_add) +abstract class SpaceAddItem : VectorEpoxyModel() { + + @EpoxyAttribute var listener: (() -> Unit)? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener( + DebouncedClickListener({ + listener?.invoke() + }) + ) + } + + class Holder : VectorEpoxyHolder() +} diff --git a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt similarity index 57% rename from vector/src/main/java/im/vector/app/features/grouplist/GroupListViewEvents.kt rename to vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt index 7cd12b8364..8014dfad3d 100644 --- a/vector/src/main/java/im/vector/app/features/grouplist/GroupListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceBetaHeaderItem.kt @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 New Vector Ltd + * Copyright (c) 2021 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,13 +14,15 @@ * limitations under the License. */ -package im.vector.app.features.grouplist +package im.vector.app.features.spaces -import im.vector.app.core.platform.VectorViewEvents +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel -/** - * Transient events for group list screen - */ -sealed class GroupListViewEvents : VectorViewEvents { - object OpenGroupSummary : GroupListViewEvents() +@EpoxyModelClass(layout = R.layout.item_space_beta_header) +abstract class SpaceBetaHeaderItem : VectorEpoxyModel() { + + class Holder : VectorEpoxyHolder() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt index 58812bd77e..ce4ac38a72 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListAction.kt @@ -17,6 +17,7 @@ package im.vector.app.features.spaces import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary sealed class SpaceListAction : VectorViewModelAction { @@ -25,4 +26,6 @@ sealed class SpaceListAction : VectorViewModelAction { data class LeaveSpace(val spaceSummary: RoomSummary) : SpaceListAction() data class ToggleExpand(val spaceSummary: RoomSummary) : SpaceListAction() object AddSpace : SpaceListAction() + + data class SelectLegacyGroup(val groupSummary: GroupSummary?) : SpaceListAction() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt index 6455b18d13..50d06d8238 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListFragment.kt @@ -32,6 +32,7 @@ import im.vector.app.core.platform.VectorBaseFragment import im.vector.app.databinding.FragmentGroupListBinding import im.vector.app.features.home.HomeActivitySharedAction import im.vector.app.features.home.HomeSharedActionViewModel +import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.RoomSummary import javax.inject.Inject @@ -56,8 +57,9 @@ class SpaceListFragment @Inject constructor( viewModel.observeViewEvents { when (it) { is SpaceListViewEvents.OpenSpaceSummary -> sharedActionViewModel.post(HomeActivitySharedAction.OpenSpacePreview(it.id)) - is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup) - is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace) + is SpaceListViewEvents.OpenSpace -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged)) + is SpaceListViewEvents.AddSpace -> sharedActionViewModel.post(HomeActivitySharedAction.AddSpace) + is SpaceListViewEvents.OpenGroup -> sharedActionViewModel.post(HomeActivitySharedAction.OpenGroup(it.groupingMethodHasChanged)) }.exhaustive } } @@ -94,4 +96,8 @@ class SpaceListFragment @Inject constructor( override fun onAddSpaceSelected() { viewModel.handle(SpaceListAction.AddSpace) } + + override fun onGroupSelected(groupSummary: GroupSummary?) { + viewModel.handle(SpaceListAction.SelectLegacyGroup(groupSummary)) + } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt index 7f05172cd1..b7e31d28f2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewEvents.kt @@ -22,7 +22,8 @@ import im.vector.app.core.platform.VectorViewEvents * Transient events for group list screen */ sealed class SpaceListViewEvents : VectorViewEvents { - object OpenSpace : SpaceListViewEvents() + data class OpenSpace(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents() data class OpenSpaceSummary(val id: String) : SpaceListViewEvents() object AddSpace : SpaceListViewEvents() + data class OpenGroup(val groupingMethodHasChanged: Boolean) : SpaceListViewEvents() } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt index 558b631c16..b57a5d364b 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceListViewState.kt @@ -19,13 +19,18 @@ package im.vector.app.features.spaces import com.airbnb.mvrx.Async import com.airbnb.mvrx.MvRxState import com.airbnb.mvrx.Uninitialized +import im.vector.app.RoomGroupingMethod +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.api.session.room.summary.RoomAggregateNotificationCount +import org.matrix.android.sdk.api.util.MatrixItem data class SpaceListViewState( + val myMxItem : Async = Uninitialized, val asyncSpaces: Async> = Uninitialized, - val selectedSpace: RoomSummary? = null, + val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null), val rootSpaces: List? = null, + val legacyGroups: List? = null, val expandedStates: Map = emptyMap(), val homeAggregateCount : RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0) ) : MvRxState diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt index 8b30687a12..7d01032eb5 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryController.kt @@ -18,14 +18,17 @@ package im.vector.app.features.spaces import com.airbnb.epoxy.EpoxyController import im.vector.app.R +import im.vector.app.RoomGroupingMethod import im.vector.app.core.resources.StringProvider -import im.vector.app.core.ui.list.genericButtonItem import im.vector.app.core.ui.list.genericFooterItem import im.vector.app.core.ui.list.genericItemHeader -import im.vector.app.core.utils.DebouncedClickListener +import im.vector.app.features.grouplist.groupSummaryItem import im.vector.app.features.grouplist.homeSpaceSummaryItem import im.vector.app.features.home.AvatarRenderer import im.vector.app.features.home.room.list.UnreadCounterBadgeView +import im.vector.app.group +import im.vector.app.space +import org.matrix.android.sdk.api.session.group.model.GroupSummary import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.summary.RoomAggregateNotificationCount @@ -53,63 +56,101 @@ class SpaceSummaryController @Inject constructor( val nonNullViewState = viewState ?: return buildGroupModels( nonNullViewState.asyncSpaces(), - nonNullViewState.selectedSpace, + nonNullViewState.selectedGroupingMethod, nonNullViewState.rootSpaces, nonNullViewState.expandedStates, nonNullViewState.homeAggregateCount) + + if (!nonNullViewState.legacyGroups.isNullOrEmpty()) { + genericFooterItem { + id("legacy_space") + text(" ") + } + + genericItemHeader { + id("legacy_groups") + text(stringProvider.getString(R.string.groups_header)) + } + + // add home for communities + nonNullViewState.myMxItem.invoke()?.let { mxItem -> + groupSummaryItem { + avatarRenderer(avatarRenderer) + id("all_communities") + matrixItem(mxItem.copy(displayName = stringProvider.getString(R.string.group_all_communities))) + selected(nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup + && nonNullViewState.selectedGroupingMethod.group() == null) + listener { callback?.onGroupSelected(null) } + } + } + + nonNullViewState.legacyGroups.forEach { groupSummary -> + groupSummaryItem { + avatarRenderer(avatarRenderer) + id(groupSummary.groupId) + matrixItem(groupSummary.toMatrixItem()) + selected(nonNullViewState.selectedGroupingMethod is RoomGroupingMethod.ByLegacyGroup + && nonNullViewState.selectedGroupingMethod.group()?.groupId == groupSummary.groupId) + listener { callback?.onGroupSelected(groupSummary) } + } + } + } } private fun buildGroupModels(summaries: List?, - selected: RoomSummary?, + selected: RoomGroupingMethod, rootSpaces: List?, expandedStates: Map, homeCount: RoomAggregateNotificationCount) { if (summaries.isNullOrEmpty()) { return } - // show invites on top - - summaries.filter { it.membership == Membership.INVITE } - .let { invites -> - if (invites.isNotEmpty()) { - genericItemHeader { - id("invites") - text(stringProvider.getString(R.string.spaces_invited_header)) - } - invites.forEach { - spaceSummaryItem { - avatarRenderer(avatarRenderer) - id(it.roomId) - matrixItem(it.toMatrixItem()) - selected(false) - listener { callback?.onSpaceInviteSelected(it) } - } - } - genericFooterItem { - id("invite_space") - text("") - } - } - } genericItemHeader { id("spaces") text(stringProvider.getString(R.string.spaces_header)) } + spaceBetaHeaderItem { id("beta_header") } + + // show invites on top + + summaries.filter { it.membership == Membership.INVITE } + .let { invites -> + if (invites.isNotEmpty()) { +// genericItemHeader { +// id("invites") +// text(stringProvider.getString(R.string.spaces_invited_header)) +// } + invites.forEach { + spaceSummaryItem { + avatarRenderer(avatarRenderer) + id(it.roomId) + matrixItem(it.toMatrixItem()) + countState(UnreadCounterBadgeView.State(1, true)) + selected(false) + description(stringProvider.getString(R.string.you_are_invited)) + listener { callback?.onSpaceInviteSelected(it) } + } + } + } + } + summaries.firstOrNull { it.roomId == ALL_COMMUNITIES_GROUP_ID } ?.let { + val isSelected = selected is RoomGroupingMethod.BySpace && selected.space() == null homeSpaceSummaryItem { id(it.roomId) - selected(it.roomId == selected?.roomId) + selected(isSelected) countState(UnreadCounterBadgeView.State(homeCount.totalCount, homeCount.isHighlight)) listener { callback?.onSpaceSelected(it) } } } rootSpaces + ?.sortedBy { it.displayName } ?.forEach { groupSummary -> - val isSelected = groupSummary.roomId == selected?.roomId + val isSelected = selected is RoomGroupingMethod.BySpace && groupSummary.roomId == selected.space()?.roomId // does it have children? val subSpaces = groupSummary.children?.filter { childInfo -> summaries.indexOfFirst { it.roomId == childInfo.childRoomId } != -1 @@ -139,7 +180,7 @@ class SpaceSummaryController @Inject constructor( // it's expanded subSpaces?.forEach { child -> summaries.firstOrNull { it.roomId == child.childRoomId }?.let { childSum -> - val isChildSelected = childSum.roomId == selected?.roomId + val isChildSelected = selected is RoomGroupingMethod.BySpace && childSum.roomId == selected.space()?.roomId spaceSummaryItem { avatarRenderer(avatarRenderer) id(child.childRoomId) @@ -161,12 +202,9 @@ class SpaceSummaryController @Inject constructor( } // Temporary item to create a new Space (will move with final design) - - genericButtonItem { + spaceAddItem { id("create") - text(stringProvider.getString(R.string.add_space)) - iconRes(R.drawable.ic_add_black) - buttonClickAction(DebouncedClickListener({ callback?.onAddSpaceSelected() })) + listener { callback?.onAddSpaceSelected() } } } @@ -176,5 +214,7 @@ class SpaceSummaryController @Inject constructor( fun onSpaceSettings(spaceSummary: RoomSummary) fun onToggleExpand(spaceSummary: RoomSummary) fun onAddSpaceSelected() + + fun onGroupSelected(groupSummary: GroupSummary?) } } diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt index 797087e78b..105cb0dd57 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpaceSummaryItem.kt @@ -27,6 +27,7 @@ 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.core.extensions.setTextOrHide import im.vector.app.core.platform.CheckableConstraintLayout import im.vector.app.core.utils.DebouncedClickListener import im.vector.app.features.home.AvatarRenderer @@ -46,6 +47,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { @EpoxyAttribute var hasChildren: Boolean = false @EpoxyAttribute var indent: Int = 0 @EpoxyAttribute var countState : UnreadCounterBadgeView.State = UnreadCounterBadgeView.State(0, false) + @EpoxyAttribute var description: String? = null override fun bind(holder: Holder) { super.bind(holder) @@ -62,6 +64,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { holder.moreView.isVisible = false } + holder.secondLineText.setTextOrHide(description) if (hasChildren) { // holder.collapseIndicator.setOnClickListener( // DebouncedClickListener({ _ -> @@ -104,6 +107,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel() { class Holder : VectorEpoxyHolder() { val avatarImageView by bind(R.id.groupAvatarImageView) val groupNameView by bind(R.id.groupNameView) + val secondLineText by bind(R.id.groupDescView) val rootView by bind(R.id.itemGroupLayout) val moreView by bind(R.id.groupTmpLeave) val collapseIndicator by bind(R.id.groupChildrenCollapse) diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index de8e8bbc95..6066cf8c1c 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -18,16 +18,21 @@ package im.vector.app.features.spaces import androidx.lifecycle.viewModelScope import com.airbnb.mvrx.FragmentViewModelContext +import com.airbnb.mvrx.Loading import com.airbnb.mvrx.MvRxViewModelFactory +import com.airbnb.mvrx.Success import com.airbnb.mvrx.ViewModelContext import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.R +import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.VectorViewModel import im.vector.app.core.resources.StringProvider import im.vector.app.features.ui.UiStateRepository +import im.vector.app.group +import im.vector.app.space import io.reactivex.Observable import io.reactivex.functions.BiFunction import io.reactivex.schedulers.Schedulers @@ -36,10 +41,12 @@ import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.query.ActiveSpaceFilter import org.matrix.android.sdk.api.query.QueryStringValue import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.group.groupSummaryQueryParams import org.matrix.android.sdk.api.session.room.RoomSortOrder import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.api.session.room.model.RoomSummary import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.api.util.toMatrixItem import org.matrix.android.sdk.rx.asObservable import org.matrix.android.sdk.rx.rx import java.util.concurrent.TimeUnit @@ -67,23 +74,42 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } } - private var currentGroupId = "" +// private var currentGroupingMethod : RoomGroupingMethod? = null init { - observeSpaceSummaries() - observeSelectionState() - appStateHandler.selectedSpaceObservable + + session.getUserLive(session.myUserId).asObservable() .subscribe { - if (currentGroupId != it.orNull()?.roomId) { - setState { - copy( - selectedSpace = it.orNull() - ) - } + setState { + copy( + myMxItem = it?.getOrNull()?.toMatrixItem()?.let { Success(it) } ?: Loading() + ) + } + }.disposeOnClear() + + observeSpaceSummaries() +// observeSelectionState() + appStateHandler.selectedRoomGroupingObservable + .distinctUntilChanged() + .subscribe { + setState { + copy( + selectedGroupingMethod = it + ) } } .disposeOnClear() + session.getGroupSummariesLive(groupSummaryQueryParams {}) + .asObservable() + .subscribe { + setState { + copy( + legacyGroups = it + ) + } + }.disposeOnClear() + session.getPagedRoomSummariesLive( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) @@ -107,23 +133,23 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp }.disposeOnClear() } - private fun observeSelectionState() { - selectSubscribe(SpaceListViewState::selectedSpace) { spaceSummary -> - if (spaceSummary != null) { - // We only want to open group if the updated selectedGroup is a different one. - if (currentGroupId != spaceSummary.roomId) { - currentGroupId = spaceSummary.roomId - _viewEvents.post(SpaceListViewEvents.OpenSpace) - } - appStateHandler.setCurrentSpace(spaceSummary) - } else { - // If selected group is null we force to default. It can happens when leaving the selected group. - setState { - copy(selectedSpace = this.asyncSpaces()?.find { it.roomId == ALL_COMMUNITIES_GROUP_ID }) - } - } - } - } +// private fun observeSelectionState() { +// selectSubscribe(SpaceListViewState::selectedSpace) { spaceSummary -> +// if (spaceSummary != null) { +// // We only want to open group if the updated selectedGroup is a different one. +// if (currentGroupId != spaceSummary.roomId) { +// currentGroupId = spaceSummary.roomId +// _viewEvents.post(SpaceListViewEvents.OpenSpace) +// } +// appStateHandler.setCurrentSpace(spaceSummary.roomId) +// } else { +// // If selected group is null we force to default. It can happens when leaving the selected group. +// setState { +// copy(selectedSpace = this.asyncSpaces()?.find { it.roomId == ALL_COMMUNITIES_GROUP_ID }) +// } +// } +// } +// } override fun handle(action: SpaceListAction) { when (action) { @@ -132,15 +158,31 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp SpaceListAction.AddSpace -> handleAddSpace() is SpaceListAction.ToggleExpand -> handleToggleExpand(action) is SpaceListAction.OpenSpaceInvite -> handleSelectSpaceInvite(action) + is SpaceListAction.SelectLegacyGroup -> handleSelectGroup(action) } } // PRIVATE METHODS ***************************************************************************** private fun handleSelectSpace(action: SpaceListAction.SelectSpace) = withState { state -> - if (state.selectedSpace?.roomId != action.spaceSummary.roomId) { - setState { copy(selectedSpace = action.spaceSummary) } - uiStateRepository.storeSelectedSpace(action.spaceSummary.roomId, session.sessionId) + val groupingMethod = state.selectedGroupingMethod + if (groupingMethod is RoomGroupingMethod.ByLegacyGroup || groupingMethod.space()?.roomId != action.spaceSummary.roomId) { + setState { copy(selectedGroupingMethod = RoomGroupingMethod.BySpace(action.spaceSummary)) } + if (action.spaceSummary.roomId == ALL_COMMUNITIES_GROUP_ID) { + appStateHandler.setCurrentSpace(null) + } else { + appStateHandler.setCurrentSpace(action.spaceSummary.roomId) + } + _viewEvents.post(SpaceListViewEvents.OpenSpace(groupingMethod is RoomGroupingMethod.ByLegacyGroup)) + } + } + + private fun handleSelectGroup(action: SpaceListAction.SelectLegacyGroup) = withState { state -> + val groupingMethod = state.selectedGroupingMethod + if (groupingMethod is RoomGroupingMethod.BySpace || groupingMethod.group()?.groupId != action.groupSummary?.groupId) { + setState { copy(selectedGroupingMethod = RoomGroupingMethod.ByLegacyGroup(action.groupSummary)) } + appStateHandler.setCurrentGroup(action.groupSummary?.groupId) + _viewEvents.post(SpaceListViewEvents.OpenGroup(groupingMethod is RoomGroupingMethod.BySpace)) } } @@ -199,15 +241,14 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp } ) .execute { async -> - val currentSelectedGroupId = selectedSpace?.roomId - val newSelectedGroup = if (currentSelectedGroupId != null) { - async()?.find { it.roomId == currentSelectedGroupId } - } else { - async()?.firstOrNull() - } +// val currentSelectedGroupId = selectedGroupingMethod?.roomId +// val newSelectedGroup = if (currentSelectedGroupId != null) { +// async()?.find { it.roomId == currentSelectedGroupId } +// } else { +// async()?.firstOrNull() +// } copy( asyncSpaces = async, - selectedSpace = newSelectedGroup, rootSpaces = session.spaceService().getRootSpaceSummaries() ) } diff --git a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt index 696a8c69cb..482e98dc35 100644 --- a/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/SharedPreferencesUiStateRepository.kt @@ -20,6 +20,8 @@ import android.content.SharedPreferences import androidx.core.content.edit import im.vector.app.features.home.RoomListDisplayMode import im.vector.app.features.settings.VectorPreferences +import org.matrix.android.sdk.api.session.Session +import timber.log.Timber import javax.inject.Inject /** @@ -59,16 +61,38 @@ class SharedPreferencesUiStateRepository @Inject constructor( } } - override fun storeSelectedSpace(spaceId: String?, sessionId: String) { - sharedPreferences.edit { + override fun storeSelectedSpace(spaceId: String?, sessionId: Session?) { + Timber.w("VAL: storeSelectedSpace $spaceId") + sharedPreferences.edit(true) { putString("$KEY_SELECTED_SPACE@$sessionId", spaceId) } } + override fun storeSelectedGroup(groupId: String?, sessionId: Session?) { + Timber.w("VAL: storeSelectedSpace $groupId") + sharedPreferences.edit(true) { + putString("$KEY_SELECTED_GROUP@$sessionId", groupId) + } + } + + override fun storeGroupingMethod(isSpace: Boolean, sessionId: Session?) { + sharedPreferences.edit(true) { + putBoolean("$KEY_SELECTED_METHOD@$sessionId", isSpace) + } + } + + override fun getSelectedGroup(sessionId: String): String? { + return sharedPreferences.getString("$KEY_SELECTED_GROUP@$sessionId", null) + } + override fun getSelectedSpace(sessionId: String): String? { return sharedPreferences.getString("$KEY_SELECTED_SPACE@$sessionId", null) } + override fun isGroupingMethodSpace(sessionId: String): Boolean { + return sharedPreferences.getBoolean("$KEY_SELECTED_METHOD@$sessionId", true) + } + companion object { private const val KEY_DISPLAY_MODE = "UI_STATE_DISPLAY_MODE" private const val VALUE_DISPLAY_MODE_CATCHUP = 0 @@ -76,5 +100,7 @@ class SharedPreferencesUiStateRepository @Inject constructor( private const val VALUE_DISPLAY_MODE_ROOMS = 2 private const val KEY_SELECTED_SPACE = "UI_STATE_SELECTED_SPACE" + private const val KEY_SELECTED_GROUP = "UI_STATE_SELECTED_GROUP" + private const val KEY_SELECTED_METHOD = "UI_STATE_SELECTED_METHOD" } } diff --git a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt index 25e81efa83..a8fe20b066 100644 --- a/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt +++ b/vector/src/main/java/im/vector/app/features/ui/UiStateRepository.kt @@ -17,6 +17,7 @@ package im.vector.app.features.ui import im.vector.app.features.home.RoomListDisplayMode +import org.matrix.android.sdk.api.session.Session /** * This interface is used to persist UI state across application restart @@ -32,7 +33,12 @@ interface UiStateRepository { fun storeDisplayMode(displayMode: RoomListDisplayMode) - fun storeSelectedSpace(spaceId: String?, sessionId: String) + fun storeSelectedSpace(spaceId: String?, sessionId: Session?) + fun storeSelectedGroup(groupId: String?, sessionId: Session?) + + fun storeGroupingMethod(isSpace: Boolean, sessionId: Session?) fun getSelectedSpace(sessionId: String): String? + fun getSelectedGroup(sessionId: String): String? + fun isGroupingMethodSpace(sessionId: String): Boolean } diff --git a/vector/src/main/res/drawable/rounded_rect_stroke_8.xml b/vector/src/main/res/drawable/rounded_rect_stroke_8.xml new file mode 100644 index 0000000000..56696f1b58 --- /dev/null +++ b/vector/src/main/res/drawable/rounded_rect_stroke_8.xml @@ -0,0 +1,11 @@ + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space.xml b/vector/src/main/res/layout/item_space.xml index ef147f7730..91952391be 100644 --- a/vector/src/main/res/layout/item_space.xml +++ b/vector/src/main/res/layout/item_space.xml @@ -39,20 +39,20 @@ android:layout_height="wrap_content" android:layout_marginStart="4dp" android:layout_marginEnd="4dp" - android:paddingStart="4dp" - android:paddingEnd="4dp" android:gravity="center" android:minWidth="16dp" android:minHeight="16dp" + android:paddingStart="4dp" + android:paddingEnd="4dp" android:textColor="@android:color/white" android:textSize="10sp" + android:visibility="gone" app:layout_constraintCircle="@+id/groupAvatarImageView" app:layout_constraintCircleAngle="45" app:layout_constraintCircleRadius="24dp" - android:visibility="gone" - tools:visibility="visible" tools:background="@drawable/bg_unread_highlight" - tools:text="147" /> + tools:text="147" + tools:visibility="visible" /> + + diff --git a/vector/src/main/res/layout/item_space_add.xml b/vector/src/main/res/layout/item_space_add.xml new file mode 100644 index 0000000000..12f75c0356 --- /dev/null +++ b/vector/src/main/res/layout/item_space_add.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/item_space_beta_header.xml b/vector/src/main/res/layout/item_space_beta_header.xml new file mode 100644 index 0000000000..77e260d9fd --- /dev/null +++ b/vector/src/main/res/layout/item_space_beta_header.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/vector/src/main/res/layout/view_state.xml b/vector/src/main/res/layout/view_state.xml index 731f3bd5c8..51a7afe652 100644 --- a/vector/src/main/res/layout/view_state.xml +++ b/vector/src/main/res/layout/view_state.xml @@ -9,6 +9,7 @@ diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 55e898264d..46cd57ee5a 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -3337,4 +3337,7 @@ Add existing rooms and space + Welcome to Spaces! + Spaces are ways to group rooms and people for work, fun or just yourself. + You are invited