diff options
| author | Mohamed Bassem <me@mbassem.com> | 2026-01-17 19:00:51 +0000 |
|---|---|---|
| committer | Mohamed Bassem <me@mbassem.com> | 2026-01-18 14:37:24 +0000 |
| commit | 6094d360cc64806c7e2544c51b6a1f5b1ef140f6 (patch) | |
| tree | 09595cb0d32fb9dc2509da43a1385f7289d7984a /apps/mobile | |
| parent | c56cf4e24f6134547fb9c5b58eb20840f5083e9e (diff) | |
| download | karakeep-6094d360cc64806c7e2544c51b6a1f5b1ef140f6.tar.zst | |
deps(mobile): upgrade to sdk 54
Diffstat (limited to 'apps/mobile')
| -rw-r--r-- | apps/mobile/components/bookmarks/BookmarkCard.tsx | 2 | ||||
| -rw-r--r-- | apps/mobile/components/ui/List.tsx | 478 | ||||
| -rw-r--r-- | apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx | 6 | ||||
| -rw-r--r-- | apps/mobile/package.json | 64 |
4 files changed, 36 insertions, 514 deletions
diff --git a/apps/mobile/components/bookmarks/BookmarkCard.tsx b/apps/mobile/components/bookmarks/BookmarkCard.tsx index e2df31b9..6c3ef070 100644 --- a/apps/mobile/components/bookmarks/BookmarkCard.tsx +++ b/apps/mobile/components/bookmarks/BookmarkCard.tsx @@ -9,7 +9,7 @@ import { View, } from "react-native"; import * as Clipboard from "expo-clipboard"; -import * as FileSystem from "expo-file-system"; +import * as FileSystem from "expo-file-system/legacy"; import * as Haptics from "expo-haptics"; import { router, useRouter } from "expo-router"; import * as Sharing from "expo-sharing"; diff --git a/apps/mobile/components/ui/List.tsx b/apps/mobile/components/ui/List.tsx deleted file mode 100644 index 67f0a9af..00000000 --- a/apps/mobile/components/ui/List.tsx +++ /dev/null @@ -1,478 +0,0 @@ -import type { - FlashListProps, - ListRenderItem as FlashListRenderItem, - ListRenderItemInfo, -} from "@shopify/flash-list"; -import * as React from "react"; -import { - Platform, - PressableProps, - StyleProp, - TextStyle, - View, - ViewProps, - ViewStyle, -} from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { Button } from "@/components/ui/Button"; -import { Text, TextClassContext } from "@/components/ui/Text"; -import { cn } from "@/lib/utils"; -import { FlashList } from "@shopify/flash-list"; -import { cva } from "class-variance-authority"; -import { cssInterop } from "nativewind"; - -cssInterop(FlashList, { - className: "style", - contentContainerClassName: "contentContainerStyle", -}); - -type ListDataItem = string | { title: string; subTitle?: string }; -type ListVariant = "insets" | "full-width"; - -type ListRef<T extends ListDataItem> = React.Ref< - React.ComponentRef<typeof FlashList<T>> ->; - -type ListRenderItemProps<T extends ListDataItem> = ListRenderItemInfo<T> & { - variant?: ListVariant; - isFirstInSection?: boolean; - isLastInSection?: boolean; - sectionHeaderAsGap?: boolean; -}; - -type ListProps<T extends ListDataItem> = Omit< - FlashListProps<T>, - "renderItem" -> & { - renderItem?: ListRenderItem<T>; - variant?: ListVariant; - sectionHeaderAsGap?: boolean; - rootClassName?: string; - rootStyle?: StyleProp<ViewStyle>; -}; -type ListRenderItem<T extends ListDataItem> = ( - props: ListRenderItemProps<T>, -) => ReturnType<FlashListRenderItem<T>>; - -const rootVariants = cva("min-h-2 flex-1", { - variants: { - variant: { - insets: "ios:px-4", - "full-width": "ios:bg-card ios:dark:bg-background", - }, - sectionHeaderAsGap: { - true: "", - false: "", - }, - }, - compoundVariants: [ - { - variant: "full-width", - sectionHeaderAsGap: true, - className: "bg-card dark:bg-background", - }, - ], - defaultVariants: { - variant: "full-width", - sectionHeaderAsGap: false, - }, -}); - -function ListComponent<T extends ListDataItem>( - { - variant = "full-width", - rootClassName, - rootStyle, - contentContainerClassName, - renderItem, - data, - sectionHeaderAsGap = false, - contentInsetAdjustmentBehavior = "automatic", - ...props - }: ListProps<T>, - ref: ListRef<T>, -) { - const insets = useSafeAreaInsets(); - return ( - <View - className={cn( - rootVariants({ - variant, - sectionHeaderAsGap, - }), - rootClassName, - )} - style={rootStyle} - > - <FlashList - ref={ref} - data={data} - contentInsetAdjustmentBehavior={contentInsetAdjustmentBehavior} - renderItem={renderItemWithVariant( - renderItem, - variant, - data, - sectionHeaderAsGap, - )} - contentContainerClassName={cn( - variant === "insets" && - (!data || (typeof data?.[0] !== "string" && "pt-4")), - contentContainerClassName, - )} - contentContainerStyle={{ - paddingBottom: Platform.select({ - ios: - !contentInsetAdjustmentBehavior || - contentInsetAdjustmentBehavior === "never" - ? insets.bottom + 16 - : 0, - default: insets.bottom, - }), - }} - getItemType={getItemType} - showsVerticalScrollIndicator={false} - {...props} - /> - </View> - ); -} - -function getItemType<T>(item: T) { - return typeof item === "string" ? "sectioHeader" : "row"; -} - -function renderItemWithVariant<T extends ListDataItem>( - renderItem: ListRenderItem<T> | null | undefined, - variant: ListVariant, - data: readonly T[] | null | undefined, - sectionHeaderAsGap?: boolean, -) { - return (args: ListRenderItemProps<T>) => { - const previousItem = data?.[args.index - 1]; - const nextItem = data?.[args.index + 1]; - return renderItem - ? renderItem({ - ...args, - variant, - isFirstInSection: !previousItem || typeof previousItem === "string", - isLastInSection: !nextItem || typeof nextItem === "string", - sectionHeaderAsGap, - }) - : null; - }; -} - -const List = React.forwardRef(ListComponent) as <T extends ListDataItem>( - props: ListProps<T> & { ref?: ListRef<T> }, -) => React.ReactElement; - -function isPressable(props: PressableProps) { - return ( - ("onPress" in props && props.onPress) || - ("onLongPress" in props && props.onLongPress) || - ("onPressIn" in props && props.onPressIn) || - ("onPressOut" in props && props.onPressOut) || - ("onLongPress" in props && props.onLongPress) - ); -} - -type ListItemProps<T extends ListDataItem> = PressableProps & - ListRenderItemProps<T> & { - androidRootClassName?: string; - titleClassName?: string; - titleStyle?: StyleProp<TextStyle>; - textNumberOfLines?: number; - subTitleClassName?: string; - subTitleStyle?: StyleProp<TextStyle>; - subTitleNumberOfLines?: number; - textContentClassName?: string; - leftView?: React.ReactNode; - rightView?: React.ReactNode; - removeSeparator?: boolean; - }; -type ListItemRef = React.Ref<View>; - -const itemVariants = cva("ios:gap-0 flex-row gap-0 bg-card", { - variants: { - variant: { - insets: "ios:bg-card bg-card/70", - "full-width": "bg-card dark:bg-background", - }, - sectionHeaderAsGap: { - true: "", - false: "", - }, - isFirstItem: { - true: "", - false: "", - }, - isFirstInSection: { - true: "", - false: "", - }, - removeSeparator: { - true: "", - false: "", - }, - isLastInSection: { - true: "", - false: "", - }, - disabled: { - true: "opacity-70", - false: "opacity-100", - }, - }, - compoundVariants: [ - { - variant: "insets", - sectionHeaderAsGap: true, - className: "ios:dark:bg-card dark:bg-card/70", - }, - { - variant: "insets", - isFirstInSection: true, - className: "ios:rounded-t-[10px]", - }, - { - variant: "insets", - isLastInSection: true, - className: "ios:rounded-b-[10px]", - }, - { - removeSeparator: false, - isLastInSection: true, - className: - "ios:border-b-0 border-b border-border/25 dark:border-border/80", - }, - { - variant: "insets", - isFirstItem: true, - className: "border-t border-border/40", - }, - ], - defaultVariants: { - variant: "insets", - sectionHeaderAsGap: false, - isFirstInSection: false, - isLastInSection: false, - disabled: false, - }, -}); - -function ListItemComponent<T extends ListDataItem>( - { - item, - isFirstInSection, - isLastInSection, - index: _index, - variant, - className, - androidRootClassName, - titleClassName, - titleStyle, - textNumberOfLines, - subTitleStyle, - subTitleClassName, - subTitleNumberOfLines, - textContentClassName, - sectionHeaderAsGap, - removeSeparator = false, - leftView, - rightView, - disabled, - ...props - }: ListItemProps<T>, - ref: ListItemRef, -) { - if (typeof item === "string") { - console.log( - "List.tsx", - "ListItemComponent", - "Invalid item of type 'string' was provided. Use ListSectionHeader instead.", - ); - return null; - } - return ( - <> - <Button - disabled={disabled || !isPressable(props)} - variant="plain" - size="none" - unstable_pressDelay={100} - androidRootClassName={androidRootClassName} - className={itemVariants({ - variant, - sectionHeaderAsGap, - isFirstInSection, - isLastInSection, - disabled, - className, - removeSeparator, - })} - {...props} - ref={ref} - > - <TextClassContext.Provider value="font-normal leading-5"> - {!!leftView && <View>{leftView}</View>} - <View - className={cn( - "h-full flex-1 flex-row pr-4", - !item.subTitle ? "ios:py-3 py-[18px]" : "ios:py-2 py-2", - !leftView && "ml-4", - !removeSeparator && - (!isLastInSection || variant === "full-width") && - "ios:border-b ios:border-border/80", - !removeSeparator && - isFirstInSection && - variant === "full-width" && - "ios:border-t ios:border-border/80", - )} - > - <View className={cn("flex-1", textContentClassName)}> - <Text - numberOfLines={textNumberOfLines} - style={titleStyle} - className={cn("text-base", titleClassName)} - > - {item.title} - </Text> - {!!item.subTitle && ( - <Text - numberOfLines={subTitleNumberOfLines} - variant="subhead" - style={subTitleStyle} - className={cn("text-muted-foreground", subTitleClassName)} - > - {item.subTitle} - </Text> - )} - </View> - {!!rightView && ( - <View className="flex items-center justify-center"> - {rightView} - </View> - )} - </View> - </TextClassContext.Provider> - </Button> - {!removeSeparator && Platform.OS !== "ios" && !isLastInSection && ( - <View className={cn(variant === "insets" && "px-4")}> - <View className="h-px bg-border/25 dark:bg-border/80" /> - </View> - )} - </> - ); -} - -const ListItem = React.forwardRef(ListItemComponent) as < - T extends ListDataItem, ->( - props: ListItemProps<T> & { ref?: ListItemRef }, -) => React.ReactElement; - -type ListSectionHeaderProps<T extends ListDataItem> = ViewProps & - ListRenderItemProps<T> & { - textClassName?: string; - }; -type ListSectionHeaderRef = React.Ref<View>; - -function ListSectionHeaderComponent<T extends ListDataItem>( - { - item, - isFirstInSection: _isFirstInSection, - isLastInSection: _isLastInSection, - index: _index, - variant, - className, - textClassName, - sectionHeaderAsGap, - ...props - }: ListSectionHeaderProps<T>, - ref: ListSectionHeaderRef, -) { - if (typeof item !== "string") { - console.log( - "List.tsx", - "ListSectionHeaderComponent", - "Invalid item provided. Expected type 'string'. Use ListItem instead.", - ); - return null; - } - - if (sectionHeaderAsGap) { - return ( - <View - className={cn( - "bg-background", - Platform.OS !== "ios" && - "border-b border-border/25 dark:border-border/80", - className, - )} - {...props} - ref={ref} - > - <View className="h-8" /> - </View> - ); - } - return ( - <View - className={cn( - "ios:pb-1 pb-4 pl-4 pt-4", - Platform.OS !== "ios" && - "border-b border-border/25 dark:border-border/80", - variant === "full-width" - ? "bg-card dark:bg-background" - : "bg-background", - className, - )} - {...props} - ref={ref} - > - <Text - variant={Platform.select({ ios: "footnote", default: "body" })} - className={cn("ios:uppercase ios:text-muted-foreground", textClassName)} - > - {item} - </Text> - </View> - ); -} - -const ListSectionHeader = React.forwardRef(ListSectionHeaderComponent) as < - T extends ListDataItem, ->( - props: ListSectionHeaderProps<T> & { ref?: ListSectionHeaderRef }, -) => React.ReactElement; - -const ESTIMATED_ITEM_HEIGHT = { - titleOnly: Platform.select({ ios: 45, default: 57 }), - withSubTitle: 56, -}; - -function getStickyHeaderIndices<T extends ListDataItem>(data: T[]) { - if (!data) return []; - const indices: number[] = []; - for (let i = 0; i < data.length; i++) { - if (typeof data[i] === "string") { - indices.push(i); - } - } - return indices; -} - -export { - ESTIMATED_ITEM_HEIGHT, - List, - ListItem, - ListSectionHeader, - getStickyHeaderIndices, -}; -export type { - ListDataItem, - ListItemProps, - ListProps, - ListRenderItemInfo, - ListSectionHeaderProps, -}; diff --git a/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx b/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx index 0b1dd76c..1a767675 100644 --- a/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx +++ b/apps/mobile/components/ui/SearchInput/SearchInput.ios.tsx @@ -1,7 +1,3 @@ -import type { - NativeSyntheticEvent, - TextInputFocusEventData, -} from "react-native"; import * as React from "react"; import { Pressable, TextInput, View, ViewStyle } from "react-native"; import Animated, { @@ -119,7 +115,7 @@ const SearchInput = React.forwardRef< onChangeText(""); } - function onFocus(e: NativeSyntheticEvent<TextInputFocusEventData>) { + function onFocus(e: Parameters<NonNullable<typeof onFocusProp>>[0]) { setShowCancel(true); onFocusProp?.(e); } diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 4fba01cb..94ebc0e1 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -15,52 +15,56 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@expo/metro-runtime": "~6.1.2", "@karakeep/shared": "workspace:^0.1.0", "@karakeep/shared-react": "workspace:^0.1.0", "@karakeep/trpc": "workspace:^0.1.0", - "@react-native-async-storage/async-storage": "1.23.1", - "@react-native-menu/menu": "^1.2.4", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-menu/menu": "^2.0.0", + "@react-navigation/native": "^7.1.8", "@rn-primitives/hooks": "^1.3.0", "@rn-primitives/slot": "^1.2.0", - "@shopify/flash-list": "^2.0.3", + "@shopify/flash-list": "2.0.2", "@tanstack/react-query": "5.90.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", - "expo": "~53.0.19", - "expo-build-properties": "^0.14.6", - "expo-checkbox": "^4.1.4", - "expo-clipboard": "^7.1.4", - "expo-constants": "~17.1.6", - "expo-dev-client": "^5.2.0", - "expo-file-system": "~18.1.11", - "expo-haptics": "^14.1.4", - "expo-image": "^2.4.0", - "expo-image-picker": "^16.1.4", - "expo-linking": "~7.1.5", - "expo-navigation-bar": "^4.2.5", - "expo-router": "~5.0.7", - "expo-secure-store": "^14.2.3", - "expo-share-intent": "^4.0.0", - "expo-sharing": "~13.0.1", - "expo-status-bar": "~2.2.3", - "expo-system-ui": "^5.0.8", - "expo-web-browser": "^14.1.6", + "expo": "~54.0.31", + "expo-build-properties": "~1.0.10", + "expo-checkbox": "~5.0.8", + "expo-clipboard": "~8.0.8", + "expo-constants": "~18.0.13", + "expo-dev-client": "~6.0.20", + "expo-file-system": "~19.0.21", + "expo-haptics": "~15.0.8", + "expo-image": "~3.0.11", + "expo-image-picker": "~17.0.10", + "expo-linking": "~8.0.11", + "expo-navigation-bar": "~5.0.10", + "expo-router": "~6.0.21", + "expo-secure-store": "~15.0.8", + "expo-share-intent": "^5.1.1", + "expo-sharing": "~14.0.8", + "expo-status-bar": "~3.0.9", + "expo-system-ui": "~6.0.9", + "expo-web-browser": "~15.0.10", "lucide-react-native": "^0.513.0", - "nativewind": "^4.1.23", + "nativewind": "^4.2.1", "react": "^19.2.1", - "react-native": "0.79.5", + "react-native": "0.81.5", "react-native-awesome-slider": "^2.5.3", "react-native-blob-util": "^0.21.2", - "react-native-gesture-handler": "~2.24.0", + "react-native-css-interop": "0.2.1", + "react-native-gesture-handler": "~2.28.0", "react-native-image-viewing": "^0.2.2", "react-native-keyboard-controller": "^1.18.5", "react-native-markdown-display": "^7.0.2", "react-native-pdf": "7.0.3", - "react-native-reanimated": "^3.17.5", - "react-native-safe-area-context": "5.4.0", - "react-native-screens": "~4.11.1", - "react-native-svg": "^15.11.2", - "react-native-webview": "^13.13.5", + "react-native-reanimated": "~4.1.1", + "react-native-safe-area-context": "~5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1", "sonner-native": "^0.22.2", "tailwind-merge": "^2.2.1", "zod": "^3.24.2", |
