From 364ea164ebd7a6b76e77ffa49fee021834d1ed9c Mon Sep 17 00:00:00 2001 From: Mikhail Golovko Date: Thu, 4 Apr 2024 18:22:38 +0300 Subject: [PATCH 1/3] IOS-2536 Add limits for writers in space request alert --- .../SpaceRequestView/SpaceRequestAlert.swift | 7 ++++-- .../SpaceRequestAlertModel.swift | 17 ++++++++++++++ .../Mocks/WorkspacesStorageMock.swift | 4 +++- .../SpaceStorage/Models/SpaceView.swift | 20 ++++++++++++++++ .../ParticipantService.swift | 23 +++++++++++++++++++ Modules/Services/Sources/ServicesDI.swift | 4 ++++ 6 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 Modules/Services/Sources/Services/ParticipantService/ParticipantService.swift 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/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 c6fb82cf47..6305f3ac12 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 } } @@ -69,4 +75,18 @@ 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 } + let activeParticipants = participants.filter { $0.permission == .writer || $0.permission == .owner }.count + return writersLimit > activeParticipants + } + + func canAddReaders(participants: [Participant]) -> Bool { + guard let readersLimit else { return true } + let activeParticipants = participants.filter { $0.permission == .reader || $0.permission == .writer || $0.permission == .owner }.count + return readersLimit > activeParticipants + } + } 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 + } } From 67eaffe6c34b1191b9e11c37f3c988cec679117a Mon Sep 17 00:00:00 2001 From: Mikhail Golovko Date: Fri, 5 Apr 2024 10:31:57 +0300 Subject: [PATCH 2/3] IOS-2536 Support limits --- .../Modules/SpaceShare/SpaceShareView.swift | 3 ++ .../SpaceShare/SpaceShareViewModel.swift | 32 +++++++++++++------ .../Subviews/SpaceShareParticipantView.swift | 2 ++ .../SpaceStorage/Models/SpaceView.swift | 23 ++++++++++--- .../SpaceStorage/WorkspacesStorage.swift | 6 ++++ 5 files changed, 53 insertions(+), 13 deletions(-) diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift index 1863fe461c..ba67c40461 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift @@ -34,6 +34,9 @@ struct SpaceShareView: View { .task { await model.startParticipantsTask() } + .task { + await model.startSpaceTask() + } .task { await model.onAppear() } diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift index 7cea0ed043..907aab7890 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift @@ -7,28 +7,28 @@ 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 private var onMoreInfo: () -> Void private var participants: [Participant] = [] + private var spaceView: SpaceView? + private var canChangeWriterToReader = false + private var canChangeReaderToWriter = false var accountSpaceId: String { activeWorkspaceStorage.workspaceInfo.accountSpaceId } @Published var rows: [SpaceShareParticipantViewModel] = [] @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? @@ -42,7 +42,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 startSpaceTask() async { + for await space in workspacesStorage.spaceViewPublisher(spaceId: accountSpaceId).values { + self.spaceView = space + updateView() } } @@ -93,8 +101,12 @@ 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 } + + canChangeReaderToWriter = spaceView.canChangeReaderToWriter(participants: participants) + canChangeWriterToReader = spaceView.canChangeWriterToReader(participants: participants) + rows = participants.map { participant in let isYou = activeWorkspaceStorage.workspaceInfo.profileObjectID == participant.identityProfileLink return SpaceShareParticipantViewModel( @@ -106,7 +118,6 @@ final class SpaceShareViewModel: ObservableObject { contextActions: participantContextActions(participant) ) } - allowToAddMembers = Constants.participantLimit > items.count } private func participantStatus(_ participant: Participant) -> SpaceShareParticipantViewModel.Status? { @@ -145,6 +156,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) } @@ -153,6 +165,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) } @@ -161,6 +174,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/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/ServiceLayer/SpaceStorage/Models/SpaceView.swift b/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift index 6305f3ac12..e380ff4c0a 100644 --- a/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift +++ b/Anytype/Sources/ServiceLayer/SpaceStorage/Models/SpaceView.swift @@ -79,14 +79,29 @@ extension SpaceView { func canAddWriters(participants: [Participant]) -> Bool { guard canAddReaders(participants: participants) else { return false } guard let writersLimit else { return true } - let activeParticipants = participants.filter { $0.permission == .writer || $0.permission == .owner }.count - return writersLimit > activeParticipants + return writersLimit > activeWriters(participants: participants) } func canAddReaders(participants: [Participant]) -> Bool { guard let readersLimit else { return true } - let activeParticipants = participants.filter { $0.permission == .reader || $0.permission == .writer || $0.permission == .owner }.count - return readersLimit > activeParticipants + 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/Anytype/Sources/ServiceLayer/SpaceStorage/WorkspacesStorage.swift b/Anytype/Sources/ServiceLayer/SpaceStorage/WorkspacesStorage.swift index 7d95895a78..e0f07e74c0 100644 --- a/Anytype/Sources/ServiceLayer/SpaceStorage/WorkspacesStorage.swift +++ b/Anytype/Sources/ServiceLayer/SpaceStorage/WorkspacesStorage.swift @@ -24,6 +24,12 @@ extension WorkspacesStorageProtocol { .removeDuplicates() .eraseToAnyPublisher() } + + func spaceViewPublisher(spaceId: String) -> AnyPublisher { + allWorkspsacesPublisher.compactMap { $0.first { $0.targetSpaceId == spaceId } } + .removeDuplicates() + .eraseToAnyPublisher() + } } @MainActor From 3c7a5dd14ba32f93c0ad4dc03af77508328b969c Mon Sep 17 00:00:00 2001 From: Mikhail Golovko Date: Fri, 5 Apr 2024 17:27:53 +0300 Subject: [PATCH 3/3] IOS-2536 Fixes --- .../Modules/SpaceShare/SpaceShareView.swift | 3 --- .../Modules/SpaceShare/SpaceShareViewModel.swift | 13 ++++--------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift index 9b1855e3b8..78d499a513 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareView.swift @@ -34,9 +34,6 @@ struct SpaceShareView: View { .task { await model.startParticipantsTask() } - .task { - await model.startSpaceTask() - } .task { await model.startSpaceViewTask() } diff --git a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift index 65facb9134..4ed19ed8ba 100644 --- a/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/SpaceShare/SpaceShareViewModel.swift @@ -52,16 +52,10 @@ final class SpaceShareViewModel: ObservableObject { } } - func startSpaceTask() async { - for await space in workspacesStorage.spaceViewPublisher(spaceId: accountSpaceId).values { - self.spaceView = space - 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() } } @@ -115,6 +109,7 @@ final class SpaceShareViewModel: ObservableObject { private func updateView() { guard let spaceView else { return } + canStopShare = spaceView.canStopShare canChangeReaderToWriter = spaceView.canChangeReaderToWriter(participants: participants) canChangeWriterToReader = spaceView.canChangeWriterToReader(participants: participants)