diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift index 7485049702..0bca4b7a1d 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/SpecificInternalModels/ObjectWidgetInternalViewModel.swift @@ -23,7 +23,6 @@ final class ObjectWidgetInternalViewModel: ObservableObject, WidgetInternalViewM // MARK: - State - private var subscriptions = [AnyCancellable]() private var linkedObjectDetails: ObjectDetails? @Published private var details: [ObjectDetails]? @Published private var name: String = "" @@ -38,22 +37,22 @@ final class ObjectWidgetInternalViewModel: ObservableObject, WidgetInternalViewM self.output = data.output } - func startHeaderSubscription() { - widgetObject.widgetTargetDetailsPublisher(widgetBlockId: widgetBlockId) - .receiveOnMain() - .sink { [weak self] details in - self?.name = details.title - self?.allowCreateObject = details.permissions(participantCanEdit: true).canEditBlocks - - self?.linkedObjectDetails = details - Task { await self?.updateLinksSubscriptions() } - } - .store(in: &subscriptions) - - subscriptionManager.handler = { [weak self] details in - // Middlware don't sort objects by passed ids - guard let links = self?.linkedObjectDetails?.links else { return } - self?.details = details.sorted { a, b in + func startHeaderSubscription() {} + + func startBlockSubscription() async { + for await details in widgetObject.widgetTargetDetailsPublisher(widgetBlockId: widgetBlockId).values { + name = details.title + allowCreateObject = details.permissions(participantCanEdit: true).canEditBlocks + + linkedObjectDetails = details + await updateLinksSubscriptions() + } + } + + func startTreeSubscription() async { + for await details in subscriptionManager.detailsPublisher.values { + guard let links = linkedObjectDetails?.links else { continue } + self.details = details.sorted { a, b in return links.firstIndex(of: a.id) ?? 0 < links.firstIndex(of: b.id) ?? 0 } } diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift index 50629b292c..979b645c02 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetView.swift @@ -37,6 +37,9 @@ struct TreeWidgetView: View { content } ) + .task { + await model.startTreeSubscription() + } } var content: some View { diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift index 8078fc705e..c428ce929b 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Common/TreeWidgetViewModel.swift @@ -60,6 +60,13 @@ final class TreeWidgetViewModel: ObservableObject { internalModel.onCreateObjectTap() } + func startTreeSubscription() async { + for await details in subscriptionManager.detailsPublisher.values { + childSubscriptionData = details + await updateLinksSubscriptionsAndTree() + } + } + // MARK: - Private private func startHeaderSubscription() { @@ -92,11 +99,6 @@ final class TreeWidgetViewModel: ObservableObject { .receiveOnMain() .assign(to: &$name) - subscriptionManager.handler = { [weak self] details in - self?.childSubscriptionData = details - Task { await self?.updateLinksSubscriptionsAndTree() } - } - internalModel.detailsPublisher .receiveOnMain() .sink { [weak self] details in diff --git a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Specific/ObjectTreeWidgetSubmoduleView.swift b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Specific/ObjectTreeWidgetSubmoduleView.swift index f9d27aaff2..7d53abd88b 100644 --- a/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Specific/ObjectTreeWidgetSubmoduleView.swift +++ b/Anytype/Sources/PresentationLayer/Modules/HomeWidgets/Widgets/Tree/Specific/ObjectTreeWidgetSubmoduleView.swift @@ -26,5 +26,11 @@ private struct ObjectTreeWidgetSubmoduleInternalView: View { data: data, internalModel: model ) + .task { + await model.startBlockSubscription() + } + .task { + await model.startTreeSubscription() + } } } diff --git a/Anytype/Sources/ServiceLayer/Subscriptions/Tree/TreeSubscriptionManager.swift b/Anytype/Sources/ServiceLayer/Subscriptions/Tree/TreeSubscriptionManager.swift index d535df19f1..d95b8d8d0b 100644 --- a/Anytype/Sources/ServiceLayer/Subscriptions/Tree/TreeSubscriptionManager.swift +++ b/Anytype/Sources/ServiceLayer/Subscriptions/Tree/TreeSubscriptionManager.swift @@ -1,28 +1,31 @@ import Foundation import Services +import Combine protocol TreeSubscriptionManagerProtocol: AnyObject { - var handler: ((_ child: [ObjectDetails]) -> Void)? { get set } + var detailsPublisher: AnyPublisher<[ObjectDetails], Never> { get } func startOrUpdateSubscription(objectIds: [String]) async -> Bool func stopAllSubscriptions() async } -final class TreeSubscriptionManager: TreeSubscriptionManagerProtocol { +actor TreeSubscriptionManager: TreeSubscriptionManagerProtocol { + + private let subscriptionDataBuilder: any TreeSubscriptionDataBuilderProtocol = Container.shared.treeSubscriptionDataBuilder() + private let subscriptionStorageProvider: any SubscriptionStorageProviderProtocol = Container.shared.subscriptionStorageProvider() + private let subscriptionStorage: any SubscriptionStorageProtocol - @Injected(\.treeSubscriptionDataBuilder) - private var subscriptionDataBuilder: any TreeSubscriptionDataBuilderProtocol - @Injected(\.subscriptionStorageProvider) - private var subscriptionStorageProvider: any SubscriptionStorageProviderProtocol - private lazy var subscriptionStorage: any SubscriptionStorageProtocol = { - subscriptionStorageProvider.createSubscriptionStorage(subId: subscriptionDataBuilder.subscriptionId) - }() private var objectIds: [String] = [] private var subscriptionStarted: Bool = false - - var handler: ((_ child: [ObjectDetails]) -> Void)? + + nonisolated let detailsPublisher: AnyPublisher<[ObjectDetails], Never> + + init() { + subscriptionStorage = subscriptionStorageProvider.createSubscriptionStorage(subId: subscriptionDataBuilder.subscriptionId) + detailsPublisher = subscriptionStorage.statePublisher.map { $0.items.filter(\.isNotDeletedAndSupportedForEdit) }.eraseToAnyPublisher() + } // MARK: - TreeSubscriptionDataBuilderProtocol - + func startOrUpdateSubscription(objectIds newObjectIds: [String]) async -> Bool { let newObjectIdsSet = Set(newObjectIds) let objectIdsSet = Set(objectIds) @@ -33,14 +36,12 @@ final class TreeSubscriptionManager: TreeSubscriptionManagerProtocol { if newObjectIds.isEmpty { try? await subscriptionStorage.stopSubscription() - handler?([]) + return true } let subscriptionData = subscriptionDataBuilder.build(objectIds: objectIds) - try? await subscriptionStorage.startOrUpdateSubscription(data: subscriptionData) { [weak self] data in - self?.handleStorage(data: data) - } + try? await subscriptionStorage.startOrUpdateSubscription(data: subscriptionData) return true } @@ -49,11 +50,4 @@ final class TreeSubscriptionManager: TreeSubscriptionManagerProtocol { objectIds.removeAll() subscriptionStarted = false } - - // MARK: - Private - - private func handleStorage(data: SubscriptionStorageState) { - let result = data.items.filter(\.isNotDeletedAndSupportedForEdit) - handler?(result) - } } diff --git a/Anytype/Sources/ServiceLayer/SubscriptionsToggler/Internals/SubscriptionStorage.swift b/Anytype/Sources/ServiceLayer/SubscriptionsToggler/Internals/SubscriptionStorage.swift index 1ea10c0093..5e06ef75d7 100644 --- a/Anytype/Sources/ServiceLayer/SubscriptionsToggler/Internals/SubscriptionStorage.swift +++ b/Anytype/Sources/ServiceLayer/SubscriptionsToggler/Internals/SubscriptionStorage.swift @@ -7,7 +7,9 @@ protocol SubscriptionStorageProtocol: AnyObject { var subId: String { get } var detailsStorage: ObjectDetailsStorage { get } + var statePublisher: AnyPublisher { get } func startOrUpdateSubscription(data: SubscriptionData, update: @MainActor @escaping (_ state: SubscriptionStorageState) -> Void) async throws + func startOrUpdateSubscription(data: SubscriptionData) async throws func stopSubscription() async throws } @@ -28,11 +30,14 @@ actor SubscriptionStorage: SubscriptionStorageProtocol { private var orderIds: [String] = [] private var state = SubscriptionStorageState(total: 0, nextCount: 0, prevCount: 0, items: []) + private let stateSubject = PassthroughSubject() + nonisolated let statePublisher: AnyPublisher init(subId: String, detailsStorage: ObjectDetailsStorage, toggler: some SubscriptionTogglerProtocol) { self.subId = subId self.detailsStorage = detailsStorage self.toggler = toggler + self.statePublisher = stateSubject.eraseToAnyPublisher() Task { await setupHandler() } } @@ -43,6 +48,10 @@ actor SubscriptionStorage: SubscriptionStorageProtocol { } } + func startOrUpdateSubscription(data: SubscriptionData) async throws { + try await startOrUpdateSubscription(data: data, update: { _ in }) + } + func startOrUpdateSubscription(data: SubscriptionData, update: @MainActor @escaping (_ state: SubscriptionStorageState) -> Void) async throws { guard subId == data.identifier else { anytypeAssertionFailure("Ids should be equals", info: ["old id": subId, "new id": data.identifier]) @@ -51,6 +60,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol { guard self.data != data else { await update(state) + stateSubject.send(state) return } @@ -72,6 +82,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol { updateItemsCache() await update(state) + stateSubject.send(state) } func stopSubscription() async throws { @@ -134,6 +145,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol { if oldState != state { updateItemsCache() await update?(state) + stateSubject.send(state) } }