diff --git a/apps/desktop/src/main/src/lib/user.ts b/apps/desktop/src/main/src/lib/user.ts index a1593a75a..f2445067c 100644 --- a/apps/desktop/src/main/src/lib/user.ts +++ b/apps/desktop/src/main/src/lib/user.ts @@ -1,6 +1,7 @@ import type { Credentials } from "@eneris/push-receiver/dist/types" import type { UserModel } from "@follow/models" +import { isLinux, isMacOS, isWindows } from "~/env" import { logger } from "~/logger" import { apiClient } from "./api-client" @@ -30,7 +31,7 @@ export const updateNotificationsToken = async (newCredentials?: Credentials) => await apiClient.messaging.$post({ json: { token: credentials.fcm.token, - channel: "desktop", + channel: isMacOS ? "macos" : isWindows ? "windows" : isLinux ? "linux" : "desktop", }, }) } catch (error) { diff --git a/apps/desktop/src/renderer/src/push-notification.ts b/apps/desktop/src/renderer/src/push-notification.ts index 18f36c89c..17e911277 100644 --- a/apps/desktop/src/renderer/src/push-notification.ts +++ b/apps/desktop/src/renderer/src/push-notification.ts @@ -37,7 +37,7 @@ export async function registerWebPushNotifications() { await apiClient.messaging.$post({ json: { token, - channel: "desktop", + channel: "web", }, }) diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts index fff64a804..292004202 100644 --- a/apps/mobile/app.config.ts +++ b/apps/mobile/app.config.ts @@ -154,6 +154,12 @@ export default ({ config }: ConfigContext): ExpoConfig => ({ }, ], "react-native-video", + [ + "expo-notifications", + { + enableBackgroundRemoteNotifications: true, + }, + ], ], experiments: { typedRoutes: true, diff --git a/apps/mobile/package.json b/apps/mobile/package.json index d8ce62a33..f6c604efd 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -37,6 +37,7 @@ "@react-native-firebase/app": "21.13.0", "@react-native-firebase/app-check": "21.13.0", "@react-native-firebase/crashlytics": "21.13.0", + "@react-native-firebase/messaging": "21.13.0", "@react-native-menu/menu": "1.2.3", "@react-native-picker/picker": "2.11.0", "@shopify/flash-list": "1.7.3", @@ -53,6 +54,7 @@ "expo-apple-authentication": "7.1.3", "expo-application": "6.0.2", "expo-av": "15.0.2", + "expo-background-fetch": "13.0.6", "expo-blur": "14.0.3", "expo-build-properties": "0.13.2", "expo-clipboard": "7.0.1", @@ -68,6 +70,7 @@ "expo-linking": "7.0.5", "expo-localization": "16.0.1", "expo-media-library": "17.0.6", + "expo-notifications": "0.29.14", "expo-secure-store": "14.0.1", "expo-sharing": "13.0.1", "expo-splash-screen": "0.29.22", @@ -75,6 +78,7 @@ "expo-status-bar": "2.0.1", "expo-symbols": "0.2.2", "expo-system-ui": "4.0.9", + "expo-task-manager": "12.0.6", "expo-updates": "0.27.4", "expo-web-browser": "14.0.2", "hono": "4.7.5", diff --git a/apps/mobile/src/App.tsx b/apps/mobile/src/App.tsx index 891bb9689..f228e9697 100644 --- a/apps/mobile/src/App.tsx +++ b/apps/mobile/src/App.tsx @@ -6,11 +6,20 @@ import { useSheet } from "react-native-sheet-transitions" import { useIntentHandler } from "./hooks/useIntentHandler" import { DebugButton, EnvProfileIndicator } from "./modules/debug" +import { usePrefetchActions } from "./store/action/hooks" +import { useMessaging, useUpdateMessagingToken } from "./store/messaging/hooks" +import { useUnreadCountBadge } from "./store/unread/hooks" import { useOnboarding, usePrefetchSessionUser } from "./store/user/hooks" export function App({ children }: { children: React.ReactNode }) { useIntentHandler() useOnboarding() + useUnreadCountBadge() + + // prefetch actions to detect if the user has any actions contains notifications + usePrefetchActions() + useUpdateMessagingToken() + useMessaging() const { scale } = useSheet() const style = useAnimatedStyle(() => ({ diff --git a/apps/mobile/src/initialize/background.ts b/apps/mobile/src/initialize/background.ts new file mode 100644 index 000000000..351012fae --- /dev/null +++ b/apps/mobile/src/initialize/background.ts @@ -0,0 +1,32 @@ +import * as BackgroundFetch from "expo-background-fetch" +import * as TaskManager from "expo-task-manager" + +import { unreadSyncService } from "../store/unread/store" +import { whoami } from "../store/user/getters" + +const BACKGROUND_FETCH_TASK = "background-fetch" + +export async function initBackgroundFetch() { + TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => { + // const now = Date.now() + // console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`) + const user = whoami() + if (!user) { + return BackgroundFetch.BackgroundFetchResult.NoData + } + + try { + const res = await unreadSyncService.updateBadgeAtBackground() + return res + ? BackgroundFetch.BackgroundFetchResult.NewData + : BackgroundFetch.BackgroundFetchResult.NoData + } catch (err) { + console.error(err) + return BackgroundFetch.BackgroundFetchResult.Failed + } + }) + + return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, { + minimumInterval: 60 * 15, // 15 minutes + }) +} diff --git a/apps/mobile/src/initialize/index.ts b/apps/mobile/src/initialize/index.ts index f9dba3258..e8c4d6631 100644 --- a/apps/mobile/src/initialize/index.ts +++ b/apps/mobile/src/initialize/index.ts @@ -5,6 +5,7 @@ import { initializeDb } from "../database" import { settingSyncQueue } from "../modules/settings/sync-queue" import { initAnalytics } from "./analytics" import { initializeAppCheck } from "./app-check" +import { initBackgroundFetch } from "./background" import { initCrashlytics } from "./crashlytics" import { initializeDayjs } from "./dayjs" import { hydrateDatabaseToStore, hydrateQueryClient, hydrateSettings } from "./hydrate" @@ -46,6 +47,7 @@ export const initializeApp = async () => { using_indexed_db: true, }) initCrashlytics() + initBackgroundFetch() console.log(`Initialize done,`, `${loadingTime}ms`) } diff --git a/apps/mobile/src/lib/permission.ts b/apps/mobile/src/lib/permission.ts new file mode 100644 index 000000000..6a8be4940 --- /dev/null +++ b/apps/mobile/src/lib/permission.ts @@ -0,0 +1,35 @@ +import * as Notifications from "expo-notifications" +import { Platform } from "react-native" + +import { toast } from "./toast" + +export async function requestNotificationPermission() { + if (Platform.OS === "android") { + Notifications.setNotificationChannelAsync("default", { + name: "default", + importance: Notifications.AndroidImportance.MAX, + vibrationPattern: [0, 250, 250, 250], + lightColor: "#FF231F7C", + }) + } + + const { status: existingStatus } = await Notifications.getPermissionsAsync() + let finalStatus = existingStatus + if (existingStatus !== "granted") { + const { status } = await Notifications.requestPermissionsAsync() + finalStatus = status + } + if (finalStatus !== "granted") { + toast.error("Permission not granted for notification!") + return false + } + return true +} + +export async function setBadgeCountAsyncWithPermission(badgeCount: number) { + const permissionGranted = await requestNotificationPermission() + if (!permissionGranted) { + return false + } + return await Notifications.setBadgeCountAsync(badgeCount) +} diff --git a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx index c1285e6ad..cc1af2d1a 100644 --- a/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx +++ b/apps/mobile/src/screens/(stack)/entries/[entryId]/index.tsx @@ -14,7 +14,7 @@ import { openLink } from "@/src/lib/native" import type { NavigationControllerView } from "@/src/lib/navigation/types" import { EntryContentContext, useEntryContentContext } from "@/src/modules/entry-content/ctx" import { EntryAISummary } from "@/src/modules/entry-content/EntryAISummary" -import { useEntry, usePrefetchEntryContent } from "@/src/store/entry/hooks" +import { useEntry, usePrefetchEntryDetail } from "@/src/store/entry/hooks" import { entrySyncServices } from "@/src/store/entry/store" import type { EntryWithTranslation } from "@/src/store/entry/types" import { useFeed } from "@/src/store/feed/hooks" @@ -27,7 +27,7 @@ export const EntryDetailScreen: NavigationControllerView<{ entryId: string view: FeedViewType }> = ({ entryId, view: viewType }) => { - usePrefetchEntryContent(entryId) + usePrefetchEntryDetail(entryId) useAutoMarkAsRead(entryId) const entry = useEntry(entryId) const translation = useEntryTranslation(entryId) diff --git a/apps/mobile/src/store/action/hooks.ts b/apps/mobile/src/store/action/hooks.ts index 4fad14edf..848490488 100644 --- a/apps/mobile/src/store/action/hooks.ts +++ b/apps/mobile/src/store/action/hooks.ts @@ -64,3 +64,9 @@ export function useActionRuleCondition({ export const useIsActionDataDirty = () => { return useActionStore((state) => state.isDirty) } + +export const useHasNotificationActions = () => { + return useActionStore((state) => { + return state.rules.some((rule) => !!rule.result.newEntryNotification && !rule.result.disabled) + }) +} diff --git a/apps/mobile/src/store/entry/hooks.ts b/apps/mobile/src/store/entry/hooks.ts index 078223633..98601a93c 100644 --- a/apps/mobile/src/store/entry/hooks.ts +++ b/apps/mobile/src/store/entry/hooks.ts @@ -22,10 +22,10 @@ export const usePrefetchEntries = (props: Omit { +export const usePrefetchEntryDetail = (entryId: string) => { return useQuery({ queryKey: ["entry", entryId], - queryFn: () => entrySyncServices.fetchEntryContent(entryId), + queryFn: () => entrySyncServices.fetchEntryDetail(entryId), }) } diff --git a/apps/mobile/src/store/entry/store.ts b/apps/mobile/src/store/entry/store.ts index 39e5a39ef..c991a38dd 100644 --- a/apps/mobile/src/store/entry/store.ts +++ b/apps/mobile/src/store/entry/store.ts @@ -426,13 +426,15 @@ class EntrySyncServices { return res } - async fetchEntryContent(entryId: EntryId) { + async fetchEntryDetail(entryId: EntryId) { const currentEntry = getEntry(entryId) const res = currentEntry?.inboxHandle ? await apiClient.entries.inbox.$get({ query: { id: entryId } }) : await apiClient.entries.$get({ query: { id: entryId } }) const entry = honoMorph.toEntry(res.data) - if (entry?.content && currentEntry?.content !== entry.content) { + if (!currentEntry && entry) { + await entryActions.upsertMany([entry]) + } else if (entry?.content && currentEntry?.content !== entry.content) { await entryActions.updateEntryContent({ entryId, content: entry.content }) } return entry diff --git a/apps/mobile/src/store/messaging/hooks.ts b/apps/mobile/src/store/messaging/hooks.ts new file mode 100644 index 000000000..4315e8618 --- /dev/null +++ b/apps/mobile/src/store/messaging/hooks.ts @@ -0,0 +1,83 @@ +import type { FirebaseMessagingTypes } from "@react-native-firebase/messaging" +import messaging from "@react-native-firebase/messaging" +import { useMutation } from "@tanstack/react-query" +import { useEffect } from "react" +import { Platform } from "react-native" + +import { apiClient } from "@/src/lib/api-fetch" +import { kv } from "@/src/lib/kv" +import { useNavigation } from "@/src/lib/navigation/hooks" +import { requestNotificationPermission } from "@/src/lib/permission" +import { EntryDetailScreen } from "@/src/screens/(stack)/entries/[entryId]" + +import { useHasNotificationActions } from "../action/hooks" +import { useWhoami } from "../user/hooks" + +const FIREBASE_MESSAGING_TOKEN_STORAGE_KEY = "firebase_messaging_token" + +async function saveMessagingToken() { + const token = await messaging().getToken() + const storedToken = await kv.get(FIREBASE_MESSAGING_TOKEN_STORAGE_KEY) + if (storedToken === token) { + return + } + await apiClient.messaging.$post({ + json: { + token, + channel: Platform.OS, + }, + }) + kv.set(FIREBASE_MESSAGING_TOKEN_STORAGE_KEY, token) +} + +export function useUpdateMessagingToken() { + const whoami = useWhoami() + const hasNotificationActions = useHasNotificationActions() + const { mutate } = useMutation({ + mutationFn: async () => { + return Promise.all([saveMessagingToken(), requestNotificationPermission()]) + }, + }) + + useEffect(() => { + if (!whoami?.id || !hasNotificationActions) return + mutate() + }, [hasNotificationActions, mutate, whoami?.id]) +} + +export function useMessaging() { + const navigation = useNavigation() + useEffect(() => { + function navigateToEntry(message: FirebaseMessagingTypes.RemoteMessage) { + if ( + !message.data || + typeof message.data.view !== "string" || + typeof message.data.entryId !== "string" + ) { + return + } + + navigation.pushControllerView(EntryDetailScreen, { + entryId: message.data.entryId, + view: Number.parseInt(message.data.view), + }) + } + + async function init() { + const message = await messaging().getInitialNotification() + if (message) { + navigateToEntry(message) + } + } + + init() + + const unsubscribe = messaging().onNotificationOpenedApp((remoteMessage) => { + navigateToEntry(remoteMessage) + }) + + return () => { + unsubscribe() + } + }, [navigation]) +} diff --git a/apps/mobile/src/store/unread/getter.ts b/apps/mobile/src/store/unread/getter.ts index c9f412126..d5c6c0ab3 100644 --- a/apps/mobile/src/store/unread/getter.ts +++ b/apps/mobile/src/store/unread/getter.ts @@ -4,3 +4,8 @@ export const getUnreadCount = (id: string) => { const state = useUnreadStore.getState() return state.data[id] ?? 0 } + +export const getAllUnreadCount = () => { + const state = useUnreadStore.getState() + return Object.values(state.data).reduce((acc, unread) => acc + unread, 0) +} diff --git a/apps/mobile/src/store/unread/hooks.ts b/apps/mobile/src/store/unread/hooks.ts index bf222192c..1f69ceb54 100644 --- a/apps/mobile/src/store/unread/hooks.ts +++ b/apps/mobile/src/store/unread/hooks.ts @@ -2,6 +2,8 @@ import type { FeedViewType } from "@follow/constants" import { useMutation, useQuery } from "@tanstack/react-query" import { useCallback, useEffect } from "react" +import { setBadgeCountAsyncWithPermission } from "@/src/lib/permission" + import { useListFeedIds } from "../list/hooks" import { useSubscriptionByView } from "../subscription/hooks" import { unreadSyncService, useUnreadStore } from "./store" @@ -23,6 +25,13 @@ export const useAutoMarkAsRead = (entryId: string) => { }, [entryId, mutate]) } +export function useUnreadCountBadge() { + const unreadCount = useUnreadCounts() + useEffect(() => { + setBadgeCountAsyncWithPermission(unreadCount) + }, [unreadCount]) +} + export const useUnreadCount = (subscriptionId: string) => { return useUnreadStore((state) => state.data[subscriptionId]) } @@ -32,10 +41,13 @@ export const useListUnreadCount = (listId: string) => { return useUnreadCounts(feedIds ?? []) } -export const useUnreadCounts = (subscriptionIds: string[]): number => { +export const useUnreadCounts = (subscriptionIds?: string[]): number => { return useUnreadStore( useCallback( (state) => { + if (!subscriptionIds) + return Object.values(state.data).reduce((acc, unread) => acc + unread, 0) + let count = 0 for (const subscriptionId of subscriptionIds) { count += state.data[subscriptionId] ?? 0 diff --git a/apps/mobile/src/store/unread/store.ts b/apps/mobile/src/store/unread/store.ts index 0ff3e93e0..ab974f9fe 100644 --- a/apps/mobile/src/store/unread/store.ts +++ b/apps/mobile/src/store/unread/store.ts @@ -1,7 +1,9 @@ import type { FeedViewType } from "@follow/constants" +import * as Notifications from "expo-notifications" import type { UnreadSchema } from "@/src/database/schemas/types" import { apiClient } from "@/src/lib/api-fetch" +import { setBadgeCountAsyncWithPermission } from "@/src/lib/permission" import { EntryService } from "@/src/services/entry" import { UnreadService } from "@/src/services/unread" @@ -9,6 +11,7 @@ import { getEntry } from "../entry/getter" import { entryActions } from "../entry/store" import { createTransaction, createZustandStore } from "../internal/helper" import { getSubscriptionByView } from "../subscription/getter" +import { getAllUnreadCount } from "./getter" type SubscriptionId = string interface UnreadStore { @@ -30,6 +33,17 @@ class UnreadSyncService { return res.data } + async updateBadgeAtBackground() { + await this.fetch() + const allUnreadCount = getAllUnreadCount() + const currentBadgeCount = await Notifications.getBadgeCountAsync() + if (allUnreadCount === currentBadgeCount) { + return false + } + setBadgeCountAsyncWithPermission(allUnreadCount) + return true + } + private async updateUnreadStatus(feedIds: string[]) { await unreadActions.upsertMany(feedIds.map((id) => ({ subscriptionId: id, count: 0 }))) entryActions.markEntryReadStatusInSession({ feedIds, read: true }) diff --git a/packages/shared/src/hono.ts b/packages/shared/src/hono.ts index 3502fadce..1bd6c6d8d 100644 --- a/packages/shared/src/hono.ts +++ b/packages/shared/src/hono.ts @@ -4604,15 +4604,15 @@ declare const messagingOpenAPISchema: z.ZodObject, { - channel: z.ZodEnum<["desktop", "mobile"]>; + channel: z.ZodEnum<["macos", "windows", "linux", "ios", "android", "web", "desktop"]>; }>, "strip", z.ZodTypeAny, { userId: string | null; token: string; - channel: "desktop" | "mobile"; + channel: "macos" | "windows" | "linux" | "ios" | "android" | "web" | "desktop"; }, { userId: string | null; token: string; - channel: "desktop" | "mobile"; + channel: "macos" | "windows" | "linux" | "ios" | "android" | "web" | "desktop"; }>; declare const messagingRelations: drizzle_orm.Relations<"messaging", { users: drizzle_orm.One<"user", false>; @@ -17391,7 +17391,7 @@ declare const _routes: hono_hono_base.HonoBase=21.0.0} @@ -5417,6 +5432,15 @@ packages: expo: optional: true + '@react-native-firebase/messaging@21.13.0': + resolution: {integrity: sha512-ZUc44XRPjVvg0os9TdcOB7GECewJXsEzsder8gJRgCrOL6wn96uAqqOOmzAja64SQeMxvnelbfOtDtwoltLCvA==} + peerDependencies: + '@react-native-firebase/app': 21.13.0 + expo: '>=47.0.0' + peerDependenciesMeta: + expo: + optional: true + '@react-native-menu/menu@1.2.3': resolution: {integrity: sha512-sEfiVIivsa0lSelFm9Wbm/RAi+XoEHc75GGhjwvSrj9KSCVvNNXwr9F8l42e1t/lzYvVYzmkYxLG6VKxrDYJiw==} peerDependencies: @@ -6927,6 +6951,9 @@ packages: resolution: {integrity: sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==} engines: {node: '>=0.8'} + assert@2.1.0: + resolution: {integrity: sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -7087,6 +7114,9 @@ packages: peerDependencies: '@babel/core': ^7.0.0 + badgin@1.2.3: + resolution: {integrity: sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==} + bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} @@ -9185,6 +9215,11 @@ packages: react-native-web: optional: true + expo-background-fetch@13.0.6: + resolution: {integrity: sha512-fhSpPA7U/CIuBwYbMVSbfPxczsvHKF8MWmFumfMgriDqop6bp/ccLbnpU1vPLQDPmRPS5dMdTz04KYIMdTpD9w==} + peerDependencies: + expo: '*' + expo-blur@14.0.3: resolution: {integrity: sha512-BL3xnqBJbYm3Hg9t/HjNjdeY7N/q8eK5tsLYxswWG1yElISWZmMvrXYekl7XaVCPfyFyz8vQeaxd7q74ZY3Wrw==} peerDependencies: @@ -9337,6 +9372,13 @@ packages: expo-modules-core@2.2.3: resolution: {integrity: sha512-01QqZzpP/wWlxnNly4G06MsOBUTbMDj02DQigZoXfDh80vd/rk3/uVXqnZgOdLSggTs6DnvOgAUy0H2q30XdUg==} + expo-notifications@0.29.14: + resolution: {integrity: sha512-AVduNx9mKOgcAqBfrXS1OHC9VAQZrDQLbVbcorMjPDGXW7m0Q5Q+BG6FYM/saVviF2eO8fhQRsTT40yYv5/bhQ==} + peerDependencies: + expo: '*' + react: 18.3.1 + react-native: '*' + expo-secure-store@14.0.1: resolution: {integrity: sha512-QUS+j4+UG4jRQalgnpmTvvrFnMVLqPiUZRzYPnG3+JrZ5kwVW2w6YS3WWerPoR7C6g3y/a2htRxRSylsDs+TaQ==} peerDependencies: @@ -9383,6 +9425,12 @@ packages: react-native-web: optional: true + expo-task-manager@12.0.6: + resolution: {integrity: sha512-yGbS64OL95z7tAQAvryy0sGHuQgrcpvnJsdyuGL8MA9bcPtr+kytLZ4dOCDac7foQS7+FLDGgtiAR6v/64B5Pg==} + peerDependencies: + expo: '*' + react-native: '*' + expo-updates-interface@1.0.0: resolution: {integrity: sha512-93oWtvULJOj+Pp+N/lpTcFfuREX1wNeHtp7Lwn8EbzYYmdn37MvZU3TPW2tYYCZuhzmKEXnUblYcruYoDu7IrQ==} peerDependencies: @@ -10400,6 +10448,10 @@ packages: is-alphanumerical@2.0.1: resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + is-array-buffer@3.0.5: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} @@ -10529,6 +10581,10 @@ packages: is-my-json-valid@2.20.6: resolution: {integrity: sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==} + is-nan@1.3.2: + resolution: {integrity: sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==} + engines: {node: '>= 0.4'} + is-natural-number@4.0.1: resolution: {integrity: sha512-Y4LTamMe0DDQIIAlaer9eKebAlDSV6huy+TWhJVPlzZh2o4tRP5SQWFlLn5N0To4mDD22/qdOq+veo1cSISLgQ==} @@ -12003,6 +12059,10 @@ packages: resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} engines: {node: '>= 0.4'} + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + object-keys@1.1.1: resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} engines: {node: '>= 0.4'} @@ -14859,6 +14919,9 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unimodules-app-loader@5.0.1: + resolution: {integrity: sha512-JI4dUMOovvLrZ1U/mrQrR73cxGH26H7NpfBxwE0hk59CBOyHO4YYpliI3hPSGgZzt+YEy2VZR6nrspSUXY8jyw==} + uniqolor@1.1.1: resolution: {integrity: sha512-HUwezlXCwm5bzsEXW7AP7ybezH13uWENRgYT+3dOdhJPvpYucSqvIGckMiLn+Uy2j0NVf3fPp43uZ4aun3t4Ww==} @@ -15049,6 +15112,9 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + utils-merge@1.0.1: resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} engines: {node: '>= 0.4.0'} @@ -19006,6 +19072,8 @@ snapshots: transitivePeerDependencies: - supports-color + '@ide/backoff@1.0.0': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -20536,6 +20604,12 @@ snapshots: optionalDependencies: expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) + '@react-native-firebase/messaging@21.13.0(2511037f97ab10cd056affaf5ecb511d)': + dependencies: + '@react-native-firebase/app': 21.13.0(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1) + optionalDependencies: + expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) + '@react-native-menu/menu@1.2.3(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)': dependencies: react: 18.3.1 @@ -22350,6 +22424,14 @@ snapshots: assert-plus@1.0.0: optional: true + assert@2.1.0: + dependencies: + call-bind: 1.0.8 + is-nan: 1.3.2 + object-is: 1.1.6 + object.assign: 4.1.7 + util: 0.12.5 + assertion-error@2.0.1: {} ast-kit@1.4.2: @@ -22551,6 +22633,8 @@ snapshots: babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.1.0(@babel/core@7.26.10) + badgin@1.2.3: {} + bail@2.0.2: {} balanced-match@1.0.2: {} @@ -25186,6 +25270,13 @@ snapshots: optionalDependencies: react-native-web: 0.20.0(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + expo-background-fetch@13.0.6(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)): + dependencies: + expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) + expo-task-manager: 12.0.6(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)) + transitivePeerDependencies: + - react-native + expo-blur@14.0.3(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1): dependencies: expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) @@ -25360,6 +25451,21 @@ snapshots: dependencies: invariant: 2.2.4 + expo-notifications@0.29.14(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1): + dependencies: + '@expo/image-utils': 0.6.5 + '@ide/backoff': 1.0.0 + abort-controller: 3.0.0 + assert: 2.1.0 + badgin: 1.2.3 + expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) + expo-application: 6.0.2(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5)) + expo-constants: 17.0.8(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)) + react: 18.3.1 + react-native: 0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5) + transitivePeerDependencies: + - supports-color + expo-secure-store@14.0.1(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5)): dependencies: expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) @@ -25404,6 +25510,12 @@ snapshots: transitivePeerDependencies: - supports-color + expo-task-manager@12.0.6(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)): + dependencies: + expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) + react-native: 0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5) + unimodules-app-loader: 5.0.1 + expo-updates-interface@1.0.0(expo@52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5)): dependencies: expo: 52.0.44(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@expo/metro-runtime@4.0.1(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5)))(bufferutil@4.0.9)(encoding@0.1.13)(graphql@16.8.1)(react-native-webview@13.13.5(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1))(react-native@0.77.2(@babel/core@7.26.10)(@babel/preset-env@7.26.9(@babel/core@7.26.10))(@types/react@18.3.12)(bufferutil@4.0.9)(react@18.3.1)(utf-8-validate@6.0.5))(react@18.3.1)(utf-8-validate@6.0.5) @@ -26742,6 +26854,11 @@ snapshots: is-alphabetical: 2.0.1 is-decimal: 2.0.1 + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + is-array-buffer@3.0.5: dependencies: call-bind: 1.0.8 @@ -26866,6 +26983,11 @@ snapshots: xtend: 4.0.2 optional: true + is-nan@1.3.2: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + is-natural-number@4.0.1: {} is-number-object@1.1.1: @@ -28725,6 +28847,11 @@ snapshots: object-inspect@1.13.4: {} + object-is@1.1.6: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + object-keys@1.1.1: {} object-treeify@1.1.33: {} @@ -31828,6 +31955,8 @@ snapshots: trough: 2.2.0 vfile: 5.3.7 + unimodules-app-loader@5.0.1: {} + uniqolor@1.1.1: {} unique-filename@2.0.1: @@ -32026,6 +32155,14 @@ snapshots: util-deprecate@1.0.2: {} + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.0 + is-typed-array: 1.1.15 + which-typed-array: 1.1.19 + utils-merge@1.0.1: {} uuid@7.0.3: {}