remove zeego

This commit is contained in:
2026-07-12 18:17:19 -04:00
parent 7b5d681370
commit 77af611490
9 changed files with 446 additions and 1647 deletions
+1
View File
@@ -8,6 +8,7 @@ node_modules
# Build outputs # Build outputs
dist dist
build build
.build
*.tsbuildinfo *.tsbuildinfo
.tanstack .tanstack
+1 -18
View File
@@ -1,7 +1,6 @@
{ {
"$schema": "./node_modules/oxlint/configuration_schema.json", "$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["oxc", "eslint", "typescript", "react", "import", "unicorn", "vitest", "jsx-a11y"], "plugins": ["oxc", "eslint", "typescript", "react", "import", "unicorn", "vitest", "jsx-a11y"],
"jsPlugins": ["@e18e/eslint-plugin"],
"env": { "env": {
"builtin": true "builtin": true
}, },
@@ -26,23 +25,7 @@
"no-await-in-loop": "off", "no-await-in-loop": "off",
"react/no-array-index-key": "off", "react/no-array-index-key": "off",
"react/jsx-no-useless-fragment": "error", "react/jsx-no-useless-fragment": "error",
"react/rules-of-hooks": "error", "react/rules-of-hooks": "error"
"e18e/prefer-array-at": "error",
"e18e/prefer-array-fill": "error",
"e18e/prefer-array-from-map": "error",
"e18e/prefer-array-some": "error",
"e18e/prefer-array-to-reversed": "error",
"e18e/prefer-array-to-sorted": "error",
"e18e/prefer-array-to-spliced": "error",
"e18e/prefer-date-now": "error",
"e18e/prefer-includes": "error",
"e18e/prefer-nullish-coalescing": "error",
"e18e/prefer-object-has-own": "error",
"e18e/prefer-regex-test": "error",
"e18e/prefer-spread-syntax": "off",
"e18e/prefer-static-regex": "error",
"e18e/prefer-timer-args": "error",
"e18e/prefer-url-canparse": "error"
}, },
"ignorePatterns": ["node_modules", "dist", "build", "docs", "**/routeTree.gen.ts"] "ignorePatterns": ["node_modules", "dist", "build", "docs", "**/routeTree.gen.ts"]
} }
+3 -6
View File
@@ -17,6 +17,7 @@
}, },
"dependencies": { "dependencies": {
"@better-auth/expo": "catalog:", "@better-auth/expo": "catalog:",
"@expo/material-symbols": "0.1.1",
"@expo/metro-runtime": "~57.0.3", "@expo/metro-runtime": "~57.0.3",
"@expo/ui": "~57.0.4", "@expo/ui": "~57.0.4",
"@formatjs/intl-datetimeformat": "7.4.9", "@formatjs/intl-datetimeformat": "7.4.9",
@@ -30,7 +31,6 @@
"@orpc/client": "catalog:", "@orpc/client": "catalog:",
"@orpc/contract": "catalog:", "@orpc/contract": "catalog:",
"@orpc/tanstack-query": "catalog:", "@orpc/tanstack-query": "catalog:",
"@react-native-menu/menu": "2.0.0",
"@sentry/react-native": "~8.18.0", "@sentry/react-native": "~8.18.0",
"@shopify/flash-list": "2.0.2", "@shopify/flash-list": "2.0.2",
"@sofa/api": "workspace:*", "@sofa/api": "workspace:*",
@@ -70,11 +70,9 @@
"react-dom": "catalog:", "react-dom": "catalog:",
"react-native": "0.86.0", "react-native": "0.86.0",
"react-native-gesture-handler": "~2.32.0", "react-native-gesture-handler": "~2.32.0",
"react-native-ios-context-menu": "3.2.0",
"react-native-ios-utilities": "5.2.0",
"react-native-keyboard-controller": "1.21.9", "react-native-keyboard-controller": "1.21.9",
"react-native-mmkv": "4.3.2", "react-native-mmkv": "~4.3.2",
"react-native-nitro-modules": "0.36.1", "react-native-nitro-modules": "~0.36.1",
"react-native-reanimated": "4.5.0", "react-native-reanimated": "4.5.0",
"react-native-safe-area-context": "~5.7.0", "react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2", "react-native-screens": "4.25.2",
@@ -83,7 +81,6 @@
"tailwind-merge": "catalog:", "tailwind-merge": "catalog:",
"tailwindcss": "catalog:", "tailwindcss": "catalog:",
"uniwind": "1.10.0", "uniwind": "1.10.0",
"zeego": "3.0.6",
"zod": "catalog:" "zod": "catalog:"
}, },
"devDependencies": { "devDependencies": {
+54 -47
View File
@@ -1,3 +1,5 @@
import { Icon } from "@expo/ui";
import { MenuView } from "@expo/ui/community/menu";
import { plural } from "@lingui/core/macro"; import { plural } from "@lingui/core/macro";
import { Trans, useLingui } from "@lingui/react/macro"; import { Trans, useLingui } from "@lingui/react/macro";
import { import {
@@ -38,7 +40,6 @@ import {
} from "react-native"; } from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated"; import Animated, { FadeInDown } from "react-native-reanimated";
import { useCSSVariable } from "uniwind"; import { useCSSVariable } from "uniwind";
import * as DropdownMenu from "zeego/dropdown-menu";
import { IntegrationsSection } from "@/components/settings/integrations-section"; import { IntegrationsSection } from "@/components/settings/integrations-section";
import { SettingsRow } from "@/components/settings/settings-row"; import { SettingsRow } from "@/components/settings/settings-row";
@@ -66,6 +67,15 @@ const settingsContentContainerStyle = {
paddingHorizontal: 16, paddingHorizontal: 16,
}; };
const changePhotoIcon = Icon.select({
ios: "photo.on.rectangle.angled",
android: import("@expo/material-symbols/photo_library.xml"),
});
const removePhotoIcon = Icon.select({
ios: "trash",
android: import("@expo/material-symbols/delete.xml"),
});
export default function SettingsScreen() { export default function SettingsScreen() {
const { t, i18n } = useLingui(); const { t, i18n } = useLingui();
const { push } = useRouter(); const { push } = useRouter();
@@ -241,52 +251,49 @@ export default function SettingsScreen() {
<SettingsSection title={t`Account`} icon={IconUser}> <SettingsSection title={t`Account`} icon={IconUser}>
<View className="flex-row items-center py-3.5"> <View className="flex-row items-center py-3.5">
{hasAvatarImage ? ( {hasAvatarImage ? (
<DropdownMenu.Root> <MenuView
<DropdownMenu.Trigger asChild> actions={[
<Pressable { id: "change", title: t`Change Photo`, image: changePhotoIcon },
accessibilityRole="button" {
accessibilityLabel={t`Edit profile photo`} id: "remove",
accessibilityHint={t`Opens options to change or remove your photo`} title: t`Remove Photo`,
className="mr-3" image: removePhotoIcon,
hitSlop={8} attributes: { destructive: true },
> },
<View className="bg-secondary size-11 overflow-hidden rounded-full"> ]}
{isUploadingAvatar ? ( onPressAction={({ nativeEvent }) => {
<View className="flex-1 items-center justify-center"> if (nativeEvent.event === "change") {
<Spinner size="sm" colorClassName="accent-primary" /> pickAvatar();
</View> } else if (nativeEvent.event === "remove") {
) : ( removeAvatar.mutate();
<Image }
source={{ uri: session.user.image ?? undefined }} }}
style={{ width: "100%", height: "100%" }} >
contentFit="cover" <Pressable
/> accessibilityRole="button"
)} accessibilityLabel={t`Edit profile photo`}
</View> accessibilityHint={t`Opens options to change or remove your photo`}
<View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full"> className="mr-3"
<IconCamera size={10} color={primaryFgColor} /> hitSlop={8}
</View> >
</Pressable> <View className="bg-secondary size-11 overflow-hidden rounded-full">
</DropdownMenu.Trigger> {isUploadingAvatar ? (
<DropdownMenu.Content> <View className="flex-1 items-center justify-center">
<DropdownMenu.Item key="change" onSelect={() => pickAvatar()}> <Spinner size="sm" colorClassName="accent-primary" />
<DropdownMenu.ItemIcon ios={{ name: "photo.on.rectangle.angled" }} /> </View>
<DropdownMenu.ItemTitle> ) : (
<Trans>Change Photo</Trans> <Image
</DropdownMenu.ItemTitle> source={{ uri: session.user.image ?? undefined }}
</DropdownMenu.Item> style={{ width: "100%", height: "100%" }}
<DropdownMenu.Item contentFit="cover"
key="remove" />
destructive )}
onSelect={() => removeAvatar.mutate()} </View>
> <View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full">
<DropdownMenu.ItemIcon ios={{ name: "trash" }} /> <IconCamera size={10} color={primaryFgColor} />
<DropdownMenu.ItemTitle> </View>
<Trans>Remove Photo</Trans> </Pressable>
</DropdownMenu.ItemTitle> </MenuView>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
) : ( ) : (
<Pressable <Pressable
onPress={pickAvatar} onPress={pickAvatar}
@@ -1,7 +1,7 @@
import { MenuView } from "@expo/ui/community/menu";
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import type { Icon } from "@tabler/icons-react-native"; import type { Icon } from "@tabler/icons-react-native";
import { Pressable, View } from "react-native"; import { Pressable, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu";
import { ScaledIcon } from "@/components/ui/scaled-icon"; import { ScaledIcon } from "@/components/ui/scaled-icon";
import { Sparkline } from "@/components/ui/sparkline"; import { Sparkline } from "@/components/ui/sparkline";
@@ -86,29 +86,25 @@ export function StatsCard(props: StatsCardProps) {
return ( return (
<View style={cardStyle}> <View style={cardStyle}>
<DropdownMenu.Root> <MenuView
<DropdownMenu.Trigger asChild> actions={periods.map((value) => ({
<Pressable id: value,
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} title: periodLabels[value],
accessibilityRole="button" state: value === period ? "on" : "off",
> }))}
<CardInner {...props} /> onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
</Pressable> onPressAction={({ nativeEvent }) => {
</DropdownMenu.Trigger> const nextPeriod = periods.find((value) => value === nativeEvent.event);
if (nextPeriod) onPeriodChange(nextPeriod);
<DropdownMenu.Content> }}
{periods.map((p) => ( >
<DropdownMenu.CheckboxItem <Pressable
key={p} onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
value={p === period ? "on" : "off"} accessibilityRole="button"
onValueChange={() => onPeriodChange(p)} >
> <CardInner {...props} />
<DropdownMenu.ItemIndicator /> </Pressable>
<DropdownMenu.ItemTitle>{periodLabels[p]}</DropdownMenu.ItemTitle> </MenuView>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
</View> </View>
); );
} }
+66 -61
View File
@@ -1,7 +1,8 @@
import { Icon } from "@expo/ui";
import { MenuView } from "@expo/ui/community/menu";
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import { useRouter } from "expo-router"; import { useRouter } from "expo-router";
import { Alert, Pressable, View } from "react-native"; import { Alert, Pressable, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu";
import { Image } from "@/components/ui/image"; import { Image } from "@/components/ui/image";
import { Text } from "@/components/ui/text"; import { Text } from "@/components/ui/text";
@@ -9,6 +10,15 @@ import { queryClient } from "@/lib/query-client";
import { authClient } from "@/lib/server"; import { authClient } from "@/lib/server";
import * as Haptics from "@/utils/haptics"; import * as Haptics from "@/utils/haptics";
const settingsIcon = Icon.select({
ios: "gear",
android: import("@expo/material-symbols/settings.xml"),
});
const signOutIcon = Icon.select({
ios: "rectangle.portrait.and.arrow.right",
android: import("@expo/material-symbols/logout.xml"),
});
export function HeaderAvatar() { export function HeaderAvatar() {
const { t } = useLingui(); const { t } = useLingui();
const { data: session } = authClient.useSession(); const { data: session } = authClient.useSession();
@@ -19,66 +29,61 @@ export function HeaderAvatar() {
const { user } = session; const { user } = session;
return ( return (
<DropdownMenu.Root> <MenuView
<DropdownMenu.Trigger asChild> actions={[
<Pressable { id: "settings", title: t`Settings`, image: settingsIcon },
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} {
accessibilityRole="button" id: "sign-out",
accessibilityLabel={t`User menu`} title: t`Sign out`,
hitSlop={8} image: signOutIcon,
> attributes: { destructive: true },
<View className="size-8 overflow-hidden rounded-full" accessible={false}> },
{user.image ? ( ]}
<Image onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
source={{ uri: user.image }} onPressAction={({ nativeEvent }) => {
style={{ width: "100%", height: "100%" }} if (nativeEvent.event === "settings") {
contentFit="cover" navigate("/(tabs)/(settings)");
accessible={false} } else if (nativeEvent.event === "sign-out") {
/> Alert.alert(t`Sign out`, t`Are you sure you want to sign out?`, [
) : ( { text: t`Cancel`, style: "cancel" },
<View className="bg-primary/[0.08] flex-1 items-center justify-center"> {
<Text text: t`Sign out`,
maxFontSizeMultiplier={1.0} style: "destructive",
className="font-display text-primary text-sm font-medium" onPress: () => {
> authClient.signOut();
{user.name?.charAt(0)?.toUpperCase() ?? "?"} queryClient.clear();
</Text>
</View>
)}
</View>
</Pressable>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item key="settings" onSelect={() => navigate("/(tabs)/(settings)")}>
<DropdownMenu.ItemIcon ios={{ name: "gear" }} androidIconName="ic_menu_preferences" />
<DropdownMenu.ItemTitle>{t`Settings`}</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
<DropdownMenu.Item
key="sign-out"
destructive
onSelect={() => {
Alert.alert(t`Sign out`, t`Are you sure you want to sign out?`, [
{ text: t`Cancel`, style: "cancel" },
{
text: t`Sign out`,
style: "destructive",
onPress: () => {
authClient.signOut();
queryClient.clear();
},
}, },
]); },
}} ]);
> }
<DropdownMenu.ItemIcon }}
ios={{ name: "rectangle.portrait.and.arrow.right" }} >
androidIconName="ic_menu_close_clear_cancel" <Pressable
/> onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
<DropdownMenu.ItemTitle>{t`Sign out`}</DropdownMenu.ItemTitle> accessibilityRole="button"
</DropdownMenu.Item> accessibilityLabel={t`User menu`}
</DropdownMenu.Content> hitSlop={8}
</DropdownMenu.Root> >
<View className="size-8 overflow-hidden rounded-full" accessible={false}>
{user.image ? (
<Image
source={{ uri: user.image }}
style={{ width: "100%", height: "100%" }}
contentFit="cover"
accessible={false}
/>
) : (
<View className="bg-primary/[0.08] flex-1 items-center justify-center">
<Text
maxFontSizeMultiplier={1.0}
className="font-display text-primary text-sm font-medium"
>
{user.name?.charAt(0)?.toUpperCase() ?? "?"}
</Text>
</View>
)}
</View>
</Pressable>
</MenuView>
); );
} }
@@ -1,8 +1,8 @@
import { MenuView } from "@expo/ui/community/menu";
import { useLingui } from "@lingui/react/macro"; import { useLingui } from "@lingui/react/macro";
import { IconArrowsSort } from "@tabler/icons-react-native"; import { IconArrowsSort } from "@tabler/icons-react-native";
import { Pressable } from "react-native"; import { Pressable } from "react-native";
import { useCSSVariable } from "uniwind"; import { useCSSVariable } from "uniwind";
import * as DropdownMenu from "zeego/dropdown-menu";
import { ScaledIcon } from "@/components/ui/scaled-icon"; import { ScaledIcon } from "@/components/ui/scaled-icon";
import * as Haptics from "@/utils/haptics"; import * as Haptics from "@/utils/haptics";
@@ -37,33 +37,28 @@ export function SortMenu({ sortBy, sortDirection, onSortChange }: SortMenuProps)
]; ];
return ( return (
<DropdownMenu.Root> <MenuView
<DropdownMenu.Trigger asChild> actions={sortOptions.map((option) => ({
<Pressable id: `${option.sortBy}-${option.sortDirection}`,
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)} title: option.label,
accessibilityRole="button" state: option.sortBy === sortBy && option.sortDirection === sortDirection ? "on" : "off",
accessibilityLabel={t`Sort`} }))}
hitSlop={8} onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
> onPressAction={({ nativeEvent }) => {
<ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} /> const option = sortOptions.find(
</Pressable> (candidate) => `${candidate.sortBy}-${candidate.sortDirection}` === nativeEvent.event,
</DropdownMenu.Trigger> );
if (option) onSortChange(option.sortBy, option.sortDirection);
<DropdownMenu.Content> }}
{sortOptions.map((option) => { >
const isSelected = option.sortBy === sortBy && option.sortDirection === sortDirection; <Pressable
return ( onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
<DropdownMenu.CheckboxItem accessibilityRole="button"
key={`${option.sortBy}-${option.sortDirection}`} accessibilityLabel={t`Sort`}
value={isSelected ? "on" : "off"} hitSlop={8}
onValueChange={() => onSortChange(option.sortBy, option.sortDirection)} >
> <ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} />
<DropdownMenu.ItemIndicator /> </Pressable>
<DropdownMenu.ItemTitle>{option.label}</DropdownMenu.ItemTitle> </MenuView>
</DropdownMenu.CheckboxItem>
);
})}
</DropdownMenu.Content>
</DropdownMenu.Root>
); );
} }
+277 -1461
View File
File diff suppressed because it is too large Load Diff
-1
View File
@@ -62,7 +62,6 @@
"sync:providers": "bun scripts/sync-tmdb-providers.ts" "sync:providers": "bun scripts/sync-tmdb-providers.ts"
}, },
"devDependencies": { "devDependencies": {
"@e18e/eslint-plugin": "0.5.1",
"@lingui/cli": "catalog:", "@lingui/cli": "catalog:",
"@lingui/conf": "catalog:", "@lingui/conf": "catalog:",
"@lingui/format-po": "catalog:", "@lingui/format-po": "catalog:",