@ramonclaudio/create-vexpo 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/index.js +8 -7
- package/dist/templates/default/.eas/workflows/asc-events.yml +9 -6
- package/dist/templates/default/.eas/workflows/deploy-production.yml +28 -15
- package/dist/templates/default/.eas/workflows/e2e-tests.yml +3 -2
- package/dist/templates/default/.eas/workflows/pr-preview.yml +12 -21
- package/dist/templates/default/.eas/workflows/release.yml +3 -7
- package/dist/templates/default/.eas/workflows/rollback.yml +54 -28
- package/dist/templates/default/.eas/workflows/rollout.yml +27 -33
- package/dist/templates/default/.eas/workflows/rotate-apple-jwt.yml +1 -5
- package/dist/templates/default/.eas/workflows/testflight.yml +3 -7
- package/dist/templates/default/.github/workflows/check.yml +20 -12
- package/dist/templates/default/.maestro/launch.yaml +19 -10
- package/dist/templates/default/AGENTS.md +25 -8
- package/dist/templates/default/DESIGN.md +14 -10
- package/dist/templates/default/README.md +83 -78
- package/dist/templates/default/SETUP.md +159 -152
- package/dist/templates/default/__tests__/convex/_auth-harness.test.ts +112 -0
- package/dist/templates/default/__tests__/convex/_harness.ts +132 -0
- package/dist/templates/default/__tests__/convex/appAttest.test.ts +172 -0
- package/dist/templates/default/__tests__/convex/appAttestStore.test.ts +48 -0
- package/dist/templates/default/__tests__/convex/pushTokens-remove.test.ts +106 -0
- package/dist/templates/default/__tests__/convex/pushTokens-upsert.test.ts +146 -0
- package/dist/templates/default/__tests__/convex/users-deleteAccount.test.ts +140 -0
- package/dist/templates/default/__tests__/convex/users-deleteAvatar.test.ts +104 -0
- package/dist/templates/default/__tests__/convex/users-getMe.test.ts +98 -0
- package/dist/templates/default/__tests__/convex/users-getUser.test.ts +120 -0
- package/dist/templates/default/__tests__/convex/users-hardDeleteExpired.test.ts +67 -0
- package/dist/templates/default/__tests__/convex/users-restoreAccount.test.ts +96 -0
- package/dist/templates/default/__tests__/convex/users-updateAvatar.test.ts +92 -0
- package/dist/templates/default/__tests__/convex/users-updateProfile.test.ts +126 -0
- package/dist/templates/default/__tests__/convex/webhook.test.ts +31 -0
- package/dist/templates/default/__tests__/lib/deep-link.test.ts +51 -6
- package/dist/templates/default/__tests__/lib/schemas.test.ts +205 -0
- package/dist/templates/default/__tests__/lib/text-style.test.ts +31 -0
- package/dist/templates/default/_env.example +7 -7
- package/dist/templates/default/_gitattributes +1 -1
- package/dist/templates/default/_gitignore +17 -2
- package/dist/templates/default/_npmrc +7 -0
- package/dist/templates/default/_oxlintrc.json +1 -1
- package/dist/templates/default/app-store/accessibility.config.json +20 -0
- package/dist/templates/default/app-store/privacy.config.json +27 -0
- package/dist/templates/default/app.config.ts +105 -33
- package/dist/templates/default/app.json +1 -9
- package/dist/templates/default/convex/_generated/api.d.ts +12 -0
- package/dist/templates/default/convex/admin.ts +0 -13
- package/dist/templates/default/convex/appAttest.ts +467 -0
- package/dist/templates/default/convex/appAttestStore.ts +141 -0
- package/dist/templates/default/convex/apple.ts +53 -0
- package/dist/templates/default/convex/auth.ts +6 -45
- package/dist/templates/default/convex/constants.ts +2 -7
- package/dist/templates/default/convex/crons.ts +12 -5
- package/dist/templates/default/convex/email.ts +4 -24
- package/dist/templates/default/convex/env.ts +0 -4
- package/dist/templates/default/convex/errors.ts +0 -7
- package/dist/templates/default/convex/functions.ts +0 -26
- package/dist/templates/default/convex/http.ts +3 -5
- package/dist/templates/default/convex/log.ts +2 -25
- package/dist/templates/default/convex/pushSender.ts +145 -0
- package/dist/templates/default/convex/pushTokens.ts +110 -13
- package/dist/templates/default/convex/rateLimit.ts +8 -39
- package/dist/templates/default/convex/schema.ts +48 -5
- package/dist/templates/default/convex/tsconfig.json +1 -0
- package/dist/templates/default/convex/users.ts +143 -61
- package/dist/templates/default/convex/validators.ts +1 -38
- package/dist/templates/default/convex/webhook.ts +1 -31
- package/dist/templates/default/convex.json +1 -2
- package/dist/templates/default/metro.config.js +9 -1
- package/dist/templates/default/package.json +67 -70
- package/dist/templates/default/plugins/README.md +5 -1
- package/dist/templates/default/scripts/README.md +9 -9
- package/dist/templates/default/scripts/_run.mjs +3 -20
- package/dist/templates/default/scripts/clean.ts +81 -69
- package/dist/templates/default/scripts/gen-update-cert.mjs +98 -0
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home)/index.tsx +21 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(home,search)/_layout.tsx +9 -8
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/(search)/index.tsx +26 -24
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/_layout.tsx +3 -4
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/_layout.tsx +10 -6
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/index.tsx +81 -51
- package/dist/templates/default/{app → src/app}/(app)/(tabs)/settings/preferences.tsx +72 -12
- package/dist/templates/default/src/app/(app)/_layout.tsx +147 -0
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/_layout.tsx +4 -5
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/forgot-password.tsx +15 -9
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/reset-password.tsx +88 -14
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-in.tsx +65 -35
- package/dist/templates/default/{app/(auth) → src/app/(app)/auth}/sign-up.tsx +131 -196
- package/dist/templates/default/src/app/(app)/debug.tsx +479 -0
- package/dist/templates/default/{app → src/app}/(app)/help.tsx +76 -64
- package/dist/templates/default/{app → src/app}/(app)/linked.tsx +21 -27
- package/dist/templates/default/{app → src/app}/(app)/privacy.tsx +35 -8
- package/dist/templates/default/src/app/(app)/profile/change-password.tsx +264 -0
- package/dist/templates/default/{app/(app)/profile.tsx → src/app/(app)/profile/index.tsx} +179 -255
- package/dist/templates/default/src/app/(app)/restore-account.tsx +192 -0
- package/dist/templates/default/src/app/(app)/sessions.tsx +287 -0
- package/dist/templates/default/src/app/(app)/welcome.tsx +194 -0
- package/dist/templates/default/src/app/+native-intent.tsx +25 -0
- package/dist/templates/default/src/app/+not-found.tsx +43 -0
- package/dist/templates/default/{app → src/app}/_layout.tsx +28 -37
- package/dist/templates/default/src/components/auth/apple-button.tsx +51 -0
- package/dist/templates/default/{components → src/components}/auth/otp-verification.tsx +79 -58
- package/dist/templates/default/{components → src/components}/auth/password-field.tsx +74 -18
- package/dist/templates/default/src/components/auth/segmented-toggle.tsx +71 -0
- package/dist/templates/default/src/components/ui/content-unavailable.tsx +81 -0
- package/dist/templates/default/src/components/ui/convex-error.tsx +21 -0
- package/dist/templates/default/src/components/ui/error-boundary.tsx +89 -0
- package/dist/templates/default/{components → src/components}/ui/loading-screen.tsx +5 -4
- package/dist/templates/default/{components → src/components}/ui/material.tsx +50 -17
- package/dist/templates/default/src/components/ui/offline-banner.tsx +59 -0
- package/dist/templates/default/{components → src/components}/ui/prominent-button.tsx +8 -11
- package/dist/templates/default/{components → src/components}/ui/skeleton.tsx +31 -13
- package/dist/templates/default/src/components/ui/status-text.tsx +64 -0
- package/dist/templates/default/src/components/ui/update-banner.tsx +85 -0
- package/dist/templates/default/{constants → src/constants}/layout.ts +0 -6
- package/dist/templates/default/{constants → src/constants}/theme.ts +49 -64
- package/dist/templates/default/{constants → src/constants}/ui.ts +13 -4
- package/dist/templates/default/src/hooks/use-debounce.ts +12 -0
- package/dist/templates/default/src/hooks/use-deep-link.ts +51 -0
- package/dist/templates/default/src/hooks/use-delete-account.ts +35 -0
- package/dist/templates/default/src/hooks/use-motion-screen-options.ts +13 -0
- package/dist/templates/default/src/hooks/use-network.ts +34 -0
- package/dist/templates/default/{hooks → src/hooks}/use-notifications.ts +39 -30
- package/dist/templates/default/src/hooks/use-reduce-transparency.ts +30 -0
- package/dist/templates/default/{hooks → src/hooks}/use-theme.ts +0 -5
- package/dist/templates/default/src/lib/appAttest.ts +78 -0
- package/dist/templates/default/src/lib/assets.ts +9 -0
- package/dist/templates/default/src/lib/deep-link.ts +82 -0
- package/dist/templates/default/{lib → src/lib}/dev-menu.ts +0 -4
- package/dist/templates/default/{lib → src/lib}/device.ts +1 -13
- package/dist/templates/default/{lib → src/lib}/dynamic-font.ts +13 -10
- package/dist/templates/default/src/lib/dynamic-symbol-size.ts +33 -0
- package/dist/templates/default/src/lib/masks.ts +21 -0
- package/dist/templates/default/src/lib/native-state.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/notifications.ts +7 -45
- package/dist/templates/default/{lib → src/lib}/preferences.ts +0 -2
- package/dist/templates/default/{lib → src/lib}/schemas.ts +19 -16
- package/dist/templates/default/src/lib/text-style.ts +20 -0
- package/dist/templates/default/{lib → src/lib}/updates.ts +0 -7
- package/dist/templates/default/store.config.json +1 -1
- package/dist/templates/default/tsconfig.json +3 -1
- package/dist/templates/default/vitest.config.ts +8 -1
- package/package.json +5 -5
- package/dist/templates/default/app/(app)/_layout.tsx +0 -73
- package/dist/templates/default/app/(app)/debug.tsx +0 -389
- package/dist/templates/default/app/(app)/sessions.tsx +0 -191
- package/dist/templates/default/app/(app)/welcome.tsx +0 -140
- package/dist/templates/default/app/+native-intent.tsx +0 -14
- package/dist/templates/default/app/+not-found.tsx +0 -51
- package/dist/templates/default/bun.lock +0 -1860
- package/dist/templates/default/components/auth/segmented-toggle.tsx +0 -47
- package/dist/templates/default/components/ui/convex-error.tsx +0 -32
- package/dist/templates/default/components/ui/error-boundary.tsx +0 -57
- package/dist/templates/default/components/ui/offline-banner.tsx +0 -58
- package/dist/templates/default/components/ui/status-text.tsx +0 -49
- package/dist/templates/default/components/ui/update-banner.tsx +0 -82
- package/dist/templates/default/fingerprint.config.js +0 -9
- package/dist/templates/default/hooks/use-debounce.ts +0 -20
- package/dist/templates/default/hooks/use-deep-link.ts +0 -43
- package/dist/templates/default/hooks/use-network.ts +0 -11
- package/dist/templates/default/lib/assets.ts +0 -17
- package/dist/templates/default/lib/deep-link.ts +0 -71
- package/dist/templates/default/patches/PR-368.patch +0 -91
- package/dist/templates/default/patches/convex-dev-better-auth-0.12.2.tgz +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-navigation-tracking.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-onboarding.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-reduced-motion.ts +0 -0
- /package/dist/templates/default/{hooks → src/hooks}/use-updates.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/a11y.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/app.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/auth-client.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/convex-auth.tsx +0 -0
- /package/dist/templates/default/{lib → src/lib}/env.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/haptics.ts +0 -0
- /package/dist/templates/default/{lib → src/lib}/storage.ts +0 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { Stack } from "expo-router";
|
|
2
2
|
|
|
3
3
|
import { useColors } from "@/hooks/use-theme";
|
|
4
|
-
import {
|
|
4
|
+
import { useMotionScreenOptions } from "@/hooks/use-motion-screen-options";
|
|
5
|
+
import { FontFamily } from "@/constants/layout";
|
|
5
6
|
import { HeaderTint } from "@/constants/theme";
|
|
6
7
|
import { LoadingScreen } from "@/components/ui/loading-screen";
|
|
7
8
|
|
|
@@ -10,26 +11,29 @@ export const unstable_settings = {
|
|
|
10
11
|
};
|
|
11
12
|
|
|
12
13
|
export function SuspenseFallback() {
|
|
13
|
-
return <LoadingScreen />;
|
|
14
|
+
return <LoadingScreen testID="settings-loading" />;
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
export default function SettingsLayout() {
|
|
17
18
|
const colors = useColors();
|
|
18
|
-
const
|
|
19
|
+
const motion = useMotionScreenOptions("default");
|
|
19
20
|
|
|
20
21
|
return (
|
|
21
22
|
<Stack
|
|
22
23
|
screenOptions={{
|
|
24
|
+
...motion,
|
|
23
25
|
headerShown: false,
|
|
24
26
|
contentStyle: { backgroundColor: colors.background as string },
|
|
25
|
-
animation: reduceMotion ? "fade" : "default",
|
|
26
|
-
animationDuration: reduceMotion ? 150 : undefined,
|
|
27
27
|
}}
|
|
28
28
|
>
|
|
29
29
|
<Stack.Screen name="index" />
|
|
30
30
|
<Stack.Screen name="preferences" options={{ headerShown: true }}>
|
|
31
31
|
<Stack.Header transparent />
|
|
32
|
-
<Stack.Screen.Title
|
|
32
|
+
<Stack.Screen.Title
|
|
33
|
+
style={{ color: HeaderTint as string, fontFamily: FontFamily.semiBold }}
|
|
34
|
+
>
|
|
35
|
+
Preferences
|
|
36
|
+
</Stack.Screen.Title>
|
|
33
37
|
<Stack.Screen.BackButton>Settings</Stack.Screen.BackButton>
|
|
34
38
|
</Stack.Screen>
|
|
35
39
|
</Stack>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { useState, type ComponentProps } from "react";
|
|
2
2
|
import Constants from "expo-constants";
|
|
3
3
|
import * as Clipboard from "expo-clipboard";
|
|
4
|
-
import
|
|
4
|
+
import { useDeleteAccount } from "@/hooks/use-delete-account";
|
|
5
5
|
import { Image as ExpoImage, useImage } from "expo-image";
|
|
6
|
-
import { router,
|
|
6
|
+
import { router, type Href } from "expo-router";
|
|
7
7
|
|
|
8
8
|
const PROFILE_HREF = "/profile" as Href;
|
|
9
9
|
const DEBUG_HREF = "/debug" as Href;
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Spacer,
|
|
19
19
|
Image,
|
|
20
20
|
RNHostView,
|
|
21
|
+
Alert,
|
|
21
22
|
ConfirmationDialog,
|
|
22
23
|
} from "@expo/ui/swift-ui";
|
|
23
24
|
import {
|
|
@@ -27,7 +28,7 @@ import {
|
|
|
27
28
|
foregroundStyle,
|
|
28
29
|
frame,
|
|
29
30
|
padding,
|
|
30
|
-
|
|
31
|
+
accessibilityHidden,
|
|
31
32
|
accessibilityLabel,
|
|
32
33
|
lineLimit,
|
|
33
34
|
truncationMode,
|
|
@@ -36,12 +37,14 @@ import {
|
|
|
36
37
|
tint,
|
|
37
38
|
} from "@expo/ui/swift-ui/modifiers";
|
|
38
39
|
import { useDynamicFont } from "@/lib/dynamic-font";
|
|
40
|
+
import { useSymbolSize } from "@/lib/dynamic-symbol-size";
|
|
39
41
|
import { Button as ButtonTokens } from "@/constants/layout";
|
|
40
42
|
|
|
41
43
|
import { api } from "@/convex/_generated/api";
|
|
42
44
|
import { authClient } from "@/lib/auth-client";
|
|
43
45
|
import { haptics } from "@/lib/haptics";
|
|
44
46
|
import { announce } from "@/lib/a11y";
|
|
47
|
+
import { ErrorText } from "@/components/ui/status-text";
|
|
45
48
|
import { useColors } from "@/hooks/use-theme";
|
|
46
49
|
import { useDebugEnabled } from "@/lib/preferences";
|
|
47
50
|
|
|
@@ -49,17 +52,16 @@ const HEADER_AVATAR_SIZE = 56;
|
|
|
49
52
|
|
|
50
53
|
export default function SettingsScreen() {
|
|
51
54
|
const dfont = useDynamicFont();
|
|
55
|
+
const symbolSize = useSymbolSize();
|
|
52
56
|
const colors = useColors();
|
|
53
57
|
const me = useQuery(api.users.getMe);
|
|
54
58
|
const removeAllTokens = useMutation(api.pushTokens.removeAll);
|
|
55
|
-
const
|
|
59
|
+
const { deleteAccount, deleteError } = useDeleteAccount();
|
|
56
60
|
|
|
57
61
|
const [showSignOut, setShowSignOut] = useState(false);
|
|
58
62
|
const [showDeleteAccount, setShowDeleteAccount] = useState(false);
|
|
59
63
|
const [debugOn] = useDebugEnabled();
|
|
60
64
|
|
|
61
|
-
usePreventZoomTransitionDismissal();
|
|
62
|
-
|
|
63
65
|
const navigate = (path: Href) => {
|
|
64
66
|
haptics.light();
|
|
65
67
|
router.push(path);
|
|
@@ -67,8 +69,6 @@ export default function SettingsScreen() {
|
|
|
67
69
|
|
|
68
70
|
const handleSignOut = async () => {
|
|
69
71
|
haptics.medium();
|
|
70
|
-
// Push-token cleanup is best-effort. A stale token gets garbage-collected
|
|
71
|
-
// by `pushTokens.cleanupStale` after 30 days, so don't gate sign-out on it.
|
|
72
72
|
try {
|
|
73
73
|
await removeAllTokens();
|
|
74
74
|
} catch (err) {
|
|
@@ -77,16 +77,6 @@ export default function SettingsScreen() {
|
|
|
77
77
|
await authClient.signOut();
|
|
78
78
|
};
|
|
79
79
|
|
|
80
|
-
const handleDeleteAccount = async () => {
|
|
81
|
-
haptics.error();
|
|
82
|
-
const result = await LocalAuthentication.authenticateAsync({
|
|
83
|
-
promptMessage: "Confirm with Face ID",
|
|
84
|
-
});
|
|
85
|
-
if (!result.success) return;
|
|
86
|
-
await deleteAccountMutation();
|
|
87
|
-
await authClient.signOut();
|
|
88
|
-
};
|
|
89
|
-
|
|
90
80
|
const version = Constants.expoConfig?.version ?? "1.0.0";
|
|
91
81
|
|
|
92
82
|
const handleCopyVersion = async () => {
|
|
@@ -98,12 +88,14 @@ export default function SettingsScreen() {
|
|
|
98
88
|
|
|
99
89
|
type SFSymbol = NonNullable<ComponentProps<typeof Image>["systemName"]>;
|
|
100
90
|
const rowButton = ({
|
|
91
|
+
testID,
|
|
101
92
|
label,
|
|
102
93
|
systemImage,
|
|
103
94
|
onPress,
|
|
104
95
|
role,
|
|
105
96
|
fg,
|
|
106
97
|
}: {
|
|
98
|
+
testID: string;
|
|
107
99
|
label: string;
|
|
108
100
|
systemImage: SFSymbol;
|
|
109
101
|
onPress: () => void;
|
|
@@ -115,9 +107,10 @@ export default function SettingsScreen() {
|
|
|
115
107
|
(role === "destructive" ? (colors.destructive as string) : (colors.foreground as string));
|
|
116
108
|
return (
|
|
117
109
|
<Button
|
|
110
|
+
testID={testID}
|
|
118
111
|
modifiers={[
|
|
119
112
|
buttonStyle("plain"),
|
|
120
|
-
frame({ maxWidth:
|
|
113
|
+
frame({ maxWidth: Infinity }),
|
|
121
114
|
background(colors.muted as string),
|
|
122
115
|
clipShape("capsule"),
|
|
123
116
|
]}
|
|
@@ -127,17 +120,27 @@ export default function SettingsScreen() {
|
|
|
127
120
|
spacing={12}
|
|
128
121
|
alignment="center"
|
|
129
122
|
modifiers={[
|
|
130
|
-
frame({ maxWidth:
|
|
123
|
+
frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
|
|
131
124
|
padding({ horizontal: 16 }),
|
|
132
125
|
]}
|
|
133
126
|
>
|
|
134
|
-
<Image
|
|
127
|
+
<Image
|
|
128
|
+
systemName={systemImage}
|
|
129
|
+
size={symbolSize(18)}
|
|
130
|
+
color={labelColor}
|
|
131
|
+
modifiers={[accessibilityHidden(true)]}
|
|
132
|
+
/>
|
|
135
133
|
<Text modifiers={[dfont({ size: 16, weight: "medium" }), foregroundStyle(labelColor)]}>
|
|
136
134
|
{label}
|
|
137
135
|
</Text>
|
|
138
136
|
<Spacer />
|
|
139
137
|
{role !== "destructive" ? (
|
|
140
|
-
<Image
|
|
138
|
+
<Image
|
|
139
|
+
systemName="chevron.right"
|
|
140
|
+
size={symbolSize(13)}
|
|
141
|
+
color={colors.mutedForeground as string}
|
|
142
|
+
modifiers={[accessibilityHidden(true)]}
|
|
143
|
+
/>
|
|
141
144
|
) : null}
|
|
142
145
|
</HStack>
|
|
143
146
|
</Button>
|
|
@@ -145,7 +148,7 @@ export default function SettingsScreen() {
|
|
|
145
148
|
};
|
|
146
149
|
|
|
147
150
|
return (
|
|
148
|
-
<Host style={{ flex: 1, backgroundColor: colors.background }}>
|
|
151
|
+
<Host testID="settings-screen" style={{ flex: 1, backgroundColor: colors.background }}>
|
|
149
152
|
<ScrollView
|
|
150
153
|
modifiers={[scrollDismissesKeyboard("interactively"), tint(colors.primary as string)]}
|
|
151
154
|
>
|
|
@@ -154,32 +157,32 @@ export default function SettingsScreen() {
|
|
|
154
157
|
alignment="leading"
|
|
155
158
|
modifiers={[padding({ horizontal: 24, top: 24, bottom: 40 })]}
|
|
156
159
|
>
|
|
157
|
-
{/* Profile header */}
|
|
158
160
|
<Button
|
|
161
|
+
testID="settings-profile"
|
|
159
162
|
modifiers={[
|
|
160
163
|
buttonStyle("plain"),
|
|
161
|
-
frame({ maxWidth:
|
|
164
|
+
frame({ maxWidth: Infinity }),
|
|
162
165
|
background(colors.muted as string),
|
|
163
166
|
clipShape("capsule"),
|
|
164
|
-
onTapGesture(() => {
|
|
165
|
-
haptics.light();
|
|
166
|
-
navigate(PROFILE_HREF);
|
|
167
|
-
}),
|
|
168
167
|
accessibilityLabel("Open profile"),
|
|
169
168
|
]}
|
|
170
|
-
onPress={() =>
|
|
169
|
+
onPress={() => {
|
|
170
|
+
haptics.light();
|
|
171
|
+
navigate(PROFILE_HREF);
|
|
172
|
+
}}
|
|
171
173
|
>
|
|
172
174
|
<HStack
|
|
173
175
|
spacing={16}
|
|
174
176
|
alignment="center"
|
|
175
177
|
modifiers={[
|
|
176
|
-
frame({ maxWidth:
|
|
178
|
+
frame({ maxWidth: Infinity, minHeight: 80 }),
|
|
177
179
|
padding({ leading: 8, trailing: 16 }),
|
|
178
180
|
]}
|
|
179
181
|
>
|
|
180
182
|
<ProfileHeaderAvatar avatarUrl={me?.avatarUrl ?? null} />
|
|
181
183
|
<VStack alignment="leading" spacing={2}>
|
|
182
184
|
<Text
|
|
185
|
+
testID="settings-profile-name"
|
|
183
186
|
modifiers={[
|
|
184
187
|
dfont({ size: 17, weight: "semibold" }),
|
|
185
188
|
foregroundStyle(colors.foreground as string),
|
|
@@ -191,6 +194,7 @@ export default function SettingsScreen() {
|
|
|
191
194
|
</Text>
|
|
192
195
|
{me?.email ? (
|
|
193
196
|
<Text
|
|
197
|
+
testID="settings-profile-email"
|
|
194
198
|
modifiers={[
|
|
195
199
|
dfont({ size: 14 }),
|
|
196
200
|
foregroundStyle(colors.mutedForeground as string),
|
|
@@ -206,19 +210,22 @@ export default function SettingsScreen() {
|
|
|
206
210
|
<Spacer />
|
|
207
211
|
<Image
|
|
208
212
|
systemName="chevron.right"
|
|
209
|
-
size={13}
|
|
213
|
+
size={symbolSize(13)}
|
|
210
214
|
color={colors.mutedForeground as string}
|
|
215
|
+
modifiers={[accessibilityHidden(true)]}
|
|
211
216
|
/>
|
|
212
217
|
</HStack>
|
|
213
218
|
</Button>
|
|
214
219
|
|
|
215
220
|
<VStack spacing={8} modifiers={[frame({ maxWidth: Infinity })]}>
|
|
216
221
|
{rowButton({
|
|
222
|
+
testID: "settings-sessions",
|
|
217
223
|
label: "Sessions",
|
|
218
224
|
systemImage: "list.bullet.rectangle.portrait",
|
|
219
225
|
onPress: () => navigate("/sessions"),
|
|
220
226
|
})}
|
|
221
227
|
{rowButton({
|
|
228
|
+
testID: "settings-preferences",
|
|
222
229
|
label: "Preferences",
|
|
223
230
|
systemImage: "slider.horizontal.3",
|
|
224
231
|
onPress: () => navigate("/settings/preferences"),
|
|
@@ -227,22 +234,26 @@ export default function SettingsScreen() {
|
|
|
227
234
|
|
|
228
235
|
<VStack spacing={8} modifiers={[frame({ maxWidth: Infinity })]}>
|
|
229
236
|
{rowButton({
|
|
237
|
+
testID: "settings-help",
|
|
230
238
|
label: "Help & Feedback",
|
|
231
|
-
systemImage: "bubble.
|
|
239
|
+
systemImage: "questionmark.bubble.fill",
|
|
232
240
|
onPress: () => navigate("/help"),
|
|
233
241
|
})}
|
|
234
242
|
{rowButton({
|
|
243
|
+
testID: "settings-privacy",
|
|
235
244
|
label: "Privacy",
|
|
236
|
-
systemImage: "
|
|
245
|
+
systemImage: "lock.shield.fill",
|
|
237
246
|
onPress: () => navigate("/privacy"),
|
|
238
247
|
})}
|
|
239
248
|
{rowButton({
|
|
249
|
+
testID: "settings-copy-version",
|
|
240
250
|
label: "Copy version",
|
|
241
251
|
systemImage: "doc.on.doc",
|
|
242
252
|
onPress: handleCopyVersion,
|
|
243
253
|
})}
|
|
244
254
|
{debugOn
|
|
245
255
|
? rowButton({
|
|
256
|
+
testID: "settings-debug",
|
|
246
257
|
label: "Debug",
|
|
247
258
|
systemImage: "ant.circle",
|
|
248
259
|
onPress: () => navigate(DEBUG_HREF),
|
|
@@ -259,6 +270,7 @@ export default function SettingsScreen() {
|
|
|
259
270
|
>
|
|
260
271
|
<ConfirmationDialog.Trigger>
|
|
261
272
|
{rowButton({
|
|
273
|
+
testID: "settings-sign-out",
|
|
262
274
|
label: "Sign out",
|
|
263
275
|
systemImage: "rectangle.portrait.and.arrow.right",
|
|
264
276
|
onPress: () => setShowSignOut(true),
|
|
@@ -266,8 +278,13 @@ export default function SettingsScreen() {
|
|
|
266
278
|
})}
|
|
267
279
|
</ConfirmationDialog.Trigger>
|
|
268
280
|
<ConfirmationDialog.Actions>
|
|
269
|
-
<Button
|
|
270
|
-
|
|
281
|
+
<Button
|
|
282
|
+
testID="settings-sign-out-confirm"
|
|
283
|
+
label="Sign Out"
|
|
284
|
+
role="destructive"
|
|
285
|
+
onPress={handleSignOut}
|
|
286
|
+
/>
|
|
287
|
+
<Button testID="settings-sign-out-cancel" label="Cancel" role="cancel" />
|
|
271
288
|
</ConfirmationDialog.Actions>
|
|
272
289
|
<ConfirmationDialog.Message>
|
|
273
290
|
<Text modifiers={[dfont({ size: 16 })]}>
|
|
@@ -276,35 +293,45 @@ export default function SettingsScreen() {
|
|
|
276
293
|
</ConfirmationDialog.Message>
|
|
277
294
|
</ConfirmationDialog>
|
|
278
295
|
|
|
279
|
-
|
|
296
|
+
{/* upstream expo/expo#45700: Alert component, SwiftUI .alert(...) on iOS 15+ */}
|
|
297
|
+
<Alert
|
|
280
298
|
title="Delete account?"
|
|
281
299
|
isPresented={showDeleteAccount}
|
|
282
300
|
onIsPresentedChange={setShowDeleteAccount}
|
|
283
|
-
titleVisibility="visible"
|
|
284
301
|
>
|
|
285
|
-
<
|
|
302
|
+
<Alert.Trigger>
|
|
286
303
|
{rowButton({
|
|
304
|
+
testID: "settings-delete-account",
|
|
287
305
|
label: "Delete account",
|
|
288
306
|
systemImage: "trash",
|
|
289
307
|
onPress: () => setShowDeleteAccount(true),
|
|
290
308
|
role: "destructive",
|
|
291
309
|
})}
|
|
292
|
-
</
|
|
293
|
-
<
|
|
294
|
-
<Button
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
310
|
+
</Alert.Trigger>
|
|
311
|
+
<Alert.Actions>
|
|
312
|
+
<Button
|
|
313
|
+
testID="settings-delete-account-confirm"
|
|
314
|
+
label="Delete Account"
|
|
315
|
+
role="destructive"
|
|
316
|
+
onPress={deleteAccount}
|
|
317
|
+
/>
|
|
318
|
+
<Button testID="settings-delete-account-cancel" label="Cancel" role="cancel" />
|
|
319
|
+
</Alert.Actions>
|
|
320
|
+
<Alert.Message>
|
|
298
321
|
<Text modifiers={[dfont({ size: 16 })]}>
|
|
299
|
-
|
|
322
|
+
Your account is scheduled for permanent deletion in 30 days. Sign in within that
|
|
323
|
+
window to restore it.
|
|
300
324
|
</Text>
|
|
301
|
-
</
|
|
302
|
-
</
|
|
325
|
+
</Alert.Message>
|
|
326
|
+
</Alert>
|
|
303
327
|
</VStack>
|
|
304
328
|
|
|
305
|
-
<
|
|
329
|
+
{deleteError ? <ErrorText testID="settings-delete-error">{deleteError}</ErrorText> : null}
|
|
330
|
+
|
|
331
|
+
<HStack modifiers={[frame({ maxWidth: Infinity }), padding({ top: 16 })]}>
|
|
306
332
|
<Spacer />
|
|
307
333
|
<Text
|
|
334
|
+
testID="settings-version"
|
|
308
335
|
modifiers={[dfont({ size: 12 }), foregroundStyle(colors.tertiaryLabel as string)]}
|
|
309
336
|
>
|
|
310
337
|
v{version}
|
|
@@ -327,7 +354,10 @@ function ProfileHeaderAvatar({ avatarUrl }: { avatarUrl: string | null }) {
|
|
|
327
354
|
systemName="person.crop.circle.fill"
|
|
328
355
|
size={HEADER_AVATAR_SIZE}
|
|
329
356
|
color={colors.mutedForeground as string}
|
|
330
|
-
modifiers={[
|
|
357
|
+
modifiers={[
|
|
358
|
+
frame({ width: HEADER_AVATAR_SIZE, height: HEADER_AVATAR_SIZE }),
|
|
359
|
+
accessibilityHidden(true),
|
|
360
|
+
]}
|
|
331
361
|
/>
|
|
332
362
|
);
|
|
333
363
|
}
|
|
@@ -341,7 +371,7 @@ function RemoteAvatar({ url, size }: { url: string; size: number }) {
|
|
|
341
371
|
systemName="person.crop.circle.fill"
|
|
342
372
|
size={size}
|
|
343
373
|
color={colors.mutedForeground as string}
|
|
344
|
-
modifiers={[frame({ width: size, height: size })]}
|
|
374
|
+
modifiers={[frame({ width: size, height: size }), accessibilityHidden(true)]}
|
|
345
375
|
/>
|
|
346
376
|
);
|
|
347
377
|
}
|
|
@@ -10,9 +10,12 @@ import {
|
|
|
10
10
|
Image,
|
|
11
11
|
} from "@expo/ui/swift-ui";
|
|
12
12
|
import {
|
|
13
|
+
accessibilityHidden,
|
|
14
|
+
accessibilityLabel,
|
|
13
15
|
background,
|
|
14
16
|
clipShape,
|
|
15
17
|
controlSize,
|
|
18
|
+
dynamicTypeSize,
|
|
16
19
|
foregroundStyle,
|
|
17
20
|
frame,
|
|
18
21
|
padding,
|
|
@@ -22,6 +25,7 @@ import {
|
|
|
22
25
|
tint,
|
|
23
26
|
} from "@expo/ui/swift-ui/modifiers";
|
|
24
27
|
import { Button as ButtonTokens } from "@/constants/layout";
|
|
28
|
+
import { DynamicType } from "@/constants/ui";
|
|
25
29
|
|
|
26
30
|
import { haptics } from "@/lib/haptics";
|
|
27
31
|
import { useColors, useThemeMode, type ThemeMode } from "@/hooks/use-theme";
|
|
@@ -32,6 +36,7 @@ import {
|
|
|
32
36
|
type ReduceMotionPref,
|
|
33
37
|
} from "@/lib/preferences";
|
|
34
38
|
import { useDynamicFont } from "@/lib/dynamic-font";
|
|
39
|
+
import { useSymbolSize } from "@/lib/dynamic-symbol-size";
|
|
35
40
|
|
|
36
41
|
const MODE_BY_INDEX: ThemeMode[] = ["light", "dark", "system"];
|
|
37
42
|
const INDEX_BY_MODE: Record<ThemeMode, number> = { light: 0, dark: 1, system: 2 };
|
|
@@ -45,6 +50,7 @@ const INDEX_BY_MOTION: Record<ReduceMotionPref, number> = {
|
|
|
45
50
|
|
|
46
51
|
export default function PreferencesScreen() {
|
|
47
52
|
const dfont = useDynamicFont();
|
|
53
|
+
const symbolSize = useSymbolSize();
|
|
48
54
|
const colors = useColors();
|
|
49
55
|
const { mode, setMode } = useThemeMode();
|
|
50
56
|
const [hapticsOn, setHapticsOn] = useHapticsEnabled();
|
|
@@ -64,11 +70,13 @@ export default function PreferencesScreen() {
|
|
|
64
70
|
];
|
|
65
71
|
|
|
66
72
|
const toggleRow = ({
|
|
73
|
+
testID,
|
|
67
74
|
icon,
|
|
68
75
|
label,
|
|
69
76
|
value,
|
|
70
77
|
onChange,
|
|
71
78
|
}: {
|
|
79
|
+
testID: string;
|
|
72
80
|
icon: NonNullable<React.ComponentProps<typeof Image>["systemName"]>;
|
|
73
81
|
label: string;
|
|
74
82
|
value: boolean;
|
|
@@ -78,13 +86,18 @@ export default function PreferencesScreen() {
|
|
|
78
86
|
spacing={12}
|
|
79
87
|
alignment="center"
|
|
80
88
|
modifiers={[
|
|
81
|
-
frame({ maxWidth:
|
|
89
|
+
frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
|
|
82
90
|
padding({ horizontal: 16 }),
|
|
83
91
|
background(colors.muted as string),
|
|
84
92
|
clipShape("capsule"),
|
|
85
93
|
]}
|
|
86
94
|
>
|
|
87
|
-
<Image
|
|
95
|
+
<Image
|
|
96
|
+
systemName={icon}
|
|
97
|
+
size={symbolSize(18)}
|
|
98
|
+
color={colors.foreground as string}
|
|
99
|
+
modifiers={[accessibilityHidden(true)]}
|
|
100
|
+
/>
|
|
88
101
|
<Text
|
|
89
102
|
modifiers={[
|
|
90
103
|
dfont({ size: 16, weight: "medium" }),
|
|
@@ -94,12 +107,17 @@ export default function PreferencesScreen() {
|
|
|
94
107
|
{label}
|
|
95
108
|
</Text>
|
|
96
109
|
<Spacer />
|
|
97
|
-
<Toggle
|
|
110
|
+
<Toggle
|
|
111
|
+
testID={testID}
|
|
112
|
+
isOn={value}
|
|
113
|
+
onIsOnChange={onChange}
|
|
114
|
+
modifiers={[tint(colors.primary as string), accessibilityLabel(label)]}
|
|
115
|
+
/>
|
|
98
116
|
</HStack>
|
|
99
117
|
);
|
|
100
118
|
|
|
101
119
|
return (
|
|
102
|
-
<Host style={{ flex: 1, backgroundColor: colors.background }}>
|
|
120
|
+
<Host testID="preferences-screen" style={{ flex: 1, backgroundColor: colors.background }}>
|
|
103
121
|
<ScrollView
|
|
104
122
|
modifiers={[scrollDismissesKeyboard("interactively"), tint(colors.primary as string)]}
|
|
105
123
|
>
|
|
@@ -111,10 +129,15 @@ export default function PreferencesScreen() {
|
|
|
111
129
|
<VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
|
|
112
130
|
<Text modifiers={sectionLabelModifiers}>APPEARANCE</Text>
|
|
113
131
|
<Picker
|
|
132
|
+
testID="preferences-appearance"
|
|
114
133
|
modifiers={[
|
|
115
134
|
pickerStyle("segmented"),
|
|
116
135
|
controlSize("large"),
|
|
117
|
-
frame({ maxWidth:
|
|
136
|
+
frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
|
|
137
|
+
// upstream expo/expo#46540: fixed segments can't reflow, so cap
|
|
138
|
+
// Dynamic Type before the labels truncate at AX sizes.
|
|
139
|
+
dynamicTypeSize({ max: DynamicType.control }),
|
|
140
|
+
accessibilityLabel("Appearance"),
|
|
118
141
|
]}
|
|
119
142
|
selection={INDEX_BY_MODE[mode]}
|
|
120
143
|
onSelectionChange={(v) => {
|
|
@@ -122,19 +145,39 @@ export default function PreferencesScreen() {
|
|
|
122
145
|
setMode(MODE_BY_INDEX[v as number] ?? "system");
|
|
123
146
|
}}
|
|
124
147
|
>
|
|
125
|
-
<Text
|
|
126
|
-
|
|
127
|
-
|
|
148
|
+
<Text
|
|
149
|
+
testID="preferences-appearance-light"
|
|
150
|
+
modifiers={[tag(0), dfont({ size: 14, weight: "medium" })]}
|
|
151
|
+
>
|
|
152
|
+
Light
|
|
153
|
+
</Text>
|
|
154
|
+
<Text
|
|
155
|
+
testID="preferences-appearance-dark"
|
|
156
|
+
modifiers={[tag(1), dfont({ size: 14, weight: "medium" })]}
|
|
157
|
+
>
|
|
158
|
+
Dark
|
|
159
|
+
</Text>
|
|
160
|
+
<Text
|
|
161
|
+
testID="preferences-appearance-system"
|
|
162
|
+
modifiers={[tag(2), dfont({ size: 14, weight: "medium" })]}
|
|
163
|
+
>
|
|
164
|
+
System
|
|
165
|
+
</Text>
|
|
128
166
|
</Picker>
|
|
129
167
|
</VStack>
|
|
130
168
|
|
|
131
169
|
<VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
|
|
132
170
|
<Text modifiers={sectionLabelModifiers}>REDUCE MOTION</Text>
|
|
133
171
|
<Picker
|
|
172
|
+
testID="preferences-reduce-motion"
|
|
134
173
|
modifiers={[
|
|
135
174
|
pickerStyle("segmented"),
|
|
136
175
|
controlSize("large"),
|
|
137
|
-
frame({ maxWidth:
|
|
176
|
+
frame({ maxWidth: Infinity, minHeight: ButtonTokens.height }),
|
|
177
|
+
// upstream expo/expo#46540: fixed segments can't reflow, so cap
|
|
178
|
+
// Dynamic Type before the labels truncate at AX sizes.
|
|
179
|
+
dynamicTypeSize({ max: DynamicType.control }),
|
|
180
|
+
accessibilityLabel("Reduce motion"),
|
|
138
181
|
]}
|
|
139
182
|
selection={INDEX_BY_MOTION[motion]}
|
|
140
183
|
onSelectionChange={(v) => {
|
|
@@ -142,15 +185,31 @@ export default function PreferencesScreen() {
|
|
|
142
185
|
setMotion(MOTION_BY_INDEX[v as number] ?? "system");
|
|
143
186
|
}}
|
|
144
187
|
>
|
|
145
|
-
<Text
|
|
146
|
-
|
|
147
|
-
|
|
188
|
+
<Text
|
|
189
|
+
testID="preferences-reduce-motion-system"
|
|
190
|
+
modifiers={[tag(0), dfont({ size: 14, weight: "medium" })]}
|
|
191
|
+
>
|
|
192
|
+
System
|
|
193
|
+
</Text>
|
|
194
|
+
<Text
|
|
195
|
+
testID="preferences-reduce-motion-always"
|
|
196
|
+
modifiers={[tag(1), dfont({ size: 14, weight: "medium" })]}
|
|
197
|
+
>
|
|
198
|
+
Always
|
|
199
|
+
</Text>
|
|
200
|
+
<Text
|
|
201
|
+
testID="preferences-reduce-motion-never"
|
|
202
|
+
modifiers={[tag(2), dfont({ size: 14, weight: "medium" })]}
|
|
203
|
+
>
|
|
204
|
+
Never
|
|
205
|
+
</Text>
|
|
148
206
|
</Picker>
|
|
149
207
|
</VStack>
|
|
150
208
|
|
|
151
209
|
<VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
|
|
152
210
|
<Text modifiers={sectionLabelModifiers}>HAPTICS</Text>
|
|
153
211
|
{toggleRow({
|
|
212
|
+
testID: "preferences-haptics",
|
|
154
213
|
icon: "iphone.radiowaves.left.and.right",
|
|
155
214
|
label: "Haptic feedback",
|
|
156
215
|
value: hapticsOn,
|
|
@@ -164,6 +223,7 @@ export default function PreferencesScreen() {
|
|
|
164
223
|
<VStack spacing={8} alignment="leading" modifiers={[frame({ maxWidth: Infinity })]}>
|
|
165
224
|
<Text modifiers={sectionLabelModifiers}>DEBUG</Text>
|
|
166
225
|
{toggleRow({
|
|
226
|
+
testID: "preferences-debug",
|
|
167
227
|
icon: "ant.circle.fill",
|
|
168
228
|
label: "Debug mode",
|
|
169
229
|
value: debugOn,
|