Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ struct MembershipDebugView: View {
@State private var transactions: [StoreKit.Transaction] = []
@State private var toastBarData: ToastBarData?

@State private var currentStatus: MembershipStatus = .empty
@State private var refundId: StoreKit.Transaction.ID?
@State private var showRefund = false
@State private var showMembership = false
Expand All @@ -33,29 +34,35 @@ struct MembershipDebugView: View {
transactionsView
}
.frame(maxWidth: .infinity)
.background(storage.currentStatus.tier?.gradient.ignoresSafeArea())

.background(currentStatus.tier?.gradient.ignoresSafeArea())
.snackbar(toastBarData: $toastBarData)
.refundRequestSheet(for: refundId ?? 0, isPresented: $showRefund)

.sheet(isPresented: $showMembership) {
.sheet(isPresented: $showMembership) {
MembershipCoordinator()
}

.task {
currentStatus = await storage.currentStatus()
await loadTiers()
await loadTransactions()
}
.task {
for await status in storage.statusStream() {
currentStatus = status
}
}
}

@MainActor
private var membershipInfo: some View {
VStack(alignment: .center) {
AnytypeText("Current tier", style: .heading)
if let mediumIcon = storage.currentStatus.tier?.mediumIcon {
if let mediumIcon = currentStatus.tier?.mediumIcon {
Image(asset: mediumIcon)
}
AnytypeText(storage.currentStatus.debugDescription, style: .codeBlock)
AnytypeText(currentStatus.debugDescription, style: .codeBlock)
Spacer()
}
.padding()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ struct MembershipCoordinator: View {
style: .error,
buttonData: EmptyStateView.ButtonData(
title: Loc.tryAgain,
action: { model.loadTiers() }
action: { model.retryLoadTiers() }
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,75 @@ final class MembershipCoordinatorModel: ObservableObject {
@Published var fireConfetti = false
@Published var emailUrl: URL?

@Injected(\.membershipService)
private var membershipService: any MembershipServiceProtocol
@Injected(\.membershipStatusStorage)
private var membershipStatusStorage: any MembershipStatusStorageProtocol
@Injected(\.accountManager)
private var accountManager: any AccountManagerProtocol

private let initialTierId: Int?

private var statusTask: Task<Void, Never>?
private var tiersTask: Task<Void, Never>?

init(initialTierId: Int?) {
self.initialTierId = initialTierId
membershipStatusStorage.statusPublisher.receiveOnMain().assign(to: &$userMembership)

statusTask = Task { [weak self] in
guard let self else { return }
for await status in membershipStatusStorage.statusStream() {
self.userMembership = status
}
}

tiersTask = Task { [weak self] in
guard let self else { return }
for await (status, allTiers) in self.combinedStream() {
let currentTierId = status.tier?.type.id ?? 0
self.tiers = allTiers
.filter { FeatureFlags.membershipTestTiers || !$0.isTest }
.filter { !$0.iosProductID.isEmpty || $0.type.id == currentTierId }
}
}
}

deinit {
statusTask?.cancel()
tiersTask?.cancel()
}

private func combinedStream() -> AsyncStream<(MembershipStatus, [MembershipTier])> {
let storage = membershipStatusStorage
return AsyncStream { continuation in
let task = Task {
var currentStatus = await storage.currentStatus()
var currentTiers = await storage.currentTiers()

continuation.yield((currentStatus, currentTiers))

await withTaskGroup(of: Void.self) { group in
group.addTask {
for await status in storage.statusStream() {
currentStatus = status
continuation.yield((currentStatus, currentTiers))
}
}

group.addTask {
for await tiers in storage.tiersStream() {
currentTiers = tiers
continuation.yield((currentStatus, currentTiers))
}
}
}
}

continuation.onTermination = { _ in
task.cancel()
}
}
}

func onAppear() {
Task {
await loadTiers()

guard let initialTierId else { return }
guard let initialTier = tiers.first(where: { $0.type.id == initialTierId }) else {
anytypeAssertionFailure("Not found initial id for Memberhsip coordinator", info: ["tierId": String(initialTierId)])
Expand All @@ -40,17 +91,15 @@ final class MembershipCoordinatorModel: ObservableObject {
onTierSelected(tier: initialTier)
}
}

func loadTiers(noCache: Bool = false) {
Task { await loadTiers(noCache: noCache) }
}

private func loadTiers(noCache: Bool = false) async {
do {
tiers = try await membershipService.getTiers(noCache: noCache)
showTiersLoadingError = false
} catch {
showTiersLoadingError = true

func retryLoadTiers() {
Task {
do {
try await membershipStatusStorage.refreshMembership()
showTiersLoadingError = false
} catch {
showTiersLoadingError = true
}
}
}

Expand All @@ -64,14 +113,14 @@ final class MembershipCoordinatorModel: ObservableObject {

private func showSuccessScreen(tier: MembershipTier) {
showTier = nil
loadTiers(noCache: true)

Task {
try? await membershipStatusStorage.refreshMembership()

// https://linear.app/anytype/issue/IOS-2434/bottom-sheet-nesting
try await Task.sleep(seconds: 0.5)
showSuccess = tier

try await Task.sleep(seconds:0.5)
try await Task.sleep(seconds: 0.5)
UINotificationFeedbackGenerator().notificationOccurred(.success)
fireConfetti = true
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ final class SpaceHubCoordinatorViewModel: SpaceHubModuleOutput {

func startHandleMembershipStatus() async {
for await membership in Container.shared.membershipStatusStorage.resolve()
.statusPublisher.values {
.statusStream() {
guard membership.status == .pendingRequiresFinalization else { continue }

membershipNameFinalizationData = membership.tier
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ final class MembershipUpgradeViewModifierModel: ObservableObject {

nonisolated init() { }

func updateState(reason: MembershipUpgradeReason?) {
func updateState(reason: MembershipUpgradeReason?) async {
guard let reason else { return }
guard let currentTier = statusStorage.currentStatus.tier else { return }

guard let currentTier = await statusStorage.currentStatus().tier else { return }
if accountManager.account.allowMembership && currentTier.isPossibleToUpgrade(reason: reason) {
showMembershipScreen = true
} else {
Expand Down Expand Up @@ -56,11 +56,13 @@ struct MembershipUpgradeViewModifier: ViewModifier {
}
})

.onAppear {
model.updateState(reason: reason)
.task {
await model.updateState(reason: reason)
}
.onChange(of: reason) { _, reason in
model.updateState(reason: reason)
Task {
await model.updateState(reason: reason)
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ struct MembershipPricingView: View {
AnytypeText(info.localizedPeriod ?? "", style: .caption1Regular)
.foregroundColor(.Text.primary)
case nil:
Rectangle().hidden().onAppear {
Rectangle().hidden().task {
anytypeAssertionFailure(
"No pricing view for empty payment info",
info: [
"Tier": String(reflecting: tier),
"Status": String(reflecting: Container.shared.membershipStatusStorage.resolve().currentStatus)
"Status": String(reflecting: await Container.shared.membershipStatusStorage.resolve().currentStatus())
]
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ struct MembershipTierView: View {
#Preview("No tier") {
ScrollView(.horizontal) {
MockView {
MembershipStatusStorageMock.shared._status = .mock(tier: nil, status: .pending)
MembershipStatusStorageMock.shared.setStatus(.mock(tier: nil, status: .pending))
} content: {
HStack {
MembershipTierView(tierToDisplay: .mockStarter, onTap: { })
Expand All @@ -129,7 +129,7 @@ struct MembershipTierView: View {
#Preview("Pending starter") {
ScrollView(.horizontal) {
MockView {
MembershipStatusStorageMock.shared._status = .mock(tier: .mockStarter, status: .pending)
MembershipStatusStorageMock.shared.setStatus(.mock(tier: .mockStarter, status: .pending))
} content: {
HStack {
MembershipTierView(tierToDisplay: .mockStarter, onTap: { })
Expand All @@ -144,7 +144,7 @@ struct MembershipTierView: View {
#Preview("Active starter") {
ScrollView(.horizontal) {
MockView {
MembershipStatusStorageMock.shared._status = .mock(tier: .mockStarter)
MembershipStatusStorageMock.shared.setStatus(.mock(tier: .mockStarter))
} content: {
HStack {
MembershipTierView(tierToDisplay: .mockStarter, onTap: { })
Expand All @@ -159,7 +159,7 @@ struct MembershipTierView: View {
#Preview("Active builder") {
ScrollView(.horizontal) {
MockView {
MembershipStatusStorageMock.shared._status = .mock(tier: .mockBuilder)
MembershipStatusStorageMock.shared.setStatus(.mock(tier: .mockBuilder))
} content: {
HStack {
MembershipTierView(tierToDisplay: .mockStarter, onTap: { })
Expand All @@ -174,7 +174,7 @@ struct MembershipTierView: View {
#Preview("Active custom") {
ScrollView(.horizontal) {
MockView {
MembershipStatusStorageMock.shared._status = .mock(tier: .mockCustom, paymentMethod: .methodCrypto)
MembershipStatusStorageMock.shared.setStatus(.mock(tier: .mockCustom, paymentMethod: .methodCrypto))
} content: {
HStack {
MembershipTierView(tierToDisplay: .mockStarter, onTap: { })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ final class MembershipTierViewModel: ObservableObject {

@Injected(\.membershipMetadataProvider)
private var tierMetadataProvider: any MembershipMetadataProviderProtocol

private var statusTask: Task<Void, Never>?

init(
tierToDisplay: MembershipTier,
onTap: @escaping () -> Void
Expand All @@ -22,9 +23,18 @@ final class MembershipTierViewModel: ObservableObject {
self.onTap = onTap

let storage = Container.shared.membershipStatusStorage.resolve()
storage.statusPublisher.receiveOnMain().assign(to: &$userMembership)
statusTask = Task { [weak self] in
guard let self else { return }
for await status in storage.statusStream() {
self.userMembership = status
}
}
}

deinit {
statusTask?.cancel()
}

func updateState() {
Task {
state = await tierMetadataProvider.owningState(tier: tierToDisplay)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ extension MembershipTier {
Loc.Membership.Feature.viewers(3)
],
paymentType: nil,
color: .green
color: .green,
isTest: false,
iosProductID: ""
)
}

Expand All @@ -32,7 +34,9 @@ extension MembershipTier {
Loc.Membership.Feature.viewers(999)
],
paymentType: .mockExternal,
color: .blue
color: .blue,
isTest: false,
iosProductID: "io.anytype.membership.builder"
)
}

Expand All @@ -49,7 +53,9 @@ extension MembershipTier {
Loc.Membership.Feature.viewers(999)
],
paymentType: .mockExternal,
color: .red
color: .red,
isTest: false,
iosProductID: "io.anytype.membership.cocreator"
)
}

Expand All @@ -66,7 +72,9 @@ extension MembershipTier {
Loc.Membership.Feature.viewers(999)
],
paymentType: .mockExternal,
color: .purple
color: .purple,
isTest: false,
iosProductID: "io.anytype.membership.custom"
)
}

Expand All @@ -83,7 +91,9 @@ extension MembershipTier {
Loc.Membership.Feature.viewers(999)
],
paymentType: .mockExternal,
color: .blue
color: .blue,
isTest: true,
iosProductID: "io.anytype.membership.builder.test"
)
}
}
Loading