@@ -62,7 +62,7 @@ extension SettingsProTab {
6262 title: " Notifications " ,
6363 detail: " Approval and event alert channel " ,
6464 value: self . notificationStatusText,
65- color: self . notificationStatus . color )
65+ color: self . notificationStatusColor )
6666 self . diagnosticCheckRow (
6767 icon: " rectangle.on.rectangle " ,
6868 title: " Screen Capture " ,
@@ -183,7 +183,7 @@ extension SettingsProTab {
183183 gatewayConnected: self . gatewayDiagnosticConnected,
184184 discoveredGatewayCount: self . gatewayController. gateways. count,
185185 talkConfigLoaded: self . gatewayDiagnosticTalkConfigLoaded,
186- notificationsAllowed: self . notificationStatus == . allowed )
186+ notificationsAllowed: self . notificationServingActive )
187187 self . diagnosticsIssueCount = issueCount
188188 self . diagnosticsLastRunText = SettingsDiagnostics . timestamp ( Date ( ) )
189189 }
@@ -611,18 +611,58 @@ extension SettingsProTab {
611611 }
612612 }
613613
614- func handleNotificationAction( ) {
615- if self . notificationStatus. shouldOpenNotificationSettings {
616- self . openNotificationSettings ( )
614+ func handleNotificationServingToggleChange( _ isOn: Bool ) {
615+ guard isOn else {
616+ self . notificationServingEnabled = false
617+ // UIKit stops APNs delivery here; re-enabling registers again and the
618+ // app delegate republishes the current token to the active gateway.
619+ UIApplication . shared. unregisterForRemoteNotifications ( )
617620 return
618621 }
619- guard self . notificationStatus == . notSet else { return }
620622
621- if PushBuildConfig . current. usesOpenClawHostedRelay {
623+ switch self . notificationStatus {
624+ case . allowed:
625+ self . enableNotificationServing ( )
626+ case . notSet:
627+ guard self . prepareNotificationEnrollment ( ) else { return }
628+ self . requestNotificationAuthorizationFromSettings ( )
629+ case . notAllowed, . unknown:
630+ self . notificationServingEnabled = true
631+ self . openNotificationSettings ( )
632+ case . checking:
633+ break
634+ }
635+ }
636+
637+ private func prepareNotificationEnrollment( ) -> Bool {
638+ if PushBuildConfig . current. usesOpenClawHostedRelay,
639+ !PushEnrollmentConsent. disclosureAccepted
640+ {
622641 self . showNotificationRelayDisclosure = true
623- return
642+ return false
643+ }
644+ return true
645+ }
646+
647+ private func enableNotificationServing( ) {
648+ guard self . prepareNotificationEnrollment ( ) else { return }
649+ self . notificationServingEnabled = true
650+ self . registerForRemoteNotificationsIfEnrollmentReady ( )
651+ }
652+
653+ func acceptNotificationRelayDisclosure( ) {
654+ PushEnrollmentConsent . markDisclosureAccepted ( )
655+ switch self . notificationStatus {
656+ case . allowed:
657+ self . enableNotificationServing ( )
658+ case . notSet:
659+ self . requestNotificationAuthorizationFromSettings ( )
660+ case . notAllowed, . unknown:
661+ self . notificationServingEnabled = true
662+ self . openNotificationSettings ( )
663+ case . checking:
664+ self . notificationServingEnabled = false
624665 }
625- self . requestNotificationAuthorizationFromSettings ( )
626666 }
627667
628668 func requestNotificationAuthorizationFromSettings( ) {
@@ -639,15 +679,19 @@ extension SettingsProTab {
639679 await MainActor . run {
640680 self . isRequestingNotificationAuthorization = false
641681 self . notificationStatus = SettingsNotificationStatus ( settings. authorizationStatus)
642- guard granted else { return }
682+ self . notificationServingEnabled = granted && self . notificationStatus. allowsNotifications
683+ guard self . notificationServingEnabled else { return }
643684 self . registerForRemoteNotificationsIfEnrollmentReady ( )
644685 }
645686 }
646687 }
647688
648689 @MainActor
649690 func registerForRemoteNotificationsIfEnrollmentReady( ) {
650- guard PushEnrollmentConsent . disclosureAccepted else { return }
691+ guard self . notificationServingEnabled else { return }
692+ guard !PushBuildConfig. current. usesOpenClawHostedRelay
693+ || PushEnrollmentConsent . disclosureAccepted
694+ else { return }
651695 guard self . notificationStatus. allowsNotifications else { return }
652696 UIApplication . shared. registerForRemoteNotifications ( )
653697 }
@@ -1019,12 +1063,7 @@ extension SettingsProTab {
10191063 }
10201064
10211065 var notificationsNeedAttention : Bool {
1022- switch self . notificationStatus {
1023- case . allowed, . checking:
1024- false
1025- case . notAllowed, . notSet, . unknown:
1026- true
1027- }
1066+ self . notificationPresentation. needsAttention
10281067 }
10291068
10301069 var approvalItems : [ SettingsApprovalItem ] {
@@ -1105,28 +1144,53 @@ extension SettingsProTab {
11051144 }
11061145
11071146 var notificationStatusText : String {
1108- self . notificationStatus . text
1147+ self . notificationPresentation . text
11091148 }
11101149
1111- var notificationActionText : String {
1112- self . notificationStatus . actionTitle
1150+ var notificationStatusColor : Color {
1151+ self . notificationPresentation . color
11131152 }
11141153
1115- var notificationStatusDetail : String {
1154+ var notificationServingActive : Bool {
1155+ self . notificationPresentation. isActive
1156+ }
1157+
1158+ var notificationDisclosureAccepted : Bool {
1159+ !PushBuildConfig. current. usesOpenClawHostedRelay
1160+ || PushEnrollmentConsent . disclosureAccepted
1161+ }
1162+
1163+ var notificationToggleBinding : Binding < Bool > {
1164+ Binding (
1165+ get: { self . notificationServingActive } ,
1166+ set: { self . handleNotificationServingToggleChange ( $0) } )
1167+ }
1168+
1169+ var notificationPresentation : SettingsNotificationPresentation {
11161170 switch self . notificationStatus {
11171171 case . checking:
1118- " Checking iOS notification permission. "
1172+ return . checking
11191173 case . allowed:
1120- " OpenClaw can show approval prompts and event alerts when the app is not active. "
1174+ if !self . notificationServingEnabled {
1175+ return . off
1176+ }
1177+ if !self . notificationDisclosureAccepted {
1178+ return . setup
1179+ }
1180+ return . enabled
11211181 case . notAllowed:
1122- " Notifications have been denied. Enable them in iOS Settings. "
1182+ return . denied
11231183 case . notSet:
1124- " Enable notifications to receive approval prompts and event alerts outside the app. "
1184+ return . notSet
11251185 case . unknown:
1126- " OpenClaw cannot determine the current notification permission state. "
1186+ return . unknown
11271187 }
11281188 }
11291189
1190+ var notificationStatusDetail : String {
1191+ self . notificationPresentation. detail
1192+ }
1193+
11301194 var notificationRelayDetail : String {
11311195 if PushBuildConfig . current. usesOpenClawHostedRelay {
11321196 let host = PushBuildConfig . current. relayBaseURL. flatMap {
0 commit comments