diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift index 1bbcd78334..4ed19ed8ba 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift @@ -7,16 +7,14 @@ import Combine @MainActor final class SpaceShareViewModel: ObservableObject { - private enum Constants { - static let participantLimit = 11 // 10 participants and 1 owner - } - @Injected(\.activeSpaceParticipantStorage) private var activeSpaceParticipantStorage: ActiveSpaceParticipantStorageProtocol @Injected(\.workspaceService) private var workspaceService: WorkspaceServiceProtocol @Injected(\.activeWorkspaceStorage) private var activeWorkspaceStorage: ActiveWorkpaceStorageProtocol + @Injected(\.workspaceStorage) + private var workspacesStorage: WorkspacesStorageProtocol @Injected(\.deepLinkParser) private var deppLinkParser: DeepLinkParserProtocol @Injected(\.workspaceStorage) @@ -24,8 +22,10 @@ final class SpaceShareViewModel: ObservableObject { private var onMoreInfo: () -> Void private var participants: [Participant] = [] - private lazy var workspaceInfo = activeWorkspaceStorage.workspaceInfo private var spaceView: SpaceView? + private var canChangeWriterToReader = false + private var canChangeReaderToWriter = false + private lazy var workspaceInfo = activeWorkspaceStorage.workspaceInfo var accountSpaceId: String { workspaceInfo.accountSpaceId } @@ -33,7 +33,6 @@ final class SpaceShareViewModel: ObservableObject { @Published var inviteLink: URL? @Published var shareInviteLink: URL? @Published var qrCodeInviteLink: URL? - @Published var allowToAddMembers = false @Published var toastBarData: ToastBarData = .empty @Published var requestAlertModel: SpaceRequestAlertData? @Published var changeAccessAlertModel: SpaceChangeAccessViewModel? @@ -48,13 +47,15 @@ final class SpaceShareViewModel: ObservableObject { func startParticipantsTask() async { for await items in activeSpaceParticipantStorage.participantsPublisher.values { - updateParticipant(items: items) + participants = items.sorted { $0.sortingWeight > $1.sortingWeight } + updateView() } } func startSpaceViewTask() async { - for await spaceView in workspaceStorage.spaceViewPublisher(spaceId: accountSpaceId).values { - canStopShare = spaceView.canStopShare + for await spaceView in workspacesStorage.spaceViewPublisher(spaceId: accountSpaceId).values { + self.spaceView = spaceView + updateView() } } @@ -105,8 +106,13 @@ final class SpaceShareViewModel: ObservableObject { // MARK: - Private - private func updateParticipant(items: [Participant]) { - participants = items.sorted { $0.sortingWeight > $1.sortingWeight } + private func updateView() { + guard let spaceView else { return } + + canStopShare = spaceView.canStopShare + canChangeReaderToWriter = spaceView.canChangeReaderToWriter(participants: participants) + canChangeWriterToReader = spaceView.canChangeWriterToReader(participants: participants) + rows = participants.map { participant in let isYou = workspaceInfo.profileObjectID == participant.identityProfileLink return SpaceShareParticipantViewModel( @@ -118,7 +124,6 @@ final class SpaceShareViewModel: ObservableObject { contextActions: participantContextActions(participant) ) } - allowToAddMembers = Constants.participantLimit > items.count } private func participantStatus(_ participant: Participant) -> SpaceShareParticipantViewModel.Status? { @@ -157,6 +162,7 @@ final class SpaceShareViewModel: ObservableObject { title: Loc.SpaceShare.Permissions.reader, isSelected: participant.permission == .reader, destructive: false, + disabled: !canChangeWriterToReader && participant.permission != .reader, action: { [weak self] in self?.showPermissionAlert(participant: participant, newPermission: .reader) } @@ -165,6 +171,7 @@ final class SpaceShareViewModel: ObservableObject { title: Loc.SpaceShare.Permissions.writer, isSelected: participant.permission == .writer, destructive: false, + disabled: !canChangeReaderToWriter && participant.permission != .writer, action: { [weak self] in self?.showPermissionAlert(participant: participant, newPermission: .writer) } @@ -173,6 +180,7 @@ final class SpaceShareViewModel: ObservableObject { title: Loc.SpaceShare.RemoveMember.title, isSelected: false, destructive: true, + disabled: false, action: { [weak self] in self?.showRemoveAlert(participant: participant) } diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlert.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlert.swift index 0c96d5494d..0bc3800650 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlert.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlert.swift @@ -20,11 +20,11 @@ struct SpaceRequestAlert: View { var body: some View { BottomAlertView(title: model.title, message: "") { - BottomAlertButton(text: Loc.SpaceShare.ViewRequest.viewAccess, style: .secondary) { + BottomAlertButton(text: Loc.SpaceShare.ViewRequest.viewAccess, style: .secondary, disable: !model.canAddReaded) { try await model.onViewAccess() dismiss() } - BottomAlertButton(text: Loc.SpaceShare.ViewRequest.editAccess, style: .secondary) { + BottomAlertButton(text: Loc.SpaceShare.ViewRequest.editAccess, style: .secondary, disable: !model.canAddWriter) { try await model.onEditAccess() dismiss() } @@ -33,5 +33,8 @@ struct SpaceRequestAlert: View { dismiss() } } + .throwTask { + try await model.onAppear() + } } } diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlertModel.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlertModel.swift index 83e5873df9..da16262951 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlertModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceRequestView/SpaceRequestAlertModel.swift @@ -6,10 +6,16 @@ final class SpaceRequestAlertModel: ObservableObject { @Injected(\.workspaceService) private var workspaceService: WorkspaceServiceProtocol + @Injected(\.participantService) + private var participantService: ParticipantServiceProtocol + @Injected(\.workspaceStorage) + private var workspaceStorage: WorkspacesStorageProtocol private let data: SpaceRequestAlertData @Published var title = "" + @Published var canAddReaded = false + @Published var canAddWriter = false init(data: SpaceRequestAlertData) { self.data = data @@ -19,6 +25,17 @@ final class SpaceRequestAlertModel: ObservableObject { ) } + func onAppear() async throws { + guard let spaceView = workspaceStorage.spaceView(spaceId: data.spaceId) else { + throw CommonError.undefined + } + // Don't use participant from active subscription, because active space and space for request can be different + let participants = try await participantService.searchParticipants(spaceId: data.spaceId) + + canAddReaded = spaceView.canAddReaders(participants: participants) + canAddWriter = spaceView.canAddWriters(participants: participants) + } + func onViewAccess() async throws { try await workspaceService.requestApprove( spaceId: data.spaceId, diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceShareParticipantView.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceShareParticipantView.swift index b79baeaec0..c7d1c16654 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceShareParticipantView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/Subviews/SpaceShareParticipantView.swift @@ -24,6 +24,7 @@ struct SpaceShareParticipantViewModel: Identifiable { let title: String let isSelected: Bool let destructive: Bool + let disabled: Bool let action: () -> Void } } @@ -86,6 +87,7 @@ struct SpaceShareParticipantView: View { } } } + .disabled(action.disabled) } } label: { diff --git a/Anytype/Sources/PreviewMocks/Mocks/WorkspacesStorageMock.swift b/Anytype/Sources/PreviewMocks/Mocks/WorkspacesStorageMock.swift index c2e2918b18..f5bfab28e4 100644 --- a/Anytype/Sources/PreviewMocks/Mocks/WorkspacesStorageMock.swift +++ b/Anytype/Sources/PreviewMocks/Mocks/WorkspacesStorageMock.swift @@ -17,7 +17,9 @@ final class WorkspacesStorageMock: WorkspacesStorageProtocol { createdDate: nil, accountStatus: .spaceActive, localStatus: .spaceActive, - spaceAccessType: .shared + spaceAccessType: .shared, + readersLimit: nil, + writersLimit: nil ) ] } diff --git a/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift b/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift index cbfcb991ca..7635e86aae 100644 --- a/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift +++ b/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift @@ -10,6 +10,8 @@ struct SpaceView: Identifiable, Equatable { let accountStatus: SpaceStatus? let localStatus: SpaceStatus? let spaceAccessType: SpaceAccessType? + let readersLimit: Int? + let writersLimit: Int? } extension SpaceView: DetailsModel { @@ -22,6 +24,8 @@ extension SpaceView: DetailsModel { self.accountStatus = details.spaceAccountStatusValue self.localStatus = details.spaceLocalStatusValue self.spaceAccessType = details.spaceAccessTypeValue + self.readersLimit = details.readersLimit + self.writersLimit = details.writersLimit } static var subscriptionKeys: [BundledRelationKey] = .builder { @@ -33,6 +37,8 @@ extension SpaceView: DetailsModel { BundledRelationKey.spaceAccessType BundledRelationKey.spaceAccountStatus BundledRelationKey.spaceLocalStatus + BundledRelationKey.readersLimit + BundledRelationKey.writersLimit } } @@ -73,4 +79,33 @@ extension SpaceView { var isActive: Bool { localStatus == .ok && accountStatus != .spaceRemoving && accountStatus != .spaceDeleted } + + func canAddWriters(participants: [Participant]) -> Bool { + guard canAddReaders(participants: participants) else { return false } + guard let writersLimit else { return true } + return writersLimit > activeWriters(participants: participants) + } + + func canAddReaders(participants: [Participant]) -> Bool { + guard let readersLimit else { return true } + return readersLimit > activeReaders(participants: participants) + } + + func canChangeWriterToReader(participants: [Participant]) -> Bool { + guard let readersLimit else { return true } + return readersLimit >= activeReaders(participants: participants) + } + + func canChangeReaderToWriter(participants: [Participant]) -> Bool { + guard let writersLimit else { return true } + return writersLimit > activeWriters(participants: participants) + } + + private func activeReaders(participants: [Participant]) -> Int { + participants.filter { $0.permission == .reader || $0.permission == .writer || $0.permission == .owner }.count + } + + private func activeWriters(participants: [Participant]) -> Int { + participants.filter { $0.permission == .writer || $0.permission == .owner }.count + } } diff --git a/Modules/Services/Sources/Services/ParticipantService/ParticipantService.swift b/Modules/Services/Sources/Services/ParticipantService/ParticipantService.swift new file mode 100644 index 0000000000..35b3223011 --- /dev/null +++ b/Modules/Services/Sources/Services/ParticipantService/ParticipantService.swift @@ -0,0 +1,23 @@ +import Foundation + +public protocol ParticipantServiceProtocol: AnyObject { + func searchParticipants(spaceId: String) async throws -> [Participant] +} + +final class ParticipantService: ParticipantServiceProtocol { + + @Injected(\.searchMiddleService) + private var searchService: SearchMiddleServiceProtocol + + func searchParticipants(spaceId: String) async throws -> [Participant] { + + let filters: [DataviewFilter] = .builder { + SearchHelper.spaceId(spaceId) + SearchHelper.layoutFilter([.participant]) + } + + let data = SearchRequest(filters: filters, sorts: [], fullText: "", keys: Participant.subscriptionKeys.map(\.rawValue), limit: 0) + let details = try await searchService.search(data: data) + return details.compactMap { try? Participant(details: $0) } + } +} diff --git a/Modules/Services/Sources/ServicesDI.swift b/Modules/Services/Sources/ServicesDI.swift index c2bfe3cfb3..e3ecc86943 100644 --- a/Modules/Services/Sources/ServicesDI.swift +++ b/Modules/Services/Sources/ServicesDI.swift @@ -115,4 +115,8 @@ public extension Container { var nameService: Factory { self { NameService() }.shared } + + var participantService: Factory { + self { ParticipantService() }.shared + } }