Something went wrong. Try again.
Bluesky app fork with some witchin' additions 💫 forked from jollywhoppers.com/witchsky.app
Something went wrong. Try again.
52 kB · 1523 lines
TSX
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524import { type ComponentProps, type ComponentType, type JSX, useCallback, useRef, useState,} from 'react'import * as Linking from 'expo-linking'import * as Notifications from 'expo-notifications'import {i18n, type MessageDescriptor} from '@lingui/core'import {msg} from '@lingui/core/macro'import { type BottomTabBarProps, createBottomTabNavigator,} from '@react-navigation/bottom-tabs'import { CommonActions, createNavigationContainerRef, DarkTheme, DefaultTheme, type LinkingOptions, NavigationContainer, StackActions,} from '@react-navigation/native'
import {timeout} from '#/lib/async/timeout'import {useAccountSwitcher} from '#/lib/hooks/useAccountSwitcher'import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle'import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'import { type ChatNotificationPayload, getNotificationPayload, isChatNotificationPayload, notificationToURL, storePayloadForAccountSwitch,} from '#/lib/hooks/useNotificationHandler'import {useTitleUnreadCountLabel} from '#/lib/hooks/useTitleUnreadCountLabel'import {useWebScrollRestoration} from '#/lib/hooks/useWebScrollRestoration'import {useCallOnce} from '#/lib/once'import {buildStateObject, getCurrentRoute} from '#/lib/routes/helpers'import { type AllNavigatorParams, type BottomTabNavigatorParams, type FlatNavigatorParams, type HomeTabNavigatorParams, type MessagesTabNavigatorParams, type MyProfileTabNavigatorParams, type NotificationsTabNavigatorParams, type RouteParams, type SearchTabNavigatorParams, type State,} from '#/lib/routes/types'import {bskyTitle} from '#/lib/strings/headings'import {CHAT_INVITE_CODE_REGEX} from '#/lib/strings/url-helpers'import {useDisableVerifyEmailReminder} from '#/state/preferences/disable-verify-email-reminder'import {useSession} from '#/state/session'import {useLoggedOutViewControls} from '#/state/shell/logged-out'import { shouldRequestEmailConfirmation, snoozeEmailConfirmationPrompt,} from '#/state/shell/reminders'import {useCloseAllActiveElements} from '#/state/util'import {CommunityGuidelinesScreen} from '#/view/screens/CommunityGuidelines'import {CopyrightPolicyScreen} from '#/view/screens/CopyrightPolicy'import {DebugModScreen} from '#/view/screens/DebugMod'import {FeedsScreen} from '#/view/screens/Feeds'import {HomeScreen} from '#/view/screens/Home'import {ListsScreen} from '#/view/screens/Lists'import {ModerationBlockedAccounts} from '#/view/screens/ModerationBlockedAccounts'import {ModerationModlistsScreen} from '#/view/screens/ModerationModlists'import {ModerationMutedAccounts} from '#/view/screens/ModerationMutedAccounts'import {NotFoundScreen} from '#/view/screens/NotFound'import {NotificationsScreen} from '#/view/screens/Notifications'import {PostThreadScreen} from '#/view/screens/PostThread'import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy'import {ProfileScreen} from '#/view/screens/Profile'import {StorybookScreen} from '#/view/screens/Storybook'import {SupportScreen} from '#/view/screens/Support'import {TermsOfServiceScreen} from '#/view/screens/TermsOfService'import {BottomBar} from '#/view/shell/bottom-bar/BottomBar'import {createNativeStackNavigatorWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth'import {CustomFeedScreen} from '#/screens/CustomFeed'import {CustomFeedLikedByScreen} from '#/screens/CustomFeed/CustomFeedLikedBy'import HashtagScreen from '#/screens/Hashtag'import {AuthCallback} from '#/screens/Login/AuthCallback'import {MessagesScreen} from '#/screens/Messages/ChatList'import {renderMessagesSplitViewLayout} from '#/screens/Messages/components/splitView/MessagesSplitViewLayout'import {MessagesConversationScreen} from '#/screens/Messages/Conversation'import {MessagesConversationSettingsScreen} from '#/screens/Messages/ConversationSettings'import {MessagesInboxScreen} from '#/screens/Messages/Inbox'import {MessagesJoinRequestsScreen} from '#/screens/Messages/JoinRequests'import {MessagesSettingsScreen} from '#/screens/Messages/Settings'import {ModerationScreen} from '#/screens/Moderation'import {Screen as ModerationVerificationSettings} from '#/screens/Moderation/VerificationSettings'import {ModerationInboxScreen} from '#/screens/ModerationInbox'import {ModerationInboxReportDetailsScreen} from '#/screens/ModerationInbox/Report'import {ModerationInboxSettingsScreen} from '#/screens/ModerationInbox/Settings'import {ModerationInboxSubjectDetailsScreen} from '#/screens/ModerationInbox/Subject'import {Screen as ModerationInteractionSettings} from '#/screens/ModerationInteractionSettings'import {NotificationsActivityListScreen} from '#/screens/Notifications/ActivityList'import {PostLikedByScreen} from '#/screens/Post/PostLikedBy'import {PostQuotesScreen} from '#/screens/Post/PostQuotes'import {PostRepostedByScreen} from '#/screens/Post/PostRepostedBy'import {PostQuoteChainScreen} from '#/screens/Post/QuoteChain'import {ProfileKnownFollowersScreen} from '#/screens/Profile/KnownFollowers'import {ProfileFollowersScreen} from '#/screens/Profile/ProfileFollowers'import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows'import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'import {ProfileSearchScreen} from '#/screens/Profile/ProfileSearch'import {ProfileListScreen} from '#/screens/ProfileList'import {SavedFeeds} from '#/screens/SavedFeeds'import {SearchScreen} from '#/screens/Search'import TopicScreen from '#/screens/Topic'import {type Theme, useTheme} from '#/alf'import { EmailDialogScreenID, useEmailDialogControl,} from '#/components/dialogs/EmailDialog'import {EphemeralLoginHost} from '#/components/EphemeralLoginHost'import {useAnalytics} from '#/analytics'import {setNavigationMetadata} from '#/analytics/metadata'import {IS_LIQUID_GLASS, IS_NATIVE, IS_WEB} from '#/env'import {router} from '#/routes'import {Referrer} from '../modules/expo-bluesky-swiss-army'
function deferredNamedScreen< Name extends string, // Screen route props are intentionally heterogeneous. // eslint-disable-next-line @typescript-eslint/no-explicit-any Module extends Record<Name, ComponentType<any>>,>(load: () => Module, exportName: Name) { type Screen = Module[Name] let Screen: Screen | undefined
return function DeferredScreen(props: ComponentProps<Screen>) { Screen ??= load()[exportName] return <Screen {...props} /> }}
const BookmarksScreen = deferredNamedScreen( () => require('#/screens/Bookmarks'), 'BookmarksScreen',)const SharedPreferencesTesterScreen = deferredNamedScreen( () => require('#/screens/E2E/SharedPreferencesTesterScreen'), 'SharedPreferencesTesterScreen',)const FindContactsFlowScreen = deferredNamedScreen( () => require('#/screens/FindContactsFlowScreen'), 'FindContactsFlowScreen',)const LogScreen = deferredNamedScreen( () => require('#/screens/Log'), 'LogScreen',)const AboutSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AboutSettings'), 'AboutSettingsScreen',)const AccessibilitySettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AccessibilitySettings'), 'AccessibilitySettingsScreen',)const AltTextAiSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AltTextAiSettings'), 'AltTextAiSettingsScreen',)const AccountSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AccountSettings'), 'AccountSettingsScreen',)const ActivityPrivacySettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/ActivityPrivacySettings'), 'ActivityPrivacySettingsScreen',)const ActivityNotificationSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/NotificationSettings/ActivityNotificationSettings'), 'ActivityNotificationSettingsScreen',)const AppearanceSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AppearanceSettings'), 'AppearanceSettingsScreen',)const AppearanceColorThemeSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AppearanceSettings/ColorThemeSettings'), 'AppearanceColorThemeSettingsScreen',)const ThemeGalleryScreen = deferredNamedScreen( () => require('#/screens/Settings/AppearanceSettings/ThemeGallery'), 'ThemeGalleryScreen',)const ThemeEditorScreen = deferredNamedScreen( () => require('#/screens/Settings/AppearanceSettings/ThemeEditor'), 'ThemeEditorScreen',)const ThemeScreen = deferredNamedScreen( () => require('#/screens/Theme'), 'ThemeScreen',)const AppIconSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AppIconSettings'), 'AppIconSettingsScreen',)const AppPasswordsScreen = deferredNamedScreen( () => require('#/screens/Settings/AppPasswords'), 'AppPasswordsScreen',)const AutomationLabelSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/AutomationLabelSettings'), 'AutomationLabelSettingsScreen',)const BetaFeaturesSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/BetaFeaturesSettings'), 'BetaFeaturesSettingsScreen',)const ContentAndMediaSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/ContentAndMediaSettings'), 'ContentAndMediaSettingsScreen',)const ExternalMediaPreferencesScreen = deferredNamedScreen( () => require('#/screens/Settings/ExternalMediaPreferences'), 'ExternalMediaPreferencesScreen',)const FindContactsSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/FindContactsSettings'), 'FindContactsSettingsScreen',)const FollowingFeedPreferencesScreen = deferredNamedScreen( () => require('#/screens/Settings/FollowingFeedPreferences'), 'FollowingFeedPreferencesScreen',)const InterestsSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/InterestsSettings'), 'InterestsSettingsScreen',)const LanguageSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/LanguageSettings'), 'LanguageSettingsScreen',)const LegacyNotificationSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/LegacyNotificationSettings'), 'LegacyNotificationSettingsScreen',)const NotificationSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/NotificationSettings'), 'NotificationSettingsScreen',)const PetLabelSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/PetLabelSettings'), 'PetLabelSettingsScreen',)const PrivacyAndSecuritySettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/PrivacyAndSecuritySettings'), 'PrivacyAndSecuritySettingsScreen',)const RunesSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings'), 'RunesSettingsScreen',)const RunesDisplayAlsoLikedSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/AlsoLikedSettings'), 'RunesDisplayAlsoLikedSettingsScreen',)const RunesBadgesSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/BadgesSettings'), 'RunesBadgesSettingsScreen',)const RunesDisplaySettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/DisplaySettings'), 'RunesDisplaySettingsScreen',)const RunesExtraSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/ExtraSettings'), 'RunesExtraSettingsScreen',)const RunesExtraFeatureGatesSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/FeatureGatesSettings'), 'RunesExtraFeatureGatesSettingsScreen',)const RunesUsabilityFeedSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/FeedSettings'), 'RunesUsabilityFeedSettingsScreen',)const RunesImpressionsSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/ImpressionsSettings'), 'RunesImpressionsSettingsScreen',)const RunesInfrastructureSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/InfrastructureSettings'), 'RunesInfrastructureSettingsScreen',)const RunesMenusSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/MenusSettings'), 'RunesMenusSettingsScreen',)const RunesUsabilityProfileSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/ProfileSettings'), 'RunesUsabilityProfileSettingsScreen',)const RunesSettingsSyncSettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/SettingsSyncSettings'), 'RunesSettingsSyncSettingsScreen',)const RunesUsabilitySettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/RunesSettings/UsabilitySettings'), 'RunesUsabilitySettingsScreen',)const SettingsScreen = deferredNamedScreen( () => require('#/screens/Settings/Settings'), 'SettingsScreen',)const ThreadPreferencesScreen = deferredNamedScreen( () => require('#/screens/Settings/ThreadPreferences'), 'ThreadPreferencesScreen',)const StarterPackScreen = deferredNamedScreen( () => require('#/screens/StarterPack/StarterPackScreen'), 'StarterPackScreen',)const StarterPackScreenShort = deferredNamedScreen( () => require('#/screens/StarterPack/StarterPackScreen'), 'StarterPackScreenShort',)const Wizard = deferredNamedScreen( () => require('#/screens/StarterPack/Wizard'), 'Wizard',)const VideoFeed = deferredNamedScreen( () => require('#/screens/VideoFeed'), 'VideoFeed',)const InviteScannerScreen = deferredNamedScreen( () => require('#/features/inviteFriends'), 'InviteScannerScreen',)
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
/** * Stores the navigation state before account switch on native. * This allows the user to stay on the same page when switching accounts. */let storedNavigationStateForAccountSwitch: State | undefined
export function storeNavigationStateForAccountSwitch() { if (IS_NATIVE && navigationRef.isReady()) { storedNavigationStateForAccountSwitch = navigationRef.getRootState() }}
function consumeStoredNavigationState(): State | undefined { const state = storedNavigationStateForAccountSwitch storedNavigationStateForAccountSwitch = undefined return state}
const HomeTab = createNativeStackNavigatorWithAuth<HomeTabNavigatorParams>()const SearchTab = createNativeStackNavigatorWithAuth<SearchTabNavigatorParams>()const NotificationsTab = createNativeStackNavigatorWithAuth<NotificationsTabNavigatorParams>()const MyProfileTab = createNativeStackNavigatorWithAuth<MyProfileTabNavigatorParams>()const MessagesTab = createNativeStackNavigatorWithAuth<MessagesTabNavigatorParams>()const Flat = createNativeStackNavigatorWithAuth<FlatNavigatorParams>()const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
/** * These "common screens" are reused across stacks. */function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), unreadCountLabel)
return ( <> <Stack.Screen name="NotFound" getComponent={() => NotFoundScreen} options={{title: title(msg`Not Found`)}} /> <Stack.Screen name="AuthCallback" getComponent={() => AuthCallback} options={{title: title(msg`Signing in...`)}} /> <Stack.Screen name="Lists" component={ListsScreen} options={{title: title(msg`Lists`), requireAuth: true}} /> <Stack.Screen name="Moderation" getComponent={() => ModerationScreen} options={{title: title(msg`Moderation`), requireAuth: true}} /> <Stack.Screen name="ModerationInbox" getComponent={() => ModerationInboxScreen} options={{title: title(msg`Moderation inbox`), requireAuth: true}} /> <Stack.Screen name="ModerationInboxSettings" getComponent={() => ModerationInboxSettingsScreen} options={{title: title(msg`Mod inbox settings`), requireAuth: true}} /> <Stack.Screen name="ModerationInboxReportDetails" getComponent={() => ModerationInboxReportDetailsScreen} options={{title: title(msg`Your report`), requireAuth: true}} /> <Stack.Screen name="ModerationInboxSubjectDetails" getComponent={() => ModerationInboxSubjectDetailsScreen} options={{title: title(msg`Notice`), requireAuth: true}} /> <Stack.Screen name="ModerationModlists" getComponent={() => ModerationModlistsScreen} options={{title: title(msg`Moderation Lists`), requireAuth: true}} /> <Stack.Screen name="ModerationMutedAccounts" getComponent={() => ModerationMutedAccounts} options={{title: title(msg`Muted Accounts`), requireAuth: true}} /> <Stack.Screen name="ModerationBlockedAccounts" getComponent={() => ModerationBlockedAccounts} options={{title: title(msg`Blocked Accounts`), requireAuth: true}} /> <Stack.Screen name="ModerationInteractionSettings" getComponent={() => ModerationInteractionSettings} options={{ title: title(msg`Post Interaction Settings`), requireAuth: true, }} /> <Stack.Screen name="ModerationVerificationSettings" getComponent={() => ModerationVerificationSettings} options={{ title: title(msg`Verification Settings`), requireAuth: true, }} /> <Stack.Screen name="Settings" getComponent={() => SettingsScreen} options={{title: title(msg`Settings`), requireAuth: true}} /> <Stack.Screen name="LanguageSettings" getComponent={() => LanguageSettingsScreen} options={{title: title(msg`Language Settings`), requireAuth: true}} /> <Stack.Screen name="Profile" getComponent={() => ProfileScreen} options={({route}) => ({ title: bskyTitle(`@${route.params.name}`, unreadCountLabel), })} /> <Stack.Screen name="ProfileFollowers" getComponent={() => ProfileFollowersScreen} options={({route}) => ({ title: title(msg`People following @${route.params.name}`), })} /> <Stack.Screen name="ProfileFollows" getComponent={() => ProfileFollowsScreen} options={({route}) => ({ title: title(msg`People followed by @${route.params.name}`), })} /> <Stack.Screen name="ProfileKnownFollowers" getComponent={() => ProfileKnownFollowersScreen} options={({route}) => ({ title: title(msg`Followers of @${route.params.name} that you know`), })} /> <Stack.Screen name="ProfileList" getComponent={() => ProfileListScreen} options={{title: title(msg`List`), requireAuth: true}} /> <Stack.Screen name="ProfileSearch" getComponent={() => ProfileSearchScreen} options={({route}) => ({ title: title(msg`Search @${route.params.name}'s posts`), })} /> <Stack.Screen name="PostThread" getComponent={() => PostThreadScreen} options={({route}) => ({ title: title(msg`Post by @${route.params.name}`), })} /> <Stack.Screen name="PostLikedBy" getComponent={() => PostLikedByScreen} options={({route}) => ({ title: title(msg`Post by @${route.params.name}`), })} /> <Stack.Screen name="PostRepostedBy" getComponent={() => PostRepostedByScreen} options={({route}) => ({ title: title(msg`Post by @${route.params.name}`), })} /> <Stack.Screen name="PostQuoteChain" getComponent={() => PostQuoteChainScreen} options={{title: title(msg`Quote chain`)}} /> <Stack.Screen name="PostQuotes" getComponent={() => PostQuotesScreen} options={({route}) => ({ title: title(msg`Post by @${route.params.name}`), })} /> <Stack.Screen name="CustomFeed" getComponent={() => CustomFeedScreen} options={{title: title(msg`Feed`)}} /> <Stack.Screen name="CustomFeedLikedBy" getComponent={() => CustomFeedLikedByScreen} options={{title: title(msg`Liked by`)}} /> <Stack.Screen name="ProfileLabelerLikedBy" getComponent={() => ProfileLabelerLikedByScreen} options={{title: title(msg`Liked by`)}} /> <Stack.Screen name="Debug" getComponent={() => StorybookScreen} options={{title: title(msg`Storybook`), requireAuth: true}} /> <Stack.Screen name="DebugMod" getComponent={() => DebugModScreen} options={{title: title(msg`Moderation states`), requireAuth: true}} /> <Stack.Screen name="InviteScanner" getComponent={() => InviteScannerScreen} options={{title: title(msg`Scan QR code`), requireAuth: true}} /> <Stack.Screen name="SharedPreferencesTester" getComponent={() => SharedPreferencesTesterScreen} options={{title: title(msg`Shared Preferences Tester`)}} /> <Stack.Screen name="Log" getComponent={() => LogScreen} options={{title: title(msg`Log`), requireAuth: true}} /> <Stack.Screen name="Support" getComponent={() => SupportScreen} options={{title: title(msg`Support`)}} /> <Stack.Screen name="PrivacyPolicy" getComponent={() => PrivacyPolicyScreen} options={{title: title(msg`Privacy Policy`)}} /> <Stack.Screen name="TermsOfService" getComponent={() => TermsOfServiceScreen} options={{title: title(msg`Terms of Service`)}} /> <Stack.Screen name="CommunityGuidelines" getComponent={() => CommunityGuidelinesScreen} options={{title: title(msg`Community Guidelines`)}} /> <Stack.Screen name="CopyrightPolicy" getComponent={() => CopyrightPolicyScreen} options={{title: title(msg`Copyright Policy`)}} /> <Stack.Screen name="AppPasswords" getComponent={() => AppPasswordsScreen} options={{title: title(msg`App Passwords`), requireAuth: true}} /> <Stack.Screen name="SavedFeeds" getComponent={() => SavedFeeds} options={{title: title(msg`Edit My Feeds`), requireAuth: true}} /> <Stack.Screen name="PreferencesFollowingFeed" getComponent={() => FollowingFeedPreferencesScreen} options={{ title: title(msg`Following Feed Preferences`), requireAuth: true, }} /> <Stack.Screen name="PreferencesThreads" getComponent={() => ThreadPreferencesScreen} options={{title: title(msg`Threads Preferences`), requireAuth: true}} /> <Stack.Screen name="PreferencesExternalEmbeds" getComponent={() => ExternalMediaPreferencesScreen} options={{ title: title(msg`External Media Preferences`), requireAuth: true, }} /> <Stack.Screen name="AccessibilitySettings" getComponent={() => AccessibilitySettingsScreen} options={{ title: title(msg`Accessibility Settings`), requireAuth: true, }} /> <Stack.Screen name="AltTextAiSettings" getComponent={() => AltTextAiSettingsScreen} options={{ title: title(msg`Automatic alt text generation`), requireAuth: true, }} /> <Stack.Screen name="RunesSettings" getComponent={() => RunesSettingsScreen} options={{ title: title(msg`Runes`), requireAuth: true, }} /> <Stack.Screen name="RunesMenusSettings" getComponent={() => RunesMenusSettingsScreen} options={{title: title(msg`Menus`), requireAuth: true}} /> <Stack.Screen name="RunesBadgesSettings" getComponent={() => RunesBadgesSettingsScreen} options={{title: title(msg`Badges`), requireAuth: true}} /> <Stack.Screen name="RunesImpressionsSettings" getComponent={() => RunesImpressionsSettingsScreen} options={{title: title(msg`Impressions`), requireAuth: true}} /> <Stack.Screen name="RunesUsabilitySettings" getComponent={() => RunesUsabilitySettingsScreen} options={{title: title(msg`Usability`), requireAuth: true}} /> <Stack.Screen name="RunesUsabilityFeedSettings" getComponent={() => RunesUsabilityFeedSettingsScreen} options={{title: title(msg`Feeds`), requireAuth: true}} /> <Stack.Screen name="RunesUsabilityProfileSettings" getComponent={() => RunesUsabilityProfileSettingsScreen} options={{title: title(msg`Profiles`), requireAuth: true}} /> <Stack.Screen name="RunesDisplaySettings" getComponent={() => RunesDisplaySettingsScreen} options={{title: title(msg`Display`), requireAuth: true}} /> <Stack.Screen name="RunesDisplayAlsoLikedSettings" getComponent={() => RunesDisplayAlsoLikedSettingsScreen} options={{title: title(msg`Also liked`), requireAuth: true}} /> <Stack.Screen name="RunesInfrastructureSettings" getComponent={() => RunesInfrastructureSettingsScreen} options={{title: title(msg`Infrastructure`)}} /> <Stack.Screen name="RunesExtraSettings" getComponent={() => RunesExtraSettingsScreen} options={{title: title(msg`Extra settings`), requireAuth: true}} /> <Stack.Screen name="RunesExtraFeatureGatesSettings" getComponent={() => RunesExtraFeatureGatesSettingsScreen} options={{title: title(msg`Feature gates`), requireAuth: true}} /> <Stack.Screen name="RunesSettingsSyncSettings" getComponent={() => RunesSettingsSyncSettingsScreen} options={{title: title(msg`Settings sync`), requireAuth: true}} /> <Stack.Screen name="AppearanceSettings" getComponent={() => AppearanceSettingsScreen} options={{ title: title(msg`Appearance`), requireAuth: true, }} /> <Stack.Screen name="AppearanceColorThemeSettings" getComponent={() => AppearanceColorThemeSettingsScreen} options={{ title: title(msg`Color Theme`), requireAuth: true, }} /> <Stack.Screen name="ThemeGallery" getComponent={() => ThemeGalleryScreen} options={{title: title(msg`Themes`), requireAuth: true}} /> <Stack.Screen name="ThemeEditor" getComponent={() => ThemeEditorScreen} options={{title: title(msg`Theme editor`), requireAuth: true}} /> <Stack.Screen name="Theme" getComponent={() => ThemeScreen} options={{title: title(msg`Theme`)}} /> <Stack.Screen name="AccountSettings" getComponent={() => AccountSettingsScreen} options={{ title: title(msg`Account`), requireAuth: true, }} /> <Stack.Screen name="BetaFeaturesSettings" getComponent={() => BetaFeaturesSettingsScreen} options={{ title: title(msg`Beta features`), requireAuth: true, }} /> <Stack.Screen name="AutomationLabelSettings" getComponent={() => AutomationLabelSettingsScreen} options={{ title: title(msg`Automation Label`), requireAuth: true, }} /> <Stack.Screen name="PetLabelSettings" getComponent={() => PetLabelSettingsScreen} options={{ title: title(msg`Pet Label`), requireAuth: true, }} /> <Stack.Screen name="PrivacyAndSecuritySettings" getComponent={() => PrivacyAndSecuritySettingsScreen} options={{ title: title(msg`Privacy and Security`), requireAuth: true, }} /> <Stack.Screen name="ActivityPrivacySettings" getComponent={() => ActivityPrivacySettingsScreen} options={{ title: title(msg`Privacy and Security`), requireAuth: true, }} /> <Stack.Screen name="FindContactsSettings" getComponent={() => FindContactsSettingsScreen} options={{ title: title(msg`Find Contacts`), requireAuth: true, }} /> <Stack.Screen name="NotificationSettings" getComponent={() => NotificationSettingsScreen} options={{title: title(msg`Notification settings`), requireAuth: true}} /> <Stack.Screen name="ActivityNotificationSettings" getComponent={() => ActivityNotificationSettingsScreen} options={{ title: title(msg`Activity notifications`), requireAuth: true, }} /> <Stack.Screen name="ContentAndMediaSettings" getComponent={() => ContentAndMediaSettingsScreen} options={{ title: title(msg`Content and Media`), requireAuth: true, }} /> <Stack.Screen name="InterestsSettings" getComponent={() => InterestsSettingsScreen} options={{ title: title(msg`Your interests`), requireAuth: true, }} /> <Stack.Screen name="AboutSettings" getComponent={() => AboutSettingsScreen} options={{ title: title(msg`About`), requireAuth: true, }} /> <Stack.Screen name="AppIconSettings" getComponent={() => AppIconSettingsScreen} options={{ title: title(msg`App Icon`), requireAuth: true, }} /> <Stack.Screen name="Hashtag" getComponent={() => HashtagScreen} options={{title: title(msg`Hashtag`)}} /> <Stack.Screen name="Topic" getComponent={() => TopicScreen} options={{title: title(msg`Topic`)}} /> <Stack.Group screenLayout={renderMessagesSplitViewLayout}> <Stack.Screen name="MessagesConversation" getComponent={() => MessagesConversationScreen} options={{title: title(msg`Chat`), requireAuth: true}} /> <Stack.Screen name="MessagesConversationSettings" getComponent={() => MessagesConversationSettingsScreen} options={{title: title(msg`Group chat settings`), requireAuth: true}} /> <Stack.Screen name="MessagesJoinRequests" getComponent={() => MessagesJoinRequestsScreen} options={{title: title(msg`Requests to join`), requireAuth: true}} /> <Stack.Screen name="MessagesSettings" getComponent={() => MessagesSettingsScreen} options={{title: title(msg`Chat settings`), requireAuth: true}} /> <Stack.Screen name="MessagesInbox" getComponent={() => MessagesInboxScreen} options={{title: title(msg`Chat request inbox`), requireAuth: true}} /> </Stack.Group> <Stack.Screen name="NotificationsActivityList" getComponent={() => NotificationsActivityListScreen} options={{title: title(msg`Notifications`), requireAuth: true}} /> <Stack.Screen name="LegacyNotificationSettings" getComponent={() => LegacyNotificationSettingsScreen} options={{title: title(msg`Notification settings`), requireAuth: true}} /> <Stack.Screen name="Feeds" getComponent={() => FeedsScreen} options={{title: title(msg`Feeds`)}} /> <Stack.Screen name="StarterPack" getComponent={() => StarterPackScreen} options={{title: title(msg`Starter Pack`)}} /> <Stack.Screen name="StarterPackShort" getComponent={() => StarterPackScreenShort} options={{title: title(msg`Starter Pack`)}} /> <Stack.Screen name="StarterPackWizard" getComponent={() => Wizard} options={{title: title(msg`Create a Starter Pack`), requireAuth: true}} /> <Stack.Screen name="StarterPackEdit" getComponent={() => Wizard} options={{title: title(msg`Edit your Starter Pack`), requireAuth: true}} /> <Stack.Screen name="VideoFeed" getComponent={() => VideoFeed} options={{ title: title(msg`Video Feed`), requireAuth: true, }} /> <Stack.Screen name="Bookmarks" getComponent={() => BookmarksScreen} options={{ title: title(msg`Saved Posts`), requireAuth: true, }} /> <Stack.Screen name="FindContactsFlow" getComponent={() => FindContactsFlowScreen} options={{ title: title(msg`Find Contacts`), requireAuth: true, gestureEnabled: false, }} /> </> )}
/** * The TabsNavigator is used by native mobile to represent the routes * in 3 distinct tab-stacks with a different root screen on each. */function TabsNavigator({ layout,}: { layout: React.ComponentProps<typeof Tab.Navigator>['layout']}) { const tabBar = useCallback( (props: JSX.IntrinsicAttributes & BottomTabBarProps) => ( <BottomBar {...props} /> ), [], )
return ( <Tab.Navigator initialRouteName="HomeTab" backBehavior="initialRoute" screenOptions={{headerShown: false, lazy: true}} tabBar={tabBar} layout={layout}> <Tab.Screen name="HomeTab" getComponent={() => HomeTabNavigator} /> <Tab.Screen name="SearchTab" getComponent={() => SearchTabNavigator} /> <Tab.Screen name="MessagesTab" getComponent={() => MessagesTabNavigator} /> <Tab.Screen name="NotificationsTab" getComponent={() => NotificationsTabNavigator} /> <Tab.Screen name="MyProfileTab" getComponent={() => MyProfileTabNavigator} /> </Tab.Navigator> )}
function screenOptions(t: Theme) { return { fullScreenGestureEnabled: true, headerShown: false, contentStyle: t.atoms.bg, } as const}
function HomeTabNavigator() { const t = useTheme()
const BLURRED_SCROLL_EDGE_EFFECT = IS_LIQUID_GLASS ? ({ headerShown: true, headerTransparent: true, headerTitle: '', headerBackVisible: false, scrollEdgeEffects: { top: 'soft', }, } as const) : {}
return ( <HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home"> <HomeTab.Screen name="Home" getComponent={() => HomeScreen} options={BLURRED_SCROLL_EDGE_EFFECT} /> <HomeTab.Screen name="Start" getComponent={() => HomeScreen} options={BLURRED_SCROLL_EDGE_EFFECT} /> {commonScreens(HomeTab as typeof Flat)} </HomeTab.Navigator> )}
function SearchTabNavigator() { const t = useTheme() return ( <SearchTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Search"> <SearchTab.Screen name="Search" getComponent={() => SearchScreen} /> {commonScreens(SearchTab as typeof Flat)} </SearchTab.Navigator> )}
function NotificationsTabNavigator() { const t = useTheme() return ( <NotificationsTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Notifications"> <NotificationsTab.Screen name="Notifications" getComponent={() => NotificationsScreen} options={{requireAuth: true}} /> {commonScreens(NotificationsTab as typeof Flat)} </NotificationsTab.Navigator> )}
function MyProfileTabNavigator() { const t = useTheme() return ( <MyProfileTab.Navigator screenOptions={screenOptions(t)} initialRouteName="MyProfile"> <MyProfileTab.Screen // MyProfile is not in AllNavigationParams - asserting as Profile at least // gives us typechecking for initialParams -sfn name={'MyProfile' as 'Profile'} getComponent={() => ProfileScreen} initialParams={{name: 'me', hideBackButton: true}} /> {commonScreens(MyProfileTab as unknown as typeof Flat)} </MyProfileTab.Navigator> )}
function MessagesTabNavigator() { const t = useTheme() return ( <MessagesTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Messages"> <MessagesTab.Screen name="Messages" getComponent={() => MessagesScreen} options={({route}) => ({ requireAuth: true, animationTypeForReplace: route.params?.animation ?? 'push', })} /> {commonScreens(MessagesTab as typeof Flat)} </MessagesTab.Navigator> )}
/** * The FlatNavigator is used by Web to represent the routes * in a single ("flat") stack. */const FlatNavigator = ({ layout,}: { layout: React.ComponentProps<typeof Flat.Navigator>['layout']}) => { const t = useTheme() const unreadCountLabel = useTitleUnreadCountLabel() const screenListeners = useWebScrollRestoration() const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), unreadCountLabel)
return ( <Flat.Navigator layout={layout} screenListeners={screenListeners} screenOptions={screenOptions(t)}> <Flat.Screen name="Home" getComponent={() => HomeScreen} options={{title: title(msg`Home`)}} /> <Flat.Screen name="Search" getComponent={() => SearchScreen} options={{title: title(msg`Explore`)}} /> <Flat.Screen name="Notifications" getComponent={() => NotificationsScreen} options={{title: title(msg`Notifications`), requireAuth: true}} /> <Flat.Screen name="Messages" getComponent={() => MessagesScreen} options={{title: title(msg`Messages`), requireAuth: true}} layout={renderMessagesSplitViewLayout} /> <Flat.Screen name="Start" getComponent={() => HomeScreen} options={{title: title(msg`Home`)}} /> {commonScreens(Flat, unreadCountLabel)} </Flat.Navigator> )}
/** * The RoutesContainer should wrap all components which need access * to the navigation context. */
const LINKING = { // TODO figure out what we are going to use // note: `bluesky://` and `witchsky://` and `deersocial://` is what is used in app.config.js prefixes: [ 'bsky://', 'bluesky://', 'witchsky://', 'deersocial://', 'https://bsky.app', 'https://witchsky.app', 'https://deer.social', 'https://mu.social', 'https://blacksky.community', 'https://northsky.app', ],
getPathFromState(state: State) { // find the current node in the navigation tree const node = getCurrentRoute(state)
// build the path const route = router.matchName(node.name) if (typeof route === 'undefined') { return '/' // default to home } return route.build((node.params || {}) as RouteParams) },
getStateFromPath(path: string) { const [name, params] = router.matchPath(path)
// Any time we receive a url that starts with `intent/` we want to ignore it here. It will be handled in the // intent handler hook. We should check for the trailing slash, because if there isn't one then it isn't a valid // intent // On web, there is no route state that's created by default, so we should initialize it as the home route. On // native, since the home tab and the home screen are defined as initial routes, we don't need to return a state // since it will be created by react-navigation. if (path.includes('intent/')) { if (IS_NATIVE) return return buildStateObject('Flat', 'Home', params) }
// Chat invite URLs (`/chat/:code`) are handled by `useIntentHandler`, which // opens the GroupChatJoinDialog (or the logged-out join flow). Route the // path to Home so the dialog overlays Home instead of NotFound. On native, // react-navigation strips the `bluesky://` prefix and passes the path // without a leading slash, so normalize before matching. const normalizedPath = path.startsWith('/') ? path : `/${path}` if (CHAT_INVITE_CODE_REGEX.test(normalizedPath.split('?')[0])) { if (IS_NATIVE) { return buildStateObject('HomeTab', 'Home', params) } return buildStateObject('Flat', 'Home', params) }
if (IS_NATIVE) { if (name === 'Search') { return buildStateObject('SearchTab', 'Search', params) } if (name === 'Notifications') { return buildStateObject('NotificationsTab', 'Notifications', params) } if (name === 'Home') { return buildStateObject('HomeTab', 'Home', params) } if (name === 'Messages') { return buildStateObject('MessagesTab', 'Messages', params) } // if the path is something else, like a post, profile, or even settings, we need to initialize the home tab as pre-existing state otherwise the back button will not work return buildStateObject('HomeTab', name, params, [ { name: 'Home', params: {}, }, ]) } else { const res = buildStateObject('Flat', name, params) return res } },} satisfies LinkingOptions<AllNavigatorParams>
let didHandlePushNotificationEntry = false
function RoutesContainer({children}: React.PropsWithChildren<{}>) { const ax = useAnalytics() // eslint-disable-next-line react-compiler/react-compiler const notyLogger = ax.logger.useChild(ax.logger.Context.Notifications) const theme = useColorSchemeStyle(DefaultTheme, DarkTheme) const {currentAccount, accounts} = useSession() const {onPressSwitchAccount} = useAccountSwitcher() const {setShowLoggedOut} = useLoggedOutViewControls() const previousScreen = useRef<string | undefined>(undefined) const emailDialogControl = useEmailDialogControl() const closeAllActiveElements = useCloseAllActiveElements() const linkingUrl = Linking.useLinkingURL()
const disableVerifyEmailReminder = useDisableVerifyEmailReminder()
// Consume stored navigation state exactly once for this mount. // Account switches trigger session updates before the keyed shell subtree // remounts, so consuming during render can clear the stored back stack too // early and leave React Navigation to reconstruct only the active route. const [initialState, setInitialState] = useState(() => IS_NATIVE ? consumeStoredNavigationState() : undefined, ) void setInitialState
/** * Handle navigation to the messages tab, or prepares for account switch. * * Non-reactive because we need the latest data from some hooks * after an async call - sfn */ const handleChatNotification = useNonReactiveCallback( (payload: ChatNotificationPayload) => { notyLogger.debug(`handleChatNotification`, {payload})
if (payload.recipientDid !== currentAccount?.did) { // handled in useNotificationHandler after account switch finishes storePayloadForAccountSwitch(payload) closeAllActiveElements()
const account = accounts.find(a => a.did === payload.recipientDid) if (account) { void onPressSwitchAccount(account, 'Notification') } else { setShowLoggedOut(true) } } else if ( payload.reason === 'chat-message' || payload.reason === 'chat-reaction' || payload.reason === 'chat-added-to-group' ) { // chat-added-to-group routes to the convo because the recipient was // just added and now has access. // @ts-expect-error nested navigators aren't typed -sfn void navigate('MessagesTab', { screen: 'Messages', params: { pushToConversation: payload.convoId, }, }) } else { // chat-removed-from-group, chat-join-request-rejected: the convo is // no longer accessible to the recipient, so just open the list. // @ts-expect-error nested navigators aren't typed -sfn void navigate('MessagesTab', {screen: 'Messages'}) } }, )
function handlePushNotificationEntry() { if (!IS_NATIVE) return
// Only consume a launching notification once per JS runtime. Account // switches remount the entire tree (see `key={currentAccount?.did}` in // `App.native.tsx`), which re-fires `onNavigationReady` and would // otherwise re-process whatever `getLastNotificationResponse` still has // cached natively (APP-2338). if (didHandlePushNotificationEntry) return didHandlePushNotificationEntry = true
// intent urls are handled by `useIntentHandler` if (linkingUrl) return
const notificationResponse = Notifications.getLastNotificationResponse()
if (notificationResponse) { notyLogger.debug(`handlePushNotificationEntry: response`, { response: notificationResponse, })
// Clear the last notification response to ensure it's not used again try { Notifications.clearLastNotificationResponse() } catch (error) { notyLogger.error( `handlePushNotificationEntry: error clearing notification response`, {error}, ) }
const payload = getNotificationPayload(notificationResponse.notification)
if (payload) { ax.metric('notifications:openApp', { reason: payload.reason, causedBoot: true, })
if (isChatNotificationPayload(payload)) { handleChatNotification(payload) } else { const path = notificationToURL(payload)
if (path === '/notifications') { resetToTab('NotificationsTab') notyLogger.debug(`handlePushNotificationEntry: default navigate`) } else if (path) { const [screen, params] = router.matchPath(path) // @ts-expect-error nested navigators aren't typed -sfn void navigate('HomeTab', {screen, params}) notyLogger.debug(`handlePushNotificationEntry: navigate`, { screen, params, }) } } } } }
const onNavigationReady = useCallOnce(() => { const currentScreen = getCurrentRouteName() setNavigationMetadata({ previousScreen: currentScreen, currentScreen, }) previousScreen.current = currentScreen
handlePushNotificationEntry()
ax.metric('router:navigate', {})
if ( currentAccount && shouldRequestEmailConfirmation(currentAccount) && !disableVerifyEmailReminder ) { emailDialogControl.open({ id: EmailDialogScreenID.VerificationReminder, }) snoozeEmailConfirmationPrompt() }
ax.metric('init', { initMs: Math.round( // @ts-expect-error Emitted by Metro in the bundle prelude performance.now() - global.__BUNDLE_START_TIME__, ), })
if (IS_WEB) { void Referrer.getReferrerInfo().then(referrerInfo => { if (referrerInfo && referrerInfo.hostname !== 'bsky.app') { ax.metric('deepLink:referrerReceived', { to: window.location.href, referrer: referrerInfo?.referrer, hostname: referrerInfo?.hostname, }) } }) }
// temp, just testing void ax.features.enabled(ax.features.AATest) })
return ( <NavigationContainer ref={navigationRef} linking={LINKING} theme={theme} initialState={initialState} onStateChange={() => { const currentScreen = getCurrentRouteName() // do this before metric setNavigationMetadata({ previousScreen: previousScreen.current, currentScreen, }) ax.metric('router:navigate', {from: previousScreen.current}) previousScreen.current = currentScreen }} onReady={onNavigationReady} // WARNING: Implicit navigation to nested navigators is depreciated in React Navigation 7.x // However, there's a fair amount of places we do that, especially in when popping to the top of stacks. // See BottomBar.tsx for an example of how to handle nested navigators in the tabs correctly. // I'm scared of missing a spot (esp. with push notifications etc) so let's enable this legacy behaviour for now. // We will need to confirm we handle nested navigators correctly by the time we migrate to React Navigation 8.x // -sfn navigationInChildEnabled> {children} {/* Login links need navigation context, including inside the modal. */} <EphemeralLoginHost /> </NavigationContainer> )}
function getCurrentRouteName() { if (navigationRef.isReady()) { return navigationRef.getCurrentRoute()?.name } else { return undefined }}
/** * These helpers can be used from outside of the RoutesContainer * (eg in the state models). */
function navigate<K extends keyof AllNavigatorParams>( name: K, params?: AllNavigatorParams[K],) { if (navigationRef.isReady()) { return Promise.race([ new Promise<void>(resolve => { const handler = () => { resolve() navigationRef.removeListener('state', handler) } navigationRef.addListener('state', handler)
// @ts-expect-error I don't know what would make typescript happy but I have a life -prf navigationRef.navigate(name, params) }), timeout(1e3), ]) } return Promise.resolve()}
/** Returns to the previous app screen when one is available. */export function goBack() { if (!navigationRef.isReady() || !navigationRef.canGoBack()) return false navigationRef.goBack() return true}
function resetToTab( tabName: 'HomeTab' | 'SearchTab' | 'MessagesTab' | 'NotificationsTab',) { if (navigationRef.isReady()) { void navigate(tabName) if (navigationRef.canGoBack()) { navigationRef.dispatch(StackActions.popToTop()) //we need to check .canGoBack() before calling it } }}
// returns a promise that resolves after the state reset is completefunction reset(): Promise<void> { if (navigationRef.isReady()) { navigationRef.dispatch( CommonActions.reset({ index: 0, routes: [{name: IS_NATIVE ? 'HomeTab' : 'Home'}], }), ) return Promise.race([ timeout(1e3), new Promise<void>(resolve => { const handler = () => { resolve() navigationRef.removeListener('state', handler) } navigationRef.addListener('state', handler) }), ]) } else { return Promise.resolve() }}
export { FlatNavigator, navigate, reset, resetToTab, RoutesContainer, TabsNavigator,}