diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt index 871c5378a6..b7377df1b3 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/RoomService.kt @@ -125,6 +125,12 @@ interface RoomService { */ suspend fun deleteRoomAlias(roomAlias: String) + /** + * Return the current local changes membership for the given room. + * see [getChangeMembershipsLive] for more details. + */ + fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState + /** * Return a live data of all local changes membership that happened since the session has been opened. * It allows you to track this in your client to known what is currently being processed by the SDK. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt index e6da21315b..c750cb1f73 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/SessionModule.kt @@ -386,4 +386,7 @@ internal abstract class SessionModule { @Binds abstract fun bindEventSenderProcessor(processor: EventSenderProcessorCoroutine): EventSenderProcessor + + + } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt index d9fe1288e2..632ea4c450 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/DefaultRoomService.kt @@ -134,6 +134,10 @@ internal class DefaultRoomService @Inject constructor( deleteRoomAliasTask.execute(DeleteRoomAliasTask.Params(roomAlias)) } + override fun getChangeMemberships(roomIdOrAlias: String): ChangeMembershipState { + return roomChangeMembershipStateDataSource.getState(roomIdOrAlias) + } + override fun getChangeMembershipsLive(): LiveData> { return roomChangeMembershipStateDataSource.getLiveStates() } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt index b9c547d4fb..35d8cb08af 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/RoomChangeMembershipStateDataSource.kt @@ -21,6 +21,7 @@ import androidx.lifecycle.MutableLiveData import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState import org.matrix.android.sdk.api.session.room.model.Membership import org.matrix.android.sdk.internal.session.SessionScope +import java.util.concurrent.ConcurrentHashMap import javax.inject.Inject /** @@ -30,7 +31,7 @@ import javax.inject.Inject internal class RoomChangeMembershipStateDataSource @Inject constructor() { private val mutableLiveStates = MutableLiveData>(emptyMap()) - private val states = HashMap() + private val states = ConcurrentHashMap() /** * This will update local states to be synced with the server. diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt index 33776e4f6e..562b25683b 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/membership/joining/JoinRoomTask.kt @@ -54,6 +54,10 @@ internal class DefaultJoinRoomTask @Inject constructor( ) : JoinRoomTask { override suspend fun execute(params: JoinRoomTask.Params) { + val currentState = roomChangeMembershipStateDataSource.getState(params.roomIdOrAlias) + if (currentState.isInProgress() || currentState == ChangeMembershipState.Joined) { + return + } roomChangeMembershipStateDataSource.updateState(params.roomIdOrAlias, ChangeMembershipState.Joining) val joinRoomResponse = try { executeRequest(globalErrorReceiver) { diff --git a/vector/src/main/java/im/vector/app/AppStateHandler.kt b/vector/src/main/java/im/vector/app/AppStateHandler.kt index eb60a66436..3822bd6c08 100644 --- a/vector/src/main/java/im/vector/app/AppStateHandler.kt +++ b/vector/src/main/java/im/vector/app/AppStateHandler.kt @@ -22,22 +22,16 @@ 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.invite.InvitesAcceptor import im.vector.app.features.session.coroutineScope import im.vector.app.features.ui.UiStateRepository -import io.reactivex.Observable import io.reactivex.disposables.CompositeDisposable import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch 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.members.ChangeMembershipState -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.rx.rx -import timber.log.Timber -import java.util.concurrent.TimeUnit import javax.inject.Inject import javax.inject.Singleton @@ -58,7 +52,8 @@ fun RoomGroupingMethod.group() = (this as? RoomGroupingMethod.ByLegacyGroup)?.gr class AppStateHandler @Inject constructor( private val sessionDataSource: ActiveSessionDataSource, private val uiStateRepository: UiStateRepository, - private val activeSessionHolder: ActiveSessionHolder + private val activeSessionHolder: ActiveSessionHolder, + private val invitesAcceptor: InvitesAcceptor ) : LifecycleObserver { private val compositeDisposable = CompositeDisposable() @@ -102,13 +97,13 @@ class AppStateHandler @Inject constructor( } } - private fun observeActiveSession(){ + private fun observeActiveSession() { sessionDataSource.observe() .distinctUntilChanged() .subscribe { // sessionDataSource could already return a session while activeSession holder still returns null it.orNull()?.let { session -> - observeInvitesForAutoAccept(session) + invitesAcceptor.onSessionActive(session) if (uiStateRepository.isGroupingMethodSpace(session.sessionId)) { setCurrentSpace(uiStateRepository.getSelectedSpace(session.sessionId), session) } else { @@ -120,41 +115,6 @@ class AppStateHandler @Inject constructor( } } - private fun observeInvitesForAutoAccept(session: Session?) { - if (session == null) return - val roomQueryParams = roomSummaryQueryParams { - this.memberships = listOf(Membership.INVITE) - } - val rxSession = session.rx() - Observable - .combineLatest( - rxSession.liveRoomSummaries(roomQueryParams).debounce(1, TimeUnit.SECONDS), - rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), - { invitedRooms, membershipsChanged -> - val roomIdsToJoin = mutableListOf() - for (room in invitedRooms) { - val roomMembershipChanged = membershipsChanged[room.roomId] ?: ChangeMembershipState.Unknown - if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { - roomIdsToJoin.add(room.roomId) - } - } - roomIdsToJoin - } - ) - .doOnNext { roomIdsToJoin -> - session.coroutineScope.launch { - for (roomId in roomIdsToJoin) { - Timber.v("Auto accept invite for room: $roomId") - tryOrNull { session.joinRoom(roomId) } - } - } - } - .subscribe() - .also { - compositeDisposable.add(it) - } - } - fun safeActiveSpaceId(): String? { return (selectedSpaceDataSource.currentValue?.orNull() as? RoomGroupingMethod.BySpace)?.spaceSummary?.roomId } diff --git a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt index 3c11bfcd13..38edb771bb 100644 --- a/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/ScreenComponent.kt @@ -50,6 +50,7 @@ import im.vector.app.features.home.room.detail.widget.RoomWidgetsBottomSheet import im.vector.app.features.home.room.filtered.FilteredRoomsActivity import im.vector.app.features.home.room.list.RoomListModule import im.vector.app.features.home.room.list.actions.RoomListQuickActionsBottomSheet +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.invite.InviteUsersToRoomActivity import im.vector.app.features.invite.VectorInviteView import im.vector.app.features.link.LinkHandlerActivity @@ -122,6 +123,7 @@ interface ScreenComponent { fun errorFormatter(): ErrorFormatter fun uiStateRepository(): UiStateRepository fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog + fun autoAcceptInvites(): AutoAcceptInvites /* ========================================================================================== * Activities diff --git a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt index e5a47e872c..4a3379cb5a 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorComponent.kt @@ -42,6 +42,7 @@ import im.vector.app.features.home.room.detail.timeline.helper.MatrixItemColorPr import im.vector.app.features.home.room.detail.timeline.helper.RoomSummariesHolder import im.vector.app.features.html.EventHtmlRenderer import im.vector.app.features.html.VectorHtmlCompressor +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.login.ReAuthHelper import im.vector.app.features.navigation.Navigator import im.vector.app.features.notifications.NotifiableEventResolver @@ -160,6 +161,8 @@ interface VectorComponent { fun pinLocker(): PinLocker + fun autoAcceptInvites(): AutoAcceptInvites + fun webRtcCallManager(): WebRtcCallManager fun roomSummaryHolder(): RoomSummariesHolder diff --git a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt index 77cad0ae73..006a2f5aa0 100644 --- a/vector/src/main/java/im/vector/app/core/di/VectorModule.kt +++ b/vector/src/main/java/im/vector/app/core/di/VectorModule.kt @@ -25,6 +25,8 @@ import dagger.Module import dagger.Provides import im.vector.app.core.error.DefaultErrorFormatter import im.vector.app.core.error.ErrorFormatter +import im.vector.app.features.invite.AutoAcceptInvites +import im.vector.app.features.invite.CompileTimeAutoAcceptInvites import im.vector.app.features.navigation.DefaultNavigator import im.vector.app.features.navigation.Navigator import im.vector.app.features.pin.PinCodeStore @@ -105,4 +107,7 @@ abstract class VectorModule { @Binds abstract fun bindPinCodeStore(store: SharedPrefPinCodeStore): PinCodeStore + + @Binds + abstract fun bindAutoAcceptInvites(autoAcceptInvites: CompileTimeAutoAcceptInvites): AutoAcceptInvites } diff --git a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt index d636259512..f282a3137c 100644 --- a/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/HomeDetailViewModel.kt @@ -31,6 +31,7 @@ import im.vector.app.features.call.dialpad.DialPadLookup import im.vector.app.features.call.lookup.CallProtocolsChecker import im.vector.app.features.call.webrtc.WebRtcCallManager import im.vector.app.features.createdirect.DirectRoomHelper +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.ui.UiStateRepository import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.Dispatchers @@ -56,7 +57,8 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho private val uiStateRepository: UiStateRepository, private val callManager: WebRtcCallManager, private val directRoomHelper: DirectRoomHelper, - private val appStateHandler: AppStateHandler) + private val appStateHandler: AppStateHandler, +private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState), CallProtocolsChecker.Listener { @@ -204,8 +206,25 @@ class HomeDetailViewModel @AssistedInject constructor(@Assisted initialState: Ho } is RoomGroupingMethod.BySpace -> { val activeSpaceRoomId = groupingMethod.spaceSummary?.roomId - val dmInvites = 0 - val roomsInvite = 0 + var dmInvites = 0 + var roomsInvite = 0 + if(!autoAcceptInvites.hideInvites) { + dmInvites = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_DM + activeSpaceFilter = activeSpaceRoomId?.let { ActiveSpaceFilter.ActiveSpace(it) } ?: ActiveSpaceFilter.None + } + ).size + + roomsInvite = session.getRoomSummaries( + roomSummaryQueryParams { + memberships = listOf(Membership.INVITE) + roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(groupingMethod.spaceSummary?.roomId) + } + ).size + } val dmRooms = session.getNotificationCountForRooms( roomSummaryQueryParams { diff --git a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt index 44a2f8cf09..e9e2447b39 100644 --- a/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/UnreadMessagesSharedViewModel.kt @@ -29,6 +29,7 @@ import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.EmptyAction import im.vector.app.core.platform.EmptyViewEvents import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import io.reactivex.Observable import io.reactivex.schedulers.Schedulers @@ -54,7 +55,8 @@ data class CountInfo( class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initialState: UnreadMessagesState, session: Session, private val vectorPreferences: VectorPreferences, - appStateHandler: AppStateHandler) + appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites) : VectorViewModel(initialState) { @AssistedFactory @@ -92,7 +94,17 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) } ) - val invites = 0 + val invites = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + this.activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(null) + } + ).size + } + copy( homeSpaceUnread = RoomAggregateNotificationCount( counts.notificationCount + invites, @@ -124,8 +136,13 @@ class UnreadMessagesSharedViewModel @AssistedInject constructor(@Assisted initia is RoomGroupingMethod.BySpace -> { val selectedSpace = appStateHandler.safeActiveSpaceId() - val inviteCount = 0 - + val inviteCount = if (autoAcceptInvites.hideInvites) { + 0 + } else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN) diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt index 62b0612d4b..a23251510f 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/GroupRoomListSectionBuilder.kt @@ -22,6 +22,7 @@ import im.vector.app.R import im.vector.app.RoomGroupingMethod import im.vector.app.core.resources.StringProvider import im.vector.app.features.home.RoomListDisplayMode +import im.vector.app.features.invite.AutoAcceptInvites import io.reactivex.disposables.Disposable import io.reactivex.schedulers.Schedulers import kotlinx.coroutines.CoroutineScope @@ -38,6 +39,7 @@ class GroupRoomListSectionBuilder( val stringProvider: StringProvider, val viewModelScope: CoroutineScope, val appStateHandler: AppStateHandler, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit ) : RoomListSectionBuilder { @@ -48,15 +50,15 @@ class GroupRoomListSectionBuilder( val actualGroupId = appStateHandler.safeActiveGroupId() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { // 3 sections Invites / Fav / Dms buildPeopleSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { // 5 sections invites / Fav / Rooms / Low Priority / Server notice buildRoomsSections(sections, activeGroupAwareQueries, actualGroupId) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { // Used when searching for rooms withQueryParams( { @@ -73,17 +75,18 @@ class GroupRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { - addSection( - sections, - activeGroupAwareQueries, - R.string.invitations_header, - true - ) { - it.memberships = listOf(Membership.INVITE) - it.roomCategoryFilter = RoomCategoryFilter.ALL - it.activeGroupId = actualGroupId + if (!autoAcceptInvites.hideInvites) { + addSection( + sections, + activeGroupAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + it.activeGroupId = actualGroupId + } } - addSection( sections, activeGroupAwareQueries, @@ -115,6 +118,18 @@ class GroupRoomListSectionBuilder( private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList, actualGroupId: String?) { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + it.activeGroupId = actualGroupId + } + } addSection( sections, @@ -170,6 +185,17 @@ class GroupRoomListSectionBuilder( activeSpaceAwareQueries: MutableList, actualGroupId: String? ) { + if (!autoAcceptInvites.hideInvites) { + addSection(sections, + activeSpaceAwareQueries, + R.string.invitations_header, + true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + it.activeGroupId = actualGroupId + } + } addSection( sections, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt index fbb8faebb0..c5f166ea5b 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModel.kt @@ -30,6 +30,7 @@ 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.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -49,7 +50,8 @@ class RoomListViewModel @Inject constructor( private val session: Session, private val stringProvider: StringProvider, private val appStateHandler: AppStateHandler, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { interface Factory { @@ -126,6 +128,7 @@ class RoomListViewModel @Inject constructor( appStateHandler, viewModelScope, suggestedRoomJoiningState, + autoAcceptInvites, { it.disposeOnClear() }, @@ -140,6 +143,7 @@ class RoomListViewModel @Inject constructor( stringProvider, viewModelScope, appStateHandler, + autoAcceptInvites, { it.disposeOnClear() }, diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt index a30c175f41..6b269356c7 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/RoomListViewModelFactory.kt @@ -18,6 +18,7 @@ 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.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import org.matrix.android.sdk.api.session.Session import javax.inject.Inject @@ -26,16 +27,18 @@ import javax.inject.Provider class RoomListViewModelFactory @Inject constructor(private val session: Provider, private val appStateHandler: AppStateHandler, private val stringProvider: StringProvider, - private val vectorPreferences: VectorPreferences) + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites) : RoomListViewModel.Factory { override fun create(initialState: RoomListViewState): RoomListViewModel { return RoomListViewModel( - initialState, - session.get(), - stringProvider, - appStateHandler, - vectorPreferences + initialState = initialState, + session = session.get(), + stringProvider = stringProvider, + appStateHandler = appStateHandler, + vectorPreferences = vectorPreferences, + autoAcceptInvites = autoAcceptInvites ) } } diff --git a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt index e1b205eca2..7d3816af26 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/list/SpaceRoomListSectionBuilder.kt @@ -26,6 +26,7 @@ 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.features.invite.AutoAcceptInvites import im.vector.app.space import io.reactivex.Observable import io.reactivex.disposables.Disposable @@ -50,6 +51,7 @@ class SpaceRoomListSectionBuilder( val appStateHandler: AppStateHandler, val viewModelScope: CoroutineScope, private val suggestedRoomJoiningState: LiveData>>, + private val autoAcceptInvites: AutoAcceptInvites, val onDisposable: (Disposable) -> Unit, val onUdpatable: (UpdatableLivePageResult) -> Unit, val onlyOrphansInHome: Boolean = false @@ -66,13 +68,13 @@ class SpaceRoomListSectionBuilder( val sections = mutableListOf() val activeSpaceAwareQueries = mutableListOf() when (mode) { - RoomListDisplayMode.PEOPLE -> { + RoomListDisplayMode.PEOPLE -> { buildDmSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.ROOMS -> { + RoomListDisplayMode.ROOMS -> { buildRoomsSections(sections, activeSpaceAwareQueries) } - RoomListDisplayMode.FILTERED -> { + RoomListDisplayMode.FILTERED -> { withQueryParams( { it.memberships = Membership.activeMemberships() @@ -88,6 +90,23 @@ class SpaceRoomListSectionBuilder( ) } RoomListDisplayMode.NOTIFICATIONS -> { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = if (onlyOrphansInHome) { + RoomListViewModel.SpaceFilterStrategy.ORPHANS_IF_SPACE_NULL + } else { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL + }, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ALL + } + } addSection( sections = sections, @@ -121,6 +140,20 @@ class SpaceRoomListSectionBuilder( } private fun buildRoomsSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { + if (!autoAcceptInvites.hideInvites) { + addSection( + sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_ROOMS + } + } + addSection( sections, activeSpaceAwareQueries, @@ -226,6 +259,18 @@ class SpaceRoomListSectionBuilder( } private fun buildDmSections(sections: MutableList, activeSpaceAwareQueries: MutableList) { + if (!autoAcceptInvites.hideInvites) { + addSection(sections = sections, + activeSpaceUpdaters = activeSpaceAwareQueries, + nameRes = R.string.invitations_header, + notifyOfLocalEcho = true, + spaceFilterStrategy = RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL, + countRoomAsNotif = true + ) { + it.memberships = listOf(Membership.INVITE) + it.roomCategoryFilter = RoomCategoryFilter.ONLY_DM + } + } addSection(sections, activeSpaceAwareQueries, @@ -350,7 +395,7 @@ class SpaceRoomListSectionBuilder( activeSpaceFilter = ActiveSpaceFilter.ActiveSpace(currentSpace) ) } - RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { + RoomListViewModel.SpaceFilterStrategy.ALL_IF_SPACE_NULL -> { if (currentSpace == null) { copy( activeSpaceFilter = ActiveSpaceFilter.None @@ -361,7 +406,7 @@ class SpaceRoomListSectionBuilder( ) } } - RoomListViewModel.SpaceFilterStrategy.NONE -> this + RoomListViewModel.SpaceFilterStrategy.NONE -> this } } } diff --git a/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt new file mode 100644 index 0000000000..4027ba23d5 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/AutoAcceptInvites.kt @@ -0,0 +1,29 @@ +/* + * 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.invite + +import javax.inject.Inject + +interface AutoAcceptInvites { + val isEnabled: Boolean + val hideInvites: Boolean +} + +class CompileTimeAutoAcceptInvites @Inject constructor() : AutoAcceptInvites { + override val isEnabled = true + override val hideInvites = false +} diff --git a/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt new file mode 100644 index 0000000000..f084eb9bb4 --- /dev/null +++ b/vector/src/main/java/im/vector/app/features/invite/InvitesAcceptor.kt @@ -0,0 +1,97 @@ +/* + * 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.invite + +import im.vector.app.features.session.coroutineScope +import io.reactivex.Observable +import io.reactivex.disposables.Disposable +import kotlinx.coroutines.async +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Semaphore +import kotlinx.coroutines.sync.withPermit +import org.matrix.android.sdk.api.session.Session +import org.matrix.android.sdk.api.session.room.members.ChangeMembershipState +import org.matrix.android.sdk.api.session.room.model.Membership +import org.matrix.android.sdk.api.session.room.roomSummaryQueryParams +import org.matrix.android.sdk.rx.rx +import timber.log.Timber +import java.util.concurrent.TimeUnit +import javax.inject.Inject +import javax.inject.Singleton + +/** + * This class is responsible for auto accepting invites. + * It's listening to invites and membershipChanges so it can retry automatically if needed. + * This mechanism will be on only if AutoAcceptInvites.isEnabled is true. + */ +@Singleton +class InvitesAcceptor @Inject constructor(private val autoAcceptInvites: AutoAcceptInvites) : Session.Listener { + + private val disposables = HashMap() + private val semaphore = Semaphore(1) + + fun onSessionActive(session: Session) { + if (!autoAcceptInvites.isEnabled) { + return + } + if (disposables.containsKey(session.sessionId)) { + return + } + session.addListener(this) + val roomQueryParams = roomSummaryQueryParams { + this.memberships = listOf(Membership.INVITE) + } + val rxSession = session.rx() + Observable + .combineLatest( + rxSession.liveRoomSummaries(roomQueryParams), + rxSession.liveRoomChangeMembershipState().debounce(1, TimeUnit.SECONDS), + { invitedRooms, _ -> invitedRooms.map { it.roomId } } + ) + .filter { it.isNotEmpty() } + .subscribe { invitedRoomIds -> + session.coroutineScope.launch { + semaphore.withPermit { + Timber.v("Invited roomIds: $invitedRoomIds") + for (roomId in invitedRoomIds) { + async { session.joinRoomSafely(roomId) }.start() + } + } + } + } + .also { + disposables[session.sessionId] = it + } + } + + private suspend fun Session.joinRoomSafely(roomId: String) { + val roomMembershipChanged = getChangeMemberships(roomId) + if (roomMembershipChanged != ChangeMembershipState.Joined && !roomMembershipChanged.isInProgress()) { + try { + Timber.v("Try auto join room: $roomId") + joinRoom(roomId) + } catch (failure: Throwable) { + Timber.v("Failed auto join room: $roomId") + } + } + } + + override fun onSessionStopped(session: Session) { + session.removeListener(this) + disposables.remove(session.sessionId)?.dispose() + } +} diff --git a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt index cf49b5ce0f..c051a866b5 100644 --- a/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt +++ b/vector/src/main/java/im/vector/app/features/notifications/NotificationDrawerManager.kt @@ -27,6 +27,7 @@ import im.vector.app.BuildConfig import im.vector.app.R import im.vector.app.core.resources.StringProvider import im.vector.app.core.utils.FirstThrottler +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import me.gujun.android.span.span import org.matrix.android.sdk.api.session.Session @@ -50,7 +51,8 @@ class NotificationDrawerManager @Inject constructor(private val context: Context private val activeSessionDataSource: ActiveSessionDataSource, private val iconLoader: IconLoader, private val bitmapLoader: BitmapLoader, - private val outdatedDetector: OutdatedEventDetector?) { + private val outdatedDetector: OutdatedEventDetector?, + private val autoAcceptInvites: AutoAcceptInvites) { private val handlerThread: HandlerThread = HandlerThread("NotificationDrawerManager", Thread.MIN_PRIORITY) private var backgroundHandler: Handler @@ -254,7 +256,12 @@ class NotificationDrawerManager @Inject constructor(private val context: Context } } is InviteNotifiableEvent -> { - //invitationEvents.add(event) + if(autoAcceptInvites.hideInvites){ + // Forget this event + eventIterator.remove() + }else { + invitationEvents.add(event) + } } is SimpleNotifiableEvent -> simpleEvents.add(event) else -> Timber.w("Type not handled") diff --git a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt index c77a0f36c9..93205364f2 100644 --- a/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/spaces/SpacesListViewModel.kt @@ -28,6 +28,7 @@ import dagger.assisted.AssistedInject import im.vector.app.AppStateHandler import im.vector.app.RoomGroupingMethod import im.vector.app.core.platform.VectorViewModel +import im.vector.app.features.invite.AutoAcceptInvites import im.vector.app.features.settings.VectorPreferences import im.vector.app.group import im.vector.app.space @@ -54,7 +55,8 @@ import java.util.concurrent.TimeUnit class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: SpaceListViewState, private val appStateHandler: AppStateHandler, private val session: Session, - private val vectorPreferences: VectorPreferences + private val vectorPreferences: VectorPreferences, + private val autoAcceptInvites: AutoAcceptInvites ) : VectorViewModel(initialState) { @AssistedFactory @@ -119,7 +121,13 @@ class SpacesListViewModel @AssistedInject constructor(@Assisted initialState: Sp .throttleFirst(300, TimeUnit.MILLISECONDS) .observeOn(Schedulers.computation()) .subscribe { - val inviteCount = 0 + val inviteCount = if(autoAcceptInvites.hideInvites){ + 0 + }else { + session.getRoomSummaries( + roomSummaryQueryParams { this.memberships = listOf(Membership.INVITE) } + ).size + } val totalCount = session.getNotificationCountForRooms( roomSummaryQueryParams { this.memberships = listOf(Membership.JOIN)