mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
fix(ios): remove permission pre-prompts
This commit is contained in:
+46
-206
@@ -26113,70 +26113,6 @@
|
||||
"surface": "apple",
|
||||
"id": "native.apple.1a8232361f3d72fb"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 14,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Allow access",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.9dcd64ab340a6c15"
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 15,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Choose what your agent can use on this iPhone. Nothing is on until you allow it.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.933a2ac28d1d2aec"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 31,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "You can change any of these later in Settings.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.47114a39f7a71c3b"
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 43,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Continue",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.eabf54e2b640a4a1"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 70,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Limited",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.5f425282ece878eb"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 78,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Allow",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.c5db9e6eae115f7a"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 80,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Open Settings",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.f591e16105268e99"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 82,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingPermissionsStep.swift",
|
||||
"source": "Manage",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.12ed416835c36555"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 11,
|
||||
@@ -26595,7 +26531,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-modifier",
|
||||
"line": 193,
|
||||
"line": 191,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "QR Scanner Unavailable",
|
||||
"surface": "apple",
|
||||
@@ -26603,7 +26539,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 199,
|
||||
"line": 197,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "OK",
|
||||
"surface": "apple",
|
||||
@@ -26611,7 +26547,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 304,
|
||||
"line": 302,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Scan Setup Code",
|
||||
"surface": "apple",
|
||||
@@ -26619,7 +26555,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 312,
|
||||
"line": 310,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Cancel",
|
||||
"surface": "apple",
|
||||
@@ -26627,7 +26563,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 319,
|
||||
"line": 317,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Photos",
|
||||
"surface": "apple",
|
||||
@@ -26635,7 +26571,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-modifier",
|
||||
"line": 360,
|
||||
"line": 358,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Back",
|
||||
"surface": "apple",
|
||||
@@ -26643,7 +26579,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 368,
|
||||
"line": 366,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Close",
|
||||
"surface": "apple",
|
||||
@@ -26651,7 +26587,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-modifier",
|
||||
"line": 394,
|
||||
"line": 392,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Dismiss Keyboard",
|
||||
"surface": "apple",
|
||||
@@ -26659,7 +26595,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 449,
|
||||
"line": 443,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Mode",
|
||||
"surface": "apple",
|
||||
@@ -26667,7 +26603,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 450,
|
||||
"line": 444,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Discovery",
|
||||
"surface": "apple",
|
||||
@@ -26675,7 +26611,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 452,
|
||||
"line": 446,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Status",
|
||||
"surface": "apple",
|
||||
@@ -26683,7 +26619,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 470,
|
||||
"line": 464,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Choose a mode first.",
|
||||
"surface": "apple",
|
||||
@@ -26691,7 +26627,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 475,
|
||||
"line": 469,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Back to Mode Selection",
|
||||
"surface": "apple",
|
||||
@@ -26699,7 +26635,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 519,
|
||||
"line": 513,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Manual Fallback",
|
||||
"surface": "apple",
|
||||
@@ -26707,7 +26643,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-named-argument",
|
||||
"line": 523,
|
||||
"line": 517,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Domain Settings",
|
||||
"surface": "apple",
|
||||
@@ -26715,7 +26651,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 534,
|
||||
"line": 528,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Developer Local",
|
||||
"surface": "apple",
|
||||
@@ -26723,7 +26659,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 537,
|
||||
"line": 531,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Default host is localhost. Use your Mac LAN IP if simulator networking requires it.",
|
||||
"surface": "apple",
|
||||
@@ -26731,7 +26667,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 565,
|
||||
"line": 559,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Gateway rejected credentials. Scan a fresh setup code or update token/password.",
|
||||
"surface": "apple",
|
||||
@@ -26739,7 +26675,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 573,
|
||||
"line": 567,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "OpenClaw is checking gateway and node access.",
|
||||
"surface": "apple",
|
||||
@@ -26747,7 +26683,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 587,
|
||||
"line": 581,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Resume After Approval",
|
||||
"surface": "apple",
|
||||
@@ -26755,7 +26691,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 593,
|
||||
"line": 587,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Pairing Approval",
|
||||
"surface": "apple",
|
||||
@@ -26763,7 +26699,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 599,
|
||||
"line": 593,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Request ID: %@",
|
||||
"surface": "apple",
|
||||
@@ -26771,7 +26707,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 602,
|
||||
"line": 596,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Request ID: check `openclaw devices list`.",
|
||||
"surface": "apple",
|
||||
@@ -26779,7 +26715,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call-multiline",
|
||||
"line": 606,
|
||||
"line": 600,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Approve this device on the gateway.\n1) `%1$@`\n2) `/pair approve` in your OpenClaw chat\n%2$@\nOpenClaw will also retry automatically when you return to this app.",
|
||||
"surface": "apple",
|
||||
@@ -26787,7 +26723,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 623,
|
||||
"line": 617,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Scan Setup Code Again",
|
||||
"surface": "apple",
|
||||
@@ -26795,7 +26731,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 636,
|
||||
"line": 630,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Retry Connection",
|
||||
"surface": "apple",
|
||||
@@ -26803,7 +26739,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 667,
|
||||
"line": 661,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Enter setup code",
|
||||
"surface": "apple",
|
||||
@@ -26811,7 +26747,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 683,
|
||||
"line": 677,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Apply",
|
||||
"surface": "apple",
|
||||
@@ -26819,7 +26755,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 701,
|
||||
"line": 695,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Setup Code",
|
||||
"surface": "apple",
|
||||
@@ -26827,7 +26763,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 704,
|
||||
"line": 698,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Use this if you have a setup code instead of scanning.",
|
||||
"surface": "apple",
|
||||
@@ -26835,7 +26771,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 716,
|
||||
"line": 710,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Host",
|
||||
"surface": "apple",
|
||||
@@ -26843,7 +26779,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 717,
|
||||
"line": 711,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Port",
|
||||
"surface": "apple",
|
||||
@@ -26851,7 +26787,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 720,
|
||||
"line": 714,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Discovery Domain (optional)",
|
||||
"surface": "apple",
|
||||
@@ -26859,7 +26795,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 725,
|
||||
"line": 719,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Gateway Auth Token",
|
||||
"surface": "apple",
|
||||
@@ -26867,7 +26803,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 729,
|
||||
"line": 723,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Gateway Password",
|
||||
"surface": "apple",
|
||||
@@ -26875,7 +26811,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 759,
|
||||
"line": 753,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Unencrypted",
|
||||
"surface": "apple",
|
||||
@@ -26883,7 +26819,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 762,
|
||||
"line": 756,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Secure (TLS)",
|
||||
"surface": "apple",
|
||||
@@ -26891,7 +26827,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 766,
|
||||
"line": 760,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Connection security",
|
||||
"surface": "apple",
|
||||
@@ -26899,7 +26835,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 836,
|
||||
"line": 830,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Connecting…",
|
||||
"surface": "apple",
|
||||
@@ -26907,7 +26843,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-call",
|
||||
"line": 840,
|
||||
"line": 834,
|
||||
"path": "apps/ios/Sources/Onboarding/OnboardingWizardView.swift",
|
||||
"source": "Connect",
|
||||
"surface": "apple",
|
||||
@@ -26947,31 +26883,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 60,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Notifications",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.96d65a5563690719"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 61,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Camera",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.d290a46515add091"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 62,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Microphone",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.0eeedbc8aaafcd2c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 63,
|
||||
"line": 41,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Photos",
|
||||
"surface": "apple",
|
||||
@@ -26979,7 +26891,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 64,
|
||||
"line": 42,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Contacts",
|
||||
"surface": "apple",
|
||||
@@ -26987,7 +26899,7 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 65,
|
||||
"line": 43,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Calendar",
|
||||
"surface": "apple",
|
||||
@@ -26995,84 +26907,12 @@
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 66,
|
||||
"line": 44,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Reminders",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.9ed5c19cf1e84490"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 67,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Location",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.866b9df8afcadf07"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 73,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Pairing approvals and replies from your agent.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.f205ac6778705a7d"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 74,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Scan setup codes and take photos on request.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.061a17077ee3be94"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 75,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Talk with your agent using voice.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.c735ac6b282765f9"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 76,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Share recent photos when you ask.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.ebe8aadcf512ecdb"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 77,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Look up and add people you mention.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.4c4bc12c0787383c"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 78,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Check and add calendar events.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.5a4006fbb9b23db1"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 79,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "List, add, and complete reminders.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.2b2a4f1aab6a12ec"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
"line": 80,
|
||||
"path": "apps/ios/Sources/Permissions/DevicePermissions.swift",
|
||||
"source": "Answer location and nearby questions.",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.3854ae1f0606bafe"
|
||||
},
|
||||
{
|
||||
"kind": "conditional-branch",
|
||||
"line": 490,
|
||||
@@ -27685,9 +27525,9 @@
|
||||
"kind": "ui-localized-call",
|
||||
"line": 169,
|
||||
"path": "apps/ios/Sources/Settings/PrivacyAccessSectionView.swift",
|
||||
"source": "Allow",
|
||||
"source": "Continue",
|
||||
"surface": "apple",
|
||||
"id": "native.apple.0b10201d7e452dc4"
|
||||
"id": "native.apple.856d1960275796ef"
|
||||
},
|
||||
{
|
||||
"kind": "ui-localized-call",
|
||||
|
||||
@@ -169,6 +169,12 @@ The push wake and node status demo completed.
|
||||
|
||||
## Device Permissions
|
||||
|
||||
OpenClaw does not present an aggregate permissions page during onboarding.
|
||||
Optional access is requested when the reviewer invokes the related feature or
|
||||
taps `Continue` for a permission in Settings. `Continue` immediately presents
|
||||
the native iOS authorization prompt, with no additional OpenClaw dialog or
|
||||
deferral action.
|
||||
|
||||
1. Tap `Settings`.
|
||||
2. Tap `Permissions`.
|
||||
3. Confirm these current app controls are available:
|
||||
@@ -177,11 +183,19 @@ The push wake and node status demo completed.
|
||||
- `Keep Awake`
|
||||
4. Confirm the always-visible `Apple Health` section identifies the integration
|
||||
as `Apple Health Summaries`.
|
||||
5. Expand `Privacy & Access` and confirm these request controls are available:
|
||||
- `Contacts` / `Request Access`
|
||||
- `Calendar (Add Events)` / `Request Access`
|
||||
- `Calendar (View Events)` / `Request Full Access`
|
||||
- `Reminders` / `Request Access`
|
||||
5. Expand `Privacy & Access` and confirm these controls show `Continue` when
|
||||
access has not previously been requested:
|
||||
- `Contacts`
|
||||
- `Photos`
|
||||
- `Calendar (Add Events)`
|
||||
- `Calendar (View Events)`
|
||||
- `Reminders`
|
||||
6. Tap `Continue` for any permission that has not been requested. Confirm the
|
||||
corresponding native iOS authorization prompt appears immediately, then
|
||||
make the permission choice in that system prompt.
|
||||
|
||||
If the review device already has a decision for a permission, the row shows
|
||||
its current status or a management action instead of `Continue`.
|
||||
|
||||
### Optional Apple Health summary
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
import SwiftUI
|
||||
|
||||
/// First-run permissions page: one tap per grant, live status, always skippable.
|
||||
/// System prompts fire only from the row actions; product opt-ins stay in Settings.
|
||||
struct OnboardingPermissionsStep: View {
|
||||
@Environment(\.scenePhase) private var scenePhase
|
||||
@State private var permissions = DevicePermissionsModel()
|
||||
let onContinue: () -> Void
|
||||
|
||||
var body: some View {
|
||||
OnboardingActivationCanvas {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
OnboardingHeroHeader(
|
||||
title: "Allow access",
|
||||
subtitle: "Choose what your agent can use on this iPhone. Nothing is on until you allow it.")
|
||||
.padding(.top, 18)
|
||||
|
||||
OnboardingIntroPanel {
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
ForEach(DevicePermissionKind.allCases) { kind in
|
||||
self.row(for: kind)
|
||||
if kind != DevicePermissionKind.allCases.last {
|
||||
Divider()
|
||||
.padding(.leading, 48)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding(.top, 30)
|
||||
|
||||
Text("You can change any of these later in Settings.")
|
||||
.font(OpenClawType.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(.top, 14)
|
||||
|
||||
Spacer(minLength: 32)
|
||||
|
||||
Button {
|
||||
self.onContinue()
|
||||
} label: {
|
||||
Text("Continue")
|
||||
.font(OpenClawType.subheadSemiBold)
|
||||
}
|
||||
.buttonStyle(OpenClawPrimaryActionButtonStyle())
|
||||
.padding(.top, 22)
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await self.permissions.refresh()
|
||||
}
|
||||
.onChange(of: self.scenePhase) { _, phase in
|
||||
guard phase == .active else { return }
|
||||
Task { await self.permissions.refresh() }
|
||||
}
|
||||
}
|
||||
|
||||
private func row(for kind: DevicePermissionKind) -> some View {
|
||||
let grant = self.permissions.grant(for: kind)
|
||||
return DevicePermissionRow(
|
||||
identifierPrefix: "onboarding-permission",
|
||||
identifier: kind.rawValue,
|
||||
symbol: kind.symbol,
|
||||
tint: kind.tint,
|
||||
title: kind.title,
|
||||
detail: kind.detail,
|
||||
grant: grant,
|
||||
isRequesting: self.permissions.requesting.contains(kind),
|
||||
statusLabel: grant == .limited ? LocalizedStringResource("Limited") : nil,
|
||||
actionTitle: Self.actionTitle(for: grant),
|
||||
action: self.action(for: kind, grant: grant))
|
||||
}
|
||||
|
||||
private static func actionTitle(for grant: DevicePermissionGrant) -> LocalizedStringResource? {
|
||||
switch grant {
|
||||
case .notRequested:
|
||||
LocalizedStringResource("Allow")
|
||||
case .denied:
|
||||
LocalizedStringResource("Open Settings")
|
||||
case .limited:
|
||||
LocalizedStringResource("Manage")
|
||||
case .granted:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private func action(for kind: DevicePermissionKind, grant: DevicePermissionGrant) -> (() -> Void)? {
|
||||
switch grant {
|
||||
case .notRequested:
|
||||
{ Task { await self.permissions.request(kind) } }
|
||||
case .denied, .limited:
|
||||
{ self.openSystemSettings() }
|
||||
case .granted:
|
||||
nil
|
||||
}
|
||||
}
|
||||
|
||||
private func openSystemSettings() {
|
||||
guard let url = URL(string: UIApplication.openSettingsURLString) else { return }
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import SwiftUI
|
||||
|
||||
enum OnboardingStep: Int, CaseIterable {
|
||||
case intro
|
||||
case permissions
|
||||
case welcome
|
||||
case mode
|
||||
case connect
|
||||
@@ -24,7 +23,6 @@ enum OnboardingStep: Int, CaseIterable {
|
||||
var title: LocalizedStringKey {
|
||||
switch self {
|
||||
case .intro: "Welcome"
|
||||
case .permissions: "Permissions"
|
||||
case .welcome: "Connect Gateway"
|
||||
case .mode: "Gateway Setup"
|
||||
case .connect: "Gateway Details"
|
||||
@@ -35,7 +33,7 @@ enum OnboardingStep: Int, CaseIterable {
|
||||
|
||||
var canGoBack: Bool {
|
||||
switch self {
|
||||
case .intro, .permissions, .welcome, .success:
|
||||
case .intro, .welcome, .success:
|
||||
false
|
||||
case .mode, .connect, .auth:
|
||||
true
|
||||
|
||||
@@ -74,7 +74,7 @@ struct OnboardingWizardView: View {
|
||||
}
|
||||
|
||||
private var isFullScreenStep: Bool {
|
||||
self.step == .intro || self.step == .permissions || self.step == .welcome || self.step == .success
|
||||
self.step == .intro || self.step == .welcome || self.step == .success
|
||||
}
|
||||
|
||||
private var currentProblem: GatewayConnectionProblem? {
|
||||
@@ -139,8 +139,6 @@ struct OnboardingWizardView: View {
|
||||
switch self.step {
|
||||
case .intro:
|
||||
self.introStep
|
||||
case .permissions:
|
||||
self.permissionsStep
|
||||
case .welcome:
|
||||
self.welcomeStep
|
||||
case .success:
|
||||
@@ -403,10 +401,6 @@ struct OnboardingWizardView: View {
|
||||
OnboardingIntroStep(onContinue: self.advanceFromIntro)
|
||||
}
|
||||
|
||||
private var permissionsStep: some View {
|
||||
OnboardingPermissionsStep(onContinue: self.advanceFromPermissions)
|
||||
}
|
||||
|
||||
private var welcomeStep: some View {
|
||||
OnboardingWelcomeStep(
|
||||
statusLine: self.statusLine,
|
||||
@@ -1148,13 +1142,7 @@ extension OnboardingWizardView {
|
||||
}
|
||||
|
||||
private func advanceFromIntro() {
|
||||
self.statusLine = ""
|
||||
self.navigate(to: .permissions)
|
||||
}
|
||||
|
||||
private func advanceFromPermissions() {
|
||||
// Marked here, not on the intro Continue: an interrupted first run must
|
||||
// replay intro + permissions on relaunch instead of skipping them forever.
|
||||
// An interrupted first run replays the intro until the user explicitly continues.
|
||||
OnboardingStateStore.markFirstRunIntroSeen()
|
||||
self.requestLocalNetworkAccess(reason: "onboarding_continue")
|
||||
self.statusLine = ""
|
||||
@@ -1162,9 +1150,8 @@ extension OnboardingWizardView {
|
||||
}
|
||||
|
||||
private func requestLocalNetworkAccessIfPastIntro(reason: String) {
|
||||
// The local-network prompt waits until pairing starts so it never stacks
|
||||
// on top of the permission prompts users trigger on the permissions step.
|
||||
guard self.step != .intro, self.step != .permissions else { return }
|
||||
// Keep the first-run intro focused; request local-network access when pairing starts.
|
||||
guard self.step != .intro else { return }
|
||||
self.requestLocalNetworkAccess(reason: reason)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import SwiftUI
|
||||
|
||||
/// Capsule action used by permission rows: filled for the initial "Allow",
|
||||
/// Capsule action used by permission rows: filled for the initial "Continue",
|
||||
/// bordered for repair actions like "Open Settings" or "Upgrade".
|
||||
struct DevicePermissionActionButtonStyle: ButtonStyle {
|
||||
let prominent: Bool
|
||||
@@ -38,7 +38,7 @@ struct DevicePermissionActionButtonStyle: ButtonStyle {
|
||||
}
|
||||
|
||||
/// One device permission with an icon tile, explanation, and a single clear
|
||||
/// affordance: Allow when unset, a green check when granted, a repair action otherwise.
|
||||
/// affordance: Continue when unset, a green check when granted, a repair action otherwise.
|
||||
struct DevicePermissionRow: View {
|
||||
let identifierPrefix: String
|
||||
let identifier: String
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import AVFoundation
|
||||
import Contacts
|
||||
import CoreLocation
|
||||
import EventKit
|
||||
import Photos
|
||||
import SwiftUI
|
||||
import UserNotifications
|
||||
|
||||
/// Closed grant state for one device permission, shared by onboarding and Settings rows.
|
||||
/// Closed grant state for one device permission shown in Settings.
|
||||
enum DevicePermissionGrant: Equatable {
|
||||
case granted
|
||||
case limited
|
||||
@@ -14,75 +11,42 @@ enum DevicePermissionGrant: Equatable {
|
||||
case denied
|
||||
}
|
||||
|
||||
/// Device permissions the connected agent can use from this iPhone.
|
||||
enum DevicePermissionKind: String, CaseIterable, Identifiable {
|
||||
case notifications
|
||||
case camera
|
||||
case microphone
|
||||
/// Device permissions managed by the Privacy & Access Settings section.
|
||||
enum DevicePermissionKind {
|
||||
case photos
|
||||
case contacts
|
||||
case calendar
|
||||
case reminders
|
||||
case location
|
||||
|
||||
var id: String {
|
||||
self.rawValue
|
||||
}
|
||||
|
||||
var symbol: String {
|
||||
switch self {
|
||||
case .notifications: "bell.badge.fill"
|
||||
case .camera: "camera.fill"
|
||||
case .microphone: "mic.fill"
|
||||
case .photos: "photo.on.rectangle"
|
||||
case .contacts: "person.crop.circle.fill"
|
||||
case .calendar: "calendar"
|
||||
case .reminders: "checklist"
|
||||
case .location: "location.fill"
|
||||
}
|
||||
}
|
||||
|
||||
var tint: Color {
|
||||
switch self {
|
||||
case .notifications: .red
|
||||
case .camera: .indigo
|
||||
case .microphone: .pink
|
||||
case .photos: .orange
|
||||
case .contacts: .blue
|
||||
case .calendar: .teal
|
||||
case .reminders: .green
|
||||
case .location: .purple
|
||||
}
|
||||
}
|
||||
|
||||
var title: LocalizedStringResource {
|
||||
switch self {
|
||||
case .notifications: LocalizedStringResource("Notifications")
|
||||
case .camera: LocalizedStringResource("Camera")
|
||||
case .microphone: LocalizedStringResource("Microphone")
|
||||
case .photos: LocalizedStringResource("Photos")
|
||||
case .contacts: LocalizedStringResource("Contacts")
|
||||
case .calendar: LocalizedStringResource("Calendar")
|
||||
case .reminders: LocalizedStringResource("Reminders")
|
||||
case .location: LocalizedStringResource("Location")
|
||||
}
|
||||
}
|
||||
|
||||
var detail: LocalizedStringResource {
|
||||
switch self {
|
||||
case .notifications: LocalizedStringResource("Pairing approvals and replies from your agent.")
|
||||
case .camera: LocalizedStringResource("Scan setup codes and take photos on request.")
|
||||
case .microphone: LocalizedStringResource("Talk with your agent using voice.")
|
||||
case .photos: LocalizedStringResource("Share recent photos when you ask.")
|
||||
case .contacts: LocalizedStringResource("Look up and add people you mention.")
|
||||
case .calendar: LocalizedStringResource("Check and add calendar events.")
|
||||
case .reminders: LocalizedStringResource("List, add, and complete reminders.")
|
||||
case .location: LocalizedStringResource("Answer location and nearby questions.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Pure status→grant maps so onboarding and Settings agree on one vocabulary.
|
||||
/// Pure status→grant maps keep every Settings row on one vocabulary.
|
||||
enum DevicePermissionStatusMap {
|
||||
static func contacts(_ status: CNAuthorizationStatus) -> DevicePermissionGrant {
|
||||
switch status {
|
||||
@@ -124,117 +88,4 @@ enum DevicePermissionStatusMap {
|
||||
@unknown default: .denied
|
||||
}
|
||||
}
|
||||
|
||||
static func capture(_ status: AVAuthorizationStatus) -> DevicePermissionGrant {
|
||||
switch status {
|
||||
case .authorized: .granted
|
||||
case .notDetermined: .notRequested
|
||||
case .denied, .restricted: .denied
|
||||
@unknown default: .denied
|
||||
}
|
||||
}
|
||||
|
||||
static func microphone(_ permission: AVAudioApplication.recordPermission) -> DevicePermissionGrant {
|
||||
switch permission {
|
||||
case .granted: .granted
|
||||
case .undetermined: .notRequested
|
||||
case .denied: .denied
|
||||
@unknown default: .denied
|
||||
}
|
||||
}
|
||||
|
||||
static func notifications(_ status: UNAuthorizationStatus) -> DevicePermissionGrant {
|
||||
switch status {
|
||||
case .authorized, .ephemeral, .provisional: .granted
|
||||
case .notDetermined: .notRequested
|
||||
case .denied: .denied
|
||||
@unknown default: .denied
|
||||
}
|
||||
}
|
||||
|
||||
static func location(_ status: CLAuthorizationStatus) -> DevicePermissionGrant {
|
||||
switch status {
|
||||
case .authorizedAlways, .authorizedWhenInUse: .granted
|
||||
case .notDetermined: .notRequested
|
||||
case .denied, .restricted: .denied
|
||||
@unknown default: .denied
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Live grant state + request plumbing for the onboarding permissions step.
|
||||
/// Requests only raise the system prompts; product opt-ins (push relay serving,
|
||||
/// Health sharing) remain explicit choices in Settings.
|
||||
@MainActor
|
||||
@Observable
|
||||
final class DevicePermissionsModel {
|
||||
private(set) var grants: [DevicePermissionKind: DevicePermissionGrant] = [:]
|
||||
private(set) var requesting: Set<DevicePermissionKind> = []
|
||||
private let eventKitPermissions: EventKitPermissionRequester
|
||||
|
||||
/// Owns its manager so authorization callbacks resolve without the app-wide service.
|
||||
private let locationService = LocationService()
|
||||
|
||||
init(eventKitPermissions: EventKitPermissionRequester = EventKitPermissionRequester()) {
|
||||
self.eventKitPermissions = eventKitPermissions
|
||||
self.locationService.setAuthorizationChangeHandler { [weak self] snapshot in
|
||||
self?.grants[.location] = DevicePermissionStatusMap.location(snapshot.authorizationStatus)
|
||||
}
|
||||
}
|
||||
|
||||
func grant(for kind: DevicePermissionKind) -> DevicePermissionGrant {
|
||||
self.grants[kind] ?? .notRequested
|
||||
}
|
||||
|
||||
func refresh() async {
|
||||
var next: [DevicePermissionKind: DevicePermissionGrant] = [
|
||||
.camera: DevicePermissionStatusMap.capture(AVCaptureDevice.authorizationStatus(for: .video)),
|
||||
.microphone: DevicePermissionStatusMap.microphone(AVAudioApplication.shared.recordPermission),
|
||||
.photos: DevicePermissionStatusMap.photos(PhotoLibraryAccess.authorizationStatus()),
|
||||
.contacts: DevicePermissionStatusMap.contacts(CNContactStore.authorizationStatus(for: .contacts)),
|
||||
.calendar: DevicePermissionStatusMap.eventKitRead(EKEventStore.authorizationStatus(for: .event)),
|
||||
.reminders: DevicePermissionStatusMap.eventKitRead(EKEventStore.authorizationStatus(for: .reminder)),
|
||||
.location: DevicePermissionStatusMap.location(self.locationService.authorizationStatus()),
|
||||
]
|
||||
let settings = await UNUserNotificationCenter.current().notificationSettings()
|
||||
next[.notifications] = DevicePermissionStatusMap.notifications(settings.authorizationStatus)
|
||||
self.grants = next
|
||||
}
|
||||
|
||||
func request(_ kind: DevicePermissionKind) async {
|
||||
guard self.grant(for: kind) == .notRequested, !self.requesting.contains(kind) else { return }
|
||||
self.requesting.insert(kind)
|
||||
defer { self.requesting.remove(kind) }
|
||||
|
||||
switch kind {
|
||||
case .notifications:
|
||||
_ = try? await UNUserNotificationCenter.current().requestAuthorization(options: [
|
||||
.alert,
|
||||
.badge,
|
||||
.sound,
|
||||
])
|
||||
case .camera:
|
||||
_ = await PermissionRequestBridge.awaitRequest { completion in
|
||||
AVCaptureDevice.requestAccess(for: .video, completionHandler: completion)
|
||||
}
|
||||
case .microphone:
|
||||
_ = await TalkModeManager.requestMicrophonePermission()
|
||||
case .photos:
|
||||
_ = await PhotoLibraryAccess.requestReadWrite()
|
||||
case .contacts:
|
||||
_ = await PermissionRequestBridge.awaitRequest { completion in
|
||||
CNContactStore().requestAccess(for: .contacts) { granted, _ in
|
||||
completion(granted)
|
||||
}
|
||||
}
|
||||
case .calendar:
|
||||
_ = await self.eventKitPermissions.requestFullAccessToEvents()
|
||||
case .reminders:
|
||||
_ = await self.eventKitPermissions.requestFullAccessToReminders()
|
||||
case .location:
|
||||
_ = await self.locationService.ensureAuthorization(mode: .whileUsing)
|
||||
}
|
||||
|
||||
await self.refresh()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ struct PrivacyAccessSectionView: View {
|
||||
{
|
||||
switch grant {
|
||||
case .notRequested:
|
||||
LocalizedStringResource("Allow")
|
||||
LocalizedStringResource("Continue")
|
||||
case .denied:
|
||||
LocalizedStringResource("Open Settings")
|
||||
case .limited:
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
import AVFoundation
|
||||
import Contacts
|
||||
import EventKit
|
||||
import Foundation
|
||||
import Photos
|
||||
import Testing
|
||||
import UserNotifications
|
||||
@testable import OpenClaw
|
||||
|
||||
struct DevicePermissionsTests {
|
||||
@@ -31,31 +30,58 @@ struct DevicePermissionsTests {
|
||||
#expect(DevicePermissionStatusMap.eventKitRead(.denied) == .denied)
|
||||
}
|
||||
|
||||
@Test func `capture microphone notification and location statuses map to shared grants`() {
|
||||
#expect(DevicePermissionStatusMap.capture(.authorized) == .granted)
|
||||
#expect(DevicePermissionStatusMap.capture(.notDetermined) == .notRequested)
|
||||
#expect(DevicePermissionStatusMap.capture(.denied) == .denied)
|
||||
|
||||
#expect(DevicePermissionStatusMap.microphone(.granted) == .granted)
|
||||
#expect(DevicePermissionStatusMap.microphone(.undetermined) == .notRequested)
|
||||
#expect(DevicePermissionStatusMap.microphone(.denied) == .denied)
|
||||
|
||||
#expect(DevicePermissionStatusMap.notifications(.authorized) == .granted)
|
||||
#expect(DevicePermissionStatusMap.notifications(.provisional) == .granted)
|
||||
#expect(DevicePermissionStatusMap.notifications(.notDetermined) == .notRequested)
|
||||
#expect(DevicePermissionStatusMap.notifications(.denied) == .denied)
|
||||
|
||||
#expect(DevicePermissionStatusMap.location(.authorizedWhenInUse) == .granted)
|
||||
#expect(DevicePermissionStatusMap.location(.authorizedAlways) == .granted)
|
||||
#expect(DevicePermissionStatusMap.location(.notDetermined) == .notRequested)
|
||||
#expect(DevicePermissionStatusMap.location(.restricted) == .denied)
|
||||
@Test func `first-run onboarding moves directly from intro to pairing`() {
|
||||
#expect(OnboardingStep.intro.previous == nil)
|
||||
#expect(OnboardingStep.welcome.previous == .intro)
|
||||
#expect(!OnboardingStep.welcome.canGoBack)
|
||||
#expect(OnboardingStep.welcome.manualProgressTitle.isEmpty)
|
||||
}
|
||||
|
||||
@Test func `first-run onboarding inserts permissions between intro and pairing`() {
|
||||
#expect(OnboardingStep.permissions.previous == .intro)
|
||||
#expect(OnboardingStep.welcome.previous == .permissions)
|
||||
#expect(!OnboardingStep.permissions.canGoBack)
|
||||
// Permissions is a first-run page, not part of the manual connect progress trail.
|
||||
#expect(OnboardingStep.permissions.manualProgressTitle.isEmpty)
|
||||
@Test func `onboarding has no aggregate system permission prompt`() throws {
|
||||
let onboardingDirectory = Self.sourceRoot()
|
||||
.appending(path: "Onboarding", directoryHint: .isDirectory)
|
||||
let wizard = try String(
|
||||
contentsOf: onboardingDirectory.appending(path: "OnboardingWizardView.swift"),
|
||||
encoding: .utf8)
|
||||
|
||||
#expect(!FileManager.default.fileExists(
|
||||
atPath: onboardingDirectory.appending(path: "OnboardingPermissionsStep.swift").path))
|
||||
#expect(!wizard.contains("OnboardingPermissionsStep"))
|
||||
#expect(!wizard.contains("navigate(to: .permissions)"))
|
||||
}
|
||||
|
||||
@Test func `settings first request says Continue and immediately requests permission`() throws {
|
||||
let settings = try String(
|
||||
contentsOf: Self.sourceRoot()
|
||||
.appending(path: "Settings", directoryHint: .isDirectory)
|
||||
.appending(path: "PrivacyAccessSectionView.swift"),
|
||||
encoding: .utf8)
|
||||
let actionTitles = try #require(Self.extract(
|
||||
settings,
|
||||
from: " private func standardActionTitle(",
|
||||
to: " /// `limitedRequests`"))
|
||||
let action = try #require(Self.extract(
|
||||
settings,
|
||||
from: " private func standardAction(",
|
||||
to: " private func requestContacts()"))
|
||||
|
||||
#expect(actionTitles.contains("case .notRequested:\n LocalizedStringResource(\"Continue\")"))
|
||||
#expect(!actionTitles.contains("LocalizedStringResource(\"Allow\")"))
|
||||
#expect(action.contains("await request()"))
|
||||
#expect(action.contains("case .notRequested:\n return run"))
|
||||
}
|
||||
|
||||
private static func sourceRoot() -> URL {
|
||||
URL(fileURLWithPath: #filePath)
|
||||
.deletingLastPathComponent()
|
||||
.deletingLastPathComponent()
|
||||
.appending(path: "Sources", directoryHint: .isDirectory)
|
||||
}
|
||||
|
||||
private static func extract(_ source: String, from start: String, to end: String) -> String? {
|
||||
guard let startRange = source.range(of: start),
|
||||
let endRange = source.range(of: end, range: startRange.upperBound..<source.endIndex)
|
||||
else { return nil }
|
||||
return String(source[startRange.lowerBound..<endRange.lowerBound])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -317,7 +317,8 @@ struct SwiftUIRenderSmokeTests {
|
||||
#expect(kind == .image)
|
||||
return OpenClawChatLoadedMedia.data(OpenClawChatMediaData(
|
||||
data: Data(base64Encoded:
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAusB9Y9Zl1sAAAAASUVORK5CYII=")!,
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8A" +
|
||||
"AusB9Y9Zl1sAAAAASUVORK5CYII=")!,
|
||||
mimeType: "image/png"))
|
||||
})
|
||||
let window = Self.host(root, size: CGSize(width: 393, height: 420))
|
||||
@@ -501,7 +502,6 @@ struct SwiftUIRenderSmokeTests {
|
||||
@Test @MainActor func `onboarding activation screens build across appearance and type size`() {
|
||||
let screens: [AnyView] = [
|
||||
AnyView(OnboardingIntroStep(onContinue: {})),
|
||||
AnyView(OnboardingPermissionsStep(onContinue: {})),
|
||||
AnyView(OnboardingWelcomeStep(
|
||||
statusLine: "",
|
||||
isConnecting: false,
|
||||
|
||||
@@ -681,9 +681,8 @@ final class OpenClawSnapshotUITests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(app.buttons["Continue"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
XCTAssertTrue(app.staticTexts["Allow access"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
app.tap()
|
||||
XCTAssertFalse(app.staticTexts["Allow access"].exists)
|
||||
|
||||
let copySetupCommand = app.buttons["Copy setup code command"]
|
||||
XCTAssertTrue(copySetupCommand.waitForExistence(timeout: 8))
|
||||
@@ -893,8 +892,6 @@ final class OpenClawSnapshotUITests: XCTestCase {
|
||||
|
||||
XCTAssertTrue(app.buttons["Continue"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
XCTAssertTrue(app.staticTexts["Allow access"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
app.tap()
|
||||
XCTAssertTrue(app.buttons["Connect Manually"].waitForExistence(timeout: 8))
|
||||
app.buttons["Connect Manually"].tap()
|
||||
@@ -954,6 +951,7 @@ final class OpenClawSnapshotUITests: XCTestCase {
|
||||
|
||||
let request = try XCTUnwrap(self.app?.buttons["privacy-access-photos-action"])
|
||||
XCTAssertTrue(request.waitForExistence(timeout: 5))
|
||||
XCTAssertEqual(request.label, "Continue")
|
||||
request.tap()
|
||||
self.app?.tap()
|
||||
|
||||
@@ -1280,8 +1278,6 @@ extension OpenClawSnapshotUITests {
|
||||
|
||||
XCTAssertTrue(app.buttons["Continue"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
XCTAssertTrue(app.staticTexts["Allow access"].waitForExistence(timeout: 8))
|
||||
app.buttons["Continue"].tap()
|
||||
app.tap()
|
||||
XCTAssertTrue(app.buttons["Connect Manually"].waitForExistence(timeout: 8))
|
||||
app.buttons["Connect Manually"].tap()
|
||||
|
||||
@@ -33,10 +33,12 @@ Availability: iPhone app builds are distributed through Apple channels when enab
|
||||
|
||||
## Quick start (pair + connect)
|
||||
|
||||
On first launch the app walks through a short pairing explainer and a
|
||||
permissions page (notifications, camera, microphone, photos, contacts,
|
||||
calendar, reminders, location). Every grant is optional and can be changed
|
||||
later in **Settings** -> **Permissions**, or in the iOS Settings app.
|
||||
On first launch the app walks through a short pairing explainer, then Gateway
|
||||
setup. It does not present an aggregate permissions page. Optional access is
|
||||
requested when you use the related feature, or after you tap **Continue** for
|
||||
that permission under **Settings** -> **Permissions** -> **Privacy & Access**.
|
||||
**Continue** immediately presents the native iOS authorization prompt. You can
|
||||
change granted access later in the iOS Settings app.
|
||||
|
||||
1. Start an authenticated Gateway with a route your phone can reach. Tailscale
|
||||
Serve is the recommended remote path:
|
||||
|
||||
Reference in New Issue
Block a user