-
Notifications
You must be signed in to change notification settings - Fork 63
IOS-5365 implement the async membership handling #4111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,15 +2,19 @@ import Foundation | |
| import ProtobufMessages | ||
| import Combine | ||
| import Services | ||
| import AnytypeCore | ||
|
|
||
|
|
||
| @MainActor | ||
| protocol MembershipStatusStorageProtocol: Sendable { | ||
| var statusPublisher: AnyPublisher<MembershipStatus, Never> { get } | ||
| var currentStatus: MembershipStatus { get } | ||
|
|
||
| var tiersPublisher: AnyPublisher<[MembershipTier], Never> { get } | ||
| var currentTiers: [MembershipTier] { get } | ||
|
|
||
| func startSubscription() async | ||
| func stopSubscriptionAndClean() async | ||
| func refreshMembership() async | ||
| } | ||
|
|
||
| @MainActor | ||
|
|
@@ -24,46 +28,79 @@ final class MembershipStatusStorage: MembershipStatusStorageProtocol { | |
| var statusPublisher: AnyPublisher<MembershipStatus, Never> { $_status.eraseToAnyPublisher() } | ||
| var currentStatus: MembershipStatus { _status } | ||
| @Published private var _status: MembershipStatus = .empty | ||
|
|
||
|
|
||
| var tiersPublisher: AnyPublisher<[MembershipTier], Never> { $_tiers.eraseToAnyPublisher() } | ||
| var currentTiers: [MembershipTier] { _tiers } | ||
| @Published private var _tiers: [MembershipTier] = [] | ||
|
|
||
| private var subscription: AnyCancellable? | ||
|
|
||
| nonisolated init() { } | ||
|
|
||
| func startSubscription() async { | ||
| _status = (try? await membershipService.getMembership(noCache: true)) ?? .empty | ||
| _status = (try? await membershipService.getMembership(noCache: false)) ?? .empty | ||
| _tiers = (try? await membershipService.getTiers(noCache: false)) ?? [] | ||
| AnytypeAnalytics.instance().setMembershipTier(tier: _status.tier) | ||
|
|
||
| setupSubscription() | ||
| } | ||
|
|
||
| func stopSubscriptionAndClean() async { | ||
| subscription = nil | ||
| _status = .empty | ||
| _tiers = [] | ||
| AnytypeAnalytics.instance().setMembershipTier(tier: _status.tier) | ||
| } | ||
|
|
||
|
|
||
| func refreshMembership() async { | ||
| _status = (try? await membershipService.getMembership(noCache: true)) ?? _status | ||
| AnytypeAnalytics.instance().setMembershipTier(tier: _status.tier) | ||
| } | ||
|
|
||
| // MARK: - Private | ||
|
|
||
| private func setupSubscription() { | ||
| private func setupSubscription() { | ||
| subscription = EventBunchSubscribtion.default.addHandler { [weak self] events in | ||
| Task { @MainActor [weak self] in | ||
| self?.handle(events: events) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| private func handle(events: EventsBunch) { | ||
| for event in events.middlewareEvents { | ||
| switch event.value { | ||
| case .membershipUpdate(let update): | ||
| Task { | ||
| let allTiers = try await membershipService.getTiers() | ||
|
|
||
| _status = try builder.buildMembershipStatus(membership: update.data, allTiers: allTiers) | ||
| _status.tier.flatMap { AnytypeAnalytics.instance().logChangePlan(tier: $0) } | ||
|
|
||
| AnytypeAnalytics.instance().setMembershipTier(tier: _status.tier) | ||
| guard !_tiers.isEmpty else { | ||
| print("[Membership] Skipping membershipUpdate - no tiers available yet") | ||
| return | ||
| } | ||
|
|
||
| do { | ||
| _status = try builder.buildMembershipStatus( | ||
| membership: update.data, | ||
| allTiers: _tiers | ||
| ) | ||
| _status.tier.flatMap { AnytypeAnalytics.instance().logChangePlan(tier: $0) } | ||
| AnytypeAnalytics.instance().setMembershipTier(tier: _status.tier) | ||
| print("[Membership] Updated membership status - tier: \(_status.tier?.name ?? "none")") | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As claude mentioned - no prints in production code. It should be either assert or remove it |
||
| } catch { | ||
| print("[Membership] Failed to build status: \(error)") | ||
| } | ||
| } | ||
|
|
||
| case .membershipTiersUpdate(let update): | ||
| Task { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Task created inside main actor will still be called on the main actor. If you want to do it in the backgroind you need to call Task.detached |
||
| var built: [MembershipTier] = [] | ||
| for tier in update.tiers { | ||
| if let builtTier = await builder.buildMembershipTier(tier: tier) { | ||
| built.append(builtTier) | ||
| } | ||
| } | ||
| _tiers = built | ||
| } | ||
|
|
||
| default: | ||
| break | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,2 @@ | ||
| MIDDLE_VERSION=v0.44.0-nightly.20251016.1 | ||
| MIDDLE_VERSION=go-6337-make-tiersmembership-fetching-async | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // DO NOT EDIT. | ||
| // swift-format-ignore-file | ||
| // swiftlint:disable all | ||
| // | ||
| // Generated by the Swift generator plugin for the protocol buffer compiler. | ||
| // Source: pb/protos/events.proto | ||
| // | ||
| // For information on using the generated types, please see the documentation: | ||
| // https://github.com/apple/swift-protobuf/ | ||
|
|
||
| import SwiftProtobuf | ||
|
|
||
| extension Anytype_Event.Membership { | ||
| public struct TiersUpdate: Sendable { | ||
| // SwiftProtobuf.Message conformance is added in an extension below. See the | ||
| // `Message` and `Message+*Additions` files in the SwiftProtobuf library for | ||
| // methods supported on all messages. | ||
|
|
||
| public var tiers: [Anytype_Model_MembershipTierData] = [] | ||
|
|
||
| public var unknownFields = SwiftProtobuf.UnknownStorage() | ||
|
|
||
| public init() {} | ||
| } | ||
| } | ||
|
|
||
| extension Anytype_Event.Membership.TiersUpdate: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { | ||
| public static let protoMessageName: String = Anytype_Event.Membership.protoMessageName + ".TiersUpdate" | ||
| public static let _protobuf_nameMap: SwiftProtobuf._NameMap = [ | ||
| 1: .same(proto: "tiers"), | ||
| ] | ||
|
|
||
| public mutating func decodeMessage<D: SwiftProtobuf.Decoder>(decoder: inout D) throws { | ||
| while let fieldNumber = try decoder.nextFieldNumber() { | ||
| // The use of inline closures is to circumvent an issue where the compiler | ||
| // allocates stack space for every case branch when no optimizations are | ||
| // enabled. https://github.com/apple/swift-protobuf/issues/1034 | ||
| switch fieldNumber { | ||
| case 1: try { try decoder.decodeRepeatedMessageField(value: &self.tiers) }() | ||
| default: break | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public func traverse<V: SwiftProtobuf.Visitor>(visitor: inout V) throws { | ||
| if !self.tiers.isEmpty { | ||
| try visitor.visitRepeatedMessageField(value: self.tiers, fieldNumber: 1) | ||
| } | ||
| try unknownFields.traverse(visitor: &visitor) | ||
| } | ||
|
|
||
| public static func ==(lhs: Anytype_Event.Membership.TiersUpdate, rhs: Anytype_Event.Membership.TiersUpdate) -> Bool { | ||
| if lhs.tiers != rhs.tiers {return false} | ||
| if lhs.unknownFields != rhs.unknownFields {return false} | ||
| return true | ||
| } | ||
| } | ||
|
|
||
| // If the compiler emits an error on this type, it is because this file | ||
| // was generated by a version of the `protoc` Swift plug-in that is | ||
| // incompatible with the version of SwiftProtobuf to which you are linking. | ||
| // Please ensure that you are building against the same version of the API | ||
| // that was used to generate this file. | ||
| fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { | ||
| struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} | ||
| typealias Version = _2 | ||
| } | ||
|
|
||
| // MARK: - Code below here is support for the SwiftProtobuf runtime. | ||
|
|
||
| fileprivate let _protobuf_package = "anytype" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we use this on the UI level, it will really affect performance. It's better to make this a published property and discard
@Published private var allTiers: [MembershipTier] = []because seems like it's not used anymore.
Also naming is really ambiguous. We have allTiers and tiers with no significant distinction and ability to understand what is what.