) => {\n setActiveLightbox(prevLightbox => {\n if (prevLightbox) {\n // Ignore duplicate open requests. If it's already open,\n // the user has to explicitly close the previous one first.\n return prevLightbox\n } else {\n return {...lightbox, id: nanoid()}\n }\n })\n },\n )\n\n const closeLightbox = useNonReactiveCallback(() => {\n let wasActive = !!activeLightbox\n setActiveLightbox(null)\n return wasActive\n })\n\n const state = React.useMemo(\n () => ({\n activeLightbox,\n }),\n [activeLightbox],\n )\n\n const methods = React.useMemo(\n () => ({\n openLightbox,\n closeLightbox,\n }),\n [openLightbox, closeLightbox],\n )\n\n return (\n \n \n {children}\n \n \n )\n}\n\nexport function useLightbox() {\n return React.useContext(LightboxContext)\n}\n\nexport function useLightboxControls() {\n return React.useContext(LightboxControlContext)\n}\n","import {useCallback} from 'react'\n\nimport {useDialogStateControlContext} from '#/state/dialogs'\nimport {useLightboxControls} from './lightbox'\nimport {useModalControls} from './modals'\nimport {useComposerControls} from './shell/composer'\nimport {useSetDrawerOpen} from './shell/drawer-open'\n\n/**\n * returns true if something was closed\n * (used by the android hardware back btn)\n */\nexport function useCloseAnyActiveElement() {\n const {closeLightbox} = useLightboxControls()\n const {closeModal} = useModalControls()\n const {closeComposer} = useComposerControls()\n const {closeAllDialogs} = useDialogStateControlContext()\n const setDrawerOpen = useSetDrawerOpen()\n return useCallback(() => {\n if (closeLightbox()) {\n return true\n }\n if (closeModal()) {\n return true\n }\n if (closeAllDialogs()) {\n return true\n }\n if (closeComposer()) {\n return true\n }\n setDrawerOpen(false)\n return false\n }, [closeLightbox, closeModal, closeComposer, setDrawerOpen, closeAllDialogs])\n}\n\n/**\n * used to clear out any modals, eg for a navigation\n */\nexport function useCloseAllActiveElements() {\n const {closeLightbox} = useLightboxControls()\n const {closeAllModals} = useModalControls()\n const {closeComposer} = useComposerControls()\n const {closeAllDialogs: closeAlfDialogs} = useDialogStateControlContext()\n const setDrawerOpen = useSetDrawerOpen()\n return useCallback(() => {\n closeLightbox()\n closeAllModals()\n closeComposer()\n closeAlfDialogs()\n setDrawerOpen(false)\n }, [\n closeLightbox,\n closeAllModals,\n closeComposer,\n closeAlfDialogs,\n setDrawerOpen,\n ])\n}\n","import {isNetworkError} from '#/lib/strings/errors'\n\nexport async function retry(\n retries: number,\n cond: (err: any) => boolean,\n fn: () => Promise
,\n): Promise
{\n let lastErr\n while (retries > 0) {\n try {\n return await fn()\n } catch (e: any) {\n lastErr = e\n if (cond(e)) {\n retries--\n continue\n }\n throw e\n }\n }\n throw lastErr\n}\n\nexport async function networkRetry
(\n retries: number,\n fn: () => Promise
,\n): Promise
{\n return retry(retries, isNetworkError, fn)\n}\n","import {I18n} from '@lingui/core'\n\nexport function niceDate(i18n: I18n, date: number | string | Date) {\n const d = new Date(date)\n\n return i18n.date(d, {\n dateStyle: 'long',\n timeStyle: 'short',\n })\n}\n\nexport function getAge(birthDate: Date): number {\n var today = new Date()\n var age = today.getFullYear() - birthDate.getFullYear()\n var m = today.getMonth() - birthDate.getMonth()\n if (m < 0 || (m === 0 && today.getDate() < birthDate.getDate())) {\n age--\n }\n return age\n}\n\n/**\n * Compares two dates by year, month, and day only\n */\nexport function simpleAreDatesEqual(a: Date, b: Date): boolean {\n return (\n a.getFullYear() === b.getFullYear() &&\n a.getMonth() === b.getMonth() &&\n a.getDate() === b.getDate()\n )\n}\n","import {simpleAreDatesEqual} from '#/lib/strings/time'\nimport {logger} from '#/logger'\nimport * as persisted from '#/state/persisted'\nimport {SessionAccount} from '../session'\nimport {isOnboardingActive} from './onboarding'\n\nexport function shouldRequestEmailConfirmation(account: SessionAccount) {\n // ignore logged out\n if (!account) return false\n // ignore confirmed accounts, this is the success state of this reminder\n if (account.emailConfirmed) return false\n // wait for onboarding to complete\n if (isOnboardingActive()) return false\n\n const snoozedAt = persisted.get('reminders').lastEmailConfirm\n const today = new Date()\n\n logger.debug('Checking email confirmation reminder', {\n today,\n snoozedAt,\n })\n\n // never been snoozed, new account\n if (!snoozedAt) {\n return true\n }\n\n // already snoozed today\n if (simpleAreDatesEqual(new Date(Date.parse(snoozedAt)), new Date())) {\n return false\n }\n\n return true\n}\n\nexport function snoozeEmailConfirmationPrompt() {\n const lastEmailConfirm = new Date().toISOString()\n logger.debug('Snoozing email confirmation reminder', {\n snoozedAt: lastEmailConfirm,\n })\n persisted.write('reminders', {\n ...persisted.get('reminders'),\n lastEmailConfirm,\n })\n}\n","import {AtpSessionData, AtpSessionEvent} from '@atproto/api'\nimport {sha256} from 'js-sha256'\nimport {Statsig} from 'statsig-react-native-expo'\n\nimport {IS_INTERNAL} from '#/lib/app-info'\nimport {Schema} from '../persisted'\nimport {Action, State} from './reducer'\nimport {SessionAccount} from './types'\n\ntype Reducer = (state: State, action: Action) => State\n\ntype Log =\n | {\n type: 'reducer:init'\n state: State\n }\n | {\n type: 'reducer:call'\n action: Action\n prevState: State\n nextState: State\n }\n | {\n type: 'method:start'\n method:\n | 'createAccount'\n | 'login'\n | 'logout'\n | 'resumeSession'\n | 'removeAccount'\n account?: SessionAccount\n }\n | {\n type: 'method:end'\n method:\n | 'createAccount'\n | 'login'\n | 'logout'\n | 'resumeSession'\n | 'removeAccount'\n account?: SessionAccount\n }\n | {\n type: 'persisted:broadcast'\n data: Schema['session']\n }\n | {\n type: 'persisted:receive'\n data: Schema['session']\n }\n | {\n type: 'agent:switch'\n prevAgent: object\n nextAgent: object\n }\n | {\n type: 'agent:patch'\n agent: object\n prevSession: AtpSessionData | undefined\n nextSession: AtpSessionData | undefined\n }\n\nexport function wrapSessionReducerForLogging(reducer: Reducer): Reducer {\n return function loggingWrapper(prevState: State, action: Action): State {\n const nextState = reducer(prevState, action)\n addSessionDebugLog({type: 'reducer:call', prevState, action, nextState})\n return nextState\n }\n}\n\nlet nextMessageIndex = 0\nconst MAX_SLICE_LENGTH = 1000\n\n// Not gated.\nexport function addSessionErrorLog(did: string, event: AtpSessionEvent) {\n try {\n if (!Statsig.initializeCalled() || !Statsig.getStableID()) {\n return\n }\n const stack = (new Error().stack ?? '').slice(0, MAX_SLICE_LENGTH)\n Statsig.logEvent('session:error', null, {\n did,\n event,\n stack,\n })\n } catch (e) {\n console.error(e)\n }\n}\n\nexport function addSessionDebugLog(log: Log) {\n try {\n if (!Statsig.initializeCalled() || !Statsig.getStableID()) {\n // Drop these logs for now.\n return\n }\n // DISABLING THIS GATE DUE TO EME @TODO EME-GATE\n if (!IS_INTERNAL) {\n return\n }\n // if (!Statsig.checkGate('debug_session')) {\n // return\n // }\n const messageIndex = nextMessageIndex++\n const {type, ...content} = log\n let payload = JSON.stringify(content, replacer)\n\n let nextSliceIndex = 0\n while (payload.length > 0) {\n const sliceIndex = nextSliceIndex++\n const slice = payload.slice(0, MAX_SLICE_LENGTH)\n payload = payload.slice(MAX_SLICE_LENGTH)\n Statsig.logEvent('session:debug', null, {\n realmId,\n messageIndex: String(messageIndex),\n messageType: type,\n sliceIndex: String(sliceIndex),\n slice,\n })\n }\n } catch (e) {\n console.error(e)\n }\n}\n\nlet agentIds = new WeakMap