diff --git a/changelog.d/6889.wip b/changelog.d/6889.wip new file mode 100644 index 0000000000..067973aad9 --- /dev/null +++ b/changelog.d/6889.wip @@ -0,0 +1 @@ +[App Layout] new room invites screen diff --git a/vector/src/main/AndroidManifest.xml b/vector/src/main/AndroidManifest.xml index bed0b618d0..ea62aa1b58 100644 --- a/vector/src/main/AndroidManifest.xml +++ b/vector/src/main/AndroidManifest.xml @@ -349,6 +349,7 @@ + diff --git a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt index 6da47c4f7d..b21b4778e3 100644 --- a/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/MavericksViewModelModule.kt @@ -52,6 +52,7 @@ import im.vector.app.features.home.room.detail.timeline.reactions.ViewReactionsV import im.vector.app.features.home.room.detail.upgrade.MigrateRoomViewModel import im.vector.app.features.home.room.list.RoomListViewModel import im.vector.app.features.home.room.list.home.HomeRoomListViewModel +import im.vector.app.features.home.room.list.home.invites.InvitesViewModel import im.vector.app.features.homeserver.HomeServerCapabilitiesViewModel import im.vector.app.features.invite.InviteUsersToRoomViewModel import im.vector.app.features.location.LocationSharingViewModel @@ -618,4 +619,9 @@ interface MavericksViewModelModule { @IntoMap @MavericksViewModelKey(HomeRoomListViewModel::class) fun homeRoomListViewModel(factory: HomeRoomListViewModel.Factory): MavericksAssistedViewModelFactory<*, *> + + @Binds + @IntoMap + @MavericksViewModelKey(InvitesViewModel::class) + fun invitesViewModel(factory: InvitesViewModel.Factory): MavericksAssistedViewModelFactory<*, *> } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt index 8fedfef323..3e8c2b5dcd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListFragment.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.list.home +import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -48,6 +49,8 @@ import im.vector.app.features.home.room.list.actions.RoomListSharedAction import im.vector.app.features.home.room.list.actions.RoomListSharedActionViewModel import im.vector.app.features.home.room.list.home.filter.HomeFilteredRoomsController import im.vector.app.features.home.room.list.home.filter.HomeRoomFilter +import im.vector.app.features.home.room.list.home.invites.InvitesActivity +import im.vector.app.features.home.room.list.home.invites.InvitesCounterController import im.vector.app.features.home.room.list.home.recent.RecentRoomCarouselController import im.vector.app.features.spaces.SpaceListBottomSheet import kotlinx.coroutines.flow.launchIn @@ -66,6 +69,7 @@ class HomeRoomListFragment : @Inject lateinit var roomSummaryItemFactory: RoomSummaryItemFactory @Inject lateinit var userPreferencesProvider: UserPreferencesProvider @Inject lateinit var recentRoomCarouselController: RecentRoomCarouselController + @Inject lateinit var invitesCounterController: InvitesCounterController private val roomListViewModel: HomeRoomListViewModel by fragmentViewModel() private lateinit var sharedQuickActionsViewModel: RoomListQuickActionsSharedActionViewModel @@ -266,9 +270,19 @@ class HomeRoomListFragment : controller.submitList(list) } }.adapter + is HomeRoomSection.InvitesCountData -> invitesCounterController.also { controller -> + controller.clickListener = ::onInvitesCounterClicked + section.count.observe(viewLifecycleOwner) { count -> + controller.submitData(count) + } + }.adapter } } + private fun onInvitesCounterClicked() { + startActivity(Intent(activity, InvitesActivity::class.java)) + } + private fun onRoomFilterChanged(filter: HomeRoomFilter) { roomListViewModel.handle(HomeRoomListAction.ChangeRoomFilter(filter)) } @@ -285,6 +299,7 @@ class HomeRoomListFragment : override fun onDestroyView() { views.roomListView.cleanup() recentRoomCarouselController.listener = null + invitesCounterController.clickListener = null super.onDestroyView() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt index 711ba0c10a..5ecf9d6d96 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomListViewModel.kt @@ -16,6 +16,7 @@ package im.vector.app.features.home.room.list.home +import androidx.lifecycle.map import androidx.paging.PagedList import arrow.core.toOption import com.airbnb.mvrx.MavericksViewModelFactory @@ -100,9 +101,9 @@ class HomeRoomListViewModel @AssistedInject constructor( private fun configureSections() = viewModelScope.launch { val newSections = mutableSetOf() + newSections.add(getInvitesCountSection()) val areSettingsEnabled = preferencesStore.areRecentsEnabledFlow.first() - if (areSettingsEnabled) { newSections.add(getRecentRoomsSection()) } @@ -127,6 +128,19 @@ class HomeRoomListViewModel @AssistedInject constructor( ) } + private fun getInvitesCountSection(): HomeRoomSection.InvitesCountData { + val builder = RoomSummaryQueryParams.Builder().also { + it.memberships = listOf(Membership.INVITE) + } + + val liveCount = session.roomService().getRoomSummariesLive( + builder.build(), + RoomSortOrder.ACTIVITY + ).map { it.count() } + + return HomeRoomSection.InvitesCountData(liveCount) + } + private suspend fun getFilteredRoomsSection(): HomeRoomSection.RoomSummaryData { val builder = RoomSummaryQueryParams.Builder().also { it.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt index 74ec46d6b7..29df594d06 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/HomeRoomSection.kt @@ -32,4 +32,8 @@ sealed class HomeRoomSection { data class RecentRoomsData( val list: LiveData> ) : HomeRoomSection() + + data class InvitesCountData( + val count: LiveData + ) : HomeRoomSection() } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt new file mode 100644 index 0000000000..4bc292be27 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InviteCounterItem.kt @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import com.airbnb.epoxy.EpoxyAttribute +import com.airbnb.epoxy.EpoxyModelClass +import im.vector.app.R +import im.vector.app.core.epoxy.ClickListener +import im.vector.app.core.epoxy.VectorEpoxyHolder +import im.vector.app.core.epoxy.VectorEpoxyModel +import im.vector.app.features.home.room.list.UnreadCounterBadgeView + +@EpoxyModelClass +abstract class InviteCounterItem : VectorEpoxyModel(R.layout.item_invites_count) { + + @EpoxyAttribute var invitesCount: Int = 0 + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var listener: ClickListener? = null + + override fun bind(holder: Holder) { + super.bind(holder) + holder.view.setOnClickListener(listener) + holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(invitesCount, true)) + } + + class Holder : VectorEpoxyHolder() { + val unreadCounterBadgeView by bind(R.id.invites_count_badge) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt new file mode 100644 index 0000000000..ed6ed23c9d --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesAction.kt @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import im.vector.app.core.platform.VectorViewModelAction +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +sealed class InvitesAction : VectorViewModelAction { + data class AcceptInvitation(val roomSummary: RoomSummary) : InvitesAction() + data class RejectInvitation(val roomSummary: RoomSummary) : InvitesAction() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt new file mode 100644 index 0000000000..b590caab42 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesActivity.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.addFragment +import im.vector.app.core.platform.VectorBaseActivity +import im.vector.app.databinding.ActivitySimpleBinding + +@AndroidEntryPoint +class InvitesActivity : VectorBaseActivity() { + + override fun getBinding() = ActivitySimpleBinding.inflate(layoutInflater) + + override fun initUiAndData() { + if (isFirstCreation()) { + addFragment(views.simpleFragmentContainer, InvitesFragment::class.java) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt new file mode 100644 index 0000000000..1511b97c3c --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesController.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.paging.PagedListEpoxyController +import im.vector.app.core.utils.createUIHandler +import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.home.room.list.RoomListListener +import im.vector.app.features.home.room.list.RoomSummaryItemFactory +import im.vector.app.features.home.room.list.RoomSummaryItemPlaceHolder_ +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import javax.inject.Inject + +class InvitesController @Inject constructor( + private val roomSummaryItemFactory: RoomSummaryItemFactory, +) : PagedListEpoxyController( + // Important it must match the PageList builder notify Looper + modelBuildingHandler = createUIHandler() +) { + + var roomChangeMembershipStates: Map? = null + set(value) { + field = value + requestForcedModelBuild() + } + + var listener: RoomListListener? = null + + override fun buildItemModel(currentPosition: Int, item: RoomSummary?): EpoxyModel<*> { + item ?: return RoomSummaryItemPlaceHolder_().apply { id(currentPosition) } + return roomSummaryItemFactory.create(item, roomChangeMembershipStates.orEmpty(), emptySet(), RoomListDisplayMode.ROOMS, listener) + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt new file mode 100644 index 0000000000..82a31d30a9 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesCounterController.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import com.airbnb.epoxy.EpoxyController +import im.vector.app.core.resources.StringProvider +import javax.inject.Inject + +class InvitesCounterController @Inject constructor( + val stringProvider: StringProvider +) : EpoxyController() { + + private var count = 0 + var clickListener: (() -> Unit)? = null + + override fun buildModels() { + val host = this + if (count != 0) { + inviteCounterItem { + id("invites_counter") + invitesCount(host.count) + listener { host.clickListener?.invoke() } + } + } + } + + fun submitData(count: Int?) { + this.count = count ?: 0 + requestModelBuild() + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt new file mode 100644 index 0000000000..74b46cec33 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesFragment.kt @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.airbnb.mvrx.fragmentViewModel +import com.airbnb.mvrx.withState +import dagger.hilt.android.AndroidEntryPoint +import im.vector.app.core.extensions.configureWith +import im.vector.app.core.platform.VectorBaseFragment +import im.vector.app.databinding.FragmentInvitesBinding +import im.vector.app.features.analytics.plan.ViewRoom +import im.vector.app.features.home.room.list.RoomListListener +import im.vector.app.features.notifications.NotificationDrawerManager +import org.matrix.android.sdk.api.session.room.model.RoomSummary +import org.matrix.android.sdk.api.session.room.model.SpaceChildInfo +import javax.inject.Inject + +@AndroidEntryPoint +class InvitesFragment : VectorBaseFragment(), RoomListListener { + + @Inject lateinit var controller: InvitesController + @Inject lateinit var notificationDrawerManager: NotificationDrawerManager + + private val viewModel by fragmentViewModel(InvitesViewModel::class) + + override fun getBinding(inflater: LayoutInflater, container: ViewGroup?): FragmentInvitesBinding { + return FragmentInvitesBinding.inflate(inflater, container, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + setupToolbar(views.invitesToolbar) + .allowBack() + + views.invitesRecycler.configureWith(controller) + controller.listener = this + + viewModel.onEach(InvitesViewState::roomMembershipChanges) { + controller.roomChangeMembershipStates = it + } + + viewModel.observeViewEvents { + when (it) { + is InvitesViewEvents.Failure -> showFailure(it.throwable) + is InvitesViewEvents.OpenRoom -> handleOpenRoom(it.roomSummary, it.shouldCloseInviteView) + InvitesViewEvents.Close -> handleClose() + } + } + } + + private fun handleClose() { + requireActivity().finish() + } + + private fun handleOpenRoom(roomSummary: RoomSummary, shouldCloseInviteView: Boolean) { + navigator.openRoom( + context = requireActivity(), + roomId = roomSummary.roomId, + isInviteAlreadyAccepted = true, + trigger = ViewRoom.Trigger.RoomList // #6508 + ) + if (shouldCloseInviteView) { + requireActivity().finish() + } + } + + override fun invalidate(): Unit = withState(viewModel) { state -> + super.invalidate() + + state.pagedList?.observe(viewLifecycleOwner) { list -> + controller.submitList(list) + } + } + + override fun onRejectRoomInvitation(room: RoomSummary) { + notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } + viewModel.handle(InvitesAction.RejectInvitation(room)) + } + + override fun onAcceptRoomInvitation(room: RoomSummary) { + notificationDrawerManager.updateEvents { it.clearMemberShipNotificationForRoom(room.roomId) } + viewModel.handle(InvitesAction.AcceptInvitation(room)) + } + + override fun onJoinSuggestedRoom(room: SpaceChildInfo) = Unit + + override fun onSuggestedRoomClicked(room: SpaceChildInfo) = Unit + + override fun onRoomClicked(room: RoomSummary) = Unit + + override fun onRoomLongClicked(room: RoomSummary): Boolean = false +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt new file mode 100644 index 0000000000..d68577cf95 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewEvents.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import im.vector.app.core.platform.VectorViewEvents +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +sealed class InvitesViewEvents : VectorViewEvents { + data class Failure(val throwable: Throwable) : InvitesViewEvents() + data class OpenRoom(val roomSummary: RoomSummary, val shouldCloseInviteView: Boolean) : InvitesViewEvents() + object Close : InvitesViewEvents() +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt new file mode 100644 index 0000000000..b0d854be66 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewModel.kt @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import androidx.paging.PagedList +import com.airbnb.mvrx.MavericksViewModelFactory +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject +import im.vector.app.core.di.MavericksAssistedViewModelFactory +import im.vector.app.core.di.hiltMavericksViewModelFactory +import im.vector.app.core.platform.VectorViewModel +import kotlinx.coroutines.launch +import org.matrix.android.sdk.api.extensions.orFalse +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.RoomSortOrder +import org.matrix.android.sdk.api.session.room.RoomSummaryQueryParams +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership +import timber.log.Timber + +class InvitesViewModel @AssistedInject constructor( + @Assisted val initialState: InvitesViewState, + private val session: Session, +) : VectorViewModel(initialState) { + + private val pagedListConfig = PagedList.Config.Builder() + .setPageSize(10) + .setInitialLoadSizeHint(20) + .setEnablePlaceholders(true) + .setPrefetchDistance(10) + .build() + + @AssistedFactory + interface Factory : MavericksAssistedViewModelFactory { + override fun create(initialState: InvitesViewState): InvitesViewModel + } + + companion object : MavericksViewModelFactory by hiltMavericksViewModelFactory() + + init { + observeInvites() + } + + override fun handle(action: InvitesAction) { + when (action) { + is InvitesAction.AcceptInvitation -> handleAcceptInvitation(action) + is InvitesAction.RejectInvitation -> handleRejectInvitation(action) + } + } + + private fun handleRejectInvitation(action: InvitesAction.RejectInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { + // Request already sent, should not happen + Timber.w("Try to left an already leaving or joining room. Should not happen") + return@withState + } + + val shouldCloseInviteView = state.pagedList?.value?.size == 1 + + viewModelScope.launch { + try { + session.roomService().leaveRoom(roomId) + // We do not update the rejectingRoomsIds here, because, the room is not rejected yet regarding the sync data. + // Instead, we wait for the room to be rejected + // Known bug: if the user is invited again (after rejecting the first invitation), the loading will be displayed instead of the buttons. + // If we update the state, the button will be displayed again, so it's not ideal... + if (shouldCloseInviteView) { + _viewEvents.post(InvitesViewEvents.Close) + } + } catch (failure: Throwable) { + // Notify the user + _viewEvents.post(InvitesViewEvents.Failure(failure)) + } + } + } + + private fun handleAcceptInvitation(action: InvitesAction.AcceptInvitation) = withState { state -> + val roomId = action.roomSummary.roomId + val roomMembershipChange = state.roomMembershipChanges[roomId] + if (roomMembershipChange?.isInProgress().orFalse()) { + // Request already sent, should not happen + Timber.w("Try to join an already joining room. Should not happen") + return@withState + } + // close invites view when navigate to a room from the last one invite + + val shouldCloseInviteView = state.pagedList?.value?.size == 1 + + _viewEvents.post(InvitesViewEvents.OpenRoom(action.roomSummary, shouldCloseInviteView)) + + // quick echo + setState { + copy( + roomMembershipChanges = roomMembershipChanges.mapValues { + if (it.key == roomId) { + ChangeMembershipState.Joining + } else { + it.value + } + } + ) + } + } + + private fun observeInvites() { + val builder = RoomSummaryQueryParams.Builder().also { + it.memberships = listOf(Membership.INVITE) + } + val pagedList = session.roomService().getPagedRoomSummariesLive( + queryParams = builder.build(), + pagedListConfig = pagedListConfig, + sortOrder = RoomSortOrder.ACTIVITY + ) + + setState { + copy(pagedList = pagedList) + } + } +} diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt new file mode 100644 index 0000000000..708db29604 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/home/room/list/home/invites/InvitesViewState.kt @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package im.vector.app.features.home.room.list.home.invites + +import androidx.lifecycle.LiveData +import androidx.paging.PagedList +import com.airbnb.mvrx.MavericksState +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.RoomSummary + +data class InvitesViewState( + val pagedList: LiveData>? = null, + val roomMembershipChanges: Map = emptyMap(), +) : MavericksState diff --git a/vector/src/main/res/layout/fragment_invites.xml b/vector/src/main/res/layout/fragment_invites.xml new file mode 100644 index 0000000000..74226357c9 --- /dev/null +++ b/vector/src/main/res/layout/fragment_invites.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + diff --git a/vector/src/main/res/layout/item_invites_count.xml b/vector/src/main/res/layout/item_invites_count.xml new file mode 100644 index 0000000000..6408749941 --- /dev/null +++ b/vector/src/main/res/layout/item_invites_count.xml @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vector/src/main/res/values/strings.xml b/vector/src/main/res/values/strings.xml index 980524dee8..5115510057 100644 --- a/vector/src/main/res/values/strings.xml +++ b/vector/src/main/res/values/strings.xml @@ -441,6 +441,9 @@ "System Alerts" Suggested Rooms + + Invites + Conversations Matrix contacts only