Merge branch 'scroll-refactor' into web-layout

This commit is contained in:
Dan Abramov
2023-12-14 02:39:43 +00:00
44 changed files with 614 additions and 8131 deletions
+2 -2
View File
@@ -9,12 +9,12 @@ module.exports = function () {
/** /**
* iOS build number. Must be incremented for each TestFlight version. * iOS build number. Must be incremented for each TestFlight version.
*/ */
const IOS_BUILD_NUMBER = '1' const IOS_BUILD_NUMBER = '2'
/** /**
* Android build number. Must be incremented for each release. * Android build number. Must be incremented for each release.
*/ */
const ANDROID_VERSION_CODE = 48 const ANDROID_VERSION_CODE = 49
/** /**
* Uses built-in Expo env vars * Uses built-in Expo env vars
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "bsky.app", "name": "bsky.app",
"version": "1.58.0", "version": "1.59.0",
"private": true, "private": true,
"scripts": { "scripts": {
"prepare": "is-ci || husky install", "prepare": "is-ci || husky install",
@@ -35,7 +35,7 @@
"intl:compile": "lingui compile" "intl:compile": "lingui compile"
}, },
"dependencies": { "dependencies": {
"@atproto/api": "^0.7.2", "@atproto/api": "^0.7.3",
"@bam.tech/react-native-image-resizer": "^3.0.4", "@bam.tech/react-native-image-resizer": "^3.0.4",
"@braintree/sanitize-url": "^6.0.2", "@braintree/sanitize-url": "^6.0.2",
"@emoji-mart/react": "^1.1.1", "@emoji-mart/react": "^1.1.1",
+35
View File
@@ -0,0 +1,35 @@
import React, {createContext, useContext, useMemo} from 'react'
import {ScrollHandlers} from 'react-native-reanimated'
const ScrollContext = createContext<ScrollHandlers<any>>({
onBeginDrag: undefined,
onEndDrag: undefined,
onScroll: undefined,
})
export function useScrollHandlers(): ScrollHandlers<any> {
return useContext(ScrollContext)
}
type ProviderProps = {children: React.ReactNode} & ScrollHandlers<any>
// Note: this completely *overrides* the parent handlers.
// It's up to you to compose them with the parent ones via useScrollHandlers() if needed.
export function ScrollProvider({
children,
onBeginDrag,
onEndDrag,
onScroll,
}: ProviderProps) {
const handlers = useMemo(
() => ({
onBeginDrag,
onEndDrag,
onScroll,
}),
[onBeginDrag, onEndDrag, onScroll],
)
return (
<ScrollContext.Provider value={handlers}>{children}</ScrollContext.Provider>
)
}
+38 -34
View File
@@ -1,3 +1,4 @@
import {useMemo} from 'react'
import {TextStyle, ViewStyle} from 'react-native' import {TextStyle, ViewStyle} from 'react-native'
import {useTheme, PaletteColorName, PaletteColor} from '../ThemeContext' import {useTheme, PaletteColorName, PaletteColor} from '../ThemeContext'
@@ -15,38 +16,41 @@ export interface UsePaletteValue {
icon: TextStyle icon: TextStyle
} }
export function usePalette(color: PaletteColorName): UsePaletteValue { export function usePalette(color: PaletteColorName): UsePaletteValue {
const palette = useTheme().palette[color] const theme = useTheme()
return { return useMemo(() => {
colors: palette, const palette = theme.palette[color]
view: { return {
backgroundColor: palette.background, colors: palette,
}, view: {
viewLight: { backgroundColor: palette.background,
backgroundColor: palette.backgroundLight, },
}, viewLight: {
btn: { backgroundColor: palette.backgroundLight,
backgroundColor: palette.backgroundLight, },
}, btn: {
border: { backgroundColor: palette.backgroundLight,
borderColor: palette.border, },
}, border: {
borderDark: { borderColor: palette.border,
borderColor: palette.borderDark, },
}, borderDark: {
text: { borderColor: palette.borderDark,
color: palette.text, },
}, text: {
textLight: { color: palette.text,
color: palette.textLight, },
}, textLight: {
textInverted: { color: palette.textLight,
color: palette.textInverted, },
}, textInverted: {
link: { color: palette.textInverted,
color: palette.link, },
}, link: {
icon: { color: palette.link,
color: palette.icon, },
}, icon: {
} color: palette.icon,
},
}
}, [theme, color])
} }
File diff suppressed because it is too large Load Diff
+43 -43
View File
@@ -21,7 +21,7 @@ msgstr ""
#~ msgid ". This warning is only available for posts with media attached." #~ msgid ". This warning is only available for posts with media attached."
#~ msgstr "" #~ msgstr ""
#: src/view/shell/desktop/RightNav.tsx:158 #: src/view/shell/desktop/RightNav.tsx:160
msgid "{0, plural, one {# invite code available} other {# invite codes available}}" msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
msgstr "" msgstr ""
@@ -33,7 +33,7 @@ msgstr ""
msgid "{0} {purposeLabel} List" msgid "{0} {purposeLabel} List"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:141 #: src/view/shell/desktop/RightNav.tsx:143
msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}"
msgstr "" msgstr ""
@@ -175,7 +175,7 @@ msgstr ""
msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone."
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:110 #: src/view/com/modals/VerifyEmail.tsx:118
msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below."
msgstr "" msgstr ""
@@ -232,7 +232,7 @@ msgstr ""
msgid "Are you sure?" msgid "Are you sure?"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:185 #: src/view/com/util/forms/PostDropdownBtn.tsx:188
msgid "Are you sure? This cannot be undone." msgid "Are you sure? This cannot be undone."
msgstr "" msgstr ""
@@ -287,7 +287,7 @@ msgstr ""
msgid "Block these accounts?" msgid "Block these accounts?"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:121 #: src/view/screens/Moderation.tsx:123
msgid "Blocked accounts" msgid "Blocked accounts"
msgstr "" msgstr ""
@@ -331,7 +331,7 @@ msgstr ""
msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon."
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:222 #: src/view/screens/Moderation.tsx:225
msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private."
msgstr "" msgstr ""
@@ -344,7 +344,7 @@ msgid "Build version {0} {1}"
msgstr "" msgstr ""
#: src/view/com/composer/photos/OpenCameraBtn.tsx:60 #: src/view/com/composer/photos/OpenCameraBtn.tsx:60
#: src/view/com/util/UserAvatar.tsx:217 #: src/view/com/util/UserAvatar.tsx:221
#: src/view/com/util/UserBanner.tsx:38 #: src/view/com/util/UserBanner.tsx:38
msgid "Camera" msgid "Camera"
msgstr "" msgstr ""
@@ -420,7 +420,7 @@ msgstr ""
msgid "Change Handle" msgid "Change Handle"
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:133 #: src/view/com/modals/VerifyEmail.tsx:141
msgid "Change my email" msgid "Change my email"
msgstr "" msgstr ""
@@ -508,7 +508,7 @@ msgstr ""
#: src/view/com/modals/AppealLabel.tsx:98 #: src/view/com/modals/AppealLabel.tsx:98
#: src/view/com/modals/Confirm.tsx:75 #: src/view/com/modals/Confirm.tsx:75
#: src/view/com/modals/SelfLabel.tsx:154 #: src/view/com/modals/SelfLabel.tsx:154
#: src/view/com/modals/VerifyEmail.tsx:217 #: src/view/com/modals/VerifyEmail.tsx:225
#: src/view/screens/PreferencesHomeFeed.tsx:299 #: src/view/screens/PreferencesHomeFeed.tsx:299
#: src/view/screens/PreferencesThreads.tsx:153 #: src/view/screens/PreferencesThreads.tsx:153
msgid "Confirm" msgid "Confirm"
@@ -529,7 +529,7 @@ msgstr ""
#: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/ChangeEmail.tsx:157
#: src/view/com/modals/DeleteAccount.tsx:176 #: src/view/com/modals/DeleteAccount.tsx:176
#: src/view/com/modals/VerifyEmail.tsx:151 #: src/view/com/modals/VerifyEmail.tsx:159
msgid "Confirmation code" msgid "Confirmation code"
msgstr "" msgstr ""
@@ -538,7 +538,7 @@ msgstr ""
msgid "Connecting..." msgid "Connecting..."
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:79 #: src/view/screens/Moderation.tsx:81
msgid "Content filtering" msgid "Content filtering"
msgstr "" msgstr ""
@@ -577,7 +577,7 @@ msgstr ""
msgid "Copy link to list" msgid "Copy link to list"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
msgid "Copy link to post" msgid "Copy link to post"
msgstr "" msgstr ""
@@ -585,7 +585,7 @@ msgstr ""
msgid "Copy link to profile" msgid "Copy link to profile"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:112 #: src/view/com/util/forms/PostDropdownBtn.tsx:115
msgid "Copy post text" msgid "Copy post text"
msgstr "" msgstr ""
@@ -656,11 +656,11 @@ msgstr ""
msgid "Delete my account…" msgid "Delete my account…"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:180 #: src/view/com/util/forms/PostDropdownBtn.tsx:183
msgid "Delete post" msgid "Delete post"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:184 #: src/view/com/util/forms/PostDropdownBtn.tsx:187
msgid "Delete this post?" msgid "Delete this post?"
msgstr "" msgstr ""
@@ -691,7 +691,7 @@ msgstr ""
msgid "Discard draft" msgid "Discard draft"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:204 #: src/view/screens/Moderation.tsx:207
msgid "Discourage apps from showing my account to logged-out users" msgid "Discourage apps from showing my account to logged-out users"
msgstr "" msgstr ""
@@ -840,7 +840,7 @@ msgstr ""
msgid "Feed Preferences" msgid "Feed Preferences"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:64 #: src/view/shell/desktop/RightNav.tsx:65
#: src/view/shell/Drawer.tsx:292 #: src/view/shell/Drawer.tsx:292
msgid "Feedback" msgid "Feedback"
msgstr "" msgstr ""
@@ -936,7 +936,7 @@ msgstr ""
msgid "Gallery" msgid "Gallery"
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:175 #: src/view/com/modals/VerifyEmail.tsx:183
msgid "Get Started" msgid "Get Started"
msgstr "" msgstr ""
@@ -964,7 +964,7 @@ msgstr ""
msgid "Handle" msgid "Handle"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:93 #: src/view/shell/desktop/RightNav.tsx:94
#: src/view/shell/Drawer.tsx:302 #: src/view/shell/Drawer.tsx:302
msgid "Help" msgid "Help"
msgstr "" msgstr ""
@@ -1031,7 +1031,7 @@ msgstr ""
msgid "Hosting provider address" msgid "Hosting provider address"
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:200 #: src/view/com/modals/VerifyEmail.tsx:208
msgid "I have a code" msgid "I have a code"
msgstr "" msgstr ""
@@ -1047,7 +1047,7 @@ msgstr ""
msgid "Image alt text" msgid "Image alt text"
msgstr "" msgstr ""
#: src/view/com/util/UserAvatar.tsx:304 #: src/view/com/util/UserAvatar.tsx:308
#: src/view/com/util/UserBanner.tsx:116 #: src/view/com/util/UserBanner.tsx:116
msgid "Image options" msgid "Image options"
msgstr "" msgstr ""
@@ -1121,7 +1121,7 @@ msgstr ""
msgid "Learn more about this warning" msgid "Learn more about this warning"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:239 #: src/view/screens/Moderation.tsx:242
msgid "Learn more about what is public on Bluesky." msgid "Learn more about what is public on Bluesky."
msgstr "" msgstr ""
@@ -1138,7 +1138,7 @@ msgstr ""
msgid "Let's get your password reset!" msgid "Let's get your password reset!"
msgstr "" msgstr ""
#: src/view/com/util/UserAvatar.tsx:241 #: src/view/com/util/UserAvatar.tsx:245
#: src/view/com/util/UserBanner.tsx:60 #: src/view/com/util/UserBanner.tsx:60
msgid "Library" msgid "Library"
msgstr "" msgstr ""
@@ -1208,7 +1208,7 @@ msgstr ""
#~ msgid "Logged-out users" #~ msgid "Logged-out users"
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:134 #: src/view/screens/Moderation.tsx:136
msgid "Logged-out visibility" msgid "Logged-out visibility"
msgstr "" msgstr ""
@@ -1245,7 +1245,7 @@ msgstr ""
msgid "Message from server" msgid "Message from server"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:63 #: src/view/screens/Moderation.tsx:64
#: src/view/screens/Settings.tsx:563 #: src/view/screens/Settings.tsx:563
#: src/view/shell/desktop/LeftNav.tsx:391 #: src/view/shell/desktop/LeftNav.tsx:391
#: src/view/shell/Drawer.tsx:490 #: src/view/shell/Drawer.tsx:490
@@ -1253,7 +1253,7 @@ msgstr ""
msgid "Moderation" msgid "Moderation"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:93 #: src/view/screens/Moderation.tsx:95
msgid "Moderation lists" msgid "Moderation lists"
msgstr "" msgstr ""
@@ -1291,11 +1291,11 @@ msgstr ""
msgid "Mute these accounts?" msgid "Mute these accounts?"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Mute thread" msgid "Mute thread"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:107 #: src/view/screens/Moderation.tsx:109
msgid "Muted accounts" msgid "Muted accounts"
msgstr "" msgstr ""
@@ -1421,7 +1421,7 @@ msgstr ""
#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users." #~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users."
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:229 #: src/view/screens/Moderation.tsx:232
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
msgstr "" msgstr ""
@@ -1462,7 +1462,7 @@ msgstr ""
msgid "Opens configurable language settings" msgid "Opens configurable language settings"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:146 #: src/view/shell/desktop/RightNav.tsx:148
#: src/view/shell/Drawer.tsx:622 #: src/view/shell/Drawer.tsx:622
msgid "Opens list of invite codes" msgid "Opens list of invite codes"
msgstr "" msgstr ""
@@ -1611,7 +1611,7 @@ msgstr ""
msgid "Prioritize Your Follows" msgid "Prioritize Your Follows"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:75 #: src/view/shell/desktop/RightNav.tsx:76
msgid "Privacy" msgid "Privacy"
msgstr "" msgstr ""
@@ -1671,7 +1671,7 @@ msgstr ""
#: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/ListAddRemoveUsers.tsx:264
#: src/view/com/modals/SelfLabel.tsx:83 #: src/view/com/modals/SelfLabel.tsx:83
#: src/view/com/modals/UserAddRemoveLists.tsx:193 #: src/view/com/modals/UserAddRemoveLists.tsx:193
#: src/view/com/util/UserAvatar.tsx:278 #: src/view/com/util/UserAvatar.tsx:282
#: src/view/com/util/UserBanner.tsx:89 #: src/view/com/util/UserBanner.tsx:89
msgid "Remove" msgid "Remove"
msgstr "" msgstr ""
@@ -1744,7 +1744,7 @@ msgid "Report List"
msgstr "" msgstr ""
#: src/view/com/modals/report/SendReportButton.tsx:37 #: src/view/com/modals/report/SendReportButton.tsx:37
#: src/view/com/util/forms/PostDropdownBtn.tsx:162 #: src/view/com/util/forms/PostDropdownBtn.tsx:165
msgid "Report post" msgid "Report post"
msgstr "" msgstr ""
@@ -1898,7 +1898,7 @@ msgstr ""
msgid "Select your preferred language for translations in your feed." msgid "Select your preferred language for translations in your feed."
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:188 #: src/view/com/modals/VerifyEmail.tsx:196
msgid "Send Confirmation Email" msgid "Send Confirmation Email"
msgstr "" msgstr ""
@@ -1955,7 +1955,7 @@ msgid "Sexual activity or erotic nudity."
msgstr "" msgstr ""
#: src/view/com/profile/ProfileHeader.tsx:338 #: src/view/com/profile/ProfileHeader.tsx:338
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
#: src/view/screens/ProfileList.tsx:407 #: src/view/screens/ProfileList.tsx:407
msgid "Share" msgid "Share"
msgstr "" msgstr ""
@@ -2075,7 +2075,7 @@ msgstr ""
#: src/view/com/modals/AppealLabel.tsx:101 #: src/view/com/modals/AppealLabel.tsx:101
msgid "Submit" msgid "Submit"
msgstr ">>>>>>> cb8a33b6 (Fix translations)" msgstr "Submit"
#: src/view/screens/ProfileList.tsx:597 #: src/view/screens/ProfileList.tsx:597
msgid "Subscribe" msgid "Subscribe"
@@ -2110,7 +2110,7 @@ msgstr ""
msgid "Tall" msgid "Tall"
msgstr "" msgstr ""
#: src/view/shell/desktop/RightNav.tsx:84 #: src/view/shell/desktop/RightNav.tsx:85
msgid "Terms" msgid "Terms"
msgstr "" msgstr ""
@@ -2175,7 +2175,7 @@ msgstr ""
msgid "This information is not shared with other users." msgid "This information is not shared with other users."
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:105 #: src/view/com/modals/VerifyEmail.tsx:113
msgid "This is important in case you ever need to change your email or reset your password." msgid "This is important in case you ever need to change your email or reset your password."
msgstr "" msgstr ""
@@ -2212,9 +2212,9 @@ msgstr ""
msgid "Transformations" msgid "Transformations"
msgstr "" msgstr ""
#: src/view/com/post-thread/PostThreadItem.tsx:704
#: src/view/com/post-thread/PostThreadItem.tsx:706 #: src/view/com/post-thread/PostThreadItem.tsx:706
#: src/view/com/util/forms/PostDropdownBtn.tsx:98 #: src/view/com/post-thread/PostThreadItem.tsx:708
#: src/view/com/util/forms/PostDropdownBtn.tsx:101
msgid "Translate" msgid "Translate"
msgstr "" msgstr ""
@@ -2258,7 +2258,7 @@ msgstr ""
msgid "Unmute Account" msgid "Unmute Account"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Unmute thread" msgid "Unmute thread"
msgstr "" msgstr ""
@@ -2493,7 +2493,7 @@ msgstr ""
msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgid "Your email has been updated but not verified. As a next step, please verify your new email."
msgstr "" msgstr ""
#: src/view/com/modals/VerifyEmail.tsx:100 #: src/view/com/modals/VerifyEmail.tsx:108
msgid "Your email has not yet been verified. This is an important security step which we recommend." msgid "Your email has not yet been verified. This is an important security step which we recommend."
msgstr "" msgstr ""
@@ -2507,7 +2507,7 @@ msgid "Your hosting provider"
msgstr "" msgstr ""
#: src/view/screens/Settings.tsx:402 #: src/view/screens/Settings.tsx:402
#: src/view/shell/desktop/RightNav.tsx:127 #: src/view/shell/desktop/RightNav.tsx:129
#: src/view/shell/Drawer.tsx:636 #: src/view/shell/Drawer.tsx:636
msgid "Your invite codes are hidden when logged in using an App Password" msgid "Your invite codes are hidden when logged in using an App Password"
msgstr "" msgstr ""
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+42 -42
View File
@@ -21,7 +21,7 @@ msgstr ""
#~ msgid ". This warning is only available for posts with media attached." #~ msgid ". This warning is only available for posts with media attached."
#~ msgstr "यह चेतावनी केवल मीडिया वाले पोस्ट के लिए उपलब्ध है।" #~ msgstr "यह चेतावनी केवल मीडिया वाले पोस्ट के लिए उपलब्ध है।"
#: src/view/shell/desktop/RightNav.tsx:158 #: src/view/shell/desktop/RightNav.tsx:160
msgid "{0, plural, one {# invite code available} other {# invite codes available}}" msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
msgstr "" msgstr ""
@@ -33,7 +33,7 @@ msgstr "{0}"
msgid "{0} {purposeLabel} List" msgid "{0} {purposeLabel} List"
msgstr "{0} {purposeLabel} सूची" msgstr "{0} {purposeLabel} सूची"
#: src/view/shell/desktop/RightNav.tsx:141 #: src/view/shell/desktop/RightNav.tsx:143
msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}"
msgstr "" msgstr ""
@@ -175,7 +175,7 @@ msgstr "वैकल्पिक पाठ"
msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone."
msgstr "ऑल्ट टेक्स्ट अंधा और कम दृश्य लोगों के लिए छवियों का वर्णन करता है, और हर किसी को संदर्भ देने में मदद करता है।।" msgstr "ऑल्ट टेक्स्ट अंधा और कम दृश्य लोगों के लिए छवियों का वर्णन करता है, और हर किसी को संदर्भ देने में मदद करता है।।"
#: src/view/com/modals/VerifyEmail.tsx:110 #: src/view/com/modals/VerifyEmail.tsx:118
msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below."
msgstr "{0} को ईमेल भेजा गया है। इसमें एक OTP कोड शामिल है जिसे आप नीचे दर्ज कर सकते हैं।।" msgstr "{0} को ईमेल भेजा गया है। इसमें एक OTP कोड शामिल है जिसे आप नीचे दर्ज कर सकते हैं।।"
@@ -232,7 +232,7 @@ msgstr "क्या आप वाकई इस ड्राफ्ट को ह
msgid "Are you sure?" msgid "Are you sure?"
msgstr "क्या आप वास्तव में इसे करना चाहते हैं?" msgstr "क्या आप वास्तव में इसे करना चाहते हैं?"
#: src/view/com/util/forms/PostDropdownBtn.tsx:185 #: src/view/com/util/forms/PostDropdownBtn.tsx:188
msgid "Are you sure? This cannot be undone." msgid "Are you sure? This cannot be undone."
msgstr "क्या आप वास्तव में इसे करना चाहते हैं? इसे असंपादित नहीं किया जा सकता है।" msgstr "क्या आप वास्तव में इसे करना चाहते हैं? इसे असंपादित नहीं किया जा सकता है।"
@@ -287,7 +287,7 @@ msgstr ""
msgid "Block these accounts?" msgid "Block these accounts?"
msgstr "खाता ब्लॉक करें?" msgstr "खाता ब्लॉक करें?"
#: src/view/screens/Moderation.tsx:121 #: src/view/screens/Moderation.tsx:123
msgid "Blocked accounts" msgid "Blocked accounts"
msgstr "ब्लॉक किए गए खाते" msgstr "ब्लॉक किए गए खाते"
@@ -331,7 +331,7 @@ msgstr "Bluesky सार्वजनिक है।।"
msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon."
msgstr "ब्लूस्की एक स्वस्थ समुदाय बनाने के लिए आमंत्रित करता है। यदि आप किसी को आमंत्रित नहीं करते हैं, तो आप प्रतीक्षा सूची के लिए साइन अप कर सकते हैं और हम जल्द ही एक भेज देंगे।।" msgstr "ब्लूस्की एक स्वस्थ समुदाय बनाने के लिए आमंत्रित करता है। यदि आप किसी को आमंत्रित नहीं करते हैं, तो आप प्रतीक्षा सूची के लिए साइन अप कर सकते हैं और हम जल्द ही एक भेज देंगे।।"
#: src/view/screens/Moderation.tsx:222 #: src/view/screens/Moderation.tsx:225
msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private."
msgstr "" msgstr ""
@@ -344,7 +344,7 @@ msgid "Build version {0} {1}"
msgstr "Build version {0} {1}" msgstr "Build version {0} {1}"
#: src/view/com/composer/photos/OpenCameraBtn.tsx:60 #: src/view/com/composer/photos/OpenCameraBtn.tsx:60
#: src/view/com/util/UserAvatar.tsx:217 #: src/view/com/util/UserAvatar.tsx:221
#: src/view/com/util/UserBanner.tsx:38 #: src/view/com/util/UserBanner.tsx:38
msgid "Camera" msgid "Camera"
msgstr "कैमरा" msgstr "कैमरा"
@@ -420,7 +420,7 @@ msgstr "हैंडल बदलें"
msgid "Change Handle" msgid "Change Handle"
msgstr "हैंडल बदलें" msgstr "हैंडल बदलें"
#: src/view/com/modals/VerifyEmail.tsx:133 #: src/view/com/modals/VerifyEmail.tsx:141
msgid "Change my email" msgid "Change my email"
msgstr "मेरा ईमेल बदलें" msgstr "मेरा ईमेल बदलें"
@@ -504,7 +504,7 @@ msgstr "जवाब लिखो"
#: src/view/com/modals/AppealLabel.tsx:98 #: src/view/com/modals/AppealLabel.tsx:98
#: src/view/com/modals/Confirm.tsx:75 #: src/view/com/modals/Confirm.tsx:75
#: src/view/com/modals/SelfLabel.tsx:154 #: src/view/com/modals/SelfLabel.tsx:154
#: src/view/com/modals/VerifyEmail.tsx:217 #: src/view/com/modals/VerifyEmail.tsx:225
#: src/view/screens/PreferencesHomeFeed.tsx:299 #: src/view/screens/PreferencesHomeFeed.tsx:299
#: src/view/screens/PreferencesThreads.tsx:153 #: src/view/screens/PreferencesThreads.tsx:153
msgid "Confirm" msgid "Confirm"
@@ -525,7 +525,7 @@ msgstr "खाते को हटा दें"
#: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/ChangeEmail.tsx:157
#: src/view/com/modals/DeleteAccount.tsx:176 #: src/view/com/modals/DeleteAccount.tsx:176
#: src/view/com/modals/VerifyEmail.tsx:151 #: src/view/com/modals/VerifyEmail.tsx:159
msgid "Confirmation code" msgid "Confirmation code"
msgstr "OTP कोड" msgstr "OTP कोड"
@@ -534,7 +534,7 @@ msgstr "OTP कोड"
msgid "Connecting..." msgid "Connecting..."
msgstr "कनेक्टिंग ..।" msgstr "कनेक्टिंग ..।"
#: src/view/screens/Moderation.tsx:79 #: src/view/screens/Moderation.tsx:81
msgid "Content filtering" msgid "Content filtering"
msgstr "सामग्री फ़िल्टरिंग" msgstr "सामग्री फ़िल्टरिंग"
@@ -573,7 +573,7 @@ msgstr "कॉपी"
msgid "Copy link to list" msgid "Copy link to list"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
msgid "Copy link to post" msgid "Copy link to post"
msgstr "" msgstr ""
@@ -581,7 +581,7 @@ msgstr ""
msgid "Copy link to profile" msgid "Copy link to profile"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:112 #: src/view/com/util/forms/PostDropdownBtn.tsx:115
msgid "Copy post text" msgid "Copy post text"
msgstr "पोस्ट टेक्स्ट कॉपी करें" msgstr "पोस्ट टेक्स्ट कॉपी करें"
@@ -652,11 +652,11 @@ msgstr "मेरा खाता हटाएं"
msgid "Delete my account…" msgid "Delete my account…"
msgstr "मेरा खाता हटाएं…" msgstr "मेरा खाता हटाएं…"
#: src/view/com/util/forms/PostDropdownBtn.tsx:180 #: src/view/com/util/forms/PostDropdownBtn.tsx:183
msgid "Delete post" msgid "Delete post"
msgstr "पोस्ट को हटाएं" msgstr "पोस्ट को हटाएं"
#: src/view/com/util/forms/PostDropdownBtn.tsx:184 #: src/view/com/util/forms/PostDropdownBtn.tsx:187
msgid "Delete this post?" msgid "Delete this post?"
msgstr "इस पोस्ट को डीलीट करें?" msgstr "इस पोस्ट को डीलीट करें?"
@@ -687,7 +687,7 @@ msgstr ""
msgid "Discard draft" msgid "Discard draft"
msgstr "ड्राफ्ट हटाएं" msgstr "ड्राफ्ट हटाएं"
#: src/view/screens/Moderation.tsx:204 #: src/view/screens/Moderation.tsx:207
msgid "Discourage apps from showing my account to logged-out users" msgid "Discourage apps from showing my account to logged-out users"
msgstr "" msgstr ""
@@ -836,7 +836,7 @@ msgstr "फ़ीड ऑफ़लाइन है"
msgid "Feed Preferences" msgid "Feed Preferences"
msgstr "फ़ीड प्राथमिकता" msgstr "फ़ीड प्राथमिकता"
#: src/view/shell/desktop/RightNav.tsx:64 #: src/view/shell/desktop/RightNav.tsx:65
#: src/view/shell/Drawer.tsx:292 #: src/view/shell/Drawer.tsx:292
msgid "Feedback" msgid "Feedback"
msgstr "प्रतिक्रिया" msgstr "प्रतिक्रिया"
@@ -928,7 +928,7 @@ msgstr "पासवर्ड भूल गए"
msgid "Gallery" msgid "Gallery"
msgstr "गैलरी" msgstr "गैलरी"
#: src/view/com/modals/VerifyEmail.tsx:175 #: src/view/com/modals/VerifyEmail.tsx:183
msgid "Get Started" msgid "Get Started"
msgstr "प्रारंभ करें" msgstr "प्रारंभ करें"
@@ -956,7 +956,7 @@ msgstr "अगला"
msgid "Handle" msgid "Handle"
msgstr "हैंडल" msgstr "हैंडल"
#: src/view/shell/desktop/RightNav.tsx:93 #: src/view/shell/desktop/RightNav.tsx:94
#: src/view/shell/Drawer.tsx:302 #: src/view/shell/Drawer.tsx:302
msgid "Help" msgid "Help"
msgstr "सहायता" msgstr "सहायता"
@@ -1023,7 +1023,7 @@ msgstr "होस्टिंग प्रदाता"
msgid "Hosting provider address" msgid "Hosting provider address"
msgstr "होस्टिंग प्रदाता पता" msgstr "होस्टिंग प्रदाता पता"
#: src/view/com/modals/VerifyEmail.tsx:200 #: src/view/com/modals/VerifyEmail.tsx:208
msgid "I have a code" msgid "I have a code"
msgstr "मेरे पास एक OTP कोड है" msgstr "मेरे पास एक OTP कोड है"
@@ -1039,7 +1039,7 @@ msgstr "यदि किसी को चुना जाता है, तो
msgid "Image alt text" msgid "Image alt text"
msgstr "छवि alt पाठ" msgstr "छवि alt पाठ"
#: src/view/com/util/UserAvatar.tsx:304 #: src/view/com/util/UserAvatar.tsx:308
#: src/view/com/util/UserBanner.tsx:116 #: src/view/com/util/UserBanner.tsx:116
msgid "Image options" msgid "Image options"
msgstr "छवि विकल्प" msgstr "छवि विकल्प"
@@ -1113,7 +1113,7 @@ msgstr "अधिक जानें"
msgid "Learn more about this warning" msgid "Learn more about this warning"
msgstr "इस चेतावनी के बारे में अधिक जानें" msgstr "इस चेतावनी के बारे में अधिक जानें"
#: src/view/screens/Moderation.tsx:239 #: src/view/screens/Moderation.tsx:242
msgid "Learn more about what is public on Bluesky." msgid "Learn more about what is public on Bluesky."
msgstr "" msgstr ""
@@ -1130,7 +1130,7 @@ msgstr "लीविंग Bluesky"
msgid "Let's get your password reset!" msgid "Let's get your password reset!"
msgstr "चलो अपना पासवर्ड रीसेट करें!" msgstr "चलो अपना पासवर्ड रीसेट करें!"
#: src/view/com/util/UserAvatar.tsx:241 #: src/view/com/util/UserAvatar.tsx:245
#: src/view/com/util/UserBanner.tsx:60 #: src/view/com/util/UserBanner.tsx:60
msgid "Library" msgid "Library"
msgstr "चित्र पुस्तकालय" msgstr "चित्र पुस्तकालय"
@@ -1200,7 +1200,7 @@ msgstr "स्थानीय देव सर्वर"
#~ msgid "Logged-out users" #~ msgid "Logged-out users"
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:134 #: src/view/screens/Moderation.tsx:136
msgid "Logged-out visibility" msgid "Logged-out visibility"
msgstr "" msgstr ""
@@ -1237,7 +1237,7 @@ msgstr "मेनू"
msgid "Message from server" msgid "Message from server"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:63 #: src/view/screens/Moderation.tsx:64
#: src/view/screens/Settings.tsx:563 #: src/view/screens/Settings.tsx:563
#: src/view/shell/desktop/LeftNav.tsx:391 #: src/view/shell/desktop/LeftNav.tsx:391
#: src/view/shell/Drawer.tsx:490 #: src/view/shell/Drawer.tsx:490
@@ -1245,7 +1245,7 @@ msgstr ""
msgid "Moderation" msgid "Moderation"
msgstr "मॉडरेशन" msgstr "मॉडरेशन"
#: src/view/screens/Moderation.tsx:93 #: src/view/screens/Moderation.tsx:95
msgid "Moderation lists" msgid "Moderation lists"
msgstr "मॉडरेशन सूचियाँ" msgstr "मॉडरेशन सूचियाँ"
@@ -1283,11 +1283,11 @@ msgstr ""
msgid "Mute these accounts?" msgid "Mute these accounts?"
msgstr "इन खातों को म्यूट करें?" msgstr "इन खातों को म्यूट करें?"
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Mute thread" msgid "Mute thread"
msgstr "थ्रेड म्यूट करें" msgstr "थ्रेड म्यूट करें"
#: src/view/screens/Moderation.tsx:107 #: src/view/screens/Moderation.tsx:109
msgid "Muted accounts" msgid "Muted accounts"
msgstr "म्यूट किए गए खाते" msgstr "म्यूट किए गए खाते"
@@ -1413,7 +1413,7 @@ msgstr "लागू नहीं।"
#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users." #~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users."
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:229 #: src/view/screens/Moderation.tsx:232
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
msgstr "" msgstr ""
@@ -1454,7 +1454,7 @@ msgstr "ओपन नेविगेशन"
msgid "Opens configurable language settings" msgid "Opens configurable language settings"
msgstr "भाषा सेटिंग्स खोलें" msgstr "भाषा सेटिंग्स खोलें"
#: src/view/shell/desktop/RightNav.tsx:146 #: src/view/shell/desktop/RightNav.tsx:148
#: src/view/shell/Drawer.tsx:622 #: src/view/shell/Drawer.tsx:622
msgid "Opens list of invite codes" msgid "Opens list of invite codes"
msgstr "" msgstr ""
@@ -1603,7 +1603,7 @@ msgstr "प्राथमिक भाषा"
msgid "Prioritize Your Follows" msgid "Prioritize Your Follows"
msgstr "अपने फ़ॉलोअर्स को प्राथमिकता दें" msgstr "अपने फ़ॉलोअर्स को प्राथमिकता दें"
#: src/view/shell/desktop/RightNav.tsx:75 #: src/view/shell/desktop/RightNav.tsx:76
msgid "Privacy" msgid "Privacy"
msgstr "गोपनीयता" msgstr "गोपनीयता"
@@ -1663,7 +1663,7 @@ msgstr "अनुशंसित लोग"
#: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/ListAddRemoveUsers.tsx:264
#: src/view/com/modals/SelfLabel.tsx:83 #: src/view/com/modals/SelfLabel.tsx:83
#: src/view/com/modals/UserAddRemoveLists.tsx:193 #: src/view/com/modals/UserAddRemoveLists.tsx:193
#: src/view/com/util/UserAvatar.tsx:278 #: src/view/com/util/UserAvatar.tsx:282
#: src/view/com/util/UserBanner.tsx:89 #: src/view/com/util/UserBanner.tsx:89
msgid "Remove" msgid "Remove"
msgstr "निकालें" msgstr "निकालें"
@@ -1736,7 +1736,7 @@ msgid "Report List"
msgstr "रिपोर्ट सूची" msgstr "रिपोर्ट सूची"
#: src/view/com/modals/report/SendReportButton.tsx:37 #: src/view/com/modals/report/SendReportButton.tsx:37
#: src/view/com/util/forms/PostDropdownBtn.tsx:162 #: src/view/com/util/forms/PostDropdownBtn.tsx:165
msgid "Report post" msgid "Report post"
msgstr "रिपोर्ट पोस्ट" msgstr "रिपोर्ट पोस्ट"
@@ -1890,7 +1890,7 @@ msgstr "ऐप में प्रदर्शित होने वाले
msgid "Select your preferred language for translations in your feed." msgid "Select your preferred language for translations in your feed."
msgstr "अपने फ़ीड में अनुवाद के लिए अपनी पसंदीदा भाषा चुनें।" msgstr "अपने फ़ीड में अनुवाद के लिए अपनी पसंदीदा भाषा चुनें।"
#: src/view/com/modals/VerifyEmail.tsx:188 #: src/view/com/modals/VerifyEmail.tsx:196
msgid "Send Confirmation Email" msgid "Send Confirmation Email"
msgstr "पुष्टिकरण ईमेल भेजें" msgstr "पुष्टिकरण ईमेल भेजें"
@@ -1947,7 +1947,7 @@ msgid "Sexual activity or erotic nudity."
msgstr "यौन गतिविधि या कामुक नग्नता।।" msgstr "यौन गतिविधि या कामुक नग्नता।।"
#: src/view/com/profile/ProfileHeader.tsx:338 #: src/view/com/profile/ProfileHeader.tsx:338
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
#: src/view/screens/ProfileList.tsx:407 #: src/view/screens/ProfileList.tsx:407
msgid "Share" msgid "Share"
msgstr "शेयर" msgstr "शेयर"
@@ -2102,7 +2102,7 @@ msgstr "सिस्टम लॉग"
msgid "Tall" msgid "Tall"
msgstr "लंबा" msgstr "लंबा"
#: src/view/shell/desktop/RightNav.tsx:84 #: src/view/shell/desktop/RightNav.tsx:85
msgid "Terms" msgid "Terms"
msgstr "शर्तें" msgstr "शर्तें"
@@ -2167,7 +2167,7 @@ msgstr ""
msgid "This information is not shared with other users." msgid "This information is not shared with other users."
msgstr "यह जानकारी अन्य उपयोगकर्ताओं के साथ साझा नहीं की जाती है।।" msgstr "यह जानकारी अन्य उपयोगकर्ताओं के साथ साझा नहीं की जाती है।।"
#: src/view/com/modals/VerifyEmail.tsx:105 #: src/view/com/modals/VerifyEmail.tsx:113
msgid "This is important in case you ever need to change your email or reset your password." msgid "This is important in case you ever need to change your email or reset your password."
msgstr "अगर आपको कभी अपना ईमेल बदलने या पासवर्ड रीसेट करने की आवश्यकता है तो यह महत्वपूर्ण है।।" msgstr "अगर आपको कभी अपना ईमेल बदलने या पासवर्ड रीसेट करने की आवश्यकता है तो यह महत्वपूर्ण है।।"
@@ -2204,9 +2204,9 @@ msgstr "ड्रॉपडाउन टॉगल करें"
msgid "Transformations" msgid "Transformations"
msgstr "परिवर्तन" msgstr "परिवर्तन"
#: src/view/com/post-thread/PostThreadItem.tsx:704
#: src/view/com/post-thread/PostThreadItem.tsx:706 #: src/view/com/post-thread/PostThreadItem.tsx:706
#: src/view/com/util/forms/PostDropdownBtn.tsx:98 #: src/view/com/post-thread/PostThreadItem.tsx:708
#: src/view/com/util/forms/PostDropdownBtn.tsx:101
msgid "Translate" msgid "Translate"
msgstr "अनुवाद" msgstr "अनुवाद"
@@ -2250,7 +2250,7 @@ msgstr ""
msgid "Unmute Account" msgid "Unmute Account"
msgstr "अनम्यूट खाता" msgstr "अनम्यूट खाता"
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Unmute thread" msgid "Unmute thread"
msgstr "थ्रेड को अनम्यूट करें" msgstr "थ्रेड को अनम्यूट करें"
@@ -2485,7 +2485,7 @@ msgstr "आपका ईमेल बचाया गया है! हम ज
msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgid "Your email has been updated but not verified. As a next step, please verify your new email."
msgstr "आपका ईमेल अद्यतन किया गया है लेकिन सत्यापित नहीं किया गया है। अगले चरण के रूप में, कृपया अपना नया ईमेल सत्यापित करें।।" msgstr "आपका ईमेल अद्यतन किया गया है लेकिन सत्यापित नहीं किया गया है। अगले चरण के रूप में, कृपया अपना नया ईमेल सत्यापित करें।।"
#: src/view/com/modals/VerifyEmail.tsx:100 #: src/view/com/modals/VerifyEmail.tsx:108
msgid "Your email has not yet been verified. This is an important security step which we recommend." msgid "Your email has not yet been verified. This is an important security step which we recommend."
msgstr "आपका ईमेल अभी तक सत्यापित नहीं हुआ है। यह एक महत्वपूर्ण सुरक्षा कदम है जिसे हम अनुशंसा करते हैं।।" msgstr "आपका ईमेल अभी तक सत्यापित नहीं हुआ है। यह एक महत्वपूर्ण सुरक्षा कदम है जिसे हम अनुशंसा करते हैं।।"
@@ -2499,7 +2499,7 @@ msgid "Your hosting provider"
msgstr "आपका होस्टिंग प्रदाता" msgstr "आपका होस्टिंग प्रदाता"
#: src/view/screens/Settings.tsx:402 #: src/view/screens/Settings.tsx:402
#: src/view/shell/desktop/RightNav.tsx:127 #: src/view/shell/desktop/RightNav.tsx:129
#: src/view/shell/Drawer.tsx:636 #: src/view/shell/Drawer.tsx:636
msgid "Your invite codes are hidden when logged in using an App Password" msgid "Your invite codes are hidden when logged in using an App Password"
msgstr "" msgstr ""
+42 -42
View File
@@ -13,7 +13,7 @@ msgstr ""
"Language-Team: \n" "Language-Team: \n"
"Plural-Forms: \n" "Plural-Forms: \n"
#: src/view/shell/desktop/RightNav.tsx:158 #: src/view/shell/desktop/RightNav.tsx:160
msgid "{0, plural, one {# invite code available} other {# invite codes available}}" msgid "{0, plural, one {# invite code available} other {# invite codes available}}"
msgstr "" msgstr ""
@@ -25,7 +25,7 @@ msgstr "{0}"
msgid "{0} {purposeLabel} List" msgid "{0} {purposeLabel} List"
msgstr "{0} {purposeLabel} リスト" msgstr "{0} {purposeLabel} リスト"
#: src/view/shell/desktop/RightNav.tsx:141 #: src/view/shell/desktop/RightNav.tsx:143
msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}" msgid "{invitesAvailable, plural, one {Invite codes: # available} other {Invite codes: # available}}"
msgstr "" msgstr ""
@@ -155,7 +155,7 @@ msgstr "ALTテキスト"
msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone." msgid "Alt text describes images for blind and low-vision users, and helps give context to everyone."
msgstr "ALTテキストは、視覚障害者や低視力者のために画像を説明し、すべての人に文脈を与えるのに役立ちます。" msgstr "ALTテキストは、視覚障害者や低視力者のために画像を説明し、すべての人に文脈を与えるのに役立ちます。"
#: src/view/com/modals/VerifyEmail.tsx:110 #: src/view/com/modals/VerifyEmail.tsx:118
msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below." msgid "An email has been sent to {0}. It includes a confirmation code which you can enter below."
msgstr "Eメールが{0}に送信されました。以下に入力できる確認コードが含まれています。" msgstr "Eメールが{0}に送信されました。以下に入力できる確認コードが含まれています。"
@@ -208,7 +208,7 @@ msgstr "本当にこの下書きを破棄しますか?"
msgid "Are you sure?" msgid "Are you sure?"
msgstr "本当ですか?" msgstr "本当ですか?"
#: src/view/com/util/forms/PostDropdownBtn.tsx:185 #: src/view/com/util/forms/PostDropdownBtn.tsx:188
msgid "Are you sure? This cannot be undone." msgid "Are you sure? This cannot be undone."
msgstr "本当ですか?これは元に戻せません。" msgstr "本当ですか?これは元に戻せません。"
@@ -259,7 +259,7 @@ msgstr ""
msgid "Block these accounts?" msgid "Block these accounts?"
msgstr "これらのアカウントをブロックしますか?" msgstr "これらのアカウントをブロックしますか?"
#: src/view/screens/Moderation.tsx:121 #: src/view/screens/Moderation.tsx:123
msgid "Blocked accounts" msgid "Blocked accounts"
msgstr "ブロックされたブロック" msgstr "ブロックされたブロック"
@@ -303,7 +303,7 @@ msgstr "Blueskyはオープンです。"
msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon." msgid "Bluesky uses invites to build a healthier community. If you don't know anybody with an invite, you can sign up for the waitlist and we'll send one soon."
msgstr "Blueskyはより健全なコミュニティを構築するために招待状を使用します。招待状をお持ちでない方の場合、waitlistに申し込めば招待状をお送りします。" msgstr "Blueskyはより健全なコミュニティを構築するために招待状を使用します。招待状をお持ちでない方の場合、waitlistに申し込めば招待状をお送りします。"
#: src/view/screens/Moderation.tsx:222 #: src/view/screens/Moderation.tsx:225
msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private." msgid "Bluesky will not show your profile and posts to logged-out users. Other apps may not honor this request. This does not make your account private."
msgstr "" msgstr ""
@@ -316,7 +316,7 @@ msgid "Build version {0} {1}"
msgstr "ビルドバージョン {0} {1}" msgstr "ビルドバージョン {0} {1}"
#: src/view/com/composer/photos/OpenCameraBtn.tsx:60 #: src/view/com/composer/photos/OpenCameraBtn.tsx:60
#: src/view/com/util/UserAvatar.tsx:217 #: src/view/com/util/UserAvatar.tsx:221
#: src/view/com/util/UserBanner.tsx:38 #: src/view/com/util/UserBanner.tsx:38
msgid "Camera" msgid "Camera"
msgstr "カメラ" msgstr "カメラ"
@@ -392,7 +392,7 @@ msgstr "ハンドルを変更"
msgid "Change Handle" msgid "Change Handle"
msgstr "ハンドルを変更" msgstr "ハンドルを変更"
#: src/view/com/modals/VerifyEmail.tsx:133 #: src/view/com/modals/VerifyEmail.tsx:141
msgid "Change my email" msgid "Change my email"
msgstr "Eメールの変更" msgstr "Eメールの変更"
@@ -476,7 +476,7 @@ msgstr "返信を作成"
#: src/view/com/modals/AppealLabel.tsx:98 #: src/view/com/modals/AppealLabel.tsx:98
#: src/view/com/modals/Confirm.tsx:75 #: src/view/com/modals/Confirm.tsx:75
#: src/view/com/modals/SelfLabel.tsx:154 #: src/view/com/modals/SelfLabel.tsx:154
#: src/view/com/modals/VerifyEmail.tsx:217 #: src/view/com/modals/VerifyEmail.tsx:225
#: src/view/screens/PreferencesHomeFeed.tsx:299 #: src/view/screens/PreferencesHomeFeed.tsx:299
#: src/view/screens/PreferencesThreads.tsx:153 #: src/view/screens/PreferencesThreads.tsx:153
msgid "Confirm" msgid "Confirm"
@@ -497,7 +497,7 @@ msgstr "アカウントの削除を確認"
#: src/view/com/modals/ChangeEmail.tsx:157 #: src/view/com/modals/ChangeEmail.tsx:157
#: src/view/com/modals/DeleteAccount.tsx:176 #: src/view/com/modals/DeleteAccount.tsx:176
#: src/view/com/modals/VerifyEmail.tsx:151 #: src/view/com/modals/VerifyEmail.tsx:159
msgid "Confirmation code" msgid "Confirmation code"
msgstr "確認コード" msgstr "確認コード"
@@ -506,7 +506,7 @@ msgstr "確認コード"
msgid "Connecting..." msgid "Connecting..."
msgstr "接続中..." msgstr "接続中..."
#: src/view/screens/Moderation.tsx:79 #: src/view/screens/Moderation.tsx:81
msgid "Content filtering" msgid "Content filtering"
msgstr "コンテンツフィルタリング" msgstr "コンテンツフィルタリング"
@@ -545,7 +545,7 @@ msgstr "コピー"
msgid "Copy link to list" msgid "Copy link to list"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
msgid "Copy link to post" msgid "Copy link to post"
msgstr "" msgstr ""
@@ -553,7 +553,7 @@ msgstr ""
msgid "Copy link to profile" msgid "Copy link to profile"
msgstr "" msgstr ""
#: src/view/com/util/forms/PostDropdownBtn.tsx:112 #: src/view/com/util/forms/PostDropdownBtn.tsx:115
msgid "Copy post text" msgid "Copy post text"
msgstr "投稿テキストをコピー" msgstr "投稿テキストをコピー"
@@ -620,11 +620,11 @@ msgstr "マイアカウントを削除"
msgid "Delete my account…" msgid "Delete my account…"
msgstr "マイアカウントを削除…" msgstr "マイアカウントを削除…"
#: src/view/com/util/forms/PostDropdownBtn.tsx:180 #: src/view/com/util/forms/PostDropdownBtn.tsx:183
msgid "Delete post" msgid "Delete post"
msgstr "投稿を削除" msgstr "投稿を削除"
#: src/view/com/util/forms/PostDropdownBtn.tsx:184 #: src/view/com/util/forms/PostDropdownBtn.tsx:187
msgid "Delete this post?" msgid "Delete this post?"
msgstr "この投稿を削除しますか?" msgstr "この投稿を削除しますか?"
@@ -655,7 +655,7 @@ msgstr "破棄"
msgid "Discard draft" msgid "Discard draft"
msgstr "ドラフトを破棄" msgstr "ドラフトを破棄"
#: src/view/screens/Moderation.tsx:204 #: src/view/screens/Moderation.tsx:207
msgid "Discourage apps from showing my account to logged-out users" msgid "Discourage apps from showing my account to logged-out users"
msgstr "" msgstr ""
@@ -804,7 +804,7 @@ msgstr "フィードはオフライン"
msgid "Feed Preferences" msgid "Feed Preferences"
msgstr "フィード設定" msgstr "フィード設定"
#: src/view/shell/desktop/RightNav.tsx:64 #: src/view/shell/desktop/RightNav.tsx:65
#: src/view/shell/Drawer.tsx:292 #: src/view/shell/Drawer.tsx:292
msgid "Feedback" msgid "Feedback"
msgstr "フィードバック" msgstr "フィードバック"
@@ -896,7 +896,7 @@ msgstr "パスワードを失念"
msgid "Gallery" msgid "Gallery"
msgstr "ギャラリー" msgstr "ギャラリー"
#: src/view/com/modals/VerifyEmail.tsx:175 #: src/view/com/modals/VerifyEmail.tsx:183
msgid "Get Started" msgid "Get Started"
msgstr "はじめに" msgstr "はじめに"
@@ -924,7 +924,7 @@ msgstr "次へ"
msgid "Handle" msgid "Handle"
msgstr "ハンドル" msgstr "ハンドル"
#: src/view/shell/desktop/RightNav.tsx:93 #: src/view/shell/desktop/RightNav.tsx:94
#: src/view/shell/Drawer.tsx:302 #: src/view/shell/Drawer.tsx:302
msgid "Help" msgid "Help"
msgstr "ヘルプ" msgstr "ヘルプ"
@@ -983,7 +983,7 @@ msgstr "ホスティングプロバイダー"
msgid "Hosting provider address" msgid "Hosting provider address"
msgstr "ホスティングプロバイダーアドレス" msgstr "ホスティングプロバイダーアドレス"
#: src/view/com/modals/VerifyEmail.tsx:200 #: src/view/com/modals/VerifyEmail.tsx:208
msgid "I have a code" msgid "I have a code"
msgstr "コードを持っている" msgstr "コードを持っている"
@@ -999,7 +999,7 @@ msgstr "選択されていない場合は、すべての年齢に適していま
msgid "Image alt text" msgid "Image alt text"
msgstr "画像のALTテキスト" msgstr "画像のALTテキスト"
#: src/view/com/util/UserAvatar.tsx:304 #: src/view/com/util/UserAvatar.tsx:308
#: src/view/com/util/UserBanner.tsx:116 #: src/view/com/util/UserBanner.tsx:116
msgid "Image options" msgid "Image options"
msgstr "イメージオプション" msgstr "イメージオプション"
@@ -1068,7 +1068,7 @@ msgstr "詳細"
msgid "Learn more about this warning" msgid "Learn more about this warning"
msgstr "この警告の詳細" msgstr "この警告の詳細"
#: src/view/screens/Moderation.tsx:239 #: src/view/screens/Moderation.tsx:242
msgid "Learn more about what is public on Bluesky." msgid "Learn more about what is public on Bluesky."
msgstr "" msgstr ""
@@ -1085,7 +1085,7 @@ msgstr "Blueskyから離れる"
msgid "Let's get your password reset!" msgid "Let's get your password reset!"
msgstr "パスワードをリセットしましょう!" msgstr "パスワードをリセットしましょう!"
#: src/view/com/util/UserAvatar.tsx:241 #: src/view/com/util/UserAvatar.tsx:245
#: src/view/com/util/UserBanner.tsx:60 #: src/view/com/util/UserBanner.tsx:60
msgid "Library" msgid "Library"
msgstr "ライブラリー" msgstr "ライブラリー"
@@ -1147,7 +1147,7 @@ msgstr "ローカル開発者サーバー"
#~ msgid "Logged-out users" #~ msgid "Logged-out users"
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:134 #: src/view/screens/Moderation.tsx:136
msgid "Logged-out visibility" msgid "Logged-out visibility"
msgstr "" msgstr ""
@@ -1184,7 +1184,7 @@ msgstr "メニュー"
msgid "Message from server" msgid "Message from server"
msgstr "" msgstr ""
#: src/view/screens/Moderation.tsx:63 #: src/view/screens/Moderation.tsx:64
#: src/view/screens/Settings.tsx:563 #: src/view/screens/Settings.tsx:563
#: src/view/shell/desktop/LeftNav.tsx:391 #: src/view/shell/desktop/LeftNav.tsx:391
#: src/view/shell/Drawer.tsx:490 #: src/view/shell/Drawer.tsx:490
@@ -1192,7 +1192,7 @@ msgstr ""
msgid "Moderation" msgid "Moderation"
msgstr "モデレート" msgstr "モデレート"
#: src/view/screens/Moderation.tsx:93 #: src/view/screens/Moderation.tsx:95
msgid "Moderation lists" msgid "Moderation lists"
msgstr "モデレートリスト" msgstr "モデレートリスト"
@@ -1226,11 +1226,11 @@ msgstr ""
msgid "Mute these accounts?" msgid "Mute these accounts?"
msgstr "これらのアカウントをミュートしますか?" msgstr "これらのアカウントをミュートしますか?"
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Mute thread" msgid "Mute thread"
msgstr "スレッドをミュート" msgstr "スレッドをミュート"
#: src/view/screens/Moderation.tsx:107 #: src/view/screens/Moderation.tsx:109
msgid "Muted accounts" msgid "Muted accounts"
msgstr "ミュート済みアカウント" msgstr "ミュート済みアカウント"
@@ -1343,7 +1343,7 @@ msgstr "該当なし。"
#~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users." #~ msgid "Note: Bluesky is an open and public network, and enabling this will not make your profile private or limit the ability of logged in users to see your posts. This setting only limits the visibility of posts on the Bluesky app and website; third-party apps that display Bluesky content may not respect this setting, and could show your content to logged-out users."
#~ msgstr "" #~ msgstr ""
#: src/view/screens/Moderation.tsx:229 #: src/view/screens/Moderation.tsx:232
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites." msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
msgstr "" msgstr ""
@@ -1380,7 +1380,7 @@ msgstr "ナビゲーションを開く"
msgid "Opens configurable language settings" msgid "Opens configurable language settings"
msgstr "構成可能な言語設定を開く" msgstr "構成可能な言語設定を開く"
#: src/view/shell/desktop/RightNav.tsx:146 #: src/view/shell/desktop/RightNav.tsx:148
#: src/view/shell/Drawer.tsx:622 #: src/view/shell/Drawer.tsx:622
msgid "Opens list of invite codes" msgid "Opens list of invite codes"
msgstr "" msgstr ""
@@ -1529,7 +1529,7 @@ msgstr "第一言語"
msgid "Prioritize Your Follows" msgid "Prioritize Your Follows"
msgstr "フォローの優先順位付け" msgstr "フォローの優先順位付け"
#: src/view/shell/desktop/RightNav.tsx:75 #: src/view/shell/desktop/RightNav.tsx:76
msgid "Privacy" msgid "Privacy"
msgstr "プライバシー" msgstr "プライバシー"
@@ -1584,7 +1584,7 @@ msgstr "推奨ユーザー"
#: src/view/com/modals/ListAddRemoveUsers.tsx:264 #: src/view/com/modals/ListAddRemoveUsers.tsx:264
#: src/view/com/modals/SelfLabel.tsx:83 #: src/view/com/modals/SelfLabel.tsx:83
#: src/view/com/modals/UserAddRemoveLists.tsx:193 #: src/view/com/modals/UserAddRemoveLists.tsx:193
#: src/view/com/util/UserAvatar.tsx:278 #: src/view/com/util/UserAvatar.tsx:282
#: src/view/com/util/UserBanner.tsx:89 #: src/view/com/util/UserBanner.tsx:89
msgid "Remove" msgid "Remove"
msgstr "削除" msgstr "削除"
@@ -1657,7 +1657,7 @@ msgid "Report List"
msgstr "レポートリスト" msgstr "レポートリスト"
#: src/view/com/modals/report/SendReportButton.tsx:37 #: src/view/com/modals/report/SendReportButton.tsx:37
#: src/view/com/util/forms/PostDropdownBtn.tsx:162 #: src/view/com/util/forms/PostDropdownBtn.tsx:165
msgid "Report post" msgid "Report post"
msgstr "レポート投稿" msgstr "レポート投稿"
@@ -1795,7 +1795,7 @@ msgstr "アプリに表示するデフォルトのテキストのアプリ言語
msgid "Select your preferred language for translations in your feed." msgid "Select your preferred language for translations in your feed."
msgstr "フィード内の翻訳に使用する言語を選択します。" msgstr "フィード内の翻訳に使用する言語を選択します。"
#: src/view/com/modals/VerifyEmail.tsx:188 #: src/view/com/modals/VerifyEmail.tsx:196
msgid "Send Confirmation Email" msgid "Send Confirmation Email"
msgstr "確認Eメールを送信" msgstr "確認Eメールを送信"
@@ -1852,7 +1852,7 @@ msgid "Sexual activity or erotic nudity."
msgstr "性行為またはエロティックなヌード。" msgstr "性行為またはエロティックなヌード。"
#: src/view/com/profile/ProfileHeader.tsx:338 #: src/view/com/profile/ProfileHeader.tsx:338
#: src/view/com/util/forms/PostDropdownBtn.tsx:126 #: src/view/com/util/forms/PostDropdownBtn.tsx:129
#: src/view/screens/ProfileList.tsx:407 #: src/view/screens/ProfileList.tsx:407
msgid "Share" msgid "Share"
msgstr "共有" msgstr "共有"
@@ -1999,7 +1999,7 @@ msgstr "システムログ"
msgid "Tall" msgid "Tall"
msgstr "トール" msgstr "トール"
#: src/view/shell/desktop/RightNav.tsx:84 #: src/view/shell/desktop/RightNav.tsx:85
msgid "Terms" msgid "Terms"
msgstr "条件" msgstr "条件"
@@ -2064,7 +2064,7 @@ msgstr ""
msgid "This information is not shared with other users." msgid "This information is not shared with other users."
msgstr "この情報は他のユーザーと共有されません。" msgstr "この情報は他のユーザーと共有されません。"
#: src/view/com/modals/VerifyEmail.tsx:105 #: src/view/com/modals/VerifyEmail.tsx:113
msgid "This is important in case you ever need to change your email or reset your password." msgid "This is important in case you ever need to change your email or reset your password."
msgstr "これは、Eメールの変更やパスワードのリセットが必要な場合に重要です。" msgstr "これは、Eメールの変更やパスワードのリセットが必要な場合に重要です。"
@@ -2101,9 +2101,9 @@ msgstr "トグルドロップダウン"
msgid "Transformations" msgid "Transformations"
msgstr "変換" msgstr "変換"
#: src/view/com/post-thread/PostThreadItem.tsx:704
#: src/view/com/post-thread/PostThreadItem.tsx:706 #: src/view/com/post-thread/PostThreadItem.tsx:706
#: src/view/com/util/forms/PostDropdownBtn.tsx:98 #: src/view/com/post-thread/PostThreadItem.tsx:708
#: src/view/com/util/forms/PostDropdownBtn.tsx:101
msgid "Translate" msgid "Translate"
msgstr "翻訳" msgstr "翻訳"
@@ -2147,7 +2147,7 @@ msgstr "残念ながら、アカウントを作成するための要件を満た
msgid "Unmute Account" msgid "Unmute Account"
msgstr "アカウントのミュート解除" msgstr "アカウントのミュート解除"
#: src/view/com/util/forms/PostDropdownBtn.tsx:144 #: src/view/com/util/forms/PostDropdownBtn.tsx:147
msgid "Unmute thread" msgid "Unmute thread"
msgstr "スレッドのミュート解除" msgstr "スレッドのミュート解除"
@@ -2366,7 +2366,7 @@ msgstr "Eメールが保存されました!すぐにご連絡いたします
msgid "Your email has been updated but not verified. As a next step, please verify your new email." msgid "Your email has been updated but not verified. As a next step, please verify your new email."
msgstr "Eメールは更新されましたが、確認されていません。次のステップとして、新しいEメールを確認してください。" msgstr "Eメールは更新されましたが、確認されていません。次のステップとして、新しいEメールを確認してください。"
#: src/view/com/modals/VerifyEmail.tsx:100 #: src/view/com/modals/VerifyEmail.tsx:108
msgid "Your email has not yet been verified. This is an important security step which we recommend." msgid "Your email has not yet been verified. This is an important security step which we recommend."
msgstr "Eメールはまだ確認されていません。これは、当社が推奨する重要なセキュリティステップです。" msgstr "Eメールはまだ確認されていません。これは、当社が推奨する重要なセキュリティステップです。"
@@ -2380,7 +2380,7 @@ msgid "Your hosting provider"
msgstr "ホスティングプロバイダー" msgstr "ホスティングプロバイダー"
#: src/view/screens/Settings.tsx:402 #: src/view/screens/Settings.tsx:402
#: src/view/shell/desktop/RightNav.tsx:127 #: src/view/shell/desktop/RightNav.tsx:129
#: src/view/shell/Drawer.tsx:636 #: src/view/shell/Drawer.tsx:636
msgid "Your invite codes are hidden when logged in using an App Password" msgid "Your invite codes are hidden when logged in using an App Password"
msgstr "" msgstr ""
+25 -27
View File
@@ -16,7 +16,7 @@
* 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead. * 3. Don't call this query's `refetch()` if you're trying to sync latest; call `checkUnread()` instead.
*/ */
import {useEffect, useRef} from 'react' import {useEffect} from 'react'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs} from '@atproto/api'
import { import {
useInfiniteQuery, useInfiniteQuery,
@@ -49,8 +49,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
const threadMutes = useMutedThreads() const threadMutes = useMutedThreads()
const unreads = useUnreadNotificationsApi() const unreads = useUnreadNotificationsApi()
const enabled = opts?.enabled !== false const enabled = opts?.enabled !== false
// state tracked across page fetches
const pageState = useRef({pageNum: 0, hasMarkedRead: false})
const query = useInfiniteQuery< const query = useInfiniteQuery<
FeedPage, FeedPage,
@@ -66,8 +64,6 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
if (!pageParam) { if (!pageParam) {
// for the first page, we check the cached page held by the unread-checker first // for the first page, we check the cached page held by the unread-checker first
page = unreads.getCachedUnreadPage() page = unreads.getCachedUnreadPage()
// reset the page state
pageState.current = {pageNum: 0, hasMarkedRead: false}
} }
if (!page) { if (!page) {
page = await fetchPage({ page = await fetchPage({
@@ -80,27 +76,9 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
}) })
} }
// NOTE // if the first page has an unread, mark all read
// this section checks to see if we need to mark notifs read if (!pageParam && page.items[0] && !page.items[0].notification.isRead) {
// we want to wait until we've seen a read notification because unreads.markAllRead()
// of a timing challenge; marking read on the first page would
// cause subsequent pages of unread notifs to incorrectly come
// back as "read". we use page 6 as an abort condition, which means
// after ~180 notifs we give up on tracking unread state correctly
// -prf
if (!pageState.current.hasMarkedRead) {
let hasMarkedRead = false
if (
pageState.current.pageNum > 5 ||
page.items.some(item => item.notification.isRead)
) {
unreads.markAllRead()
hasMarkedRead = true
}
pageState.current = {
pageNum: pageState.current.pageNum + 1,
hasMarkedRead,
}
} }
return page return page
@@ -108,6 +86,20 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
initialPageParam: undefined, initialPageParam: undefined,
getNextPageParam: lastPage => lastPage.cursor, getNextPageParam: lastPage => lastPage.cursor,
enabled, enabled,
select(data: InfiniteData<FeedPage>) {
// override 'isRead' using the first page's returned seenAt
// we do this because the `markAllRead()` call above will
// mark subsequent pages as read prematurely
const seenAt = data.pages[0]?.seenAt || new Date()
for (const page of data.pages) {
for (const item of page.items) {
item.notification.isRead =
seenAt > new Date(item.notification.indexedAt)
}
}
return data
},
}) })
useEffect(() => { useEffect(() => {
@@ -122,7 +114,13 @@ export function useNotificationFeedQuery(opts?: {enabled?: boolean}) {
count += page.items.length count += page.items.length
} }
if (!isFetching && hasNextPage && count < PAGE_SIZE && numEmpties < 3) { if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
) {
query.fetchNextPage() query.fetchNextPage()
} }
}, [query]) }, [query])
+1
View File
@@ -24,6 +24,7 @@ export interface FeedNotification {
export interface FeedPage { export interface FeedPage {
cursor: string | undefined cursor: string | undefined
seenAt: Date
items: FeedNotification[] items: FeedNotification[]
} }
+6
View File
@@ -68,8 +68,14 @@ export async function fetchPage({
notif => !isThreadMuted(notif, threadMutes), notif => !isThreadMuted(notif, threadMutes),
) )
let seenAt = res.data.seenAt ? new Date(res.data.seenAt) : new Date()
if (Number.isNaN(seenAt.getTime())) {
seenAt = new Date()
}
return { return {
cursor: res.data.cursor, cursor: res.data.cursor,
seenAt,
items: notifsGrouped, items: notifsGrouped,
} }
} }
+7 -1
View File
@@ -291,7 +291,13 @@ export function usePostFeedQuery(
} }
} }
if (!isFetching && hasNextPage && count < PAGE_SIZE && numEmpties < 3) { if (
!isFetching &&
hasNextPage &&
count < PAGE_SIZE &&
numEmpties < 3 &&
(data?.pages.length || 0) < 6
) {
query.fetchNextPage() query.fetchNextPage()
} }
}, [query]) }, [query])
+9
View File
@@ -361,6 +361,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
} }
if (canReusePrevSession) { if (canReusePrevSession) {
logger.info(`session: attempting to reuse previous session`)
agent.session = prevSession agent.session = prevSession
__globalAgent = agent __globalAgent = agent
queryClient.clear() queryClient.clear()
@@ -370,6 +372,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
resumeSessionWithFreshAccount() resumeSessionWithFreshAccount()
.then(freshAccount => { .then(freshAccount => {
if (JSON.stringify(account) !== JSON.stringify(freshAccount)) { if (JSON.stringify(account) !== JSON.stringify(freshAccount)) {
logger.info(
`session: reuse of previous session returned a fresh account, upserting`,
)
upsertAccount(freshAccount) upsertAccount(freshAccount)
} }
}) })
@@ -385,6 +390,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
__globalAgent = PUBLIC_BSKY_AGENT __globalAgent = PUBLIC_BSKY_AGENT
}) })
} else { } else {
logger.info(`session: attempting to resume using previous session`)
try { try {
const freshAccount = await resumeSessionWithFreshAccount() const freshAccount = await resumeSessionWithFreshAccount()
__globalAgent = agent __globalAgent = agent
@@ -404,6 +411,8 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
} }
async function resumeSessionWithFreshAccount(): Promise<SessionAccount> { async function resumeSessionWithFreshAccount(): Promise<SessionAccount> {
logger.info(`session: resumeSessionWithFreshAccount`)
await networkRetry(1, () => agent.resumeSession(prevSession)) await networkRetry(1, () => agent.resumeSession(prevSession))
/* /*
+25 -21
View File
@@ -7,13 +7,15 @@ import {useNavigation} from '@react-navigation/native'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {MainScrollProvider} from '../util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useSetMinimalShellMode} from '#/state/shell'
import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed' import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {ComposeIcon2} from 'lib/icons' import {ComposeIcon2} from 'lib/icons'
import {colors, s} from 'lib/styles' import {colors, s} from 'lib/styles'
import {FlatList, View, useWindowDimensions} from 'react-native' import {View, useWindowDimensions} from 'react-native'
import {ListMethods} from '../util/List'
import {Feed} from '../posts/Feed' import {Feed} from '../posts/Feed'
import {TextLink} from '../util/Link' import {TextLink} from '../util/Link'
import {FAB} from '../util/fab/FAB' import {FAB} from '../util/fab/FAB'
@@ -51,10 +53,11 @@ export function FeedPage({
const {isDesktop} = useWebMediaQueries() const {isDesktop} = useWebMediaQueries()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const setMinimalShellMode = useSetMinimalShellMode()
const {screen, track} = useAnalytics() const {screen, track} = useAnalytics()
const headerOffset = useHeaderOffset() const headerOffset = useHeaderOffset()
const scrollElRef = React.useRef<FlatList>(null) const scrollElRef = React.useRef<ListMethods>(null)
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const scrollToTop = React.useCallback(() => { const scrollToTop = React.useCallback(() => {
@@ -62,8 +65,8 @@ export function FeedPage({
animated: isNative, animated: isNative,
offset: -headerOffset, offset: -headerOffset,
}) })
resetMainScroll() setMinimalShellMode(false)
}, [headerOffset, resetMainScroll]) }, [headerOffset, setMinimalShellMode])
const onSoftReset = React.useCallback(() => { const onSoftReset = React.useCallback(() => {
const isScreenFocused = const isScreenFocused =
@@ -164,21 +167,22 @@ export function FeedPage({
return ( return (
<View testID={testID} style={s.h100pct}> <View testID={testID} style={s.h100pct}>
<Feed <MainScrollProvider>
testID={testID ? `${testID}-feed` : undefined} <Feed
enabled={isPageFocused} testID={testID ? `${testID}-feed` : undefined}
feed={feed} enabled={isPageFocused}
feedParams={feedParams} feed={feed}
pollInterval={POLL_FREQ} feedParams={feedParams}
scrollElRef={scrollElRef} pollInterval={POLL_FREQ}
onScroll={onMainScroll} scrollElRef={scrollElRef}
onHasNew={setHasNew} onScrolledDownChange={setIsScrolledDown}
scrollEventThrottle={1} onHasNew={setHasNew}
renderEmptyState={renderEmptyState} renderEmptyState={renderEmptyState}
renderEndOfFeed={renderEndOfFeed} renderEndOfFeed={renderEndOfFeed}
ListHeaderComponent={ListHeaderComponent} ListHeaderComponent={ListHeaderComponent}
headerOffset={headerOffset} headerOffset={headerOffset}
/> />
</MainScrollProvider>
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onPressLoadLatest} onPress={onPressLoadLatest}
+5 -21
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react' import React from 'react'
import { import {
Dimensions, Dimensions,
RefreshControl, RefreshControl,
@@ -8,18 +8,16 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views' import {List, ListRef} from '../util/List'
import {FeedSourceCardLoaded} from './FeedSourceCard' import {FeedSourceCardLoaded} from './FeedSourceCard'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens' import {useProfileFeedgensQuery, RQKEY} from '#/state/queries/profile-feedgens'
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from '#/lib/ThemeContext' import {useTheme} from '#/lib/ThemeContext'
import {usePreferencesQuery} from '#/state/queries/preferences' import {usePreferencesQuery} from '#/state/queries/preferences'
import {hydrateFeedGenerator} from '#/state/queries/feed' import {hydrateFeedGenerator} from '#/state/queries/feed'
@@ -37,9 +35,7 @@ interface SectionRef {
interface ProfileFeedgensProps { interface ProfileFeedgensProps {
did: string did: string
scrollElRef: MutableRefObject<FlatList<any> | null> scrollElRef: ListRef
onScroll?: OnScrollHandler
scrollEventThrottle?: number
headerOffset: number headerOffset: number
enabled?: boolean enabled?: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
@@ -50,16 +46,7 @@ export const ProfileFeedgens = React.forwardRef<
SectionRef, SectionRef,
ProfileFeedgensProps ProfileFeedgensProps
>(function ProfileFeedgensImpl( >(function ProfileFeedgensImpl(
{ {did, scrollElRef, headerOffset, enabled, style, testID},
did,
scrollElRef,
onScroll,
scrollEventThrottle,
headerOffset,
enabled,
style,
testID,
},
ref, ref,
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
@@ -185,10 +172,9 @@ export const ProfileFeedgens = React.forwardRef<
[error, refetch, onPressRetryLoadMore, pal, preferences], [error, refetch, onPressRetryLoadMore, pal, preferences],
) )
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
<FlatList <List
testID={testID ? `${testID}-flatlist` : undefined} testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef} ref={scrollElRef}
data={items} data={items}
@@ -207,8 +193,6 @@ export const ProfileFeedgens = React.forwardRef<
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}
style={{paddingTop: headerOffset}} style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined}
scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true} removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}} contentOffset={{x: 0, y: headerOffset * -1}}
+7 -13
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react' import React from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
Dimensions, Dimensions,
@@ -8,7 +8,7 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api'
import {FlatList} from '../util/Views' import {List, ListRef} from '../util/List'
import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {ProfileCardFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -18,10 +18,8 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useListMembersQuery} from '#/state/queries/list-members' import {useListMembersQuery} from '#/state/queries/list-members'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {logger} from '#/logger' import {logger} from '#/logger'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
@@ -34,24 +32,22 @@ export function ListMembers({
list, list,
style, style,
scrollElRef, scrollElRef,
onScroll, onScrolledDownChange,
onPressTryAgain, onPressTryAgain,
renderHeader, renderHeader,
renderEmptyState, renderEmptyState,
testID, testID,
scrollEventThrottle,
headerOffset = 0, headerOffset = 0,
desktopFixedHeightOffset, desktopFixedHeightOffset,
}: { }: {
list: string list: string
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
scrollElRef?: MutableRefObject<FlatList<any> | null> scrollElRef?: ListRef
onScroll: OnScrollHandler onScrolledDownChange: (isScrolledDown: boolean) => void
onPressTryAgain?: () => void onPressTryAgain?: () => void
renderHeader: () => JSX.Element renderHeader: () => JSX.Element
renderEmptyState: () => JSX.Element renderEmptyState: () => JSX.Element
testID?: string testID?: string
scrollEventThrottle?: number
headerOffset?: number headerOffset?: number
desktopFixedHeightOffset?: number desktopFixedHeightOffset?: number
}) { }) {
@@ -209,10 +205,9 @@ export function ListMembers({
[isFetching], [isFetching],
) )
const scrollHandler = useAnimatedScrollHandler(onScroll)
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
<FlatList <List
testID={testID ? `${testID}-flatlist` : undefined} testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef} ref={scrollElRef}
data={items} data={items}
@@ -233,10 +228,9 @@ export function ListMembers({
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}
style={{paddingTop: headerOffset}} style={{paddingTop: headerOffset}}
onScroll={scrollHandler} onScrolledDownChange={onScrolledDownChange}
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={0.6} onEndReachedThreshold={0.6}
scrollEventThrottle={scrollEventThrottle}
removeClippedSubviews={true} removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}} contentOffset={{x: 0, y: headerOffset * -1}}
// @ts-ignore our .web version only -prf // @ts-ignore our .web version only -prf
+2 -2
View File
@@ -15,7 +15,7 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
import {Text} from '../util/text/Text' import {Text} from '../util/text/Text'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {FlatList} from '../util/Views' import {List} from '../util/List'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
@@ -119,7 +119,7 @@ export function MyLists({
[error, onRefresh, renderItem, pal], [error, onRefresh, renderItem, pal],
) )
const FlatListCom = inline ? RNFlatList : FlatList const FlatListCom = inline ? RNFlatList : List
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
{items.length > 0 && ( {items.length > 0 && (
+5 -21
View File
@@ -1,4 +1,4 @@
import React, {MutableRefObject} from 'react' import React from 'react'
import { import {
Dimensions, Dimensions,
RefreshControl, RefreshControl,
@@ -8,7 +8,7 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views' import {List, ListRef} from '../util/List'
import {ListCard} from './ListCard' import {ListCard} from './ListCard'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@@ -16,11 +16,9 @@ import {Text} from '../util/text/Text'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists' import {useProfileListsQuery, RQKEY} from '#/state/queries/profile-lists'
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {logger} from '#/logger' import {logger} from '#/logger'
import {Trans} from '@lingui/macro' import {Trans} from '@lingui/macro'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from '#/lib/ThemeContext' import {useTheme} from '#/lib/ThemeContext'
import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
import {isNative} from '#/platform/detection' import {isNative} from '#/platform/detection'
@@ -36,9 +34,7 @@ interface SectionRef {
interface ProfileListsProps { interface ProfileListsProps {
did: string did: string
scrollElRef: MutableRefObject<FlatList<any> | null> scrollElRef: ListRef
onScroll?: OnScrollHandler
scrollEventThrottle?: number
headerOffset: number headerOffset: number
enabled?: boolean enabled?: boolean
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
@@ -47,16 +43,7 @@ interface ProfileListsProps {
export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>( export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
function ProfileListsImpl( function ProfileListsImpl(
{ {did, scrollElRef, headerOffset, enabled, style, testID},
did,
scrollElRef,
onScroll,
scrollEventThrottle,
headerOffset,
enabled,
style,
testID,
},
ref, ref,
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
@@ -187,10 +174,9 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
[error, refetch, onPressRetryLoadMore, pal], [error, refetch, onPressRetryLoadMore, pal],
) )
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
<FlatList <List
testID={testID ? `${testID}-flatlist` : undefined} testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef} ref={scrollElRef}
data={items} data={items}
@@ -209,8 +195,6 @@ export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>(
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}
style={{paddingTop: headerOffset}} style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined}
scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
removeClippedSubviews={true} removeClippedSubviews={true}
contentOffset={{x: 0, y: headerOffset * -1}} contentOffset={{x: 0, y: headerOffset * -1}}
+8
View File
@@ -22,6 +22,7 @@ import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useSession, useSessionApi, getAgent} from '#/state/session' import {useSession, useSessionApi, getAgent} from '#/state/session'
import {logger} from '#/logger'
export const snapPoints = ['90%'] export const snapPoints = ['90%']
@@ -45,6 +46,13 @@ export function Component({showReminder}: {showReminder?: boolean}) {
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {openModal, closeModal} = useModalControls() const {openModal, closeModal} = useModalControls()
React.useEffect(() => {
if (!currentAccount) {
logger.error(`VerifyEmail modal opened without currentAccount`)
closeModal()
}
}, [currentAccount, closeModal])
const onSendEmail = async () => { const onSendEmail = async () => {
setError('') setError('')
setIsProcessing(true) setIsProcessing(true)
+8 -11
View File
@@ -1,13 +1,11 @@
import React, {MutableRefObject} from 'react' import React from 'react'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {FeedItem} from './FeedItem' import {FeedItem} from './FeedItem'
import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {NotificationFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {EmptyState} from '../util/EmptyState' import {EmptyState} from '../util/EmptyState'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useNotificationFeedQuery} from '#/state/queries/notifications/feed' import {useNotificationFeedQuery} from '#/state/queries/notifications/feed'
@@ -15,6 +13,7 @@ import {useUnreadNotificationsApi} from '#/state/queries/notifications/unread'
import {logger} from '#/logger' import {logger} from '#/logger'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useModerationOpts} from '#/state/queries/preferences' import {useModerationOpts} from '#/state/queries/preferences'
import {List, ListRef} from '../util/List'
const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const EMPTY_FEED_ITEM = {_reactKey: '__empty__'}
const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'} const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
@@ -23,12 +22,12 @@ const LOADING_ITEM = {_reactKey: '__loading__'}
export function Feed({ export function Feed({
scrollElRef, scrollElRef,
onPressTryAgain, onPressTryAgain,
onScroll, onScrolledDownChange,
ListHeaderComponent, ListHeaderComponent,
}: { }: {
scrollElRef?: MutableRefObject<FlatList<any> | null> scrollElRef?: ListRef
onPressTryAgain?: () => void onPressTryAgain?: () => void
onScroll?: OnScrollHandler onScrolledDownChange: (isScrolledDown: boolean) => void
ListHeaderComponent?: () => JSX.Element ListHeaderComponent?: () => JSX.Element
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
@@ -135,7 +134,6 @@ export function Feed({
[isFetchingNextPage], [isFetchingNextPage],
) )
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return ( return (
<View style={s.hContentRegion}> <View style={s.hContentRegion}>
{error && ( {error && (
@@ -146,7 +144,7 @@ export function Feed({
/> />
</CenteredView> </CenteredView>
)} )}
<FlatList <List
testID="notifsFeed" testID="notifsFeed"
ref={scrollElRef} ref={scrollElRef}
data={items} data={items}
@@ -164,8 +162,7 @@ export function Feed({
} }
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={0.6} onEndReachedThreshold={0.6}
onScroll={scrollHandler} onScrolledDownChange={onScrolledDownChange}
scrollEventThrottle={1}
contentContainerStyle={s.contentContainer} contentContainerStyle={s.contentContainer}
// @ts-ignore our .web version only -prf // @ts-ignore our .web version only -prf
desktopFixedHeight desktopFixedHeight
+36 -44
View File
@@ -1,7 +1,6 @@
import * as React from 'react' import * as React from 'react'
import { import {
LayoutChangeEvent, LayoutChangeEvent,
FlatList,
ScrollView, ScrollView,
StyleSheet, StyleSheet,
View, View,
@@ -19,17 +18,14 @@ import Animated, {
} from 'react-native-reanimated' } from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {ListMethods} from '../util/List'
const SCROLLED_DOWN_LIMIT = 200 import {ScrollProvider} from '#/lib/ScrollContext'
export interface PagerWithHeaderChildParams { export interface PagerWithHeaderChildParams {
headerHeight: number headerHeight: number
isFocused: boolean isFocused: boolean
onScroll: OnScrollHandler scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
} }
export interface PagerWithHeaderProps { export interface PagerWithHeaderProps {
@@ -61,7 +57,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
const [currentPage, setCurrentPage] = React.useState(0) const [currentPage, setCurrentPage] = React.useState(0)
const [tabBarHeight, setTabBarHeight] = React.useState(0) const [tabBarHeight, setTabBarHeight] = React.useState(0)
const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0) const [headerOnlyHeight, setHeaderOnlyHeight] = React.useState(0)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollY = useSharedValue(0) const scrollY = useSharedValue(0)
const headerHeight = headerOnlyHeight + tabBarHeight const headerHeight = headerOnlyHeight + tabBarHeight
@@ -120,13 +115,16 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
) )
const scrollRefs = useSharedValue<AnimatedRef<any>[]>([]) const scrollRefs = useSharedValue<AnimatedRef<any>[]>([])
const registerRef = (scrollRef: AnimatedRef<any>, index: number) => { const registerRef = React.useCallback(
scrollRefs.modify(refs => { (scrollRef: AnimatedRef<any> | null, atIndex: number) => {
'worklet' scrollRefs.modify(refs => {
refs[index] = scrollRef 'worklet'
return refs refs[atIndex] = scrollRef
}) return refs
} })
},
[scrollRefs],
)
const lastForcedScrollY = useSharedValue(0) const lastForcedScrollY = useSharedValue(0)
const adjustScrollForOtherPages = () => { const adjustScrollForOtherPages = () => {
@@ -137,8 +135,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
lastForcedScrollY.value = forcedScrollY lastForcedScrollY.value = forcedScrollY
const refs = scrollRefs.value const refs = scrollRefs.value
for (let i = 0; i < refs.length; i++) { for (let i = 0; i < refs.length; i++) {
if (i !== currentPage) { if (i !== currentPage && refs[i] != null) {
// This needs to run on the UI thread.
scrollTo(refs[i], 0, forcedScrollY, false) scrollTo(refs[i], 0, forcedScrollY, false)
} }
} }
@@ -152,15 +149,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
if (!throttleTimeout.current) { if (!throttleTimeout.current) {
throttleTimeout.current = setTimeout(() => { throttleTimeout.current = setTimeout(() => {
throttleTimeout.current = null throttleTimeout.current = null
runOnUI(adjustScrollForOtherPages)() runOnUI(adjustScrollForOtherPages)()
const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== nextIsScrolledDown) {
React.startTransition(() => {
setIsScrolledDown(nextIsScrolledDown)
})
}
}, 80 /* Sync often enough you're unlikely to catch it unsynced */) }, 80 /* Sync often enough you're unlikely to catch it unsynced */)
} }
}) })
@@ -205,11 +194,11 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
<View key={i} collapsable={false}> <View key={i} collapsable={false}>
<PagerItem <PagerItem
headerHeight={headerHeight} headerHeight={headerHeight}
index={i}
isReady={isReady} isReady={isReady}
isFocused={i === currentPage} isFocused={i === currentPage}
isScrolledDown={isScrolledDown}
onScrollWorklet={i === currentPage ? onScrollWorklet : noop} onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
registerRef={(r: AnimatedRef<any>) => registerRef(r, i)} registerRef={registerRef}
renderTab={child} renderTab={child}
/> />
</View> </View>
@@ -282,42 +271,45 @@ PagerTabBar = React.memo(PagerTabBar)
function PagerItem({ function PagerItem({
headerHeight, headerHeight,
index,
isReady, isReady,
isFocused, isFocused,
isScrolledDown,
onScrollWorklet, onScrollWorklet,
renderTab, renderTab,
registerRef, registerRef,
}: { }: {
headerHeight: number headerHeight: number
index: number
isFocused: boolean isFocused: boolean
isReady: boolean isReady: boolean
isScrolledDown: boolean registerRef: (scrollRef: AnimatedRef<any> | null, atIndex: number) => void
registerRef: (scrollRef: AnimatedRef<any>) => void
onScrollWorklet: (e: NativeScrollEvent) => void onScrollWorklet: (e: NativeScrollEvent) => void
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
}) { }) {
const scrollElRef = useAnimatedRef() const scrollElRef = useAnimatedRef()
registerRef(scrollElRef)
const scrollHandler = React.useMemo( React.useEffect(() => {
() => ({onScroll: onScrollWorklet}), registerRef(scrollElRef, index)
[onScrollWorklet], return () => {
) registerRef(null, index)
}
}, [scrollElRef, registerRef, index])
if (!isReady || renderTab == null) { if (!isReady || renderTab == null) {
return null return null
} }
return renderTab({ return (
headerHeight, <ScrollProvider onScroll={onScrollWorklet}>
isFocused, {renderTab({
isScrolledDown, headerHeight,
onScroll: scrollHandler, isFocused,
scrollElRef: scrollElRef as React.MutableRefObject< scrollElRef: scrollElRef as React.MutableRefObject<
FlatList<any> | ScrollView | null ListMethods | ScrollView | null
>, >,
}) })}
</ScrollProvider>
)
} }
const styles = StyleSheet.create({ const styles = StyleSheet.create({
+5 -65
View File
@@ -1,26 +1,15 @@
import * as React from 'react' import * as React from 'react'
import { import {FlatList, ScrollView, StyleSheet, View} from 'react-native'
FlatList, import {useAnimatedRef} from 'react-native-reanimated'
ScrollView,
StyleSheet,
View,
NativeScrollEvent,
} from 'react-native'
import {useSharedValue, runOnJS, useAnimatedRef} from 'react-native-reanimated'
import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager' import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
import {TabBar} from './TabBar' import {TabBar} from './TabBar'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {ListMethods} from '../util/List'
const SCROLLED_DOWN_LIMIT = 200
export interface PagerWithHeaderChildParams { export interface PagerWithHeaderChildParams {
headerHeight: number headerHeight: number
isFocused: boolean isFocused: boolean
onScroll: OnScrollHandler
isScrolledDown: boolean
scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null> scrollElRef: React.MutableRefObject<FlatList<any> | ScrollView | null>
} }
@@ -50,8 +39,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
ref, ref,
) { ) {
const [currentPage, setCurrentPage] = React.useState(0) const [currentPage, setCurrentPage] = React.useState(0)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollY = useSharedValue(0)
const renderTabBar = React.useCallback( const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => { (props: RenderTabBarFnProps) => {
@@ -69,34 +56,6 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
[items, renderHeader, currentPage, onCurrentPageSelected, testID], [items, renderHeader, currentPage, onCurrentPageSelected, testID],
) )
const throttleTimeout = React.useRef<ReturnType<typeof setTimeout> | null>(
null,
)
const queueThrottledOnScroll = useNonReactiveCallback(() => {
if (!throttleTimeout.current) {
throttleTimeout.current = setTimeout(() => {
throttleTimeout.current = null
const nextIsScrolledDown = scrollY.value > SCROLLED_DOWN_LIMIT
if (isScrolledDown !== nextIsScrolledDown) {
React.startTransition(() => {
setIsScrolledDown(nextIsScrolledDown)
})
}
}, 80)
}
})
const onScrollWorklet = React.useCallback(
(e: NativeScrollEvent) => {
'worklet'
const nextScrollY = e.contentOffset.y
scrollY.value = nextScrollY
runOnJS(queueThrottledOnScroll)()
},
[scrollY, queueThrottledOnScroll],
)
const onPageSelectedInner = React.useCallback( const onPageSelectedInner = React.useCallback(
(index: number) => { (index: number) => {
setCurrentPage(index) setCurrentPage(index)
@@ -123,12 +82,7 @@ export const PagerWithHeader = React.forwardRef<PagerRef, PagerWithHeaderProps>(
.map((child, i) => { .map((child, i) => {
return ( return (
<View key={i} collapsable={false}> <View key={i} collapsable={false}>
<PagerItem <PagerItem isFocused={i === currentPage} renderTab={child} />
isFocused={i === currentPage}
isScrolledDown={isScrolledDown}
onScrollWorklet={i === currentPage ? onScrollWorklet : noop}
renderTab={child}
/>
</View> </View>
) )
})} })}
@@ -182,30 +136,20 @@ PagerTabBar = React.memo(PagerTabBar)
function PagerItem({ function PagerItem({
isFocused, isFocused,
isScrolledDown,
onScrollWorklet,
renderTab, renderTab,
}: { }: {
isFocused: boolean isFocused: boolean
isScrolledDown: boolean
onScrollWorklet: (e: NativeScrollEvent) => void
renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null renderTab: ((props: PagerWithHeaderChildParams) => JSX.Element) | null
}) { }) {
const scrollElRef = useAnimatedRef() const scrollElRef = useAnimatedRef()
const scrollHandler = React.useMemo(
() => ({onScroll: onScrollWorklet}),
[onScrollWorklet],
)
if (renderTab == null) { if (renderTab == null) {
return null return null
} }
return renderTab({ return renderTab({
headerHeight: 0, headerHeight: 0,
isFocused, isFocused,
isScrolledDown,
onScroll: scrollHandler,
scrollElRef: scrollElRef as React.MutableRefObject< scrollElRef: scrollElRef as React.MutableRefObject<
FlatList<any> | ScrollView | null ListMethods | ScrollView | null
>, >,
}) })
} }
@@ -238,10 +182,6 @@ const styles = StyleSheet.create({
}, },
}) })
function noop() {
'worklet'
}
function toArray<T>(v: T | T[]): T[] { function toArray<T>(v: T | T[]): T[] {
if (Array.isArray(v)) { if (Array.isArray(v)) {
return v return v
+3 -2
View File
@@ -1,7 +1,8 @@
import React, {useCallback, useMemo, useState} from 'react' import React, {useCallback, useMemo, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -84,7 +85,7 @@ export function PostLikedBy({uri}: {uri: string}) {
// loaded // loaded
// = // =
return ( return (
<FlatList <List
data={likes} data={likes}
keyExtractor={item => item.actor.did} keyExtractor={item => item.actor.did}
refreshControl={ refreshControl={
+3 -2
View File
@@ -1,7 +1,8 @@
import React, {useMemo, useCallback, useState} from 'react' import React, {useMemo, useCallback, useState} from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ProfileCardWithFollowBtn} from '../profile/ProfileCard' import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -85,7 +86,7 @@ export function PostRepostedBy({uri}: {uri: string}) {
// loaded // loaded
// = // =
return ( return (
<FlatList <List
data={repostedBy} data={repostedBy}
keyExtractor={item => item.did} keyExtractor={item => item.did}
refreshControl={ refreshControl={
+4 -3
View File
@@ -8,7 +8,8 @@ import {
View, View,
} from 'react-native' } from 'react-native'
import {AppBskyFeedDefs} from '@atproto/api' import {AppBskyFeedDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {List, ListMethods} from '../util/List'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, FontAwesomeIconStyle,
@@ -140,7 +141,7 @@ function PostThreadLoaded({
const {_} = useLingui() const {_} = useLingui()
const pal = usePalette('default') const pal = usePalette('default')
const {isTablet, isDesktop} = useWebMediaQueries() const {isTablet, isDesktop} = useWebMediaQueries()
const ref = useRef<FlatList>(null) const ref = useRef<ListMethods>(null)
const highlightedPostRef = useRef<View | null>(null) const highlightedPostRef = useRef<View | null>(null)
const needsScrollAdjustment = useRef<boolean>( const needsScrollAdjustment = useRef<boolean>(
!isNative || // web always uses scroll adjustment !isNative || // web always uses scroll adjustment
@@ -335,7 +336,7 @@ function PostThreadLoaded({
) )
return ( return (
<FlatList <List
ref={ref} ref={ref}
data={posts} data={posts}
initialNumToRender={!isNative ? posts.length : undefined} initialNumToRender={!isNative ? posts.length : undefined}
+8 -15
View File
@@ -1,4 +1,4 @@
import React, {memo, MutableRefObject} from 'react' import React, {memo} from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
AppState, AppState,
@@ -10,15 +10,13 @@ import {
ViewStyle, ViewStyle,
} from 'react-native' } from 'react-native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {FlatList} from '../util/Views' import {List, ListRef} from '../util/List'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {FeedErrorMessage} from './FeedErrorMessage' import {FeedErrorMessage} from './FeedErrorMessage'
import {FeedSlice} from './FeedSlice' import {FeedSlice} from './FeedSlice'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn' import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {useTheme} from 'lib/ThemeContext' import {useTheme} from 'lib/ThemeContext'
import {logger} from '#/logger' import {logger} from '#/logger'
import { import {
@@ -45,9 +43,8 @@ let Feed = ({
enabled, enabled,
pollInterval, pollInterval,
scrollElRef, scrollElRef,
onScroll, onScrolledDownChange,
onHasNew, onHasNew,
scrollEventThrottle,
renderEmptyState, renderEmptyState,
renderEndOfFeed, renderEndOfFeed,
testID, testID,
@@ -62,10 +59,9 @@ let Feed = ({
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
enabled?: boolean enabled?: boolean
pollInterval?: number pollInterval?: number
scrollElRef?: MutableRefObject<FlatList<any> | null> scrollElRef?: ListRef
onHasNew?: (v: boolean) => void onHasNew?: (v: boolean) => void
onScroll?: OnScrollHandler onScrolledDownChange?: (isScrolledDown: boolean) => void
scrollEventThrottle?: number
renderEmptyState: () => JSX.Element renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element renderEndOfFeed?: () => JSX.Element
testID?: string testID?: string
@@ -167,8 +163,7 @@ let Feed = ({
if (isFetched) { if (isFetched) {
if (isError && isEmpty) { if (isError && isEmpty) {
arr = arr.concat([ERROR_ITEM]) arr = arr.concat([ERROR_ITEM])
} } else if (isEmpty) {
if (isEmpty) {
arr = arr.concat([EMPTY_FEED_ITEM]) arr = arr.concat([EMPTY_FEED_ITEM])
} else if (data) { } else if (data) {
for (const page of data?.pages) { for (const page of data?.pages) {
@@ -271,10 +266,9 @@ let Feed = ({
) )
}, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset]) }, [isFetchingNextPage, shouldRenderEndOfFeed, renderEndOfFeed, headerOffset])
const scrollHandler = useAnimatedScrollHandler(onScroll || {})
return ( return (
<View testID={testID} style={style}> <View testID={testID} style={style}>
<FlatList <List
testID={testID ? `${testID}-flatlist` : undefined} testID={testID ? `${testID}-flatlist` : undefined}
ref={scrollElRef} ref={scrollElRef}
data={feedItems} data={feedItems}
@@ -295,8 +289,7 @@ let Feed = ({
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}
style={{paddingTop: headerOffset}} style={{paddingTop: headerOffset}}
onScroll={onScroll != null ? scrollHandler : undefined} onScrolledDownChange={onScrolledDownChange}
scrollEventThrottle={scrollEventThrottle}
indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'} indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
onEndReached={onEndReached} onEndReached={onEndReached}
onEndReachedThreshold={2} // number of posts left to trigger load more onEndReachedThreshold={2} // number of posts left to trigger load more
+3 -2
View File
@@ -1,7 +1,8 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard' import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -86,7 +87,7 @@ export function ProfileFollowers({name}: {name: string}) {
// loaded // loaded
// = // =
return ( return (
<FlatList <List
data={followers} data={followers}
keyExtractor={item => item.did} keyExtractor={item => item.did}
refreshControl={ refreshControl={
+3 -2
View File
@@ -1,7 +1,8 @@
import React from 'react' import React from 'react'
import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native' import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
import {AppBskyActorDefs as ActorDefs} from '@atproto/api' import {AppBskyActorDefs as ActorDefs} from '@atproto/api'
import {CenteredView, FlatList} from '../util/Views' import {CenteredView} from '../util/Views'
import {List} from '../util/List'
import {ErrorMessage} from '../util/error/ErrorMessage' import {ErrorMessage} from '../util/error/ErrorMessage'
import {ProfileCardWithFollowBtn} from './ProfileCard' import {ProfileCardWithFollowBtn} from './ProfileCard'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -86,7 +87,7 @@ export function ProfileFollows({name}: {name: string}) {
// loaded // loaded
// = // =
return ( return (
<FlatList <List
data={follows} data={follows}
keyExtractor={item => item.did} keyExtractor={item => item.did}
refreshControl={ refreshControl={
+64
View File
@@ -0,0 +1,64 @@
import React, {memo, startTransition} from 'react'
import {FlatListProps} from 'react-native'
import {FlatList_INTERNAL} from './Views'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {runOnJS, useSharedValue} from 'react-native-reanimated'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
export type ListMethods = FlatList_INTERNAL
export type ListProps<ItemT> = Omit<
FlatListProps<ItemT>,
'onScroll' // Use ScrollContext instead.
> & {
onScrolledDownChange?: (isScrolledDown: boolean) => void
}
export type ListRef = React.MutableRefObject<FlatList_INTERNAL | null>
const SCROLLED_DOWN_LIMIT = 200
function ListImpl<ItemT>(
{onScrolledDownChange, ...props}: ListProps<ItemT>,
ref: React.Ref<ListMethods>,
) {
const isScrolledDown = useSharedValue(false)
const contextScrollHandlers = useScrollHandlers()
function handleScrolledDownChange(didScrollDown: boolean) {
startTransition(() => {
onScrolledDownChange?.(didScrollDown)
})
}
const scrollHandler = useAnimatedScrollHandler({
onBeginDrag(e, ctx) {
contextScrollHandlers.onBeginDrag?.(e, ctx)
},
onEndDrag(e, ctx) {
contextScrollHandlers.onEndDrag?.(e, ctx)
},
onScroll(e, ctx) {
contextScrollHandlers.onScroll?.(e, ctx)
const didScrollDown = e.contentOffset.y > SCROLLED_DOWN_LIMIT
if (isScrolledDown.value !== didScrollDown) {
isScrolledDown.value = didScrollDown
if (onScrolledDownChange != null) {
runOnJS(handleScrolledDownChange)(didScrollDown)
}
}
},
})
return (
<FlatList_INTERNAL
{...props}
onScroll={scrollHandler}
scrollEventThrottle={1}
ref={ref}
/>
)
}
export const List = memo(React.forwardRef(ListImpl)) as <ItemT>(
props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>},
) => React.ReactElement
@@ -1,30 +1,18 @@
import {useState, useCallback, useMemo} from 'react' import React, {useCallback} from 'react'
import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' import {ScrollProvider} from '#/lib/ScrollContext'
import {NativeScrollEvent} from 'react-native'
import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell' import {useSetMinimalShellMode, useMinimalShellMode} from '#/state/shell'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {s} from 'lib/styles'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import { import {useSharedValue, interpolate} from 'react-native-reanimated'
useSharedValue,
interpolate,
runOnJS,
ScrollHandlers,
} from 'react-native-reanimated'
function clamp(num: number, min: number, max: number) { function clamp(num: number, min: number, max: number) {
'worklet' 'worklet'
return Math.min(Math.max(num, min), max) return Math.min(Math.max(num, min), max)
} }
export type OnScrollCb = ( export function MainScrollProvider({children}: {children: React.ReactNode}) {
event: NativeSyntheticEvent<NativeScrollEvent>,
) => void
export type OnScrollHandler = ScrollHandlers<any>
export type ResetCb = () => void
export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
const {headerHeight} = useShellLayout() const {headerHeight} = useShellLayout()
const [isScrolledDown, setIsScrolledDown] = useState(false)
const mode = useMinimalShellMode() const mode = useMinimalShellMode()
const setMode = useSetMinimalShellMode() const setMode = useSetMinimalShellMode()
const startDragOffset = useSharedValue<number | null>(null) const startDragOffset = useSharedValue<number | null>(null)
@@ -58,13 +46,6 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
const onScroll = useCallback( const onScroll = useCallback(
(e: NativeScrollEvent) => { (e: NativeScrollEvent) => {
'worklet' 'worklet'
// Keep track of whether we want to show "scroll to top".
if (!isScrolledDown && e.contentOffset.y > s.window.height) {
runOnJS(setIsScrolledDown)(true)
} else if (isScrolledDown && e.contentOffset.y < s.window.height) {
runOnJS(setIsScrolledDown)(false)
}
if (startDragOffset.value === null || startMode.value === null) { if (startDragOffset.value === null || startMode.value === null) {
if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) { if (mode.value !== 0 && e.contentOffset.y < headerHeight.value) {
// If we're close enough to the top, always show the shell. // If we're close enough to the top, always show the shell.
@@ -102,24 +83,15 @@ export function useOnMainScroll(): [OnScrollHandler, boolean, ResetCb] {
startMode.value = mode.value startMode.value = mode.value
} }
}, },
[headerHeight, mode, setMode, isScrolledDown, startDragOffset, startMode], [headerHeight, mode, setMode, startDragOffset, startMode],
) )
const scrollHandler: ScrollHandlers<any> = useMemo( return (
() => ({ <ScrollProvider
onBeginDrag, onBeginDrag={onBeginDrag}
onEndDrag, onEndDrag={onEndDrag}
onScroll, onScroll={onScroll}>
}), {children}
[onBeginDrag, onEndDrag, onScroll], </ScrollProvider>
) )
return [
scrollHandler,
isScrolledDown,
useCallback(() => {
setIsScrolledDown(false)
setMode(false)
}, [setMode]),
]
} }
+6 -5
View File
@@ -1,13 +1,14 @@
import React, {useEffect, useState} from 'react' import React, {useEffect, useState} from 'react'
import { import {
NativeSyntheticEvent,
NativeScrollEvent,
Pressable, Pressable,
RefreshControl, RefreshControl,
StyleSheet, StyleSheet,
View, View,
ScrollView, ScrollView,
} from 'react-native' } from 'react-native'
import {FlatList} from './Views' import {FlatList_INTERNAL} from './Views'
import {OnScrollCb} from 'lib/hooks/useOnMainScroll'
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle' import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
import {Text} from './text/Text' import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
@@ -38,7 +39,7 @@ export const ViewSelector = React.forwardRef<
| null | null
| undefined | undefined
onSelectView?: (viewIndex: number) => void onSelectView?: (viewIndex: number) => void
onScroll?: OnScrollCb onScroll?: (event: NativeSyntheticEvent<NativeScrollEvent>) => void
onRefresh?: () => void onRefresh?: () => void
onEndReached?: (info: {distanceFromEnd: number}) => void onEndReached?: (info: {distanceFromEnd: number}) => void
} }
@@ -59,7 +60,7 @@ export const ViewSelector = React.forwardRef<
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
const [selectedIndex, setSelectedIndex] = useState<number>(0) const [selectedIndex, setSelectedIndex] = useState<number>(0)
const flatListRef = React.useRef<FlatList>(null) const flatListRef = React.useRef<FlatList_INTERNAL>(null)
// events // events
// = // =
@@ -110,7 +111,7 @@ export const ViewSelector = React.forwardRef<
[items], [items],
) )
return ( return (
<FlatList <FlatList_INTERNAL
ref={flatListRef} ref={flatListRef}
data={data} data={data}
keyExtractor={keyExtractor} keyExtractor={keyExtractor}
+1 -1
View File
@@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {ViewProps} from 'react-native' import {ViewProps} from 'react-native'
export {FlatList, ScrollView} from 'react-native' export {FlatList as FlatList_INTERNAL, ScrollView} from 'react-native'
export function CenteredView({ export function CenteredView({
style, style,
sideBorders, sideBorders,
+1 -1
View File
@@ -2,7 +2,7 @@ import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import Animated from 'react-native-reanimated' import Animated from 'react-native-reanimated'
export const FlatList = Animated.FlatList export const FlatList_INTERNAL = Animated.FlatList
export const ScrollView = Animated.ScrollView export const ScrollView = Animated.ScrollView
export function CenteredView(props) { export function CenteredView(props) {
return <View {...props} /> return <View {...props} />
+1 -1
View File
@@ -49,7 +49,7 @@ export function CenteredView({
return <View style={style} {...props} /> return <View style={style} {...props} />
} }
export const FlatList = React.forwardRef(function FlatListImpl<ItemT>( export const FlatList_INTERNAL = React.forwardRef(function FlatListImpl<ItemT>(
{ {
data, data,
extraData, extraData,
+2 -2
View File
@@ -19,7 +19,7 @@ import {
import {ErrorMessage} from 'view/com/util/error/ErrorMessage' import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {FlatList} from 'view/com/util/Views' import {List} from 'view/com/util/List'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
@@ -481,7 +481,7 @@ export function FeedsScreen(_props: Props) {
{preferences ? <View /> : <ActivityIndicator />} {preferences ? <View /> : <ActivityIndicator />}
<FlatList <List
style={[!isTabletOrDesktop && s.flex1, styles.list]} style={[!isTabletOrDesktop && s.flex1, styles.list]}
data={items} data={items}
keyExtractor={item => item.key} keyExtractor={item => item.key}
+14 -11
View File
@@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {FlatList, View} from 'react-native' import {View} from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import { import {
@@ -9,8 +9,9 @@ import {
import {ViewHeader} from '../com/util/ViewHeader' import {ViewHeader} from '../com/util/ViewHeader'
import {Feed} from '../com/notifications/Feed' import {Feed} from '../com/notifications/Feed'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {ListMethods} from 'view/com/util/List'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' import {MainScrollProvider} from '../com/util/MainScrollProvider'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {s, colors} from 'lib/styles' import {s, colors} from 'lib/styles'
@@ -35,8 +36,8 @@ type Props = NativeStackScreenProps<
export function NotificationsScreen({}: Props) { export function NotificationsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
const setMinimalShellMode = useSetMinimalShellMode() const setMinimalShellMode = useSetMinimalShellMode()
const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll() const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const scrollElRef = React.useRef<FlatList>(null) const scrollElRef = React.useRef<ListMethods>(null)
const checkLatestRef = React.useRef<() => void | null>() const checkLatestRef = React.useRef<() => void | null>()
const {screen} = useAnalytics() const {screen} = useAnalytics()
const pal = usePalette('default') const pal = usePalette('default')
@@ -50,8 +51,8 @@ export function NotificationsScreen({}: Props) {
// = // =
const scrollToTop = React.useCallback(() => { const scrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0}) scrollElRef.current?.scrollToOffset({animated: isNative, offset: 0})
resetMainScroll() setMinimalShellMode(false)
}, [scrollElRef, resetMainScroll]) }, [scrollElRef, setMinimalShellMode])
const onPressLoadLatest = React.useCallback(() => { const onPressLoadLatest = React.useCallback(() => {
scrollToTop() scrollToTop()
@@ -130,11 +131,13 @@ export function NotificationsScreen({}: Props) {
return ( return (
<View testID="notificationsScreen" style={s.hContentRegion}> <View testID="notificationsScreen" style={s.hContentRegion}>
<ViewHeader title={_(msg`Notifications`)} canGoBack={false} /> <ViewHeader title={_(msg`Notifications`)} canGoBack={false} />
<Feed <MainScrollProvider>
onScroll={onMainScroll} <Feed
scrollElRef={scrollElRef} onScrolledDownChange={setIsScrolledDown}
ListHeaderComponent={ListHeaderComponent} scrollElRef={scrollElRef}
/> ListHeaderComponent={ListHeaderComponent}
/>
</MainScrollProvider>
{(isScrolledDown || hasNew) && ( {(isScrolledDown || hasNew) && (
<LoadLatestBtn <LoadLatestBtn
onPress={onPressLoadLatest} onPress={onPressLoadLatest}
+18 -64
View File
@@ -5,7 +5,8 @@ import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {CenteredView, FlatList} from '../com/util/Views' import {CenteredView} from '../com/util/Views'
import {ListRef} from '../com/util/List'
import {ScreenHider} from 'view/com/util/moderation/ScreenHider' import {ScreenHider} from 'view/com/util/moderation/ScreenHider'
import {Feed} from 'view/com/posts/Feed' import {Feed} from 'view/com/posts/Feed'
import {ProfileLists} from '../com/lists/ProfileLists' import {ProfileLists} from '../com/lists/ProfileLists'
@@ -20,7 +21,6 @@ import {useAnalytics} from 'lib/analytics/analytics'
import {ComposeIcon2} from 'lib/icons' import {ComposeIcon2} from 'lib/icons'
import {useSetTitle} from 'lib/hooks/useSetTitle' import {useSetTitle} from 'lib/hooks/useSetTitle'
import {combinedDisplayName} from 'lib/strings/display-names' import {combinedDisplayName} from 'lib/strings/display-names'
import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
import {FeedDescriptor} from '#/state/queries/post-feed' import {FeedDescriptor} from '#/state/queries/post-feed'
import {useResolveDidQuery} from '#/state/queries/resolve-uri' import {useResolveDidQuery} from '#/state/queries/resolve-uri'
import {useProfileQuery} from '#/state/queries/profile' import {useProfileQuery} from '#/state/queries/profile'
@@ -277,103 +277,67 @@ function ProfileScreenLoaded({
onPageSelected={onPageSelected} onPageSelected={onPageSelected}
onCurrentPageSelected={onCurrentPageSelected} onCurrentPageSelected={onCurrentPageSelected}
renderHeader={renderHeader}> renderHeader={renderHeader}>
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => ( {({headerHeight, isFocused, scrollElRef}) => (
<FeedSection <FeedSection
ref={postsSectionRef} ref={postsSectionRef}
feed={`author|${profile.did}|posts_and_author_threads`} feed={`author|${profile.did}|posts_and_author_threads`}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isFocused={isFocused} isFocused={isFocused}
isScrolledDown={isScrolledDown} scrollElRef={scrollElRef as ListRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
ignoreFilterFor={profile.did} ignoreFilterFor={profile.did}
/> />
)} )}
{showRepliesTab {showRepliesTab
? ({ ? ({headerHeight, isFocused, scrollElRef}) => (
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
}) => (
<FeedSection <FeedSection
ref={repliesSectionRef} ref={repliesSectionRef}
feed={`author|${profile.did}|posts_with_replies`} feed={`author|${profile.did}|posts_with_replies`}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isFocused={isFocused} isFocused={isFocused}
isScrolledDown={isScrolledDown} scrollElRef={scrollElRef as ListRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
ignoreFilterFor={profile.did} ignoreFilterFor={profile.did}
/> />
) )
: null} : null}
{({onScroll, headerHeight, isFocused, isScrolledDown, scrollElRef}) => ( {({headerHeight, isFocused, scrollElRef}) => (
<FeedSection <FeedSection
ref={mediaSectionRef} ref={mediaSectionRef}
feed={`author|${profile.did}|posts_with_media`} feed={`author|${profile.did}|posts_with_media`}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isFocused={isFocused} isFocused={isFocused}
isScrolledDown={isScrolledDown} scrollElRef={scrollElRef as ListRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
ignoreFilterFor={profile.did} ignoreFilterFor={profile.did}
/> />
)} )}
{showLikesTab {showLikesTab
? ({ ? ({headerHeight, isFocused, scrollElRef}) => (
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
}) => (
<FeedSection <FeedSection
ref={likesSectionRef} ref={likesSectionRef}
feed={`likes|${profile.did}`} feed={`likes|${profile.did}`}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isFocused={isFocused} isFocused={isFocused}
isScrolledDown={isScrolledDown} scrollElRef={scrollElRef as ListRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
ignoreFilterFor={profile.did} ignoreFilterFor={profile.did}
/> />
) )
: null} : null}
{showFeedsTab {showFeedsTab
? ({onScroll, headerHeight, isFocused, scrollElRef}) => ( ? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileFeedgens <ProfileFeedgens
ref={feedsSectionRef} ref={feedsSectionRef}
did={profile.did} did={profile.did}
scrollElRef={ scrollElRef={scrollElRef as ListRef}
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
scrollEventThrottle={1}
headerOffset={headerHeight} headerOffset={headerHeight}
enabled={isFocused} enabled={isFocused}
/> />
) )
: null} : null}
{showListsTab {showListsTab
? ({onScroll, headerHeight, isFocused, scrollElRef}) => ( ? ({headerHeight, isFocused, scrollElRef}) => (
<ProfileLists <ProfileLists
ref={listsSectionRef} ref={listsSectionRef}
did={profile.did} did={profile.did}
scrollElRef={ scrollElRef={scrollElRef as ListRef}
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
scrollEventThrottle={1}
headerOffset={headerHeight} headerOffset={headerHeight}
enabled={isFocused} enabled={isFocused}
/> />
@@ -396,28 +360,19 @@ function ProfileScreenLoaded({
interface FeedSectionProps { interface FeedSectionProps {
feed: FeedDescriptor feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number headerHeight: number
isFocused: boolean isFocused: boolean
isScrolledDown: boolean scrollElRef: ListRef
scrollElRef: React.MutableRefObject<FlatList<any> | null>
ignoreFilterFor?: string ignoreFilterFor?: string
} }
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl( function FeedSectionImpl(
{ {feed, headerHeight, isFocused, scrollElRef, ignoreFilterFor},
feed,
onScroll,
headerHeight,
isFocused,
isScrolledDown,
scrollElRef,
ignoreFilterFor,
},
ref, ref,
) { ) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const onScrollToTop = React.useCallback(() => { const onScrollToTop = React.useCallback(() => {
scrollElRef.current?.scrollToOffset({ scrollElRef.current?.scrollToOffset({
@@ -443,8 +398,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
feed={feed} feed={feed}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
onHasNew={setHasNew} onHasNew={setHasNew}
onScroll={onScroll} onScrolledDownChange={setIsScrolledDown}
scrollEventThrottle={1}
renderEmptyState={renderPostsEmpty} renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight} headerOffset={headerHeight}
renderEndOfFeed={ProfileEndOfFeed} renderEndOfFeed={ProfileEndOfFeed}
+15 -31
View File
@@ -1,11 +1,5 @@
import React, {useMemo, useCallback} from 'react' import React, {useMemo, useCallback} from 'react'
import { import {Dimensions, StyleSheet, View, ActivityIndicator} from 'react-native'
Dimensions,
StyleSheet,
View,
ActivityIndicator,
FlatList,
} from 'react-native'
import {NativeStackScreenProps} from '@react-navigation/native-stack' import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -20,6 +14,7 @@ import {PagerWithHeader} from 'view/com/pager/PagerWithHeader'
import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader' import {ProfileSubpageHeader} from 'view/com/profile/ProfileSubpageHeader'
import {Feed} from 'view/com/posts/Feed' import {Feed} from 'view/com/posts/Feed'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {RichText} from 'view/com/util/text/RichText' import {RichText} from 'view/com/util/text/RichText'
@@ -29,12 +24,13 @@ import {EmptyState} from 'view/com/util/EmptyState'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {useSetTitle} from 'lib/hooks/useSetTitle' import {useSetTitle} from 'lib/hooks/useSetTitle'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {Haptics} from 'lib/haptics' import {Haptics} from 'lib/haptics'
import {useAnalytics} from 'lib/analytics/analytics' import {useAnalytics} from 'lib/analytics/analytics'
import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown' import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
import {useScrollHandlers} from '#/lib/ScrollContext'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import {makeCustomFeedLink} from 'lib/routes/links' import {makeCustomFeedLink} from 'lib/routes/links'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {CenteredView, ScrollView} from 'view/com/util/Views' import {CenteredView, ScrollView} from 'view/com/util/Views'
@@ -46,7 +42,6 @@ import {logger} from '#/logger'
import {Trans, msg} from '@lingui/macro' import {Trans, msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED'
import { import {
useFeedSourceInfoQuery, useFeedSourceInfoQuery,
FeedSourceFeedInfo, FeedSourceFeedInfo,
@@ -403,17 +398,13 @@ export function ProfileFeedScreenInner({
isHeaderReady={true} isHeaderReady={true}
renderHeader={renderHeader} renderHeader={renderHeader}
onCurrentPageSelected={onCurrentPageSelected}> onCurrentPageSelected={onCurrentPageSelected}>
{({onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused}) => {({headerHeight, scrollElRef, isFocused}) =>
isPublicResponse?.isPublic ? ( isPublicResponse?.isPublic ? (
<FeedSection <FeedSection
ref={feedSectionRef} ref={feedSectionRef}
feed={`feedgen|${feedInfo.uri}`} feed={`feedgen|${feedInfo.uri}`}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isScrolledDown={isScrolledDown} scrollElRef={scrollElRef as ListRef}
scrollElRef={
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
isFocused={isFocused} isFocused={isFocused}
/> />
) : ( ) : (
@@ -422,13 +413,12 @@ export function ProfileFeedScreenInner({
</CenteredView> </CenteredView>
) )
} }
{({onScroll, headerHeight, scrollElRef}) => ( {({headerHeight, scrollElRef}) => (
<AboutSection <AboutSection
feedOwnerDid={feedInfo.creatorDid} feedOwnerDid={feedInfo.creatorDid}
feedRkey={feedInfo.route.params.rkey} feedRkey={feedInfo.route.params.rkey}
feedInfo={feedInfo} feedInfo={feedInfo}
headerHeight={headerHeight} headerHeight={headerHeight}
onScroll={onScroll}
scrollElRef={ scrollElRef={
scrollElRef as React.MutableRefObject<ScrollView | null> scrollElRef as React.MutableRefObject<ScrollView | null>
} }
@@ -497,18 +487,14 @@ function NonPublicFeedMessage({rawError}: {rawError?: Error}) {
interface FeedSectionProps { interface FeedSectionProps {
feed: FeedDescriptor feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number headerHeight: number
isScrolledDown: boolean scrollElRef: ListRef
scrollElRef: React.MutableRefObject<FlatList<any> | null>
isFocused: boolean isFocused: boolean
} }
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl( function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
{feed, onScroll, headerHeight, isScrolledDown, scrollElRef, isFocused},
ref,
) {
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const queryClient = useQueryClient() const queryClient = useQueryClient()
const onScrollToTop = useCallback(() => { const onScrollToTop = useCallback(() => {
@@ -536,8 +522,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
pollInterval={30e3} pollInterval={30e3}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
onHasNew={setHasNew} onHasNew={setHasNew}
onScroll={onScroll} onScrolledDownChange={setIsScrolledDown}
scrollEventThrottle={5}
renderEmptyState={renderPostsEmpty} renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight} headerOffset={headerHeight}
/> />
@@ -558,7 +543,6 @@ function AboutSection({
feedRkey, feedRkey,
feedInfo, feedInfo,
headerHeight, headerHeight,
onScroll,
scrollElRef, scrollElRef,
isOwner, isOwner,
}: { }: {
@@ -566,13 +550,13 @@ function AboutSection({
feedRkey: string feedRkey: string
feedInfo: FeedSourceFeedInfo feedInfo: FeedSourceFeedInfo
headerHeight: number headerHeight: number
onScroll: OnScrollHandler
scrollElRef: React.MutableRefObject<ScrollView | null> scrollElRef: React.MutableRefObject<ScrollView | null>
isOwner: boolean isOwner: boolean
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const scrollHandler = useAnimatedScrollHandler(onScroll) const scrollHandlers = useScrollHandlers()
const onScroll = useAnimatedScrollHandler(scrollHandlers)
const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri) const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
const {hasSession} = useSession() const {hasSession} = useSession()
const {track} = useAnalytics() const {track} = useAnalytics()
@@ -608,12 +592,12 @@ function AboutSection({
return ( return (
<ScrollView <ScrollView
ref={scrollElRef} ref={scrollElRef}
onScroll={onScroll}
scrollEventThrottle={1} scrollEventThrottle={1}
contentContainerStyle={{ contentContainerStyle={{
paddingTop: headerHeight, paddingTop: headerHeight,
minHeight: Dimensions.get('window').height * 1.5, minHeight: Dimensions.get('window').height * 1.5,
}} }}>
onScroll={scrollHandler}>
<View <View
style={[ style={[
{ {
+16 -47
View File
@@ -1,11 +1,5 @@
import React, {useCallback, useMemo} from 'react' import React, {useCallback, useMemo} from 'react'
import { import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native'
ActivityIndicator,
FlatList,
Pressable,
StyleSheet,
View,
} from 'react-native'
import {useFocusEffect} from '@react-navigation/native' import {useFocusEffect} from '@react-navigation/native'
import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types' import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
@@ -22,6 +16,7 @@ import {EmptyState} from 'view/com/util/EmptyState'
import {RichText} from 'view/com/util/text/RichText' import {RichText} from 'view/com/util/text/RichText'
import {Button} from 'view/com/util/forms/Button' import {Button} from 'view/com/util/forms/Button'
import {TextLink} from 'view/com/util/Link' import {TextLink} from 'view/com/util/Link'
import {ListRef} from 'view/com/util/List'
import * as Toast from 'view/com/util/Toast' import * as Toast from 'view/com/util/Toast'
import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
import {FAB} from 'view/com/util/fab/FAB' import {FAB} from 'view/com/util/fab/FAB'
@@ -31,7 +26,6 @@ import {usePalette} from 'lib/hooks/usePalette'
import {useSetTitle} from 'lib/hooks/useSetTitle' import {useSetTitle} from 'lib/hooks/useSetTitle'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
import {OnScrollHandler} from 'lib/hooks/useOnMainScroll'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp} from 'lib/routes/types'
import {toShareUrl} from 'lib/strings/url-helpers' import {toShareUrl} from 'lib/strings/url-helpers'
import {shareUrl} from 'lib/sharing' import {shareUrl} from 'lib/sharing'
@@ -165,36 +159,22 @@ function ProfileListScreenLoaded({
isHeaderReady={true} isHeaderReady={true}
renderHeader={renderHeader} renderHeader={renderHeader}
onCurrentPageSelected={onCurrentPageSelected}> onCurrentPageSelected={onCurrentPageSelected}>
{({ {({headerHeight, scrollElRef, isFocused}) => (
onScroll,
headerHeight,
isScrolledDown,
scrollElRef,
isFocused,
}) => (
<FeedSection <FeedSection
ref={feedSectionRef} ref={feedSectionRef}
feed={`list|${uri}`} feed={`list|${uri}`}
scrollElRef={ scrollElRef={scrollElRef as ListRef}
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
isFocused={isFocused} isFocused={isFocused}
/> />
)} )}
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => ( {({headerHeight, scrollElRef}) => (
<AboutSection <AboutSection
ref={aboutSectionRef} ref={aboutSectionRef}
scrollElRef={ scrollElRef={scrollElRef as ListRef}
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
list={list} list={list}
onPressAddUser={onPressAddUser} onPressAddUser={onPressAddUser}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
/> />
)} )}
</PagerWithHeader> </PagerWithHeader>
@@ -221,16 +201,12 @@ function ProfileListScreenLoaded({
items={SECTION_TITLES_MOD} items={SECTION_TITLES_MOD}
isHeaderReady={true} isHeaderReady={true}
renderHeader={renderHeader}> renderHeader={renderHeader}>
{({onScroll, headerHeight, isScrolledDown, scrollElRef}) => ( {({headerHeight, scrollElRef}) => (
<AboutSection <AboutSection
list={list} list={list}
scrollElRef={ scrollElRef={scrollElRef as ListRef}
scrollElRef as React.MutableRefObject<FlatList<any> | null>
}
onPressAddUser={onPressAddUser} onPressAddUser={onPressAddUser}
onScroll={onScroll}
headerHeight={headerHeight} headerHeight={headerHeight}
isScrolledDown={isScrolledDown}
/> />
)} )}
</PagerWithHeader> </PagerWithHeader>
@@ -615,19 +591,15 @@ function Header({rkey, list}: {rkey: string; list: AppBskyGraphDefs.ListView}) {
interface FeedSectionProps { interface FeedSectionProps {
feed: FeedDescriptor feed: FeedDescriptor
onScroll: OnScrollHandler
headerHeight: number headerHeight: number
isScrolledDown: boolean scrollElRef: ListRef
scrollElRef: React.MutableRefObject<FlatList<any> | null>
isFocused: boolean isFocused: boolean
} }
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
function FeedSectionImpl( function FeedSectionImpl({feed, scrollElRef, headerHeight, isFocused}, ref) {
{feed, scrollElRef, onScroll, headerHeight, isScrolledDown, isFocused},
ref,
) {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const [hasNew, setHasNew] = React.useState(false) const [hasNew, setHasNew] = React.useState(false)
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const onScrollToTop = useCallback(() => { const onScrollToTop = useCallback(() => {
scrollElRef.current?.scrollToOffset({ scrollElRef.current?.scrollToOffset({
@@ -654,8 +626,7 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
pollInterval={30e3} pollInterval={30e3}
scrollElRef={scrollElRef} scrollElRef={scrollElRef}
onHasNew={setHasNew} onHasNew={setHasNew}
onScroll={onScroll} onScrolledDownChange={setIsScrolledDown}
scrollEventThrottle={1}
renderEmptyState={renderPostsEmpty} renderEmptyState={renderPostsEmpty}
headerOffset={headerHeight} headerOffset={headerHeight}
/> />
@@ -674,20 +645,19 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
interface AboutSectionProps { interface AboutSectionProps {
list: AppBskyGraphDefs.ListView list: AppBskyGraphDefs.ListView
onPressAddUser: () => void onPressAddUser: () => void
onScroll: OnScrollHandler
headerHeight: number headerHeight: number
isScrolledDown: boolean scrollElRef: ListRef
scrollElRef: React.MutableRefObject<FlatList<any> | null>
} }
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
function AboutSectionImpl( function AboutSectionImpl(
{list, onPressAddUser, onScroll, headerHeight, isScrolledDown, scrollElRef}, {list, onPressAddUser, headerHeight, scrollElRef},
ref, ref,
) { ) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {isMobile} = useWebMediaQueries() const {isMobile} = useWebMediaQueries()
const {currentAccount} = useSession() const {currentAccount} = useSession()
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
const isOwner = list.creator.did === currentAccount?.did const isOwner = list.creator.did === currentAccount?.did
@@ -817,8 +787,7 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
renderHeader={renderHeader} renderHeader={renderHeader}
renderEmptyState={renderEmptyState} renderEmptyState={renderEmptyState}
headerOffset={headerHeight} headerOffset={headerHeight}
onScroll={onScroll} onScrolledDownChange={setIsScrolledDown}
scrollEventThrottle={1}
/> />
{isScrolledDown && ( {isScrolledDown && (
<LoadLatestBtn <LoadLatestBtn
+5 -4
View File
@@ -8,7 +8,8 @@ import {
Pressable, Pressable,
Platform, Platform,
} from 'react-native' } from 'react-native'
import {FlatList, ScrollView, CenteredView} from '#/view/com/util/Views' import {ScrollView, CenteredView} from '#/view/com/util/Views'
import {List} from '#/view/com/util/List'
import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api' import {AppBskyActorDefs, AppBskyFeedDefs, moderateProfile} from '@atproto/api'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -155,7 +156,7 @@ function SearchScreenSuggestedFollows() {
}, [currentAccount, setSuggestions, getSuggestedFollowsByActor]) }, [currentAccount, setSuggestions, getSuggestedFollowsByActor])
return suggestions.length ? ( return suggestions.length ? (
<FlatList <List
data={suggestions} data={suggestions}
renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />} renderItem={({item}) => <ProfileCardWithFollowBtn profile={item} noBg />}
keyExtractor={item => item.did} keyExtractor={item => item.did}
@@ -243,7 +244,7 @@ function SearchScreenPostResults({query}: {query: string}) {
{isFetched ? ( {isFetched ? (
<> <>
{posts.length ? ( {posts.length ? (
<FlatList <List
data={items} data={items}
renderItem={({item}) => { renderItem={({item}) => {
if (item.type === 'post') { if (item.type === 'post') {
@@ -284,7 +285,7 @@ function SearchScreenUserResults({query}: {query: string}) {
return isFetched && results ? ( return isFetched && results ? (
<> <>
{results.length ? ( {results.length ? (
<FlatList <List
data={results} data={results}
renderItem={({item}) => ( renderItem={({item}) => (
<ProfileCardWithFollowBtn profile={item} noBg /> <ProfileCardWithFollowBtn profile={item} noBg />
+67 -69
View File
@@ -15,7 +15,6 @@ import {useLingui} from '@lingui/react'
import {Plural, Trans, msg, plural} from '@lingui/macro' import {Plural, Trans, msg, plural} from '@lingui/macro'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
import {useInviteCodesQuery} from '#/state/queries/invites' import {useInviteCodesQuery} from '#/state/queries/invites'
import {ScrollView} from '#/view/com/util/Views'
export function DesktopRightNav() { export function DesktopRightNav() {
const pal = usePalette('default') const pal = usePalette('default')
@@ -30,77 +29,75 @@ export function DesktopRightNav() {
return ( return (
<View style={[styles.rightNav, pal.view]}> <View style={[styles.rightNav, pal.view]}>
<ScrollView contentContainerStyle={{borderWidth: 0}}> <View style={{paddingVertical: 20}}>
<View style={{paddingVertical: 20}}> <DesktopSearch />
<DesktopSearch />
{hasSession && ( {hasSession && (
<View style={{paddingTop: 18, marginBottom: 18}}> <View style={{paddingTop: 18, marginBottom: 18}}>
<DesktopFeeds /> <DesktopFeeds />
</View>
)}
<View
style={[
styles.message,
{
paddingTop: hasSession ? 0 : 18,
},
]}>
{isSandbox ? (
<View style={[palError.view, styles.messageLine, s.p10]}>
<Text type="md" style={[palError.text, s.bold]}>
SANDBOX. Posts and accounts are not permanent.
</Text>
</View>
) : undefined}
<View style={[s.flexRow]}>
{hasSession && (
<>
<TextLink
type="md"
style={pal.link}
href={FEEDBACK_FORM_URL({
email: currentAccount?.email,
handle: currentAccount?.handle,
})}
text={_(msg`Feedback`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
</>
)}
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text={_(msg`Privacy`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/tos"
text={_(msg`Terms`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
<TextLink
type="md"
style={pal.link}
href={HELP_DESK_URL}
text={_(msg`Help`)}
/>
</View>
</View> </View>
)}
{hasSession && <InviteCodes />} <View
style={[
styles.message,
{
paddingTop: hasSession ? 0 : 18,
},
]}>
{isSandbox ? (
<View style={[palError.view, styles.messageLine, s.p10]}>
<Text type="md" style={[palError.text, s.bold]}>
SANDBOX. Posts and accounts are not permanent.
</Text>
</View>
) : undefined}
<View style={[s.flexRow]}>
{hasSession && (
<>
<TextLink
type="md"
style={pal.link}
href={FEEDBACK_FORM_URL({
email: currentAccount?.email,
handle: currentAccount?.handle,
})}
text={_(msg`Feedback`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
</>
)}
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/privacy-policy"
text={_(msg`Privacy`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
<TextLink
type="md"
style={pal.link}
href="https://blueskyweb.xyz/support/tos"
text={_(msg`Terms`)}
/>
<Text type="md" style={pal.textLight}>
&nbsp;&middot;&nbsp;
</Text>
<TextLink
type="md"
style={pal.link}
href={HELP_DESK_URL}
text={_(msg`Help`)}
/>
</View>
</View> </View>
</ScrollView>
{hasSession && <InviteCodes />}
</View>
</View> </View>
) )
} }
@@ -177,7 +174,8 @@ const styles = StyleSheet.create({
// @ts-ignore web only // @ts-ignore web only
left: 'calc(50vw + 320px)', left: 'calc(50vw + 320px)',
width: 304, width: 304,
height: '100%', maxHeight: '100%',
overflowY: 'auto',
}, },
message: { message: {
+14
View File
@@ -48,6 +48,20 @@
typed-emitter "^2.1.0" typed-emitter "^2.1.0"
zod "^3.21.4" zod "^3.21.4"
"@atproto/api@^0.7.3":
version "0.7.3"
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.7.3.tgz#3224000353619970d5e397a157c6e189e195ef47"
integrity sha512-fKU+W+S4kKxClE6IcPBHPZAjcyBYxG28S0FW/bv3T/ZYDkNxGzDV4xuoHOyEDGtB30slltl5U83njuuRZs5xtw==
dependencies:
"@atproto/common-web" "^0.2.3"
"@atproto/lexicon" "^0.3.1"
"@atproto/syntax" "^0.1.5"
"@atproto/xrpc" "^0.4.1"
multiformats "^9.9.0"
tlds "^1.234.0"
typed-emitter "^2.1.0"
zod "^3.21.4"
"@atproto/aws@^0.1.6": "@atproto/aws@^0.1.6":
version "0.1.6" version "0.1.6"
resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.1.6.tgz#c6ecbfd92b325f3c5433688534d47f43358b415b" resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.1.6.tgz#c6ecbfd92b325f3c5433688534d47f43358b415b"