mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-10-09 03:20:48 +00:00
Handling State
This commit is contained in:
parent
7e999e8c48
commit
b22e91bdfa
@ -1054,6 +1054,7 @@ public func sgDebugController(context: AccountContext) -> ViewController {
|
||||
let updateSettingsSignal = updateSGStatusInteractively(accountManager: context.sharedContext.accountManager, { status in
|
||||
var status = status
|
||||
status.status = SGStatus.default.status
|
||||
SGSimpleSettings.shared.primaryUserId = ""
|
||||
return status
|
||||
})
|
||||
let _ = (updateSettingsSignal |> deliverOnMainQueue).start(next: {
|
||||
|
@ -91,6 +91,7 @@ public extension Notification.Name {
|
||||
static let SGIAPHelperPurchaseNotification = Notification.Name("SGIAPPurchaseNotification")
|
||||
static let SGIAPHelperErrorNotification = Notification.Name("SGIAPErrorNotification")
|
||||
static let SGIAPHelperProductsUpdatedNotification = Notification.Name("SGIAPProductsUpdatedNotification")
|
||||
static let SGIAPHelperValidationErrorNotification = Notification.Name("SGIAPValidationErrorNotification")
|
||||
}
|
||||
|
||||
public final class SGIAPManager: NSObject {
|
||||
@ -208,7 +209,7 @@ public final class SGIAPManager: NSObject {
|
||||
|
||||
SKPaymentQueue.default().add(self)
|
||||
|
||||
#if DEBUG
|
||||
#if DEBUG && false
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 20) {
|
||||
self.requestProducts()
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ public func sgPayWallController(statusSignal: Signal<Int64, NoError>, replacemen
|
||||
let swiftUIView = SGSwiftUIView<SGPayWallView>(
|
||||
legacyController: legacyController,
|
||||
content: {
|
||||
SGPayWallView(wrapperController: legacyController, replacementController: replacementController, SGIAP: SGIAPManager, statusSignal: statusSignal, lang: strings.baseLanguageCode)
|
||||
SGPayWallView(wrapperController: legacyController, replacementController: replacementController, SGIAP: SGIAPManager, statusSignal: statusSignal)
|
||||
}
|
||||
)
|
||||
let controller = UIHostingController(rootView: swiftUIView, ignoreSafeArea: true)
|
||||
@ -99,35 +99,36 @@ struct BackgroundView: View {
|
||||
struct SGPayWallView: View {
|
||||
@Environment(\.navigationBarHeight) var navigationBarHeight: CGFloat
|
||||
@Environment(\.containerViewLayout) var containerViewLayout: ContainerViewLayout?
|
||||
@Environment(\.lang) var lang: String
|
||||
|
||||
weak var wrapperController: LegacyController?
|
||||
let replacementController: ViewController
|
||||
let SGIAP: SGIAPManager
|
||||
let statusSignal: Signal<Int64, NoError>
|
||||
let lang: String
|
||||
|
||||
private enum PayWallState: Equatable {
|
||||
case ready // ready to buy
|
||||
case restoring
|
||||
case purchasing
|
||||
case validating
|
||||
case purchaseError(String) // error purchasing
|
||||
}
|
||||
|
||||
// State management
|
||||
@State private var product: SGIAPManager.SGProduct?
|
||||
@State private var currentStatus: Int64 = 1
|
||||
@State private var state: PayWallState = .ready
|
||||
@State private var showErrorAlert: Bool = false
|
||||
@State private var showConfetti: Bool = false
|
||||
|
||||
private let productsPub = NotificationCenter.default.publisher(for: .SGIAPHelperProductsUpdatedNotification, object: nil)
|
||||
private let buySuccessPub = NotificationCenter.default.publisher(for: .SGIAPHelperPurchaseNotification, object: nil)
|
||||
private let buyOrRestoreSuccessPub = NotificationCenter.default.publisher(for: .SGIAPHelperPurchaseNotification, object: nil)
|
||||
private let buyErrorPub = NotificationCenter.default.publisher(for: .SGIAPHelperErrorNotification, object: nil)
|
||||
private let validationErrorPub = NotificationCenter.default.publisher(for: .SGIAPHelperValidationErrorNotification, object: nil)
|
||||
|
||||
@State private var statusTask: Task<Void, Never>? = nil
|
||||
|
||||
@State private var hapticFeedback: HapticFeedback?
|
||||
private let confettiDuration: Double = 7.0
|
||||
private let confettiDuration: Double = 5.0
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
@ -146,7 +147,7 @@ struct SGPayWallView: View {
|
||||
.font(.largeTitle)
|
||||
.fontWeight(.bold)
|
||||
|
||||
Text("Supercharged with Pro features")
|
||||
Text("Supercharged with Pro features".i18n(lang))
|
||||
.font(.callout)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.horizontal)
|
||||
@ -179,9 +180,9 @@ struct SGPayWallView: View {
|
||||
updateSelectedProduct()
|
||||
statusTask = Task {
|
||||
let statusStream = statusSignal.awaitableStream()
|
||||
for await status in statusStream {
|
||||
for await newStatus in statusStream {
|
||||
#if DEBUG
|
||||
print("SGPayWallView: status = \(status)")
|
||||
print("SGPayWallView: newStatus = \(newStatus)")
|
||||
#endif
|
||||
if Task.isCancelled {
|
||||
#if DEBUG
|
||||
@ -190,10 +191,13 @@ struct SGPayWallView: View {
|
||||
break
|
||||
}
|
||||
|
||||
if currentStatus != status && status > 1 {
|
||||
handleUpgradedStatus()
|
||||
if currentStatus != newStatus {
|
||||
currentStatus = newStatus
|
||||
|
||||
if newStatus > 1 {
|
||||
handleUpgradedStatus()
|
||||
}
|
||||
}
|
||||
currentStatus = status
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -203,36 +207,22 @@ struct SGPayWallView: View {
|
||||
#endif
|
||||
statusTask?.cancel()
|
||||
}
|
||||
.onReceive(buySuccessPub) { _ in
|
||||
.onReceive(buyOrRestoreSuccessPub) { _ in
|
||||
state = .validating
|
||||
}
|
||||
.onReceive(buyErrorPub) { notification in
|
||||
if let userInfo = notification.userInfo, let error = userInfo["localizedError"] as? String, !error.isEmpty {
|
||||
state = .purchaseError(error)
|
||||
} else {
|
||||
state = .ready
|
||||
showErrorAlert(error)
|
||||
}
|
||||
}
|
||||
.alert(isPresented: Binding(get: {
|
||||
if case .purchaseError = state {
|
||||
return true
|
||||
.onReceive(validationErrorPub) { notification in
|
||||
if state == .validating {
|
||||
if let userInfo = notification.userInfo, let error = userInfo["error"] as? String, !error.isEmpty {
|
||||
showErrorAlert(error)
|
||||
} else {
|
||||
showErrorAlert("Validation Error")
|
||||
}
|
||||
return false
|
||||
},
|
||||
set: { _ in })
|
||||
) {
|
||||
Alert(
|
||||
title: Text("Error"),
|
||||
message: {
|
||||
if case .purchaseError(let message) = state {
|
||||
return Text(message)
|
||||
}
|
||||
return Text("")
|
||||
}(),
|
||||
dismissButton: .default(Text("OK"), action: {
|
||||
state = .ready
|
||||
})
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -273,8 +263,8 @@ struct SGPayWallView: View {
|
||||
.fontWeight(.semibold)
|
||||
.foregroundColor(Color(hex: accentColorHex))
|
||||
}
|
||||
.disabled(state == .restoring)
|
||||
.opacity(state == .restoring ? 0.5 : 1.0)
|
||||
.disabled(state == .restoring || product == nil)
|
||||
.opacity((state == .restoring || product == nil) ? 0.5 : 1.0)
|
||||
}
|
||||
|
||||
private var purchaseSection: some View {
|
||||
@ -301,7 +291,9 @@ struct SGPayWallView: View {
|
||||
}
|
||||
|
||||
private var closeButtonView: some View {
|
||||
Button(action: dismiss) {
|
||||
Button(action: {
|
||||
wrapperController?.dismiss(animated: true)
|
||||
}) {
|
||||
Image(systemName: "xmark")
|
||||
.font(.headline)
|
||||
.foregroundColor(.secondary.opacity(0.6))
|
||||
@ -311,25 +303,25 @@ struct SGPayWallView: View {
|
||||
.padding([.top, .trailing], 16)
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topTrailing)
|
||||
}
|
||||
|
||||
|
||||
private var buttonTitle: String {
|
||||
if currentStatus > 1 {
|
||||
return "Use Pro features"
|
||||
return "Use Pro features".i18n(lang)
|
||||
} else {
|
||||
if state == .purchasing {
|
||||
return "Purchasing..."
|
||||
return "Purchasing...".i18n(lang)
|
||||
} else if state == .restoring {
|
||||
return "Restoring Purchases..."
|
||||
return "Restoring Purchases...".i18n(lang)
|
||||
} else if state == .validating {
|
||||
return "Validating Purchase..."
|
||||
return "Validating Purchase...".i18n(lang)
|
||||
} else if let product = product {
|
||||
if !SGIAP.canMakePayments {
|
||||
return "Payments unavailable"
|
||||
return "Payments unavailable".i18n(lang)
|
||||
} else {
|
||||
return "Subscribe for \(product.price) / month"
|
||||
return "Subscribe for \(product.price) / month".i18n(lang, args: product.price)
|
||||
}
|
||||
} else {
|
||||
return "Contacting App Store..."
|
||||
return "Contacting App Store...".i18n(lang)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -373,8 +365,14 @@ struct SGPayWallView: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func dismiss() {
|
||||
wrapperController?.dismiss(animated: true)
|
||||
private func showErrorAlert(_ message: String) {
|
||||
let alertController = UIAlertController(title: "Error", message: message, preferredStyle: .alert)
|
||||
alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: { action in
|
||||
state = .ready
|
||||
}))
|
||||
DispatchQueue.main.async {
|
||||
wrapperController?.present(alertController, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,3 +119,10 @@ public class SGLocalizationManager {
|
||||
}
|
||||
|
||||
public let i18n = SGLocalizationManager.shared.localizedString
|
||||
|
||||
|
||||
public extension String {
|
||||
func i18n(_ locale: String = SGFallbackLocale, args: CVarArg...) -> String {
|
||||
return SGLocalizationManager.shared.localizedString(self, locale, args: args)
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ public struct ContainerViewLayoutKey: EnvironmentKey {
|
||||
public static let defaultValue: ContainerViewLayout? = nil
|
||||
}
|
||||
|
||||
@available(iOS 13.0, *)
|
||||
public struct LangKey: EnvironmentKey {
|
||||
public static let defaultValue: String = "en"
|
||||
}
|
||||
|
||||
// Perhaps, affects Performance a lot
|
||||
//@available(iOS 13.0, *)
|
||||
//public struct ContainerViewLayoutUpdateCountKey: EnvironmentKey {
|
||||
@ -41,6 +46,11 @@ public extension EnvironmentValues {
|
||||
get { self[ContainerViewLayoutKey.self] }
|
||||
set { self[ContainerViewLayoutKey.self] = newValue }
|
||||
}
|
||||
|
||||
var lang: String {
|
||||
get { self[LangKey.self] }
|
||||
set { self[LangKey.self] = newValue }
|
||||
}
|
||||
|
||||
// var containerViewLayoutUpdateCount: ObservedValue<Int64> {
|
||||
// get { self[ContainerViewLayoutUpdateCountKey.self] }
|
||||
@ -58,6 +68,8 @@ public struct SGSwiftUIView<Content: View>: View {
|
||||
@ObservedObject var containerViewLayout: ObservedValue<ContainerViewLayout?>
|
||||
// @ObservedObject var containerViewLayoutUpdateCount: ObservedValue<Int64>
|
||||
|
||||
private var lang: String
|
||||
|
||||
public init(
|
||||
legacyController: LegacySwiftUIController,
|
||||
manageSafeArea: Bool = false,
|
||||
@ -70,6 +82,7 @@ public struct SGSwiftUIView<Content: View>: View {
|
||||
#endif
|
||||
self.navigationBarHeight = legacyController.navigationBarHeightModel
|
||||
self.containerViewLayout = legacyController.containerViewLayoutModel
|
||||
self.lang = legacyController.lang
|
||||
// self.containerViewLayoutUpdateCount = legacyController.containerViewLayoutUpdateCountModel
|
||||
self.manageSafeArea = manageSafeArea
|
||||
self.content = content()
|
||||
@ -80,6 +93,7 @@ public struct SGSwiftUIView<Content: View>: View {
|
||||
.if(manageSafeArea) { $0.modifier(CustomSafeArea()) }
|
||||
.environment(\.navigationBarHeight, navigationBarHeight.value)
|
||||
.environment(\.containerViewLayout, containerViewLayout.value)
|
||||
.environment(\.lang, lang)
|
||||
// .environment(\.containerViewLayoutUpdateCount, containerViewLayoutUpdateCount)
|
||||
// .onReceive(containerViewLayoutUpdateCount.$value) { _ in
|
||||
// // Make sure View is updated when containerViewLayoutUpdateCount changes,
|
||||
@ -155,12 +169,14 @@ public final class LegacySwiftUIController: LegacyController {
|
||||
public var navigationBarHeightModel: ObservedValue<CGFloat>
|
||||
public var containerViewLayoutModel: ObservedValue<ContainerViewLayout?>
|
||||
public var inputHeightModel: ObservedValue<CGFloat?>
|
||||
public let lang: String
|
||||
// public var containerViewLayoutUpdateCountModel: ObservedValue<Int64>
|
||||
|
||||
override public init(presentation: LegacyControllerPresentation, theme: PresentationTheme? = nil, strings: PresentationStrings? = nil, initialLayout: ContainerViewLayout? = nil) {
|
||||
navigationBarHeightModel = ObservedValue<CGFloat>(0.0)
|
||||
containerViewLayoutModel = ObservedValue<ContainerViewLayout?>(initialLayout)
|
||||
inputHeightModel = ObservedValue<CGFloat?>(nil)
|
||||
lang = strings?.baseLanguageCode ?? "en"
|
||||
// containerViewLayoutUpdateCountModel = ObservedValue<Int64>(0)
|
||||
super.init(presentation: presentation, theme: theme, strings: strings, initialLayout: initialLayout)
|
||||
}
|
||||
|
@ -3075,27 +3075,45 @@ extension AppDelegate {
|
||||
NotificationCenter.default.addObserver(forName: .SGIAPHelperPurchaseNotification, object: nil, queue: nil) { [weak self] notification in
|
||||
SGLogger.shared.log("SGIAP", "Got SGIAPHelperPurchaseNotification")
|
||||
guard let strongSelf = self else { return }
|
||||
if let transaction = notification.object as? SKPaymentTransaction {
|
||||
if let transactions = notification.object as? [SKPaymentTransaction] {
|
||||
let _ = (strongSelf.context.get()
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue).start(next: { context in
|
||||
SGLogger.shared.log("SGIAP", "Got context for SGIAPHelperPurchaseNotification")
|
||||
|> deliverOnMainQueue).start(next: { [weak strongSelf] context in
|
||||
guard let veryStrongSelf = strongSelf else {
|
||||
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
|
||||
let defaultPaymentQueue = SKPaymentQueue.default()
|
||||
for transaction in transactions {
|
||||
defaultPaymentQueue.finishTransaction(transaction)
|
||||
}
|
||||
return
|
||||
}
|
||||
guard let context = context else {
|
||||
SGLogger.shared.log("SGIAP", "Empty app context (how?)")
|
||||
|
||||
SGLogger.shared.log("SGIAP", "Finishing transaction \(transaction.transactionIdentifier ?? "nil")")
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
|
||||
let defaultPaymentQueue = SKPaymentQueue.default()
|
||||
for transaction in transactions {
|
||||
defaultPaymentQueue.finishTransaction(transaction)
|
||||
}
|
||||
return
|
||||
}
|
||||
SGLogger.shared.log("SGIAP", "Got context for SGIAPHelperPurchaseNotification")
|
||||
let _ = Task {
|
||||
await strongSelf.sendReceiptForVerification(primaryContext: context.context)
|
||||
await strongSelf.fetchSGStatus(primaryContext: context.context)
|
||||
SGLogger.shared.log("SGIAP", "Finishing transaction \(transaction.transactionIdentifier ?? "nil")")
|
||||
SKPaymentQueue.default().finishTransaction(transaction)
|
||||
await veryStrongSelf.sendReceiptForVerification(primaryContext: context.context)
|
||||
await veryStrongSelf.fetchSGStatus(primaryContext: context.context)
|
||||
|
||||
SGLogger.shared.log("SGIAP", "Finishing transactions \(transactions.map({ $0.transactionIdentifier ?? "nil" }).joined(separator: ", "))")
|
||||
let defaultPaymentQueue = SKPaymentQueue.default()
|
||||
for transaction in transactions {
|
||||
defaultPaymentQueue.finishTransaction(transaction)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Wrong object in SGIAPHelperPurchaseNotification")
|
||||
#if DEBUG
|
||||
preconditionFailure("Wrong object in SGIAPHelperPurchaseNotification")
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -3104,8 +3122,6 @@ extension AppDelegate {
|
||||
var primaryUserId: Int64 = Int64(SGSimpleSettings.shared.primaryUserId) ?? 0
|
||||
if primaryUserId == 0 {
|
||||
primaryUserId = context.account.peerId.id._internalGetInt64Value()
|
||||
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(primaryUserId)")
|
||||
SGSimpleSettings.shared.primaryUserId = String(primaryUserId)
|
||||
}
|
||||
|
||||
var primaryContext = try? await getContextForUserId(context: context, userId: primaryUserId).awaitable()
|
||||
@ -3115,9 +3131,7 @@ extension AppDelegate {
|
||||
} else {
|
||||
primaryContext = context
|
||||
let newPrimaryUserId = context.account.peerId.id._internalGetInt64Value()
|
||||
SGLogger.shared.log("SGIAP", "Primary context for user id \(primaryUserId) is nil! Falling back to current context with user id: \(context.account.peerId.id._internalGetInt64Value())")
|
||||
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(primaryUserId)")
|
||||
SGSimpleSettings.shared.primaryUserId = String(newPrimaryUserId)
|
||||
SGLogger.shared.log("SGIAP", "Primary context for user id \(primaryUserId) is nil! Falling back to current context with user id: \(newPrimaryUserId)")
|
||||
return context
|
||||
}
|
||||
}
|
||||
@ -3172,6 +3186,9 @@ extension AppDelegate {
|
||||
// SGLogger.shared.log("SGIAP", "Setting user id \(userId) keep connection back to false")
|
||||
// primaryContext.account.network.shouldKeepConnection.set(.single(false))
|
||||
// }
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .SGIAPHelperValidationErrorNotification, object: nil, userInfo: ["error": "PayWall.ValidationError.TryAgain"])
|
||||
}
|
||||
return
|
||||
}
|
||||
SGLogger.shared.log("SGIAP", "Got IQTP response: \(iqtpResponse)")
|
||||
@ -3190,11 +3207,20 @@ extension AppDelegate {
|
||||
if value.status != newStatus {
|
||||
SGLogger.shared.log("SGIAP", "Updating \(userId) status \(value.status) -> \(newStatus)")
|
||||
if newStatus > 1 {
|
||||
SGSimpleSettings.shared.primaryUserId = String(userId)
|
||||
let stringUserId = String(userId)
|
||||
if SGSimpleSettings.shared.primaryUserId != stringUserId {
|
||||
SGLogger.shared.log("SGIAP", "Setting new primary user id: \(userId)")
|
||||
SGSimpleSettings.shared.primaryUserId = stringUserId
|
||||
}
|
||||
}
|
||||
value.status = newStatus
|
||||
} else {
|
||||
SGLogger.shared.log("SGIAP", "Status \(value.status) for \(userId) hasn't changed")
|
||||
if newStatus < 1 {
|
||||
DispatchQueue.main.async {
|
||||
NotificationCenter.default.post(name: .SGIAPHelperValidationErrorNotification, object: nil, userInfo: ["error": "PayWall.ValidationError.Expired"])
|
||||
}
|
||||
}
|
||||
}
|
||||
return value
|
||||
}).awaitable()
|
||||
|
Loading…
x
Reference in New Issue
Block a user