authorgravatar for danpvernon@gmail.comDaniel Vernon <danpvernon@gmail.com> 2026-08-15 13:00:18+01:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-08-15 12:00:18+00:00
log4db50757c0b618293997a7f81bcfe30b68356969
tree0d89eb0d9e7027ec427055260a3188d63da4c2c7
parent270489b887420db3319898ab4046516e4c457711
signature Signed by PGP key B5690EEEBB952194

fix(mobile): explain iOS-only settings on Android (#4981)


4 files changed, 40 insertions(+), 1 deletions(-)

apps/mobile/src/features/settings/SettingsRouteScreen.logic.test.ts created+19
...@@ -0,0 +1,19 @@
1import { describe, expect, it } from "vite-plus/test";
2
3import { resolveAgentAwarenessPlatformPresentation } from "./SettingsRouteScreen.logic";
4
5describe("resolveAgentAwarenessPlatformPresentation", () => {
6 it("explains that agent awareness settings are unavailable on Android", () => {
7 expect(resolveAgentAwarenessPlatformPresentation("android")).toEqual({
8 supported: false,
9 subtitle: "iOS only",
10 });
11 });
12
13 it("leaves supported iOS settings unchanged", () => {
14 expect(resolveAgentAwarenessPlatformPresentation("ios")).toEqual({
15 supported: true,
16 subtitle: undefined,
17 });
18 });
19});
apps/mobile/src/features/settings/SettingsRouteScreen.logic.ts created+8
...@@ -0,0 +1,8 @@
1export function resolveAgentAwarenessPlatformPresentation(platform: string): {
2 readonly supported: boolean;
3 readonly subtitle: string | undefined;
4} {
5 return platform === "ios"
6 ? { supported: true, subtitle: undefined }
7 : { supported: false, subtitle: "iOS only" };
8}
apps/mobile/src/features/settings/SettingsRouteScreen.tsx+6
...@@ -46,6 +46,7 @@ import { useSavedRemoteConnections } from "../../state/use-remote-environment-re...@@ -46,6 +46,7 @@ import { useSavedRemoteConnections } from "../../state/use-remote-environment-re
46import { SettingsRow } from "./components/SettingsRow";46import { SettingsRow } from "./components/SettingsRow";
47import { SettingsSection } from "./components/SettingsSection";47import { SettingsSection } from "./components/SettingsSection";
48import { SettingsSwitchRow } from "./components/SettingsSwitchRow";48import { SettingsSwitchRow } from "./components/SettingsSwitchRow";
49import { resolveAgentAwarenessPlatformPresentation } from "./SettingsRouteScreen.logic";
4950
50type NotificationStatus = "checking" | "enabled" | "disabled" | "unsupported";51type NotificationStatus = "checking" | "enabled" | "disabled" | "unsupported";
51type LiveActivityStatus = "checking" | "enabled" | "disabled" | "signed-out" | "linking";52type LiveActivityStatus = "checking" | "enabled" | "disabled" | "signed-out" | "linking";
...@@ -144,6 +145,7 @@ function ConfiguredSettingsRouteScreen() {...@@ -144,6 +145,7 @@ function ConfiguredSettingsRouteScreen() {
144 const preferencesResult = useAtomValue(mobilePreferencesAtom);145 const preferencesResult = useAtomValue(mobilePreferencesAtom);
145 const savePreferences = useAtomSet(updateMobilePreferencesAtom);146 const savePreferences = useAtomSet(updateMobilePreferencesAtom);
146 const agentAwarenessPushAvailable = supportsAgentAwarenessPush();147 const agentAwarenessPushAvailable = supportsAgentAwarenessPush();
148 const agentAwarenessPlatform = resolveAgentAwarenessPlatformPresentation(Platform.OS);
147 const insets = useSafeAreaInsets();149 const insets = useSafeAreaInsets();
148 const navigation = useNavigation();150 const navigation = useNavigation();
149 const { getToken, isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });151 const { getToken, isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
...@@ -473,10 +475,12 @@ function ConfiguredSettingsRouteScreen() {...@@ -473,10 +475,12 @@ function ConfiguredSettingsRouteScreen() {
473 icon="bell.badge"475 icon="bell.badge"
474 label="Device Notifications"476 label="Device Notifications"
475 disabled={477 disabled={
478 !agentAwarenessPlatform.supported ||
476 !agentAwarenessPushAvailable ||479 !agentAwarenessPushAvailable ||
477 notificationStatus === "checking" ||480 notificationStatus === "checking" ||
478 notificationStatus === "unsupported"481 notificationStatus === "unsupported"
479 }482 }
483 subtitle={agentAwarenessPlatform.subtitle}
480 // Only reads as on when this device is actually registered with the484 // Only reads as on when this device is actually registered with the
481 // relay; otherwise notifications cannot be delivered regardless of485 // relay; otherwise notifications cannot be delivered regardless of
482 // the local iOS permission.486 // the local iOS permission.
...@@ -487,6 +491,7 @@ function ConfiguredSettingsRouteScreen() {...@@ -487,6 +491,7 @@ function ConfiguredSettingsRouteScreen() {
487 />491 />
488 <SettingsSwitchRow492 <SettingsSwitchRow
489 disabled={493 disabled={
494 !agentAwarenessPlatform.supported ||
490 !agentAwarenessPushAvailable ||495 !agentAwarenessPushAvailable ||
491 !isLoaded ||496 !isLoaded ||
492 liveActivityStatus === "checking" ||497 liveActivityStatus === "checking" ||
...@@ -494,6 +499,7 @@ function ConfiguredSettingsRouteScreen() {...@@ -494,6 +499,7 @@ function ConfiguredSettingsRouteScreen() {
494 }499 }
495 icon="bolt.circle"500 icon="bolt.circle"
496 label="Live Activity Updates"501 label="Live Activity Updates"
502 subtitle={agentAwarenessPlatform.subtitle}
497 // Same gate: a saved preference is meaningless until the device503 // Same gate: a saved preference is meaningless until the device
498 // registration the relay needs to push updates has succeeded.504 // registration the relay needs to push updates has succeeded.
499 value={505 value={
apps/mobile/src/features/settings/components/SettingsSwitchRow.tsx+7-1
...@@ -11,6 +11,7 @@ export function SettingsSwitchRow(props: {...@@ -11,6 +11,7 @@ export function SettingsSwitchRow(props: {
11 readonly disabled?: boolean;11 readonly disabled?: boolean;
12 readonly icon: SymbolName;12 readonly icon: SymbolName;
13 readonly label: string;13 readonly label: string;
14 readonly subtitle?: string;
14 readonly value: boolean;15 readonly value: boolean;
15 readonly onValueChange: (value: boolean) => void;16 readonly onValueChange: (value: boolean) => void;
16}) {17}) {
...@@ -27,7 +28,12 @@ export function SettingsSwitchRow(props: {...@@ -27,7 +28,12 @@ export function SettingsSwitchRow(props: {
27 }28 }
28 >29 >
29 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />30 <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" />
30 <Text className="flex-1 text-lg text-foreground">{props.label}</Text>31 <View className="min-w-0 flex-1">
32 <Text className="text-lg text-foreground">{props.label}</Text>
33 {props.subtitle ? (
34 <Text className="text-sm text-foreground-muted">{props.subtitle}</Text>
35 ) : null}
36 </View>
31 <Switch37 <Switch
32 disabled={props.disabled}38 disabled={props.disabled}
33 ios_backgroundColor={track}39 ios_backgroundColor={track}