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