Merge Space and Communities initial commit

This commit is contained in:
Valere 2021-04-23 17:26:17 +02:00
parent 733d4185c4
commit 1ce7085ca9
40 changed files with 673 additions and 672 deletions

View File

@ -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<RoomSummary>>(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)
}
}
}
}

View File

@ -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)

View File

@ -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

View File

@ -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()
}

View File

@ -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<FragmentGroupListBinding>(),
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))
}
}

View File

@ -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<GroupListViewState, GroupListAction, GroupListViewEvents>(initialState) {
@AssistedFactory
interface Factory {
fun create(initialState: GroupListViewState): GroupListViewModel
}
companion object : MvRxViewModelFactory<GroupListViewModel, GroupListViewState> {
@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<GroupSummary, List<GroupSummary>, List<GroupSummary>>(
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)
}
}
}

View File

@ -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<List<GroupSummary>> = Uninitialized,
val selectedGroup: GroupSummary? = null
) : MvRxState

View File

@ -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<GroupSummary>?, 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)
}
}

View File

@ -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<GroupSummary>>(Option.empty())

View File

@ -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))

View File

@ -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()

View File

@ -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))
}
}
}
}

View File

@ -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<HomeDetailViewState, HomeDetailAction, EmptyViewEvents>(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()

View File

@ -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<GroupSummary> = Option.empty(),
val spaceSummary: Option<RoomSummary> = Option.empty(),
val roomGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val myMatrixItem: MatrixItem? = null,
val asyncRooms: Async<List<RoomSummary>> = Uninitialized,
val displayMode: RoomListDisplayMode = RoomListDisplayMode.PEOPLE,

View File

@ -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()

View File

@ -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()

View File

@ -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)

View File

@ -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<RoomsSection> {
val activeSpaceAwareQueries = mutableListOf<UpdatableLivePageResult>()
val activeGroupAwareQueries = mutableListOf<UpdatableLivePageResult>()
val sections = mutableListOf<RoomsSection>()
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)
}

View File

@ -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<RoomListViewState, RoomListAction, RoomListViewEvents>(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<RoomsSection> 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()
},

View File

@ -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<Session>,
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
)
}

View File

@ -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<String, ChangeMembershipState> = emptyMap(),
val asyncSuggestedRooms: Async<List<SpaceChildInfo>> = Uninitialized,
val currentUserName: String? = null,
val currentSpace: Async<RoomSummary?> = Uninitialized
val currentRoomGrouping: Async<RoomGroupingMethod> = Uninitialized
) : MvRxState {
constructor(args: RoomListParams) : this(displayMode = args.displayMode)

View File

@ -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<List<SpaceChildInfo>>()
appStateHandler.selectedSpaceObservable
appStateHandler.selectedRoomGroupingObservable
.distinctUntilChanged()
.switchMap { activeSpaceOption ->
val selectedSpace = activeSpaceOption.orNull()
.switchMap { groupingMethod ->
val selectedSpace = groupingMethod.space()
if (selectedSpace == null) {
Observable.just(emptyList())
} else {

View File

@ -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)
}
}

View File

@ -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<SpaceAddItem.Holder>() {
@EpoxyAttribute var listener: (() -> Unit)? = null
override fun bind(holder: Holder) {
super.bind(holder)
holder.view.setOnClickListener(
DebouncedClickListener({
listener?.invoke()
})
)
}
class Holder : VectorEpoxyHolder()
}

View File

@ -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<SpaceBetaHeaderItem.Holder>() {
class Holder : VectorEpoxyHolder()
}

View File

@ -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()
}

View File

@ -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))
}
}

View File

@ -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()
}

View File

@ -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<MatrixItem.UserItem> = Uninitialized,
val asyncSpaces: Async<List<RoomSummary>> = Uninitialized,
val selectedSpace: RoomSummary? = null,
val selectedGroupingMethod: RoomGroupingMethod = RoomGroupingMethod.BySpace(null),
val rootSpaces: List<RoomSummary>? = null,
val legacyGroups: List<GroupSummary>? = null,
val expandedStates: Map<String, Boolean> = emptyMap(),
val homeAggregateCount : RoomAggregateNotificationCount = RoomAggregateNotificationCount(0, 0)
) : MvRxState

View File

@ -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<RoomSummary>?,
selected: RoomSummary?,
selected: RoomGroupingMethod,
rootSpaces: List<RoomSummary>?,
expandedStates: Map<String, Boolean>,
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?)
}
}

View File

@ -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<SpaceSummaryItem.Holder>() {
@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<SpaceSummaryItem.Holder>() {
holder.moreView.isVisible = false
}
holder.secondLineText.setTextOrHide(description)
if (hasChildren) {
// holder.collapseIndicator.setOnClickListener(
// DebouncedClickListener({ _ ->
@ -104,6 +107,7 @@ abstract class SpaceSummaryItem : VectorEpoxyModel<SpaceSummaryItem.Holder>() {
class Holder : VectorEpoxyHolder() {
val avatarImageView by bind<ImageView>(R.id.groupAvatarImageView)
val groupNameView by bind<TextView>(R.id.groupNameView)
val secondLineText by bind<TextView>(R.id.groupDescView)
val rootView by bind<CheckableConstraintLayout>(R.id.itemGroupLayout)
val moreView by bind<ImageView>(R.id.groupTmpLeave)
val collapseIndicator by bind<ImageView>(R.id.groupChildrenCollapse)

View File

@ -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()
)
}

View File

@ -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"
}
}

View File

@ -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
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<size android:width="40dp" android:height="40dp"/>
<stroke android:width="1dp" android:color="?riotx_list_bottom_sheet_divider_color" />
<corners android:radius="8dp" />
</shape>

View File

@ -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" />
<TextView
@ -66,10 +66,30 @@
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
app:layout_constraintBottom_toTopOf="@+id/groupDescView"
app:layout_constraintEnd_toStartOf="@+id/groupChildrenCollapse"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem/random" />
<TextView
android:id="@+id/groupDescView"
android:visibility="gone"
tools:visibility="visible"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="1"
android:textColor="?riotx_text_secondary"
android:textSize="15sp"
app:layout_constraintBottom_toTopOf="@+id/groupBottomSeparator"
app:layout_constraintEnd_toStartOf="@+id/groupChildrenCollapse"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toBottomOf="@id/groupNameView"
app:layout_constraintVertical_chainStyle="packed"
tools:text="@tools:sample/lorem/random" />

View File

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="utf-8"?>
<im.vector.app.core.platform.CheckableConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/itemGroupLayout"
android:layout_width="match_parent"
android:layout_height="65dp"
android:background="@drawable/bg_space_item"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground">
<ImageView
android:id="@+id/groupAvatarImageView"
android:layout_width="42dp"
android:layout_height="42dp"
android:layout_gravity="center"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:background="@drawable/rounded_rect_shape_8"
android:backgroundTint="?colorPrimary"
android:contentDescription="@string/avatar"
android:duplicateParentState="true"
android:importantForAccessibility="no"
android:padding="10dp"
android:src="@drawable/ic_fab_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/groupNameView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/layout_horizontal_margin"
android:layout_marginEnd="@dimen/layout_horizontal_margin"
android:ellipsize="end"
android:maxLines="1"
android:text="@string/add_space"
android:textColor="?colorPrimary"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/groupAvatarImageView"
app:layout_constraintTop_toTopOf="parent" />
<!-- <View-->
<!-- android:id="@+id/groupBottomSeparator"-->
<!-- android:layout_width="0dp"-->
<!-- android:layout_height="1dp"-->
<!-- android:background="?riotx_header_panel_border_mobile"-->
<!-- app:layout_constraintBottom_toBottomOf="parent"-->
<!-- app:layout_constraintEnd_toEndOf="parent"-->
<!-- app:layout_constraintStart_toStartOf="parent" />-->
</im.vector.app.core.platform.CheckableConstraintLayout>

View File

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/rounded_rect_stroke_8"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:orientation="vertical">
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:importantForAccessibility="no"
android:src="@drawable/ic_beta_pill" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="@string/spaces_beta_welcome_to_spaces"
android:textColor="?riotx_text_primary"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginBottom="8dp"
android:text="@string/spaces_beta_welcome_to_spaces_desc"
android:textColor="?riotx_text_secondary"
android:textSize="15sp" />
</LinearLayout>
</FrameLayout>
</FrameLayout>

View File

@ -9,6 +9,7 @@
<ProgressBar
android:id="@+id/progressBar"
style="?progressBarStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />

View File

@ -3337,4 +3337,7 @@
<string name="space_add_existing_rooms">Add existing rooms and space</string>
<string name="spaces_beta_welcome_to_spaces">Welcome to Spaces!</string>
<string name="spaces_beta_welcome_to_spaces_desc">Spaces are ways to group rooms and people for work, fun or just yourself.</string>
<string name="you_are_invited">You are invited</string>
</resources>