diff --git a/lingui.config.js b/lingui.config.js index a4219a1b..1375d792 100644 --- a/lingui.config.js +++ b/lingui.config.js @@ -1,6 +1,6 @@ /** @type {import('@lingui/conf').LinguiConfig} */ module.exports = { - locales: ['en', 'cs', 'fr', 'hi', 'es'], + locales: ['en', 'hi'], catalogs: [ { path: '/src/locale/locales/{locale}/messages', diff --git a/src/locale/i18n.ts b/src/locale/i18n.ts index 2b9be60a..4fe3cedd 100644 --- a/src/locale/i18n.ts +++ b/src/locale/i18n.ts @@ -7,10 +7,7 @@ import {messages as messagesHi} from '#/locale/locales/hi/messages' export const locales = { en: 'English', - cs: 'Česky', - fr: 'Français', hi: 'हिंदी', - es: 'Español', } export const defaultLocale = 'en' diff --git a/src/locale/i18n.web.ts b/src/locale/i18n.web.ts index 18b05fb8..4e8b8d95 100644 --- a/src/locale/i18n.web.ts +++ b/src/locale/i18n.web.ts @@ -5,10 +5,7 @@ import {useLanguagePrefs} from '#/state/preferences' export const locales = { en: 'English', - cs: 'Česky', - fr: 'Français', hi: 'हिंदी', - es: 'Español', } export const defaultLocale = 'en' diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index c8fde995..8e413a54 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -827,6 +827,7 @@ msgid "Feedback" msgstr "" #: src/view/screens/Feeds.tsx:475 +#: src/view/screens/Profile.tsx:164 #: src/view/shell/bottom-bar/BottomBar.tsx:168 #: src/view/shell/desktop/LeftNav.tsx:341 #: src/view/shell/Drawer.tsx:463 @@ -1132,6 +1133,10 @@ msgstr "" msgid "Liked by" msgstr "" +#: src/view/screens/Profile.tsx:163 +msgid "Likes" +msgstr "" + #: src/view/screens/Moderation.tsx:203 #~ msgid "Limit the visibility of my account" #~ msgstr "" @@ -1148,6 +1153,7 @@ msgstr "" msgid "List Name" msgstr "" +#: src/view/screens/Profile.tsx:165 #: src/view/shell/desktop/LeftNav.tsx:381 #: src/view/shell/Drawer.tsx:479 #: src/view/shell/Drawer.tsx:480 @@ -1191,6 +1197,10 @@ msgstr "" msgid "Make sure this is where you intend to go!" msgstr "" +#: src/view/screens/Profile.tsx:162 +msgid "Media" +msgstr "" + #: src/view/screens/Search/Search.tsx:503 msgid "Menu" msgstr "" @@ -1534,6 +1544,10 @@ msgstr "" msgid "Post not found" msgstr "" +#: src/view/screens/Profile.tsx:160 +msgid "Posts" +msgstr "" + #: src/view/com/modals/LinkWarning.tsx:44 msgid "Potentially Misleading Link" msgstr "" @@ -1649,6 +1663,10 @@ msgstr "" msgid "Removed from list" msgstr "" +#: src/view/screens/Profile.tsx:161 +msgid "Replies" +msgstr "" + #: src/view/screens/PreferencesHomeFeed.tsx:135 msgid "Reply Filters" msgstr "" @@ -1951,8 +1969,8 @@ msgstr "" msgid "Sign into" msgstr "" -#: src/view/com/modals/SwitchAccount.tsx:60 -#: src/view/com/modals/SwitchAccount.tsx:63 +#: src/view/com/modals/SwitchAccount.tsx:64 +#: src/view/com/modals/SwitchAccount.tsx:67 msgid "Sign out" msgstr "" @@ -2020,7 +2038,7 @@ msgstr "" msgid "Support" msgstr "" -#: src/view/com/modals/SwitchAccount.tsx:111 +#: src/view/com/modals/SwitchAccount.tsx:115 msgid "Switch Account" msgstr "" @@ -2421,7 +2439,7 @@ msgstr "" msgid "Your posts, likes, and blocks are public. Mutes are private." msgstr "" -#: src/view/com/modals/SwitchAccount.tsx:78 +#: src/view/com/modals/SwitchAccount.tsx:82 msgid "Your profile" msgstr "" diff --git a/src/locale/locales/hi/messages.po b/src/locale/locales/hi/messages.po index a075ab9c..039b2b3b 100644 --- a/src/locale/locales/hi/messages.po +++ b/src/locale/locales/hi/messages.po @@ -823,6 +823,7 @@ msgid "Feedback" msgstr "प्रतिक्रिया" #: src/view/screens/Feeds.tsx:475 +#: src/view/screens/Profile.tsx:164 #: src/view/shell/bottom-bar/BottomBar.tsx:168 #: src/view/shell/desktop/LeftNav.tsx:341 #: src/view/shell/Drawer.tsx:463 @@ -1124,6 +1125,10 @@ msgstr "इस फ़ीड को लाइक करो" msgid "Liked by" msgstr "इन यूजर ने लाइक किया है" +#: src/view/screens/Profile.tsx:163 +msgid "Likes" +msgstr "" + #: src/view/screens/Moderation.tsx:203 #~ msgid "Limit the visibility of my account" #~ msgstr "" @@ -1140,6 +1145,7 @@ msgstr "सूची अवतार" msgid "List Name" msgstr "सूची का नाम" +#: src/view/screens/Profile.tsx:165 #: src/view/shell/desktop/LeftNav.tsx:381 #: src/view/shell/Drawer.tsx:479 #: src/view/shell/Drawer.tsx:480 @@ -1183,6 +1189,10 @@ msgstr "" msgid "Make sure this is where you intend to go!" msgstr "यह सुनिश्चित करने के लिए कि आप कहाँ जाना चाहते हैं!" +#: src/view/screens/Profile.tsx:162 +msgid "Media" +msgstr "" + #: src/view/screens/Search/Search.tsx:503 msgid "Menu" msgstr "मेनू" @@ -1526,6 +1536,10 @@ msgstr "पोस्ट भाषा" msgid "Post not found" msgstr "पोस्ट नहीं मिला" +#: src/view/screens/Profile.tsx:160 +msgid "Posts" +msgstr "" + #: src/view/com/modals/LinkWarning.tsx:44 msgid "Potentially Misleading Link" msgstr "शायद एक भ्रामक लिंक" @@ -1641,6 +1655,10 @@ msgstr "इस फ़ीड को सहेजे गए फ़ीड से msgid "Removed from list" msgstr "" +#: src/view/screens/Profile.tsx:161 +msgid "Replies" +msgstr "" + #: src/view/screens/PreferencesHomeFeed.tsx:135 msgid "Reply Filters" msgstr "फिल्टर" @@ -1943,8 +1961,8 @@ msgstr "... के रूप में साइन इन करें" msgid "Sign into" msgstr "साइन इन करें" -#: src/view/com/modals/SwitchAccount.tsx:60 -#: src/view/com/modals/SwitchAccount.tsx:63 +#: src/view/com/modals/SwitchAccount.tsx:64 +#: src/view/com/modals/SwitchAccount.tsx:67 msgid "Sign out" msgstr "साइन आउट" @@ -1993,7 +2011,7 @@ msgstr "Storybook" #: src/view/com/modals/AppealLabel.tsx:101 msgid "Submit" -msgstr ">>>>>>> cb8a33b6 (Fix translations)" +msgstr "" #: src/view/screens/ProfileList.tsx:597 msgid "Subscribe" @@ -2012,7 +2030,7 @@ msgstr "अनुशंसित लोग" msgid "Support" msgstr "सहायता" -#: src/view/com/modals/SwitchAccount.tsx:111 +#: src/view/com/modals/SwitchAccount.tsx:115 msgid "Switch Account" msgstr "खाते बदलें" @@ -2413,7 +2431,7 @@ msgstr "" msgid "Your posts, likes, and blocks are public. Mutes are private." msgstr "आपकी पोस्ट, पसंद और ब्लॉक सार्वजनिक हैं। म्यूट निजी हैं।।" -#: src/view/com/modals/SwitchAccount.tsx:78 +#: src/view/com/modals/SwitchAccount.tsx:82 msgid "Your profile" msgstr "आपकी प्रोफ़ाइल" diff --git a/src/state/queries/post-feed.ts b/src/state/queries/post-feed.ts index 9bd1dacb..14ffeb0d 100644 --- a/src/state/queries/post-feed.ts +++ b/src/state/queries/post-feed.ts @@ -1,4 +1,4 @@ -import {useCallback, useEffect} from 'react' +import React, {useCallback, useEffect} from 'react' import { AppBskyFeedDefs, AppBskyFeedPost, @@ -97,6 +97,22 @@ export function usePostFeedQuery( const feedTuners = useFeedTuners(feedDesc) const moderationOpts = useModerationOpts() const enabled = opts?.enabled !== false && Boolean(moderationOpts) + const lastRun = React.useRef<{ + data: InfiniteData + args: typeof selectArgs + result: InfiniteData + } | null>(null) + + // Make sure this doesn't invalidate unless really needed. + const selectArgs = React.useMemo( + () => ({ + feedTuners, + disableTuner: params?.disableTuner, + moderationOpts, + ignoreFilterFor: opts?.ignoreFilterFor, + }), + [feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor], + ) const query = useInfiniteQuery< FeedPageUnselected, @@ -147,69 +163,116 @@ export function usePostFeedQuery( : undefined, select: useCallback( (data: InfiniteData) => { - const tuner = params?.disableTuner + // If the selection depends on some data, that data should + // be included in the selectArgs object and read here. + const {feedTuners, disableTuner, moderationOpts, ignoreFilterFor} = + selectArgs + + const tuner = disableTuner ? new NoopFeedTuner() : new FeedTuner(feedTuners) - return { - pageParams: data.pageParams, - pages: data.pages.map(page => ({ - api: page.api, - tuner, - cursor: page.cursor, - slices: tuner - .tune(page.feed) - .map(slice => { - const moderations = slice.items.map(item => - moderatePost(item.post, moderationOpts!), - ) - // apply moderation filter - for (let i = 0; i < slice.items.length; i++) { - if ( - moderations[i]?.content.filter && - slice.items[i].post.author.did !== opts?.ignoreFilterFor - ) { - return undefined - } - } - - return { - _reactKey: slice._reactKey, - rootUri: slice.rootItem.post.uri, - isThread: - slice.items.length > 1 && - slice.items.every( - item => - item.post.author.did === slice.items[0].post.author.did, - ), - items: slice.items - .map((item, i) => { - if ( - AppBskyFeedPost.isRecord(item.post.record) && - AppBskyFeedPost.validateRecord(item.post.record).success - ) { - return { - _reactKey: `${slice._reactKey}-${i}`, - uri: item.post.uri, - post: item.post, - record: item.post.record, - reason: - i === 0 && slice.source - ? slice.source - : item.reason, - moderation: moderations[i], - } - } - return undefined - }) - .filter(Boolean) as FeedPostSliceItem[], - } - }) - .filter(Boolean) as FeedPostSlice[], - })), + // Keep track of the last run and whether we can reuse + // some already selected pages from there. + let reusedPages = [] + if (lastRun.current) { + const { + data: lastData, + args: lastArgs, + result: lastResult, + } = lastRun.current + let canReuse = true + for (let key in selectArgs) { + if (selectArgs.hasOwnProperty(key)) { + if ((selectArgs as any)[key] !== (lastArgs as any)[key]) { + // Can't do reuse anything if any input has changed. + canReuse = false + break + } + } + } + if (canReuse) { + for (let i = 0; i < data.pages.length; i++) { + if (data.pages[i] && lastData.pages[i] === data.pages[i]) { + reusedPages.push(lastResult.pages[i]) + // Keep the tuner in sync so that the end result is deterministic. + tuner.tune(lastData.pages[i].feed) + continue + } + // Stop as soon as pages stop matching up. + break + } + } } + + const result = { + pageParams: data.pageParams, + pages: [ + ...reusedPages, + ...data.pages.slice(reusedPages.length).map(page => ({ + api: page.api, + tuner, + cursor: page.cursor, + slices: tuner + .tune(page.feed) + .map(slice => { + const moderations = slice.items.map(item => + moderatePost(item.post, moderationOpts!), + ) + + // apply moderation filter + for (let i = 0; i < slice.items.length; i++) { + if ( + moderations[i]?.content.filter && + slice.items[i].post.author.did !== ignoreFilterFor + ) { + return undefined + } + } + + return { + _reactKey: slice._reactKey, + rootUri: slice.rootItem.post.uri, + isThread: + slice.items.length > 1 && + slice.items.every( + item => + item.post.author.did === + slice.items[0].post.author.did, + ), + items: slice.items + .map((item, i) => { + if ( + AppBskyFeedPost.isRecord(item.post.record) && + AppBskyFeedPost.validateRecord(item.post.record) + .success + ) { + return { + _reactKey: `${slice._reactKey}-${i}`, + uri: item.post.uri, + post: item.post, + record: item.post.record, + reason: + i === 0 && slice.source + ? slice.source + : item.reason, + moderation: moderations[i], + } + } + return undefined + }) + .filter(Boolean) as FeedPostSliceItem[], + } + }) + .filter(Boolean) as FeedPostSlice[], + })), + ], + } + // Save for memoization. + lastRun.current = {data, result, args: selectArgs} + return result }, - [feedTuners, params?.disableTuner, moderationOpts, opts?.ignoreFilterFor], + [selectArgs /* Don't change. Everything needs to go into selectArgs. */], ), }) diff --git a/src/view/com/modals/SwitchAccount.tsx b/src/view/com/modals/SwitchAccount.tsx index 38e1ce1e..37691e71 100644 --- a/src/view/com/modals/SwitchAccount.tsx +++ b/src/view/com/modals/SwitchAccount.tsx @@ -20,6 +20,7 @@ import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useSession, useSessionApi, SessionAccount} from '#/state/session' import {useProfileQuery} from '#/state/queries/profile' +import {useCloseAllActiveElements} from '#/state/util' export const snapPoints = ['40%', '90%'] @@ -32,11 +33,14 @@ function SwitchAccountCard({account}: {account: SessionAccount}) { const {data: profile} = useProfileQuery({did: account.did}) const isCurrentAccount = account.did === currentAccount?.did const {onPressSwitchAccount} = useAccountSwitcher() + const closeAllActiveElements = useCloseAllActiveElements() const onPressSignout = React.useCallback(() => { track('Settings:SignOutButtonClicked') - logout() - }, [track, logout]) + closeAllActiveElements() + // needs to be in timeout or the modal re-opens + setTimeout(() => logout(), 0) + }, [track, logout, closeAllActiveElements]) const contents = ( diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx index 21353e5d..ae527038 100644 --- a/src/view/screens/Profile.tsx +++ b/src/view/screens/Profile.tsx @@ -157,14 +157,14 @@ function ProfileScreenLoaded({ const showListsTab = hasSession && (isMe || extraInfoQuery.data?.hasLists) const sectionTitles = useMemo(() => { return [ - 'Posts', - showRepliesTab ? 'Replies' : undefined, - 'Media', - showLikesTab ? 'Likes' : undefined, - showFeedsTab ? 'Feeds' : undefined, - showListsTab ? 'Lists' : undefined, + _(msg`Posts`), + showRepliesTab ? _(msg`Replies`) : undefined, + _(msg`Media`), + showLikesTab ? _(msg`Likes`) : undefined, + showFeedsTab ? _(msg`Feeds`) : undefined, + showListsTab ? _(msg`Lists`) : undefined, ].filter(Boolean) as string[] - }, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab]) + }, [showRepliesTab, showLikesTab, showFeedsTab, showListsTab, _]) let nextIndex = 0 const postsIndex = nextIndex++