// // Copyright 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. // import Combine import SwiftUI typealias InvitesScreenViewModelType = StateStoreViewModel class InvitesScreenViewModel: InvitesScreenViewModelType, InvitesScreenViewModelProtocol { private let userSession: UserSessionProtocol private let appSettings: AppSettings private let analytics: AnalyticsService private let userIndicatorController: UserIndicatorControllerProtocol private let notificationCenterProtocol: NotificationCenterProtocol private let previouslySeenInvites: Set private let actionsSubject: PassthroughSubject = .init() @CancellableTask private var fetchInvitersTask: Task? var actions: AnyPublisher { actionsSubject.eraseToAnyPublisher() } init(userSession: UserSessionProtocol, appSettings: AppSettings, analytics: AnalyticsService, userIndicatorController: UserIndicatorControllerProtocol, notificationCenterProtocol: NotificationCenterProtocol = NotificationCenter.default) { self.userSession = userSession self.appSettings = appSettings self.analytics = analytics self.userIndicatorController = userIndicatorController self.notificationCenterProtocol = notificationCenterProtocol previouslySeenInvites = appSettings.seenInvites super.init(initialViewState: InvitesScreenViewState(), imageProvider: userSession.mediaProvider) setupSubscriptions() } // MARK: - Public override func process(viewAction: InvitesScreenViewAction) { switch viewAction { case .accept(let invite): accept(invite: invite) case .decline(let invite): startDeclineFlow(invite: invite) case .appeared: notificationCenterProtocol.post(name: .invitesScreenAppeared, object: nil) } } // MARK: - Private private var clientProxy: ClientProxyProtocol { userSession.clientProxy } private var inviteSummaryProvider: RoomSummaryProviderProtocol? { clientProxy.inviteSummaryProvider } private func setupSubscriptions() { guard let inviteSummaryProvider else { MXLog.error("Room summary provider unavailable") return } inviteSummaryProvider.roomListPublisher .removeDuplicates() .sink { [weak self] roomSummaries in guard let self else { return } fetchInvitersTask = Task { [weak self] in guard let self else { return } let fullInvites = await self.buildInvites(from: roomSummaries) guard !Task.isCancelled else { return } self.state.invites = fullInvites self.state.isLoading = false self.appSettings.seenInvites = Set(fullInvites.map(\.roomDetails.id)) } } .store(in: &cancellables) } private func buildInvites(from summaries: [RoomSummary]) async -> [InvitesScreenRoomDetails] { await Task.detached { let invites: [InvitesScreenRoomDetails] = summaries.compactMap { summary in guard case .filled(let details) = summary else { return nil } return InvitesScreenRoomDetails(roomDetails: details, isUnread: !self.previouslySeenInvites.contains(details.id)) } // fetch the inviters... return await withTaskGroup(of: (Int, RoomMemberProxyProtocol)?.self) { [clientProxy = self.clientProxy] group in var invitesWithInviters = invites for inviteIndex in 0..