diff --git a/apps/mobile/package.json b/apps/mobile/package.json index f824141621..97a45337b8 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -74,6 +74,7 @@ "react-hook-form": "7.54.0", "react-native": "0.76.5", "react-native-bouncy-checkbox": "4.1.2", + "react-native-color-matrix-image-filters": "^7.0.2", "react-native-context-menu-view": "1.16.0", "react-native-gesture-handler": "~2.20.2", "react-native-image-colors": "2.4.0", diff --git a/apps/mobile/src/components/common/SafeNavigationScrollView.tsx b/apps/mobile/src/components/common/SafeNavigationScrollView.tsx index ce3f8643a5..a472d6ca8f 100644 --- a/apps/mobile/src/components/common/SafeNavigationScrollView.tsx +++ b/apps/mobile/src/components/common/SafeNavigationScrollView.tsx @@ -1,5 +1,6 @@ import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs" import { useHeaderHeight } from "@react-navigation/elements" +import type { NativeStackNavigationOptions } from "@react-navigation/native-stack" import { router, Stack, useNavigation } from "expo-router" import type { FC, PropsWithChildren } from "react" import { createContext, useContext, useEffect, useMemo, useState } from "react" @@ -28,7 +29,7 @@ type SafeNavigationScrollViewProps = Omit & { withTopInset?: boolean withBottomInset?: boolean } & PropsWithChildren -const NavigationContext = createContext<{ +export const NavigationContext = createContext<{ scrollY: RNAnimated.Value } | null>(null) @@ -66,10 +67,12 @@ export const SafeNavigationScrollView: FC = ({ ) } -export interface NavigationBlurEffectHeaderProps { - title?: string -} -export const NavigationBlurEffectHeader = (props: NavigationBlurEffectHeaderProps) => { +export const NavigationBlurEffectHeader = ({ + blurThreshold = 0, + ...props +}: NativeStackNavigationOptions & { + blurThreshold?: number +}) => { const label = useColor("label") const canBack = useNavigation().canGoBack() @@ -82,13 +85,13 @@ export const NavigationBlurEffectHeader = (props: NavigationBlurEffectHeaderProp useEffect(() => { const id = scrollY.addListener(({ value }) => { - setOpacity(Math.min(1, Math.max(0, Math.min(1, value / 10)))) + setOpacity(Math.min(1, Math.max(0, Math.min(1, (value + blurThreshold) / 10)))) }) return () => { scrollY.removeListener(id) } - }, [scrollY]) + }, [blurThreshold, scrollY]) return ( ) : undefined, - title: props.title, + + ...props, }} /> ) diff --git a/apps/mobile/src/components/ui/icon/fallback-icon.tsx b/apps/mobile/src/components/ui/icon/fallback-icon.tsx index 8db1141bb4..7131e4fdc0 100644 --- a/apps/mobile/src/components/ui/icon/fallback-icon.tsx +++ b/apps/mobile/src/components/ui/icon/fallback-icon.tsx @@ -2,8 +2,9 @@ import { getBackgroundGradient, isCJKChar } from "@follow/utils" import { Image } from "expo-image" import { LinearGradient } from "expo-linear-gradient" import { useMemo, useState } from "react" -import type { StyleProp, TextStyle, ViewStyle } from "react-native" +import type { DimensionValue, StyleProp, TextStyle, ViewStyle } from "react-native" import { StyleSheet, Text, View } from "react-native" +import { useColor } from "react-native-uikit-colors" export const FallbackIcon = ({ title, @@ -13,14 +14,16 @@ export const FallbackIcon = ({ style, textClassName, textStyle, + gray, }: { title: string url?: string - size: number + size: DimensionValue className?: string style?: StyleProp textClassName?: string textStyle?: StyleProp + gray?: boolean }) => { const colors = useMemo(() => getBackgroundGradient(title || url || ""), [title, url]) const sizeStyle = useMemo(() => ({ width: size, height: size }), [size]) @@ -36,10 +39,14 @@ export const FallbackIcon = ({ ) }, [title, textStyle, textClassName]) + const grayColor = useColor("gray2") + return ( diff --git a/apps/mobile/src/components/ui/pressable/item-pressable.tsx b/apps/mobile/src/components/ui/pressable/item-pressable.tsx index e507c28464..3c285bcb7f 100644 --- a/apps/mobile/src/components/ui/pressable/item-pressable.tsx +++ b/apps/mobile/src/components/ui/pressable/item-pressable.tsx @@ -1,4 +1,4 @@ -import { cn, composeEventHandlers } from "@follow/utils" +import { composeEventHandlers } from "@follow/utils" import { memo, useEffect, useState } from "react" import type { Pressable } from "react-native" import { StyleSheet } from "react-native" @@ -49,10 +49,7 @@ export const ItemPressable: typeof Pressable = memo(({ children, ...props }) => onPressOut={composeEventHandlers(props.onPressOut, () => setIsPressing(false))} onHoverIn={composeEventHandlers(props.onHoverIn, () => setIsPressing(true))} onHoverOut={composeEventHandlers(props.onHoverOut, () => setIsPressing(false))} - className={cn( - // isPressing ? "bg-system-fill" : "bg-secondary-system-grouped-background", - props.className, - )} + className={props.className} style={StyleSheet.flatten([colorStyle, props.style])} > {children} diff --git a/apps/mobile/src/modules/entry-list/entry-list.tsx b/apps/mobile/src/modules/entry-list/entry-list.tsx index 45aabd92fa..b3f479240a 100644 --- a/apps/mobile/src/modules/entry-list/entry-list.tsx +++ b/apps/mobile/src/modules/entry-list/entry-list.tsx @@ -1,11 +1,18 @@ import type { FeedViewType } from "@follow/constants" +import { useTypeScriptHappyCallback } from "@follow/hooks" +import { useBottomTabBarHeight } from "@react-navigation/bottom-tabs" +import { useHeaderHeight } from "@react-navigation/elements" import { useIsFocused } from "@react-navigation/native" -import { router, Stack } from "expo-router" -import { useCallback, useEffect } from "react" -import { Image, Text, View } from "react-native" +import { FlashList } from "@shopify/flash-list" +import { router } from "expo-router" +import { useCallback, useEffect, useMemo } from "react" +import { Image, StyleSheet, Text, useAnimatedValue, View } from "react-native" +import { useSafeAreaInsets } from "react-native-safe-area-context" -import { BlurEffect } from "@/src/components/common/BlurEffect" -import { SafeNavigationScrollView } from "@/src/components/common/SafeNavigationScrollView" +import { + NavigationBlurEffectHeader, + NavigationContext, +} from "@/src/components/common/SafeNavigationScrollView" import { ItemPressable } from "@/src/components/ui/pressable/item-pressable" import { useSelectedFeed, @@ -16,9 +23,12 @@ import { useEntry, useEntryIdsByCategory, useEntryIdsByFeedId, + useEntryIdsByInboxId, useEntryIdsByView, } from "@/src/store/entry/hooks" +import { FEED_COLLECTION_LIST } from "@/src/store/entry/utils" import { useFeed } from "@/src/store/feed/hooks" +import { useInbox } from "@/src/store/inbox/hooks" import { useList } from "@/src/store/list/hooks" import { LeftAction, RightAction } from "./action" @@ -49,9 +59,9 @@ export function EntryList() { case "list": { return } - // case "inbox": { - // return - // } + case "inbox": { + return + } // No default } } @@ -59,13 +69,15 @@ export function EntryList() { function ViewEntryList({ viewId }: { viewId: FeedViewType }) { const entryIds = useEntryIdsByView(viewId) const viewDef = useViewDefinition(viewId) + return } function FeedEntryList({ feedId }: { feedId: string }) { const feed = useFeed(feedId) const entryIds = useEntryIdsByFeedId(feedId) - return + const title = feedId === FEED_COLLECTION_LIST ? "Collections" : (feed?.title ?? "") + return } function CategoryEntryList({ categoryName }: { categoryName: string }) { @@ -80,28 +92,72 @@ function ListEntryList({ listId }: { listId: string }) { return } +function InboxEntryList({ inboxId }: { inboxId: string }) { + const inbox = useInbox(inboxId) + const entryIds = useEntryIdsByInboxId(inboxId) + return +} + function EntryListScreen({ title, entryIds }: { title: string; entryIds: string[] }) { + const scrollY = useAnimatedValue(0) + const insets = useSafeAreaInsets() + const tabBarHeight = useBottomTabBarHeight() + const headerHeight = useHeaderHeight() return ( - <> - ({ scrollY }), [scrollY])}> + ( + + ), + [], + )} + headerRight={useCallback( + () => ( + + ), + [], + )} + /> + { + scrollY.setValue(e.nativeEvent.contentOffset.y) + }, + [scrollY], + )} + data={entryIds} + renderItem={useTypeScriptHappyCallback( + ({ item: id }) => ( + + ), + [], + )} + scrollIndicatorInsets={{ + top: headerHeight - insets.top, + bottom: tabBarHeight - insets.bottom, + }} + estimatedItemSize={100} + contentContainerStyle={{ + paddingTop: headerHeight, + paddingBottom: tabBarHeight, }} + ItemSeparatorComponent={ItemSeparator} /> + + ) +} - - - {entryIds.map((id) => ( - - ))} - - - +const ItemSeparator = () => { + return ( + ) } @@ -122,10 +178,7 @@ function EntryItem({ entryId }: { entryId: string }) { const image = media?.[0]?.url return ( - + {title} @@ -148,7 +201,7 @@ function EntryItem({ entryId }: { entryId: string }) { function EntryItemSkeleton() { return ( - + {/* Title skeleton */} diff --git a/apps/mobile/src/modules/feed-drawer/atoms.ts b/apps/mobile/src/modules/feed-drawer/atoms.ts index ccc058e7f4..00bb5536e4 100644 --- a/apps/mobile/src/modules/feed-drawer/atoms.ts +++ b/apps/mobile/src/modules/feed-drawer/atoms.ts @@ -59,7 +59,7 @@ export function useSelectedCollection() { } export const selectCollection = (state: CollectionPanelState) => { jotaiStore.set(collectionPanelStateAtom, state) - if (state.type === "view") { + if (state.type === "view" || state.type === "list") { jotaiStore.set(selectedFeedAtom, state) } } @@ -83,6 +83,10 @@ export type SelectedFeed = type: "list" listId: string } + | { + type: "inbox" + inboxId: string + } const selectedFeedAtom = atom({ type: "view", @@ -109,10 +113,10 @@ export function useSelectedFeed() { payload = { listId: selectedFeed.listId } break } - // case "inbox": { - // payload = { inboxId: selectedFeed.inboxId } - // break - // } + case "inbox": { + payload = { inboxId: selectedFeed.inboxId } + break + } // No default } usePrefetchEntries(payload) diff --git a/apps/mobile/src/modules/feed-drawer/collection-panel.tsx b/apps/mobile/src/modules/feed-drawer/collection-panel.tsx index 6437c64c7b..de0260cb0f 100644 --- a/apps/mobile/src/modules/feed-drawer/collection-panel.tsx +++ b/apps/mobile/src/modules/feed-drawer/collection-panel.tsx @@ -1,5 +1,7 @@ -import { cn } from "@follow/utils" +import type { PropsWithChildren } from "react" +import { useEffect } from "react" import { Image, ScrollView, StyleSheet, TouchableOpacity, View } from "react-native" +import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated" import { useSafeAreaInsets } from "react-native-safe-area-context" import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon" @@ -17,12 +19,12 @@ export const CollectionPanel = () => { const insets = useSafeAreaInsets() return ( @@ -46,26 +48,84 @@ const styles = StyleSheet.create({ }, }) +const Item = ({ + isActive, + onPress, + children, +}: { isActive: boolean; onPress: () => void } & PropsWithChildren) => { + const scaleY = useSharedValue(21) + + useEffect(() => { + if (isActive) { + scaleY.value = withTiming(15, { duration: 200 }) + } else { + scaleY.value = withTiming(21, { duration: 200 }) + } + }, [isActive, scaleY]) + + const animatedStyle = useAnimatedStyle(() => { + return { + borderRadius: scaleY.value, + } + }) + + return ( + + + + {children} + + + ) +} + +const ActiveIndicator = ({ isActive }: { isActive: boolean }) => { + const scaleY = useSharedValue(1) + + useEffect(() => { + if (isActive) { + scaleY.value = withTiming(1, { duration: 200 }) + } else { + scaleY.value = withTiming(0, { duration: 200 }) + } + }, [isActive, scaleY]) + + const animatedStyle = useAnimatedStyle(() => { + return { + transform: [{ scaleY: scaleY.value }], + } + }) + + return ( + + ) +} + const ViewButton = ({ viewDef }: { viewDef: ViewDefinition }) => { const selectedCollection = useSelectedCollection() const isActive = selectedCollection.type === "view" && selectedCollection.viewId === viewDef.view return ( - - selectCollection({ - type: "view", - viewId: viewDef.view, - }) - } - style={{ backgroundColor: viewDef.activeColor }} + selectCollection({ type: "view", viewId: viewDef.view })} > - - + + + + ) } @@ -76,11 +136,8 @@ const ListButton = ({ listId }: { listId: string }) => { if (!list) return null return ( - selectCollection({ type: "list", @@ -89,10 +146,10 @@ const ListButton = ({ listId }: { listId: string }) => { } > {list.image ? ( - + ) : ( - + )} - + ) } diff --git a/apps/mobile/src/modules/feed-drawer/feed-panel.tsx b/apps/mobile/src/modules/feed-drawer/feed-panel.tsx index fbd93ac76a..d00948c709 100644 --- a/apps/mobile/src/modules/feed-drawer/feed-panel.tsx +++ b/apps/mobile/src/modules/feed-drawer/feed-panel.tsx @@ -32,7 +32,7 @@ import { import { useCurrentView, useFeedListSortMethod, useFeedListSortOrder } from "../subscription/atoms" import { ViewPageCurrentViewProvider } from "../subscription/ctx" import { SubscriptionList } from "../subscription/SubscriptionLists" -import { selectFeed, useSelectedCollection } from "./atoms" +import { closeDrawer, selectFeed, useSelectedCollection } from "./atoms" import { ListHeaderComponent, ViewHeaderComponent } from "./header" export const FeedPanel = () => { @@ -153,6 +153,7 @@ const CategoryGrouped = memo( type: "category", categoryName: category, }) + closeDrawer() }} className="h-12 flex-row items-center px-3" > @@ -229,6 +230,7 @@ const SubscriptionItem = memo(({ id, className }: { id: string; className?: stri type: "feed", feedId: id, }) + closeDrawer() }} > diff --git a/apps/mobile/src/modules/feed-drawer/view-selector.tsx b/apps/mobile/src/modules/feed-drawer/view-selector.tsx new file mode 100644 index 0000000000..c775ee3510 --- /dev/null +++ b/apps/mobile/src/modules/feed-drawer/view-selector.tsx @@ -0,0 +1,102 @@ +import { cn } from "@follow/utils" +import { Image } from "expo-image" +import { Stack } from "expo-router" +import { ScrollView, TouchableOpacity, View } from "react-native" +import { Grayscale } from "react-native-color-matrix-image-filters" + +import { BlurEffect } from "@/src/components/common/BlurEffect" +import { FallbackIcon } from "@/src/components/ui/icon/fallback-icon" +import type { ViewDefinition } from "@/src/constants/views" +import { views } from "@/src/constants/views" +import { useList } from "@/src/store/list/hooks" +import { useAllListSubscription } from "@/src/store/subscription/hooks" +import { useColor } from "@/src/theme/colors" + +import { LeftAction, RightAction } from "../entry-list/action" +import { selectFeed, useSelectedFeed } from "../feed-drawer/atoms" + +export function ViewSelector() { + return ( + + ) +} + +function ViewItems() { + const lists = useAllListSubscription() + + return ( + + {views.map((view) => ( + + ))} + {lists.map((listId) => ( + + ))} + + ) +} + +function ViewItem({ view }: { view: ViewDefinition }) { + const selectedFeed = useSelectedFeed() + const isActive = selectedFeed.type === "view" && selectedFeed.viewId === view.view + + const bgColor = useColor("gray2") + + return ( + selectFeed({ type: "view", viewId: view.view })} + > + + + + + + + ) +} + +function ListItem({ listId }: { listId: string }) { + const list = useList(listId) + const selectedFeed = useSelectedFeed() + const isActive = selectedFeed.type === "list" && selectedFeed.listId === listId + + if (!list) return null + + return ( + selectFeed({ type: "list", listId })} + > + + + {list.image ? ( + isActive ? ( + + ) : ( + + + + ) + ) : ( + + )} + + + + ) +} diff --git a/apps/mobile/src/modules/subscription/SubscriptionLists.tsx b/apps/mobile/src/modules/subscription/SubscriptionLists.tsx index de9f24ad55..fd727614cc 100644 --- a/apps/mobile/src/modules/subscription/SubscriptionLists.tsx +++ b/apps/mobile/src/modules/subscription/SubscriptionLists.tsx @@ -11,6 +11,7 @@ import { useEventCallback } from "usehooks-ts" import { ItemPressable } from "@/src/components/ui/pressable/item-pressable" import { StarCuteFiIcon } from "@/src/icons/star_cute_fi" +import { FEED_COLLECTION_LIST } from "@/src/store/entry/utils" import { useGroupedSubscription, useInboxSubscription, @@ -22,6 +23,7 @@ import { } from "@/src/store/subscription/hooks" import { subscriptionSyncService } from "@/src/store/subscription/store" +import { closeDrawer, selectFeed } from "../feed-drawer/atoms" import { useFeedListSortMethod, useFeedListSortOrder, viewAtom } from "./atoms" import { CategoryGrouped } from "./CategoryGrouped" import { ViewTabHeight } from "./constants" @@ -200,7 +202,8 @@ const StarItem = () => { return ( { - // TODO + selectFeed({ type: "feed", feedId: FEED_COLLECTION_LIST }) + closeDrawer() }} className="mt-4 h-12 w-full flex-row items-center px-3" > diff --git a/apps/mobile/src/modules/subscription/items/InboxItem.tsx b/apps/mobile/src/modules/subscription/items/InboxItem.tsx index e8edbb4fb3..a65fd52eb2 100644 --- a/apps/mobile/src/modules/subscription/items/InboxItem.tsx +++ b/apps/mobile/src/modules/subscription/items/InboxItem.tsx @@ -9,6 +9,8 @@ import { useSubscription } from "@/src/store/subscription/hooks" import { getInboxStoreId } from "@/src/store/subscription/utils" import { useUnreadCount } from "@/src/store/unread/hooks" +import { closeDrawer, selectFeed } from "../../feed-drawer/atoms" + export const InboxItem = memo(({ id }: { id: string }) => { const subscription = useSubscription(getInboxStoreId(id)) const unreadCount = useUnreadCount(id) @@ -16,7 +18,13 @@ export const InboxItem = memo(({ id }: { id: string }) => { if (!subscription) return null return ( - + { + selectFeed({ type: "inbox", inboxId: id }) + closeDrawer() + }} + > + return ( + <> + {/* */} + + + ) } diff --git a/apps/mobile/src/store/inbox/hooks.ts b/apps/mobile/src/store/inbox/hooks.ts new file mode 100644 index 0000000000..5af370036f --- /dev/null +++ b/apps/mobile/src/store/inbox/hooks.ts @@ -0,0 +1,7 @@ +import { useInboxStore } from "./store" + +export const useInbox = (inboxId: string) => { + return useInboxStore((state) => { + return state.inboxes[inboxId] + }) +} diff --git a/apps/mobile/src/store/inbox/store.ts b/apps/mobile/src/store/inbox/store.ts index a344a78655..bcd77310fd 100644 --- a/apps/mobile/src/store/inbox/store.ts +++ b/apps/mobile/src/store/inbox/store.ts @@ -3,19 +3,27 @@ import { InboxService } from "@/src/services/inbox" import { createTransaction, createZustandStore } from "../internal/helper" +interface InboxState { + inboxes: Record +} + const defaultState = { - inboxes: [], + inboxes: {}, } -export const useInboxStore = createZustandStore<{ - inboxes: InboxSchema[] -}>("inbox")(() => defaultState) + +export const useInboxStore = createZustandStore("inbox")(() => defaultState) // const get = useInboxStore.getState const set = useInboxStore.setState class InboxActions { async upsertManyInSession(inboxes: InboxSchema[]) { const state = useInboxStore.getState() - const nextInboxes = [...state.inboxes, ...inboxes] + const nextInboxes: InboxState["inboxes"] = { + ...state.inboxes, + } + inboxes.forEach((inbox) => { + nextInboxes[inbox.id] = inbox + }) set({ ...state, inboxes: nextInboxes, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f232a183c..fd68d050bf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -586,6 +586,9 @@ importers: react-native-bouncy-checkbox: specifier: 4.1.2 version: 4.1.2 + react-native-color-matrix-image-filters: + specifier: ^7.0.2 + version: 7.0.2(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) react-native-context-menu-view: specifier: 1.16.0 version: 1.16.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1) @@ -7068,6 +7071,9 @@ packages: cjs-module-lexer@1.4.1: resolution: {integrity: sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA==} + clamp@1.0.1: + resolution: {integrity: sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==} + class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} @@ -7276,6 +7282,9 @@ packages: resolution: {integrity: sha512-bQJ0YRck5ak3LgtnpKkiabX5pNF7tMUh1BSy2ZBOTh0Dim0BUu6aPPwByIns6/A5Prh8PufSPerMDUklpzes2Q==} engines: {node: '>= 0.8.0'} + concat-color-matrices@1.0.0: + resolution: {integrity: sha512-K0IMtl4m9LcHg4vVFb40Txmie/GwCJBkquGSn6wtA9yIcszzFZgGc6co4sSnjFdqeJAv+q2LrCHMUSb2GHAChA==} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -12725,6 +12734,12 @@ packages: react-native-bouncy-checkbox@4.1.2: resolution: {integrity: sha512-hB7YwCGTNoMpTPOPiP+RWyQH35S6vxUbc7IGEW/Rqyp7GonEyhtqtthmxiphneRXnywMh8CZwND7OnvppJZscg==} + react-native-color-matrix-image-filters@7.0.2: + resolution: {integrity: sha512-YwEbSbzwpD99v8KNiebLB5NymY9hPBQQJ778QN2/39YZ/tA9TbFA3M3qv4kU2qyF+Tdon6KbFOd7D7zaAXUhSA==} + peerDependencies: + react: '*' + react-native: '*' + react-native-context-menu-view@1.16.0: resolution: {integrity: sha512-zqeOAizM7MVV9o6h/quS0REQikBq3J4BkIRLFygY6RiCjr6rwuzSGkif7JRCHpAQQumSKlLqYl4N2h3AdoIHVg==} peerDependencies: @@ -13309,6 +13324,11 @@ packages: ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} + rn-color-matrices@4.1.0: + resolution: {integrity: sha512-a8++z3Zi4GhA0oWwKS3etdrVuVQ2q/ByTMh6lMxo+vaSv3SRSSg5SzpZk65GS0NCAqMhFT8WSvgSgNhO/F+xag==} + peerDependencies: + react-native: '*' + roarr@2.15.4: resolution: {integrity: sha512-CHhPh+UNHD2GTXNYhPWLnU8ONHdI+5DI+4EYIAOaiD63rHeYlZvyh8P+in5999TTSFgUYuKUAjzRI4mdh/p+2A==} engines: {node: '>=8.0'} @@ -14236,6 +14256,9 @@ packages: ts-pattern@5.6.0: resolution: {integrity: sha512-SL8u60X5+LoEy9tmQHWCdPc2hhb2pKI6I1tU5Jue3v8+iRqZdcT3mWPwKKJy1fMfky6uha82c8ByHAE8PMhKHw==} + ts-tiny-invariant@2.0.5: + resolution: {integrity: sha512-NGQzWRLGLMjOUTpsxMSRj63fuFBC8HV8L4NUxzDVgU4MFeOt3qFI2534M5L+ch22x53oDEwPaRBPXgAfRjcXHA==} + ts-toolbelt@6.15.5: resolution: {integrity: sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==} @@ -22571,6 +22594,8 @@ snapshots: cjs-module-lexer@1.4.1: {} + clamp@1.0.1: {} + class-variance-authority@0.7.1: dependencies: clsx: 2.1.1 @@ -22778,6 +22803,10 @@ snapshots: transitivePeerDependencies: - supports-color + concat-color-matrices@1.0.0: + dependencies: + invariant: 2.2.4 + concat-map@0.0.1: {} confbox@0.1.8: {} @@ -29327,6 +29356,14 @@ snapshots: dependencies: '@freakycoder/react-native-bounceable': 1.0.3 + react-native-color-matrix-image-filters@7.0.2(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1): + dependencies: + concat-color-matrices: 1.0.0 + react: 18.3.1 + react-native: 0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + rn-color-matrices: 4.1.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)) + ts-tiny-invariant: 2.0.5 + react-native-context-menu-view@1.16.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1))(react@18.3.1): dependencies: react: 18.3.1 @@ -30058,6 +30095,11 @@ snapshots: hash-base: 3.1.0 inherits: 2.0.4 + rn-color-matrices@4.1.0(react-native@0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1)): + dependencies: + clamp: 1.0.1 + react-native: 0.76.5(@babel/core@7.26.0)(@babel/preset-env@7.26.0(@babel/core@7.26.0))(@react-native-community/cli-server-api@14.1.0(bufferutil@4.0.8))(@types/react@18.3.14)(bufferutil@4.0.8)(encoding@0.1.13)(react@18.3.1) + roarr@2.15.4: dependencies: boolean: 3.2.0 @@ -31149,6 +31191,8 @@ snapshots: ts-pattern@5.6.0: {} + ts-tiny-invariant@2.0.5: {} + ts-toolbelt@6.15.5: {} tsconfck@3.1.4(typescript@5.7.2):