feat(app): integrate Atomenta Feature Control bootstrap, cache and resume refresh
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@@ -15,6 +16,7 @@ struct ContentView: View {
|
||||
@State private var sessionExpiredObserver: NSObjectProtocol?
|
||||
#endif
|
||||
@State var cartResetObserver: Any?
|
||||
@State var appResumeObserver: Any?
|
||||
#if os(Android)
|
||||
@State var snackbarCenter = SnackbarCenter.shared
|
||||
#else
|
||||
@@ -80,14 +82,26 @@ struct ContentView: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
.onChange(of: appState.profile.id) { _, _ in
|
||||
Task { @MainActor in
|
||||
await refreshFeatureFlags(forceRefresh: true)
|
||||
}
|
||||
}
|
||||
.onChange(of: appState.cart.storeId) { _, _ in
|
||||
Task { @MainActor in
|
||||
await refreshFeatureFlags(forceRefresh: true)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
attachCartResetObserverIfNeeded()
|
||||
attachAppResumeObserverIfNeeded()
|
||||
#if os(iOS)
|
||||
attachSessionExpiredObserverIfNeeded()
|
||||
#endif
|
||||
}
|
||||
.onDisappear {
|
||||
detachCartResetObserver()
|
||||
detachAppResumeObserver()
|
||||
#if os(iOS)
|
||||
detachSessionExpiredObserver()
|
||||
#endif
|
||||
@@ -179,6 +193,7 @@ struct ContentView: View {
|
||||
// Keep local state when backend refresh fails transiently.
|
||||
}
|
||||
|
||||
await refreshFeatureFlags(forceRefresh: false)
|
||||
isBootstrappingSession = false
|
||||
}
|
||||
|
||||
@@ -189,7 +204,9 @@ struct ContentView: View {
|
||||
object: nil,
|
||||
queue: nil
|
||||
) { _ in
|
||||
appState.cart = CartState()
|
||||
Task { @MainActor in
|
||||
appState.cart = CartState()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,6 +216,26 @@ struct ContentView: View {
|
||||
self.cartResetObserver = nil
|
||||
}
|
||||
|
||||
private func attachAppResumeObserverIfNeeded() {
|
||||
guard appResumeObserver == nil else { return }
|
||||
appResumeObserver = NotificationCenter.default.addObserver(
|
||||
forName: .appDidResume,
|
||||
object: nil,
|
||||
queue: nil
|
||||
) { _ in
|
||||
Task { @MainActor in
|
||||
guard root == .main else { return }
|
||||
await refreshFeatureFlags(forceRefresh: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func detachAppResumeObserver() {
|
||||
guard let appResumeObserver else { return }
|
||||
NotificationCenter.default.removeObserver(appResumeObserver)
|
||||
self.appResumeObserver = nil
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func hydrateAppState(with customer: CustomerProfile) {
|
||||
appState.profile.id = customer.id
|
||||
@@ -206,6 +243,7 @@ struct ContentView: View {
|
||||
appState.profile.email = customer.email
|
||||
appState.profile.phone = customer.phoneNumber ?? ""
|
||||
appState.profile.profilePicture = customer.profilePicture ?? ""
|
||||
appState.favorites.storeIds = Set(customer.favorites ?? [])
|
||||
SessionStateStore.setActiveUserKey(
|
||||
SessionStateStore.makeUserKey(profileId: customer.id, email: customer.email)
|
||||
)
|
||||
@@ -299,6 +337,46 @@ struct ContentView: View {
|
||||
scheduleDisableAuthEntryPreparation()
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func refreshFeatureFlags(forceRefresh: Bool) async {
|
||||
guard root == .main else { return }
|
||||
|
||||
let subjectType: String
|
||||
let subjectId: String
|
||||
if let profileId = appState.profile.id?.trimmingCharacters(in: .whitespacesAndNewlines), profileId.isEmpty == false {
|
||||
subjectType = "customer"
|
||||
subjectId = profileId
|
||||
} else {
|
||||
subjectType = "anonymous"
|
||||
subjectId = "anonymous-device"
|
||||
}
|
||||
|
||||
var attrs: [String: String] = [:]
|
||||
let addressLabel = appState.address.display.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
if addressLabel.isEmpty == false, addressLabel.lowercased() != "defina seu endereco" {
|
||||
attrs["addressLabel"] = addressLabel
|
||||
}
|
||||
|
||||
let context = FeatureControlEvaluationContext(
|
||||
subjectType: subjectType,
|
||||
subjectId: subjectId,
|
||||
storeId: appState.cart.storeId,
|
||||
attributes: attrs
|
||||
)
|
||||
|
||||
let snapshot = await FeatureControlService.shared.evaluate(
|
||||
context: context,
|
||||
jwt: appState.session.jwt,
|
||||
forceRefresh: forceRefresh
|
||||
)
|
||||
appState.featureFlags = snapshot
|
||||
await FeatureControlService.shared.sendExposureEvents(
|
||||
snapshot: snapshot,
|
||||
context: context,
|
||||
jwt: appState.session.jwt
|
||||
)
|
||||
}
|
||||
|
||||
private func scheduleDisableAuthEntryPreparation() {
|
||||
Task { @MainActor in
|
||||
try? await Task.sleep(nanoseconds: 900_000_000)
|
||||
|
||||
Reference in New Issue
Block a user