use built-in documentTitle api

This commit is contained in:
Samuel Newman
2025-12-30 20:23:53 +02:00
parent 0aaf11aa7b
commit 9fdf987aa3
3 changed files with 128 additions and 98 deletions
+117 -95
View File
@@ -1,8 +1,9 @@
import {type JSX, useCallback, useRef} from 'react' import {type JSX, useCallback, useMemo, useRef} from 'react'
import {Linking} from 'react-native' import {Linking} from 'react-native'
import * as Notifications from 'expo-notifications' import * as Notifications from 'expo-notifications'
import {i18n, type MessageDescriptor} from '@lingui/core' import {type I18n} from '@lingui/core'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import { import {
type BottomTabBarProps, type BottomTabBarProps,
createBottomTabNavigator, createBottomTabNavigator,
@@ -42,10 +43,8 @@ import {
} from '#/lib/routes/types' } from '#/lib/routes/types'
import {type RouteParams, type State} from '#/lib/routes/types' import {type RouteParams, type State} from '#/lib/routes/types'
import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig' import {attachRouteToLogEvents, logEvent} from '#/lib/statsig/statsig'
import {bskyTitle} from '#/lib/strings/headings'
import {logger} from '#/logger' import {logger} from '#/logger'
import {isNative, isWeb} from '#/platform/detection' import {isNative, isWeb} from '#/platform/detection'
import {useUnreadNotifications} from '#/state/queries/notifications/unread'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useLoggedOutViewControls} from '#/state/shell/logged-out' import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import { import {
@@ -140,6 +139,8 @@ import {
} from '#/components/dialogs/EmailDialog' } from '#/components/dialogs/EmailDialog'
import {router} from '#/routes' import {router} from '#/routes'
import {Referrer} from '../modules/expo-bluesky-swiss-army' import {Referrer} from '../modules/expo-bluesky-swiss-army'
import {bskyTitle} from './lib/strings/headings'
import {useUnreadNotifications} from './state/queries/notifications/unread'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>() const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@@ -157,47 +158,44 @@ const Tab = createBottomTabNavigator<BottomTabNavigatorParams>()
/** /**
* These "common screens" are reused across stacks. * These "common screens" are reused across stacks.
*/ */
function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) { function commonScreens(Stack: typeof Flat, i18n: I18n) {
const title = (page: MessageDescriptor) =>
bskyTitle(i18n._(page), unreadCountLabel)
return ( return (
<> <>
<Stack.Screen <Stack.Screen
name="NotFound" name="NotFound"
getComponent={() => NotFoundScreen} getComponent={() => NotFoundScreen}
options={{title: title(msg`Not Found`)}} options={{title: i18n._(msg`Not Found`)}}
/> />
<Stack.Screen <Stack.Screen
name="Lists" name="Lists"
component={ListsScreen} component={ListsScreen}
options={{title: title(msg`Lists`), requireAuth: true}} options={{title: i18n._(msg`Lists`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Moderation" name="Moderation"
getComponent={() => ModerationScreen} getComponent={() => ModerationScreen}
options={{title: title(msg`Moderation`), requireAuth: true}} options={{title: i18n._(msg`Moderation`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationModlists" name="ModerationModlists"
getComponent={() => ModerationModlistsScreen} getComponent={() => ModerationModlistsScreen}
options={{title: title(msg`Moderation Lists`), requireAuth: true}} options={{title: i18n._(msg`Moderation Lists`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationMutedAccounts" name="ModerationMutedAccounts"
getComponent={() => ModerationMutedAccounts} getComponent={() => ModerationMutedAccounts}
options={{title: title(msg`Muted Accounts`), requireAuth: true}} options={{title: i18n._(msg`Muted Accounts`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationBlockedAccounts" name="ModerationBlockedAccounts"
getComponent={() => ModerationBlockedAccounts} getComponent={() => ModerationBlockedAccounts}
options={{title: title(msg`Blocked Accounts`), requireAuth: true}} options={{title: i18n._(msg`Blocked Accounts`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ModerationInteractionSettings" name="ModerationInteractionSettings"
getComponent={() => ModerationInteractionSettings} getComponent={() => ModerationInteractionSettings}
options={{ options={{
title: title(msg`Post Interaction Settings`), title: i18n._(msg`Post Interaction Settings`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -205,176 +203,176 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="ModerationVerificationSettings" name="ModerationVerificationSettings"
getComponent={() => ModerationVerificationSettings} getComponent={() => ModerationVerificationSettings}
options={{ options={{
title: title(msg`Verification Settings`), title: i18n._(msg`Verification Settings`),
requireAuth: true, requireAuth: true,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="Settings" name="Settings"
getComponent={() => SettingsScreen} getComponent={() => SettingsScreen}
options={{title: title(msg`Settings`), requireAuth: true}} options={{title: i18n._(msg`Settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="LanguageSettings" name="LanguageSettings"
getComponent={() => LanguageSettingsScreen} getComponent={() => LanguageSettingsScreen}
options={{title: title(msg`Language Settings`), requireAuth: true}} options={{title: i18n._(msg`Language Settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Profile" name="Profile"
getComponent={() => ProfileScreen} getComponent={() => ProfileScreen}
options={({route}) => ({ options={({route}) => ({
title: bskyTitle(`@${route.params.name}`, unreadCountLabel), title: `@${route.params.name}`,
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFollowers" name="ProfileFollowers"
getComponent={() => ProfileFollowersScreen} getComponent={() => ProfileFollowersScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`People following @${route.params.name}`), title: i18n._(msg`People following @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFollows" name="ProfileFollows"
getComponent={() => ProfileFollowsScreen} getComponent={() => ProfileFollowsScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`People followed by @${route.params.name}`), title: i18n._(msg`People followed by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileKnownFollowers" name="ProfileKnownFollowers"
getComponent={() => ProfileKnownFollowersScreen} getComponent={() => ProfileKnownFollowersScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Followers of @${route.params.name} that you know`), title: i18n._(msg`Followers of @${route.params.name} that you know`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileList" name="ProfileList"
getComponent={() => ProfileListScreen} getComponent={() => ProfileListScreen}
options={{title: title(msg`List`), requireAuth: true}} options={{title: i18n._(msg`List`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ProfileSearch" name="ProfileSearch"
getComponent={() => ProfileSearchScreen} getComponent={() => ProfileSearchScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Search @${route.params.name}'s posts`), title: i18n._(msg`Search @${route.params.name}'s posts`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="PostThread" name="PostThread"
getComponent={() => PostThreadScreen} getComponent={() => PostThreadScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Post by @${route.params.name}`), title: i18n._(msg`Post by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="PostLikedBy" name="PostLikedBy"
getComponent={() => PostLikedByScreen} getComponent={() => PostLikedByScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Post by @${route.params.name}`), title: i18n._(msg`Post by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="PostRepostedBy" name="PostRepostedBy"
getComponent={() => PostRepostedByScreen} getComponent={() => PostRepostedByScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Post by @${route.params.name}`), title: i18n._(msg`Post by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="PostQuotes" name="PostQuotes"
getComponent={() => PostQuotesScreen} getComponent={() => PostQuotesScreen}
options={({route}) => ({ options={({route}) => ({
title: title(msg`Post by @${route.params.name}`), title: i18n._(msg`Post by @${route.params.name}`),
})} })}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFeed" name="ProfileFeed"
getComponent={() => ProfileFeedScreen} getComponent={() => ProfileFeedScreen}
options={{title: title(msg`Feed`)}} options={{title: i18n._(msg`Feed`)}}
/> />
<Stack.Screen <Stack.Screen
name="ProfileFeedLikedBy" name="ProfileFeedLikedBy"
getComponent={() => ProfileFeedLikedByScreen} getComponent={() => ProfileFeedLikedByScreen}
options={{title: title(msg`Liked by`)}} options={{title: i18n._(msg`Liked by`)}}
/> />
<Stack.Screen <Stack.Screen
name="ProfileLabelerLikedBy" name="ProfileLabelerLikedBy"
getComponent={() => ProfileLabelerLikedByScreen} getComponent={() => ProfileLabelerLikedByScreen}
options={{title: title(msg`Liked by`)}} options={{title: i18n._(msg`Liked by`)}}
/> />
<Stack.Screen <Stack.Screen
name="Debug" name="Debug"
getComponent={() => Storybook} getComponent={() => Storybook}
options={{title: title(msg`Storybook`), requireAuth: true}} options={{title: i18n._(msg`Storybook`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="DebugMod" name="DebugMod"
getComponent={() => DebugModScreen} getComponent={() => DebugModScreen}
options={{title: title(msg`Moderation states`), requireAuth: true}} options={{title: i18n._(msg`Moderation states`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="SharedPreferencesTester" name="SharedPreferencesTester"
getComponent={() => SharedPreferencesTesterScreen} getComponent={() => SharedPreferencesTesterScreen}
options={{title: title(msg`Shared Preferences Tester`)}} options={{title: i18n._(msg`Shared Preferences Tester`)}}
/> />
<Stack.Screen <Stack.Screen
name="Log" name="Log"
getComponent={() => LogScreen} getComponent={() => LogScreen}
options={{title: title(msg`Log`), requireAuth: true}} options={{title: i18n._(msg`Log`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Support" name="Support"
getComponent={() => SupportScreen} getComponent={() => SupportScreen}
options={{title: title(msg`Support`)}} options={{title: i18n._(msg`Support`)}}
/> />
<Stack.Screen <Stack.Screen
name="PrivacyPolicy" name="PrivacyPolicy"
getComponent={() => PrivacyPolicyScreen} getComponent={() => PrivacyPolicyScreen}
options={{title: title(msg`Privacy Policy`)}} options={{title: i18n._(msg`Privacy Policy`)}}
/> />
<Stack.Screen <Stack.Screen
name="TermsOfService" name="TermsOfService"
getComponent={() => TermsOfServiceScreen} getComponent={() => TermsOfServiceScreen}
options={{title: title(msg`Terms of Service`)}} options={{title: i18n._(msg`Terms of Service`)}}
/> />
<Stack.Screen <Stack.Screen
name="CommunityGuidelines" name="CommunityGuidelines"
getComponent={() => CommunityGuidelinesScreen} getComponent={() => CommunityGuidelinesScreen}
options={{title: title(msg`Community Guidelines`)}} options={{title: i18n._(msg`Community Guidelines`)}}
/> />
<Stack.Screen <Stack.Screen
name="CopyrightPolicy" name="CopyrightPolicy"
getComponent={() => CopyrightPolicyScreen} getComponent={() => CopyrightPolicyScreen}
options={{title: title(msg`Copyright Policy`)}} options={{title: i18n._(msg`Copyright Policy`)}}
/> />
<Stack.Screen <Stack.Screen
name="AppPasswords" name="AppPasswords"
getComponent={() => AppPasswordsScreen} getComponent={() => AppPasswordsScreen}
options={{title: title(msg`App Passwords`), requireAuth: true}} options={{title: i18n._(msg`App Passwords`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="SavedFeeds" name="SavedFeeds"
getComponent={() => SavedFeeds} getComponent={() => SavedFeeds}
options={{title: title(msg`Edit My Feeds`), requireAuth: true}} options={{title: i18n._(msg`Edit My Feeds`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesFollowingFeed" name="PreferencesFollowingFeed"
getComponent={() => FollowingFeedPreferencesScreen} getComponent={() => FollowingFeedPreferencesScreen}
options={{ options={{
title: title(msg`Following Feed Preferences`), title: i18n._(msg`Following Feed Preferences`),
requireAuth: true, requireAuth: true,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesThreads" name="PreferencesThreads"
getComponent={() => ThreadPreferencesScreen} getComponent={() => ThreadPreferencesScreen}
options={{title: title(msg`Threads Preferences`), requireAuth: true}} options={{title: i18n._(msg`Threads Preferences`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesExternalEmbeds" name="PreferencesExternalEmbeds"
getComponent={() => ExternalMediaPreferencesScreen} getComponent={() => ExternalMediaPreferencesScreen}
options={{ options={{
title: title(msg`External Media Preferences`), title: i18n._(msg`External Media Preferences`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -382,7 +380,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="AccessibilitySettings" name="AccessibilitySettings"
getComponent={() => AccessibilitySettingsScreen} getComponent={() => AccessibilitySettingsScreen}
options={{ options={{
title: title(msg`Accessibility Settings`), title: i18n._(msg`Accessibility Settings`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -390,7 +388,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="AppearanceSettings" name="AppearanceSettings"
getComponent={() => AppearanceSettingsScreen} getComponent={() => AppearanceSettingsScreen}
options={{ options={{
title: title(msg`Appearance`), title: i18n._(msg`Appearance`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -398,7 +396,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="AccountSettings" name="AccountSettings"
getComponent={() => AccountSettingsScreen} getComponent={() => AccountSettingsScreen}
options={{ options={{
title: title(msg`Account`), title: i18n._(msg`Account`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -406,7 +404,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="PrivacyAndSecuritySettings" name="PrivacyAndSecuritySettings"
getComponent={() => PrivacyAndSecuritySettingsScreen} getComponent={() => PrivacyAndSecuritySettingsScreen}
options={{ options={{
title: title(msg`Privacy and Security`), title: i18n._(msg`Privacy and Security`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -414,7 +412,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="ActivityPrivacySettings" name="ActivityPrivacySettings"
getComponent={() => ActivityPrivacySettingsScreen} getComponent={() => ActivityPrivacySettingsScreen}
options={{ options={{
title: title(msg`Privacy and Security`), title: i18n._(msg`Privacy and Security`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -422,20 +420,20 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="FindContactsSettings" name="FindContactsSettings"
getComponent={() => FindContactsSettingsScreen} getComponent={() => FindContactsSettingsScreen}
options={{ options={{
title: title(msg`Find Contacts`), title: i18n._(msg`Find Contacts`),
requireAuth: true, requireAuth: true,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="NotificationSettings" name="NotificationSettings"
getComponent={() => NotificationSettingsScreen} getComponent={() => NotificationSettingsScreen}
options={{title: title(msg`Notification settings`), requireAuth: true}} options={{title: i18n._(msg`Notification settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="ReplyNotificationSettings" name="ReplyNotificationSettings"
getComponent={() => ReplyNotificationSettingsScreen} getComponent={() => ReplyNotificationSettingsScreen}
options={{ options={{
title: title(msg`Reply notifications`), title: i18n._(msg`Reply notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -443,7 +441,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="MentionNotificationSettings" name="MentionNotificationSettings"
getComponent={() => MentionNotificationSettingsScreen} getComponent={() => MentionNotificationSettingsScreen}
options={{ options={{
title: title(msg`Mention notifications`), title: i18n._(msg`Mention notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -451,7 +449,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="QuoteNotificationSettings" name="QuoteNotificationSettings"
getComponent={() => QuoteNotificationSettingsScreen} getComponent={() => QuoteNotificationSettingsScreen}
options={{ options={{
title: title(msg`Quote notifications`), title: i18n._(msg`Quote notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -459,7 +457,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="LikeNotificationSettings" name="LikeNotificationSettings"
getComponent={() => LikeNotificationSettingsScreen} getComponent={() => LikeNotificationSettingsScreen}
options={{ options={{
title: title(msg`Like notifications`), title: i18n._(msg`Like notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -467,7 +465,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="RepostNotificationSettings" name="RepostNotificationSettings"
getComponent={() => RepostNotificationSettingsScreen} getComponent={() => RepostNotificationSettingsScreen}
options={{ options={{
title: title(msg`Repost notifications`), title: i18n._(msg`Repost notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -475,7 +473,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="NewFollowerNotificationSettings" name="NewFollowerNotificationSettings"
getComponent={() => NewFollowerNotificationSettingsScreen} getComponent={() => NewFollowerNotificationSettingsScreen}
options={{ options={{
title: title(msg`New follower notifications`), title: i18n._(msg`New follower notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -483,7 +481,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="LikesOnRepostsNotificationSettings" name="LikesOnRepostsNotificationSettings"
getComponent={() => LikesOnRepostsNotificationSettingsScreen} getComponent={() => LikesOnRepostsNotificationSettingsScreen}
options={{ options={{
title: title(msg`Likes of your reposts notifications`), title: i18n._(msg`Likes of your reposts notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -491,7 +489,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="RepostsOnRepostsNotificationSettings" name="RepostsOnRepostsNotificationSettings"
getComponent={() => RepostsOnRepostsNotificationSettingsScreen} getComponent={() => RepostsOnRepostsNotificationSettingsScreen}
options={{ options={{
title: title(msg`Reposts of your reposts notifications`), title: i18n._(msg`Reposts of your reposts notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -499,7 +497,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="ActivityNotificationSettings" name="ActivityNotificationSettings"
getComponent={() => ActivityNotificationSettingsScreen} getComponent={() => ActivityNotificationSettingsScreen}
options={{ options={{
title: title(msg`Activity notifications`), title: i18n._(msg`Activity notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -507,7 +505,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="MiscellaneousNotificationSettings" name="MiscellaneousNotificationSettings"
getComponent={() => MiscellaneousNotificationSettingsScreen} getComponent={() => MiscellaneousNotificationSettingsScreen}
options={{ options={{
title: title(msg`Miscellaneous notifications`), title: i18n._(msg`Miscellaneous notifications`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -515,7 +513,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="ContentAndMediaSettings" name="ContentAndMediaSettings"
getComponent={() => ContentAndMediaSettingsScreen} getComponent={() => ContentAndMediaSettingsScreen}
options={{ options={{
title: title(msg`Content and Media`), title: i18n._(msg`Content and Media`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -523,7 +521,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="InterestsSettings" name="InterestsSettings"
getComponent={() => InterestsSettingsScreen} getComponent={() => InterestsSettingsScreen}
options={{ options={{
title: title(msg`Your interests`), title: i18n._(msg`Your interests`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -531,7 +529,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="AboutSettings" name="AboutSettings"
getComponent={() => AboutSettingsScreen} getComponent={() => AboutSettingsScreen}
options={{ options={{
title: title(msg`About`), title: i18n._(msg`About`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -539,75 +537,78 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="AppIconSettings" name="AppIconSettings"
getComponent={() => AppIconSettingsScreen} getComponent={() => AppIconSettingsScreen}
options={{ options={{
title: title(msg`App Icon`), title: i18n._(msg`App Icon`),
requireAuth: true, requireAuth: true,
}} }}
/> />
<Stack.Screen <Stack.Screen
name="Hashtag" name="Hashtag"
getComponent={() => HashtagScreen} getComponent={() => HashtagScreen}
options={{title: title(msg`Hashtag`)}} options={{title: i18n._(msg`Hashtag`)}}
/> />
<Stack.Screen <Stack.Screen
name="Topic" name="Topic"
getComponent={() => TopicScreen} getComponent={() => TopicScreen}
options={{title: title(msg`Topic`)}} options={{title: i18n._(msg`Topic`)}}
/> />
<Stack.Screen <Stack.Screen
name="MessagesConversation" name="MessagesConversation"
getComponent={() => MessagesConversationScreen} getComponent={() => MessagesConversationScreen}
options={{title: title(msg`Chat`), requireAuth: true}} options={{title: i18n._(msg`Chat`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="MessagesSettings" name="MessagesSettings"
getComponent={() => MessagesSettingsScreen} getComponent={() => MessagesSettingsScreen}
options={{title: title(msg`Chat settings`), requireAuth: true}} options={{title: i18n._(msg`Chat settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="MessagesInbox" name="MessagesInbox"
getComponent={() => MessagesInboxScreen} getComponent={() => MessagesInboxScreen}
options={{title: title(msg`Chat request inbox`), requireAuth: true}} options={{title: i18n._(msg`Chat request inbox`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="NotificationsActivityList" name="NotificationsActivityList"
getComponent={() => NotificationsActivityListScreen} getComponent={() => NotificationsActivityListScreen}
options={{title: title(msg`Notifications`), requireAuth: true}} options={{title: i18n._(msg`Notifications`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="LegacyNotificationSettings" name="LegacyNotificationSettings"
getComponent={() => LegacyNotificationSettingsScreen} getComponent={() => LegacyNotificationSettingsScreen}
options={{title: title(msg`Notification settings`), requireAuth: true}} options={{title: i18n._(msg`Notification settings`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="Feeds" name="Feeds"
getComponent={() => FeedsScreen} getComponent={() => FeedsScreen}
options={{title: title(msg`Feeds`)}} options={{title: i18n._(msg`Feeds`)}}
/> />
<Stack.Screen <Stack.Screen
name="StarterPack" name="StarterPack"
getComponent={() => StarterPackScreen} getComponent={() => StarterPackScreen}
options={{title: title(msg`Starter Pack`)}} options={{title: i18n._(msg`Starter Pack`)}}
/> />
<Stack.Screen <Stack.Screen
name="StarterPackShort" name="StarterPackShort"
getComponent={() => StarterPackScreenShort} getComponent={() => StarterPackScreenShort}
options={{title: title(msg`Starter Pack`)}} options={{title: i18n._(msg`Starter Pack`)}}
/> />
<Stack.Screen <Stack.Screen
name="StarterPackWizard" name="StarterPackWizard"
getComponent={() => Wizard} getComponent={() => Wizard}
options={{title: title(msg`Create a starter pack`), requireAuth: true}} options={{title: i18n._(msg`Create a starter pack`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="StarterPackEdit" name="StarterPackEdit"
getComponent={() => Wizard} getComponent={() => Wizard}
options={{title: title(msg`Edit your starter pack`), requireAuth: true}} options={{
title: i18n._(msg`Edit your starter pack`),
requireAuth: true,
}}
/> />
<Stack.Screen <Stack.Screen
name="VideoFeed" name="VideoFeed"
getComponent={() => VideoFeed} getComponent={() => VideoFeed}
options={{ options={{
title: title(msg`Video Feed`), title: i18n._(msg`Video Feed`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -615,7 +616,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="Bookmarks" name="Bookmarks"
getComponent={() => BookmarksScreen} getComponent={() => BookmarksScreen}
options={{ options={{
title: title(msg`Saved Posts`), title: i18n._(msg`Saved Posts`),
requireAuth: true, requireAuth: true,
}} }}
/> />
@@ -623,7 +624,7 @@ function commonScreens(Stack: typeof Flat, unreadCountLabel?: string) {
name="FindContactsFlow" name="FindContactsFlow"
getComponent={() => FindContactsFlowScreen} getComponent={() => FindContactsFlowScreen}
options={{ options={{
title: title(msg`Find Contacts`), title: i18n._(msg`Find Contacts`),
requireAuth: true, requireAuth: true,
gestureEnabled: false, gestureEnabled: false,
}} }}
@@ -683,30 +684,35 @@ function screenOptions(t: Theme) {
function HomeTabNavigator() { function HomeTabNavigator() {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui()
return ( return (
<HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home"> <HomeTab.Navigator screenOptions={screenOptions(t)} initialRouteName="Home">
<HomeTab.Screen name="Home" getComponent={() => HomeScreen} /> <HomeTab.Screen name="Home" getComponent={() => HomeScreen} />
<HomeTab.Screen name="Start" getComponent={() => HomeScreen} /> <HomeTab.Screen name="Start" getComponent={() => HomeScreen} />
{commonScreens(HomeTab as typeof Flat)} {commonScreens(HomeTab as typeof Flat, i18n)}
</HomeTab.Navigator> </HomeTab.Navigator>
) )
} }
function SearchTabNavigator() { function SearchTabNavigator() {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui()
return ( return (
<SearchTab.Navigator <SearchTab.Navigator
screenOptions={screenOptions(t)} screenOptions={screenOptions(t)}
initialRouteName="Search"> initialRouteName="Search">
<SearchTab.Screen name="Search" getComponent={() => SearchScreen} /> <SearchTab.Screen name="Search" getComponent={() => SearchScreen} />
{commonScreens(SearchTab as typeof Flat)} {commonScreens(SearchTab as typeof Flat, i18n)}
</SearchTab.Navigator> </SearchTab.Navigator>
) )
} }
function NotificationsTabNavigator() { function NotificationsTabNavigator() {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui()
return ( return (
<NotificationsTab.Navigator <NotificationsTab.Navigator
screenOptions={screenOptions(t)} screenOptions={screenOptions(t)}
@@ -716,13 +722,15 @@ function NotificationsTabNavigator() {
getComponent={() => NotificationsScreen} getComponent={() => NotificationsScreen}
options={{requireAuth: true}} options={{requireAuth: true}}
/> />
{commonScreens(NotificationsTab as typeof Flat)} {commonScreens(NotificationsTab as typeof Flat, i18n)}
</NotificationsTab.Navigator> </NotificationsTab.Navigator>
) )
} }
function MyProfileTabNavigator() { function MyProfileTabNavigator() {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui()
return ( return (
<MyProfileTab.Navigator <MyProfileTab.Navigator
screenOptions={screenOptions(t)} screenOptions={screenOptions(t)}
@@ -734,13 +742,15 @@ function MyProfileTabNavigator() {
getComponent={() => ProfileScreen} getComponent={() => ProfileScreen}
initialParams={{name: 'me', hideBackButton: true}} initialParams={{name: 'me', hideBackButton: true}}
/> />
{commonScreens(MyProfileTab as unknown as typeof Flat)} {commonScreens(MyProfileTab as unknown as typeof Flat, i18n)}
</MyProfileTab.Navigator> </MyProfileTab.Navigator>
) )
} }
function MessagesTabNavigator() { function MessagesTabNavigator() {
const t = useTheme() const t = useTheme()
const {i18n} = useLingui()
return ( return (
<MessagesTab.Navigator <MessagesTab.Navigator
screenOptions={screenOptions(t)} screenOptions={screenOptions(t)}
@@ -753,7 +763,7 @@ function MessagesTabNavigator() {
animationTypeForReplace: route.params?.animation ?? 'push', animationTypeForReplace: route.params?.animation ?? 'push',
})} })}
/> />
{commonScreens(MessagesTab as typeof Flat)} {commonScreens(MessagesTab as typeof Flat, i18n)}
</MessagesTab.Navigator> </MessagesTab.Navigator>
) )
} }
@@ -768,9 +778,8 @@ const FlatNavigator = ({
layout: React.ComponentProps<typeof Flat.Navigator>['layout'] layout: React.ComponentProps<typeof Flat.Navigator>['layout']
}) => { }) => {
const t = useTheme() const t = useTheme()
const numUnread = useUnreadNotifications() const {_, i18n} = useLingui()
const screenListeners = useWebScrollRestoration() const screenListeners = useWebScrollRestoration()
const title = (page: MessageDescriptor) => bskyTitle(i18n._(page), numUnread)
return ( return (
<Flat.Navigator <Flat.Navigator
@@ -780,29 +789,29 @@ const FlatNavigator = ({
<Flat.Screen <Flat.Screen
name="Home" name="Home"
getComponent={() => HomeScreen} getComponent={() => HomeScreen}
options={{title: title(msg`Home`)}} options={{title: _(msg`Home`)}}
/> />
<Flat.Screen <Flat.Screen
name="Search" name="Search"
getComponent={() => SearchScreen} getComponent={() => SearchScreen}
options={{title: title(msg`Explore`)}} options={{title: _(msg`Explore`)}}
/> />
<Flat.Screen <Flat.Screen
name="Notifications" name="Notifications"
getComponent={() => NotificationsScreen} getComponent={() => NotificationsScreen}
options={{title: title(msg`Notifications`), requireAuth: true}} options={{title: _(msg`Notifications`), requireAuth: true}}
/> />
<Flat.Screen <Flat.Screen
name="Messages" name="Messages"
getComponent={() => MessagesScreen} getComponent={() => MessagesScreen}
options={{title: title(msg`Messages`), requireAuth: true}} options={{title: _(msg`Messages`), requireAuth: true}}
/> />
<Flat.Screen <Flat.Screen
name="Start" name="Start"
getComponent={() => HomeScreen} getComponent={() => HomeScreen}
options={{title: title(msg`Home`)}} options={{title: _(msg`Home`)}}
/> />
{commonScreens(Flat, numUnread)} {commonScreens(Flat, i18n)}
</Flat.Navigator> </Flat.Navigator>
) )
} }
@@ -880,6 +889,7 @@ let lastHandledNotificationDateDedupe: number | undefined
function RoutesContainer({children}: React.PropsWithChildren<{}>) { function RoutesContainer({children}: React.PropsWithChildren<{}>) {
const theme = useColorSchemeStyle(DefaultTheme, DarkTheme) const theme = useColorSchemeStyle(DefaultTheme, DarkTheme)
const unreadCount = useUnreadNotifications()
const {currentAccount, accounts} = useSession() const {currentAccount, accounts} = useSession()
const {onPressSwitchAccount} = useAccountSwitcher() const {onPressSwitchAccount} = useAccountSwitcher()
const {setShowLoggedOut} = useLoggedOutViewControls() const {setShowLoggedOut} = useLoggedOutViewControls()
@@ -983,6 +993,17 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
} }
} }
const documentTitle = useMemo(() => {
return {
formatter: (page: Record<string, any> | undefined) => {
if (page?.title) {
return bskyTitle(page.title, unreadCount)
}
return 'Bluesky'
},
}
}, [unreadCount])
return ( return (
<> <>
<NavigationContainer <NavigationContainer
@@ -1010,7 +1031,8 @@ function RoutesContainer({children}: React.PropsWithChildren<{}>) {
// I'm scared of missing a spot (esp. with push notifications etc) so let's enable this legacy behaviour for now. // 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 // We will need to confirm we handle nested navigators correctly by the time we migrate to React Navigation 8.x
// -sfn // -sfn
navigationInChildEnabled> navigationInChildEnabled
documentTitle={documentTitle}>
{children} {children}
</NavigationContainer> </NavigationContainer>
</> </>
+1 -2
View File
@@ -2,7 +2,6 @@ import {useEffect} from 'react'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {type NavigationProp} from '#/lib/routes/types' import {type NavigationProp} from '#/lib/routes/types'
import {bskyTitle} from '#/lib/strings/headings'
import {useUnreadNotifications} from '#/state/queries/notifications/unread' import {useUnreadNotifications} from '#/state/queries/notifications/unread'
export function useSetTitle(title?: string) { export function useSetTitle(title?: string) {
@@ -10,7 +9,7 @@ export function useSetTitle(title?: string) {
const numUnread = useUnreadNotifications() const numUnread = useUnreadNotifications()
useEffect(() => { useEffect(() => {
if (title) { if (title) {
navigation.setOptions({title: bskyTitle(title, numUnread)}) navigation.setOptions({title})
} }
}, [title, navigation, numUnread]) }, [title, navigation, numUnread])
} }
+10 -1
View File
@@ -1,11 +1,13 @@
import {useCallback, useEffect, useMemo, useRef, useState} from 'react' import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
import {useWindowDimensions, View} from 'react-native' import {useWindowDimensions, View} from 'react-native'
import Animated, {useAnimatedStyle} from 'react-native-reanimated' import Animated, {useAnimatedStyle} from 'react-native-reanimated'
import {Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useOpenComposer} from '#/lib/hooks/useOpenComposer' import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking' import {usePostViewTracking} from '#/lib/hooks/usePostViewTracking'
import {useSetTitle} from '#/lib/hooks/useSetTitle'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useFeedFeedback} from '#/state/feed-feedback' import {useFeedFeedback} from '#/state/feed-feedback'
import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences' import {type ThreadViewOption} from '#/state/queries/preferences/useThreadPreferences'
@@ -49,6 +51,7 @@ const PARENT_CHUNK_SIZE = 5
const CHILDREN_CHUNK_SIZE = 50 const CHILDREN_CHUNK_SIZE = 50
export function PostThread({uri}: {uri: string}) { export function PostThread({uri}: {uri: string}) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
const {hasSession} = useSession() const {hasSession} = useSession()
const initialNumToRender = useInitialNumToRender() const initialNumToRender = useInitialNumToRender()
@@ -75,6 +78,12 @@ export function PostThread({uri}: {uri: string}) {
return {hasParents} return {hasParents}
}, [thread.data.items]) }, [thread.data.items])
useSetTitle(
anchor?.value.post
? _(msg`Post by @${anchor.value.post.author.handle}`)
: undefined,
)
// Track post:view event when anchor post is viewed // Track post:view event when anchor post is viewed
const seenPostUriRef = useRef<string | null>(null) const seenPostUriRef = useRef<string | null>(null)
useEffect(() => { useEffect(() => {