[push/opt-in-prompts] Implement push notifications client integration
Build the full client half of docs/api/push-notifications-integration-guide.md: OS permission + APNs device-token registration and pipeline wiring, profile notifications/biometric-login toggles on the Ver Perfil screen reflecting server truth, order-tracking opt-in fallback prompt, profile-cache refresh on every mutation, a Notification Service Extension for rich/image push, the Push Notifications capability, targeting-attributes sync, campaign open tracking, and tap-to-order deep linking with foreground notification display.
This commit is contained in:
@@ -25,6 +25,7 @@ struct OrderTrackingView: View {
|
||||
@State var reviewDraft: ReviewDraft? = nil
|
||||
@State var didSaveReviewForCurrentOrder = false
|
||||
@State var reviewSavedObserver: Any?
|
||||
@State var showPushOptInAlert = false
|
||||
|
||||
var body: some View {
|
||||
ScrollView(showsIndicators: false) {
|
||||
@@ -53,8 +54,15 @@ struct OrderTrackingView: View {
|
||||
} message: {
|
||||
Text(cancellationReasonText)
|
||||
}
|
||||
.alert("Ative as notificações", isPresented: $showPushOptInAlert) {
|
||||
Button("Agora não", role: .cancel) {}
|
||||
Button("Ativar") { Task { await enablePushNotifications() } }
|
||||
} message: {
|
||||
Text("Ative as notificações para acompanhar em tempo real as atualizações do seu pedido.")
|
||||
}
|
||||
.task {
|
||||
await loadInitialOrder()
|
||||
await maybePromptPushOptIn()
|
||||
tracker.onOrderUpdated = { updated in
|
||||
order = updated
|
||||
if let inlinePhone = updated.storePhone, inlinePhone.isEmpty == false {
|
||||
@@ -62,6 +70,7 @@ struct OrderTrackingView: View {
|
||||
}
|
||||
isLoading = false
|
||||
errorMessage = nil
|
||||
Task { await maybePromptPushOptIn() }
|
||||
}
|
||||
tracker.start(orderId: orderId, jwt: DefaultTokenStore().jwt)
|
||||
}
|
||||
@@ -827,6 +836,38 @@ struct OrderTrackingView: View {
|
||||
isLoading = false
|
||||
}
|
||||
|
||||
/// Touchpoint 2 of docs/api/push-notifications-integration-guide.md §2b —
|
||||
/// last practical moment to recover an opted-out user before order-status
|
||||
/// push (§6, `type: "order_status"`) goes silent for them for this order.
|
||||
@MainActor
|
||||
private func maybePromptPushOptIn() async {
|
||||
guard showPushOptInAlert == false, isWaitingPayment == false, isCanceled == false else { return }
|
||||
guard SessionStateStore.shouldPromptPushOptIn() else { return }
|
||||
|
||||
let osAuthorized = await PushNotificationCoordinator.shared.currentAuthorizationState() == .authorized
|
||||
var serverEnabled = false
|
||||
if let profileResponse = try? await ApiService().profile(), profileResponse.error == false {
|
||||
serverEnabled = profileResponse.result?.notificationsEnabled ?? false
|
||||
}
|
||||
guard osAuthorized == false || serverEnabled == false else { return }
|
||||
|
||||
SessionStateStore.recordPushOptInPrompted()
|
||||
showPushOptInAlert = true
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func enablePushNotifications() async {
|
||||
let profile = await PushNotificationCoordinator.shared.enableNotifications()
|
||||
if profile?.notificationsEnabled != true {
|
||||
SnackbarCenter.shared.show(
|
||||
title: "Ative notificações nos Ajustes do iPhone para acompanhar seu pedido.",
|
||||
style: .warning,
|
||||
icon: "bell.slash.fill",
|
||||
duration: 3.5
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@MainActor
|
||||
private func refreshStoreContactPhone(for order: PublicOrderResult) async {
|
||||
guard let storeId = order.storeId?.trimmingCharacters(in: .whitespacesAndNewlines), storeId.isEmpty == false else {
|
||||
|
||||
Reference in New Issue
Block a user