Skip to content

Commit

Permalink
Merge pull request #1820 from anyproto/ios-3186-objectwidgetinternalv…
Browse files Browse the repository at this point in the history
…iewmodel-fix-publishing-changes-from

IOS-3186 Widget Tree & Subscriptions | Fix main thread error
  • Loading branch information
mgolovko authored Jul 19, 2024
2 parents 298f2e1 + 4a927f5 commit 0c3a7c4
Show file tree
Hide file tree
Showing 6 changed files with 66 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 = ""
Expand All @@ -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
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ struct TreeWidgetView: View {
content
}
)
.task {
await model.startTreeSubscription()
}
}

var content: some View {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,11 @@ private struct ObjectTreeWidgetSubmoduleInternalView: View {
data: data,
internalModel: model
)
.task {
await model.startBlockSubscription()
}
.task {
await model.startTreeSubscription()
}
}
}
Original file line number Diff line number Diff line change
@@ -1,28 +1,36 @@
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)?
private let detailsSubject = PassthroughSubject<[ObjectDetails], Never>()

nonisolated let detailsPublisher: AnyPublisher<[ObjectDetails], Never>

init() {
subscriptionStorage = subscriptionStorageProvider.createSubscriptionStorage(subId: subscriptionDataBuilder.subscriptionId)
detailsPublisher = subscriptionStorage.statePublisher
.map { $0.items }
.merge(with: detailsSubject)
.map { $0.filter(\.isNotDeletedAndSupportedForEdit) }
.eraseToAnyPublisher()
}

// MARK: - TreeSubscriptionDataBuilderProtocol

func startOrUpdateSubscription(objectIds newObjectIds: [String]) async -> Bool {
let newObjectIdsSet = Set(newObjectIds)
let objectIdsSet = Set(objectIds)
Expand All @@ -33,14 +41,12 @@ final class TreeSubscriptionManager: TreeSubscriptionManagerProtocol {

if newObjectIds.isEmpty {
try? await subscriptionStorage.stopSubscription()
handler?([])
detailsSubject.send([])
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
}

Expand All @@ -49,11 +55,4 @@ final class TreeSubscriptionManager: TreeSubscriptionManagerProtocol {
objectIds.removeAll()
subscriptionStarted = false
}

// MARK: - Private

private func handleStorage(data: SubscriptionStorageState) {
let result = data.items.filter(\.isNotDeletedAndSupportedForEdit)
handler?(result)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ protocol SubscriptionStorageProtocol: AnyObject {
var subId: String { get }
var detailsStorage: ObjectDetailsStorage { get }

var statePublisher: AnyPublisher<SubscriptionStorageState, Never> { get }
func startOrUpdateSubscription(data: SubscriptionData, update: @MainActor @escaping (_ state: SubscriptionStorageState) -> Void) async throws
func startOrUpdateSubscription(data: SubscriptionData) async throws
func stopSubscription() async throws
}

Expand All @@ -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<SubscriptionStorageState, Never>()
nonisolated let statePublisher: AnyPublisher<SubscriptionStorageState, Never>

init(subId: String, detailsStorage: ObjectDetailsStorage, toggler: some SubscriptionTogglerProtocol) {
self.subId = subId
self.detailsStorage = detailsStorage
self.toggler = toggler
self.statePublisher = stateSubject.eraseToAnyPublisher()
Task { await setupHandler() }
}

Expand All @@ -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])
Expand All @@ -51,6 +60,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol {

guard self.data != data else {
await update(state)
stateSubject.send(state)
return
}

Expand All @@ -72,6 +82,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol {

updateItemsCache()
await update(state)
stateSubject.send(state)
}

func stopSubscription() async throws {
Expand Down Expand Up @@ -134,6 +145,7 @@ actor SubscriptionStorage: SubscriptionStorageProtocol {
if oldState != state {
updateItemsCache()
await update?(state)
stateSubject.send(state)
}
}

Expand Down

0 comments on commit 0c3a7c4

Please sign in to comment.