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
dist
build
.build
*.tsbuildinfo
.tanstack
+1 -18
View File
@@ -1,7 +1,6 @@
{
"$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": ["oxc", "eslint", "typescript", "react", "import", "unicorn", "vitest", "jsx-a11y"],
"jsPlugins": ["@e18e/eslint-plugin"],
"env": {
"builtin": true
},
@@ -26,23 +25,7 @@
"no-await-in-loop": "off",
"react/no-array-index-key": "off",
"react/jsx-no-useless-fragment": "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"
"react/rules-of-hooks": "error"
},
"ignorePatterns": ["node_modules", "dist", "build", "docs", "**/routeTree.gen.ts"]
}
+3 -6
View File
@@ -17,6 +17,7 @@
},
"dependencies": {
"@better-auth/expo": "catalog:",
"@expo/material-symbols": "0.1.1",
"@expo/metro-runtime": "~57.0.3",
"@expo/ui": "~57.0.4",
"@formatjs/intl-datetimeformat": "7.4.9",
@@ -30,7 +31,6 @@
"@orpc/client": "catalog:",
"@orpc/contract": "catalog:",
"@orpc/tanstack-query": "catalog:",
"@react-native-menu/menu": "2.0.0",
"@sentry/react-native": "~8.18.0",
"@shopify/flash-list": "2.0.2",
"@sofa/api": "workspace:*",
@@ -70,11 +70,9 @@
"react-dom": "catalog:",
"react-native": "0.86.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-mmkv": "4.3.2",
"react-native-nitro-modules": "0.36.1",
"react-native-mmkv": "~4.3.2",
"react-native-nitro-modules": "~0.36.1",
"react-native-reanimated": "4.5.0",
"react-native-safe-area-context": "~5.7.0",
"react-native-screens": "4.25.2",
@@ -83,7 +81,6 @@
"tailwind-merge": "catalog:",
"tailwindcss": "catalog:",
"uniwind": "1.10.0",
"zeego": "3.0.6",
"zod": "catalog:"
},
"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 { Trans, useLingui } from "@lingui/react/macro";
import {
@@ -38,7 +40,6 @@ import {
} from "react-native";
import Animated, { FadeInDown } from "react-native-reanimated";
import { useCSSVariable } from "uniwind";
import * as DropdownMenu from "zeego/dropdown-menu";
import { IntegrationsSection } from "@/components/settings/integrations-section";
import { SettingsRow } from "@/components/settings/settings-row";
@@ -66,6 +67,15 @@ const settingsContentContainerStyle = {
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() {
const { t, i18n } = useLingui();
const { push } = useRouter();
@@ -241,52 +251,49 @@ export default function SettingsScreen() {
<SettingsSection title={t`Account`} icon={IconUser}>
<View className="flex-row items-center py-3.5">
{hasAvatarImage ? (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Pressable
accessibilityRole="button"
accessibilityLabel={t`Edit profile photo`}
accessibilityHint={t`Opens options to change or remove your photo`}
className="mr-3"
hitSlop={8}
>
<View className="bg-secondary size-11 overflow-hidden rounded-full">
{isUploadingAvatar ? (
<View className="flex-1 items-center justify-center">
<Spinner size="sm" colorClassName="accent-primary" />
</View>
) : (
<Image
source={{ uri: session.user.image ?? undefined }}
style={{ width: "100%", height: "100%" }}
contentFit="cover"
/>
)}
</View>
<View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full">
<IconCamera size={10} color={primaryFgColor} />
</View>
</Pressable>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item key="change" onSelect={() => pickAvatar()}>
<DropdownMenu.ItemIcon ios={{ name: "photo.on.rectangle.angled" }} />
<DropdownMenu.ItemTitle>
<Trans>Change Photo</Trans>
</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
<DropdownMenu.Item
key="remove"
destructive
onSelect={() => removeAvatar.mutate()}
>
<DropdownMenu.ItemIcon ios={{ name: "trash" }} />
<DropdownMenu.ItemTitle>
<Trans>Remove Photo</Trans>
</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
<MenuView
actions={[
{ id: "change", title: t`Change Photo`, image: changePhotoIcon },
{
id: "remove",
title: t`Remove Photo`,
image: removePhotoIcon,
attributes: { destructive: true },
},
]}
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "change") {
pickAvatar();
} else if (nativeEvent.event === "remove") {
removeAvatar.mutate();
}
}}
>
<Pressable
accessibilityRole="button"
accessibilityLabel={t`Edit profile photo`}
accessibilityHint={t`Opens options to change or remove your photo`}
className="mr-3"
hitSlop={8}
>
<View className="bg-secondary size-11 overflow-hidden rounded-full">
{isUploadingAvatar ? (
<View className="flex-1 items-center justify-center">
<Spinner size="sm" colorClassName="accent-primary" />
</View>
) : (
<Image
source={{ uri: session.user.image ?? undefined }}
style={{ width: "100%", height: "100%" }}
contentFit="cover"
/>
)}
</View>
<View className="bg-primary absolute right-0 bottom-0 size-[18px] items-center justify-center rounded-full">
<IconCamera size={10} color={primaryFgColor} />
</View>
</Pressable>
</MenuView>
) : (
<Pressable
onPress={pickAvatar}
@@ -1,7 +1,7 @@
import { MenuView } from "@expo/ui/community/menu";
import { useLingui } from "@lingui/react/macro";
import type { Icon } from "@tabler/icons-react-native";
import { Pressable, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu";
import { ScaledIcon } from "@/components/ui/scaled-icon";
import { Sparkline } from "@/components/ui/sparkline";
@@ -86,29 +86,25 @@ export function StatsCard(props: StatsCardProps) {
return (
<View style={cardStyle}>
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
>
<CardInner {...props} />
</Pressable>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{periods.map((p) => (
<DropdownMenu.CheckboxItem
key={p}
value={p === period ? "on" : "off"}
onValueChange={() => onPeriodChange(p)}
>
<DropdownMenu.ItemIndicator />
<DropdownMenu.ItemTitle>{periodLabels[p]}</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
))}
</DropdownMenu.Content>
</DropdownMenu.Root>
<MenuView
actions={periods.map((value) => ({
id: value,
title: periodLabels[value],
state: value === period ? "on" : "off",
}))}
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
onPressAction={({ nativeEvent }) => {
const nextPeriod = periods.find((value) => value === nativeEvent.event);
if (nextPeriod) onPeriodChange(nextPeriod);
}}
>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
>
<CardInner {...props} />
</Pressable>
</MenuView>
</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 { useRouter } from "expo-router";
import { Alert, Pressable, View } from "react-native";
import * as DropdownMenu from "zeego/dropdown-menu";
import { Image } from "@/components/ui/image";
import { Text } from "@/components/ui/text";
@@ -9,6 +10,15 @@ import { queryClient } from "@/lib/query-client";
import { authClient } from "@/lib/server";
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() {
const { t } = useLingui();
const { data: session } = authClient.useSession();
@@ -19,66 +29,61 @@ export function HeaderAvatar() {
const { user } = session;
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
accessibilityLabel={t`User menu`}
hitSlop={8}
>
<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>
</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();
},
<MenuView
actions={[
{ id: "settings", title: t`Settings`, image: settingsIcon },
{
id: "sign-out",
title: t`Sign out`,
image: signOutIcon,
attributes: { destructive: true },
},
]}
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "settings") {
navigate("/(tabs)/(settings)");
} 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" },
{
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"
/>
<DropdownMenu.ItemTitle>{t`Sign out`}</DropdownMenu.ItemTitle>
</DropdownMenu.Item>
</DropdownMenu.Content>
</DropdownMenu.Root>
},
]);
}
}}
>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
accessibilityLabel={t`User menu`}
hitSlop={8}
>
<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 { IconArrowsSort } from "@tabler/icons-react-native";
import { Pressable } from "react-native";
import { useCSSVariable } from "uniwind";
import * as DropdownMenu from "zeego/dropdown-menu";
import { ScaledIcon } from "@/components/ui/scaled-icon";
import * as Haptics from "@/utils/haptics";
@@ -37,33 +37,28 @@ export function SortMenu({ sortBy, sortDirection, onSortChange }: SortMenuProps)
];
return (
<DropdownMenu.Root>
<DropdownMenu.Trigger asChild>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
accessibilityLabel={t`Sort`}
hitSlop={8}
>
<ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} />
</Pressable>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
{sortOptions.map((option) => {
const isSelected = option.sortBy === sortBy && option.sortDirection === sortDirection;
return (
<DropdownMenu.CheckboxItem
key={`${option.sortBy}-${option.sortDirection}`}
value={isSelected ? "on" : "off"}
onValueChange={() => onSortChange(option.sortBy, option.sortDirection)}
>
<DropdownMenu.ItemIndicator />
<DropdownMenu.ItemTitle>{option.label}</DropdownMenu.ItemTitle>
</DropdownMenu.CheckboxItem>
);
})}
</DropdownMenu.Content>
</DropdownMenu.Root>
<MenuView
actions={sortOptions.map((option) => ({
id: `${option.sortBy}-${option.sortDirection}`,
title: option.label,
state: option.sortBy === sortBy && option.sortDirection === sortDirection ? "on" : "off",
}))}
onOpenMenu={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
onPressAction={({ nativeEvent }) => {
const option = sortOptions.find(
(candidate) => `${candidate.sortBy}-${candidate.sortDirection}` === nativeEvent.event,
);
if (option) onSortChange(option.sortBy, option.sortDirection);
}}
>
<Pressable
onPress={() => Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light)}
accessibilityRole="button"
accessibilityLabel={t`Sort`}
hitSlop={8}
>
<ScaledIcon icon={IconArrowsSort} size={22} color={foregroundColor} />
</Pressable>
</MenuView>
);
}
+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"
},
"devDependencies": {
"@e18e/eslint-plugin": "0.5.1",
"@lingui/cli": "catalog:",
"@lingui/conf": "catalog:",
"@lingui/format-po": "catalog:",