Bluesky app fork with some witchin' additions 💫

Merge branch 'main' of https://github.com/bluesky-social/social-app

Changed files
+2899 -2636
patches
src
alf
components
ContextMenu
Dialog
LabelingServiceCard
Menu
Post
PostControls
ProfileHoverCard
ProgressGuide
ReportDialog
Select
StarterPack
anim
dialogs
dms
feeds
forms
hooks
icons
intents
moderation
verification
lib
locale
logger
platform
screens
state
types
view
com
composer
feeds
home
lightbox
lists
modals
notifications
pager
post-thread
posts
profile
util
icons
screens
shell
+3 -5
package.json
··· 123 123 "array.prototype.findlast": "^1.2.3", 124 124 "await-lock": "^2.2.2", 125 125 "babel-plugin-transform-remove-console": "^6.9.4", 126 - "base64-js": "^1.5.1", 127 126 "bcp-47": "^2.1.0", 128 127 "bcp-47-match": "^2.0.3", 129 128 "date-fns": "^2.30.0", ··· 213 212 "react-native-web-webview": "^1.0.2", 214 213 "react-native-webview": "^13.13.5", 215 214 "react-remove-scroll-bar": "^2.3.8", 216 - "react-responsive": "^9.0.2", 215 + "react-responsive": "^10.0.1", 217 216 "react-textarea-autosize": "^8.5.3", 218 217 "sonner": "^2.0.7", 219 218 "sonner-native": "^0.21.0", ··· 245 244 "@types/lodash.isequal": "^4.5.6", 246 245 "@types/lodash.shuffle": "^4.2.7", 247 246 "@types/psl": "^1.1.1", 248 - "@types/react-dom": "^19.1.2", 249 - "@types/react-responsive": "^8.0.5", 247 + "@types/react": "^19.1.12", 248 + "@types/react-dom": "^19.1.8", 250 249 "@typescript-eslint/eslint-plugin": "^7.18.0", 251 250 "@typescript-eslint/parser": "^7.18.0", 252 251 "babel-jest": "^29.7.0", ··· 284 283 "@expo/image-utils": "0.6.3", 285 284 "@react-native/babel-preset": "0.79.3", 286 285 "@react-native/normalize-colors": "0.79.3", 287 - "@types/react": "^18", 288 286 "**/expo-constants": "17.0.3", 289 287 "**/expo-device": "7.1.4", 290 288 "**/zod": "3.23.8",
+13
patches/@discord+bottom-sheet+4.6.1.patch
··· 1 + diff --git a/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts b/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 2 + index 1c788ab..d30f330 100644 3 + --- a/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 4 + +++ b/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 5 + @@ -6,7 +6,7 @@ type Callback = (...args: any[]) => any; 6 + * https://gist.github.com/JakeCoxon/c7ebf6e6496f8468226fd36b596e1985 7 + */ 8 + export const useStableCallback = (callback: Callback) => { 9 + - const callbackRef = useRef<Callback>(); 10 + + const callbackRef = useRef<Callback>(undefined); 11 + const memoCallback = useCallback( 12 + (...args: any) => callbackRef.current && callbackRef.current(...args), 13 + []
+3 -3
src/Navigation.tsx
··· 1 - import {useCallback, useRef} from 'react' 1 + import {type JSX, useCallback, useRef} from 'react' 2 2 import {Linking} from 'react-native' 3 3 import * as Notifications from 'expo-notifications' 4 4 import {i18n, type MessageDescriptor} from '@lingui/core' ··· 64 64 import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy' 65 65 import {ProfileScreen} from '#/view/screens/Profile' 66 66 import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy' 67 - import {ProfileListScreen} from '#/view/screens/ProfileList' 68 - import {SavedFeeds} from '#/view/screens/SavedFeeds' 69 67 import {Storybook} from '#/view/screens/Storybook' 70 68 import {SupportScreen} from '#/view/screens/Support' 71 69 import {TermsOfServiceScreen} from '#/view/screens/TermsOfService' ··· 92 90 import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows' 93 91 import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 94 92 import {ProfileSearchScreen} from '#/screens/Profile/ProfileSearch' 93 + import {ProfileListScreen} from '#/screens/ProfileList' 94 + import {SavedFeeds} from '#/screens/SavedFeeds' 95 95 import {SearchScreen} from '#/screens/Search' 96 96 import {AboutSettingsScreen} from '#/screens/Settings/AboutSettings' 97 97 import {AccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings'
+1 -1
src/Splash.tsx
··· 15 15 withTiming, 16 16 } from 'react-native-reanimated' 17 17 import {useSafeAreaInsets} from 'react-native-safe-area-context' 18 - import Svg, {Path, SvgProps} from 'react-native-svg' 18 + import Svg, {Path, type SvgProps} from 'react-native-svg' 19 19 import {Image} from 'expo-image' 20 20 import * as SplashScreen from 'expo-splash-screen' 21 21
+1 -1
src/alf/types.ts
··· 1 - import {StyleProp, TextStyle, ViewStyle} from 'react-native' 1 + import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native' 2 2 3 3 export type TextStyleProp = { 4 4 style?: StyleProp<TextStyle>
-1
src/alf/typography.tsx
··· 3 3 import {type StyleProp, type TextStyle} from 'react-native' 4 4 import {UITextView} from 'react-native-uitextview' 5 5 import createEmojiRegex from 'emoji-regex' 6 - import type React from 'react' 7 6 8 7 import {isNative} from '#/platform/detection' 9 8 import {isIOS} from '#/platform/detection'
+1 -1
src/alf/util/themeSelector.ts
··· 1 - import {ThemeName} from '#/alf/types' 1 + import {type ThemeName} from '#/alf/types' 2 2 3 3 export function select<T>(name: ThemeName, options: Record<ThemeName, T>) { 4 4 switch (name) {
+2 -2
src/alf/util/useColorModeTheme.ts
··· 1 1 import React from 'react' 2 - import {ColorSchemeName, useColorScheme} from 'react-native' 2 + import {type ColorSchemeName, useColorScheme} from 'react-native' 3 3 4 4 import {isWeb} from '#/platform/detection' 5 5 import {useThemePrefs} from '#/state/shell' 6 6 import {dark, dim, light} from '#/alf/themes' 7 - import {ThemeName} from '#/alf/types' 7 + import {type ThemeName} from '#/alf/types' 8 8 9 9 export function useColorModeTheme(): ThemeName { 10 10 const theme = useThemeName()
+1 -1
src/alf/util/useGutters.ts
··· 1 1 import React from 'react' 2 2 3 - import {Breakpoint, useBreakpoints} from '#/alf/breakpoints' 3 + import {type Breakpoint, useBreakpoints} from '#/alf/breakpoints' 4 4 import * as tokens from '#/alf/tokens' 5 5 6 6 type Gutter = 'compact' | 'base' | 'wide' | 0
+2 -2
src/components/Button.tsx
··· 71 71 export type ButtonContext = VariantProps & ButtonState 72 72 73 73 type NonTextElements = 74 - | React.ReactElement 75 - | Iterable<React.ReactElement | null | undefined | boolean> 74 + | React.ReactElement<any> 75 + | Iterable<React.ReactElement<any> | null | undefined | boolean> 76 76 77 77 export type ButtonProps = Pick< 78 78 PressableProps,
+2 -1
src/components/ContextMenu/index.tsx
··· 119 119 const hoverablesSV = useSharedValue< 120 120 Record<string, {id: string; rect: Measurement}> 121 121 >({}) 122 - const syncHoverablesThrottleRef = useRef<ReturnType<typeof setTimeout>>() 122 + const syncHoverablesThrottleRef = 123 + useRef<ReturnType<typeof setTimeout>>(undefined) 123 124 const [hoveredMenuItem, setHoveredMenuItem] = useState<string | null>(null) 124 125 125 126 const onHoverableTouchUp = useCallback((id: string) => {
-1
src/components/ContextMenu/types.ts
··· 5 5 type ViewStyle, 6 6 } from 'react-native' 7 7 import {type SharedValue} from 'react-native-reanimated' 8 - import type React from 'react' 9 8 10 9 import type * as Dialog from '#/components/Dialog' 11 10 import {
+1 -2
src/components/Dialog/types.ts
··· 5 5 type StyleProp, 6 6 type ViewStyle, 7 7 } from 'react-native' 8 - import type React from 'react' 9 8 10 9 import {type ViewStyleProp} from '#/alf' 11 10 import {type BottomSheetViewProps} from '../../../modules/bottom-sheet' ··· 34 33 */ 35 34 export type DialogControlProps = DialogControlRefProps & { 36 35 id: string 37 - ref: React.RefObject<DialogControlRefProps> 36 + ref: React.RefObject<DialogControlRefProps | null> 38 37 isOpen?: boolean 39 38 } 40 39
+1 -1
src/components/Dialog/utils.ts
··· 1 1 import React from 'react' 2 2 3 - import {DialogControlProps} from '#/components/Dialog/types' 3 + import {type DialogControlProps} from '#/components/Dialog/types' 4 4 5 5 export function useAutoOpen(control: DialogControlProps, showTimeout?: number) { 6 6 React.useEffect(() => {
+2 -2
src/components/Fill.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 2 + import type React from 'react' 3 3 4 - import {atoms as a, ViewStyleProp} from '#/alf' 4 + import {atoms as a, type ViewStyleProp} from '#/alf' 5 5 6 6 export function Fill({ 7 7 children,
+1 -1
src/components/GradientFill.tsx
··· 1 1 import {LinearGradient} from 'expo-linear-gradient' 2 2 3 - import {atoms as a, tokens, ViewStyleProp} from '#/alf' 3 + import {atoms as a, type tokens, type ViewStyleProp} from '#/alf' 4 4 5 5 export function GradientFill({ 6 6 gradient,
+4 -4
src/components/IconCircle.tsx
··· 3 3 import { 4 4 atoms as a, 5 5 flatten, 6 - TextStyleProp, 6 + type TextStyleProp, 7 7 useTheme, 8 - ViewStyleProp, 8 + type ViewStyleProp, 9 9 } from '#/alf' 10 - import {Props} from '#/components/icons/common' 11 - import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 10 + import {type Props} from '#/components/icons/common' 11 + import {type Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 12 12 13 13 export function IconCircle({ 14 14 icon: Icon,
+4 -4
src/components/LabelingServiceCard/index.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 2 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 3 import {msg, Plural, Trans} from '@lingui/macro' 5 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 6 6 7 7 import {getLabelingServiceTitle} from '#/lib/moderation' 8 8 import {sanitizeHandle} from '#/lib/strings/handles' 9 9 import {useLabelerInfoQuery} from '#/state/queries/labeler' 10 10 import {UserAvatar} from '#/view/com/util/UserAvatar' 11 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 11 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 12 12 import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 13 - import {Link as InternalLink, LinkProps} from '#/components/Link' 13 + import {Link as InternalLink, type LinkProps} from '#/components/Link' 14 14 import {RichText} from '#/components/RichText' 15 15 import {Text} from '#/components/Typography' 16 16 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
+1 -1
src/components/LikedByList.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 2 + import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/components/LinearGradientBackground.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import {LinearGradient} from 'expo-linear-gradient' 3 + import type React from 'react' 4 4 5 5 import {gradients} from '#/alf/tokens' 6 6
-1
src/components/Lists.tsx
··· 2 2 import {type StyleProp, View, type ViewStyle} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 - import type React from 'react' 6 5 7 6 import {cleanError} from '#/lib/strings/errors' 8 7 import {CenteredView} from '#/view/com/util/Views'
+1 -1
src/components/Loader.tsx
··· 8 8 } from 'react-native-reanimated' 9 9 10 10 import {atoms as a, flatten, useTheme} from '#/alf' 11 - import {Props, useCommonSVGProps} from '#/components/icons/common' 11 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 12 12 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 13 13 14 14 export function Loader(props: Props) {
+1 -1
src/components/Loader.web.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 3 import {atoms as a, flatten, useTheme} from '#/alf' 4 - import {Props, useCommonSVGProps} from '#/components/icons/common' 4 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 6 6 7 7 export function Loader(props: Props) {
+2 -2
src/components/MediaInsetBorder.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 3 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 4 4 import {Fill} from '#/components/Fill' 5 5 6 6 /**
+8 -8
src/components/Menu/types.ts
··· 1 - import React from 'react' 2 1 import { 3 - AccessibilityProps, 4 - AccessibilityRole, 5 - GestureResponderEvent, 6 - PressableProps, 2 + type AccessibilityProps, 3 + type AccessibilityRole, 4 + type GestureResponderEvent, 5 + type PressableProps, 7 6 } from 'react-native' 7 + import type React from 'react' 8 8 9 - import {TextStyleProp, ViewStyleProp} from '#/alf' 10 - import * as Dialog from '#/components/Dialog' 11 - import {Props as SVGIconProps} from '#/components/icons/common' 9 + import {type TextStyleProp, type ViewStyleProp} from '#/alf' 10 + import type * as Dialog from '#/components/Dialog' 11 + import {type Props as SVGIconProps} from '#/components/icons/common' 12 12 13 13 export type ContextType = { 14 14 control: Dialog.DialogOuterProps['control']
+111 -101
src/components/NewskieDialog.tsx
··· 1 - import React from 'react' 1 + import {useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, moderateProfile} from '@atproto/api' 3 + import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {differenceInSeconds} from 'date-fns' ··· 27 27 disabled?: boolean 28 28 }) { 29 29 const {_} = useLingui() 30 - const t = useTheme() 31 - const moderationOpts = useModerationOpts() 32 - const {currentAccount} = useSession() 33 - const timeAgo = useGetTimeAgo() 34 30 const control = useDialogControl() 35 31 36 - const isMe = profile.did === currentAccount?.did 37 32 const createdAt = profile.createdAt as string | undefined 38 33 39 - const profileName = React.useMemo(() => { 40 - const name = profile.displayName || profile.handle 41 - 42 - if (isMe) { 43 - return _(msg`You`) 44 - } 45 - 46 - if (!moderationOpts) return name 47 - const moderation = moderateProfile(profile, moderationOpts) 48 - 49 - return sanitizeDisplayName(name, moderation.ui('displayName')) 50 - }, [_, isMe, moderationOpts, profile]) 51 - 52 - const [now] = React.useState(() => Date.now()) 53 - const daysOld = React.useMemo(() => { 34 + const [now] = useState(() => Date.now()) 35 + const daysOld = useMemo(() => { 54 36 if (!createdAt) return Infinity 55 37 return differenceInSeconds(now, new Date(createdAt)) / 86400 56 38 }, [createdAt, now]) ··· 77 59 )} 78 60 </Button> 79 61 80 - <Dialog.Outer control={control}> 62 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 81 63 <Dialog.Handle /> 82 - <Dialog.ScrollableInner 83 - label={_(msg`New user info dialog`)} 84 - style={web({width: 'auto', maxWidth: 400, minWidth: 200})}> 85 - <View style={[a.gap_md]}> 86 - <View style={[a.align_center]}> 87 - <View 88 - style={[ 89 - { 90 - height: 60, 91 - width: 64, 92 - }, 93 - ]}> 94 - <Newskie 95 - width={64} 96 - height={64} 97 - fill="#FFC404" 98 - style={[a.absolute, a.inset_0]} 99 - /> 100 - </View> 101 - <Text style={[a.font_bold, a.text_xl]}> 102 - {isMe ? ( 103 - <Trans>Welcome, friend!</Trans> 104 - ) : ( 105 - <Trans>Say hello!</Trans> 106 - )} 107 - </Text> 108 - </View> 109 - <Text style={[a.text_md, a.text_center, a.leading_snug]}> 110 - {profile.joinedViaStarterPack ? ( 111 - <Trans> 112 - {profileName} joined Bluesky using a starter pack{' '} 113 - {timeAgo(createdAt, now, {format: 'long'})} ago 114 - </Trans> 115 - ) : ( 116 - <Trans> 117 - {profileName} joined Bluesky{' '} 118 - {timeAgo(createdAt, now, {format: 'long'})} ago 119 - </Trans> 120 - )} 121 - </Text> 122 - {profile.joinedViaStarterPack ? ( 123 - <StarterPackCard.Link 124 - starterPack={profile.joinedViaStarterPack} 125 - onPress={() => { 126 - control.close() 127 - }}> 128 - <View 129 - style={[ 130 - a.w_full, 131 - a.mt_sm, 132 - a.p_lg, 133 - a.border, 134 - a.rounded_sm, 135 - t.atoms.border_contrast_low, 136 - ]}> 137 - <StarterPackCard.Card 138 - starterPack={profile.joinedViaStarterPack} 139 - /> 140 - </View> 141 - </StarterPackCard.Link> 142 - ) : null} 64 + <DialogInner profile={profile} createdAt={createdAt} now={now} /> 65 + </Dialog.Outer> 66 + </View> 67 + ) 68 + } 143 69 144 - {isNative && ( 145 - <Button 146 - label={_(msg`Close`)} 147 - variant="solid" 148 - color="secondary" 149 - size="small" 150 - style={[a.mt_sm]} 151 - onPress={() => control.close()}> 152 - <ButtonText> 153 - <Trans>Close</Trans> 154 - </ButtonText> 155 - </Button> 156 - )} 70 + function DialogInner({ 71 + profile, 72 + createdAt, 73 + now, 74 + }: { 75 + profile: AppBskyActorDefs.ProfileViewDetailed 76 + createdAt: string 77 + now: number 78 + }) { 79 + const control = Dialog.useDialogContext() 80 + const {_} = useLingui() 81 + const t = useTheme() 82 + const moderationOpts = useModerationOpts() 83 + const {currentAccount} = useSession() 84 + const timeAgo = useGetTimeAgo() 85 + const isMe = profile.did === currentAccount?.did 86 + 87 + const profileName = useMemo(() => { 88 + const name = profile.displayName || profile.handle 89 + 90 + if (isMe) { 91 + return _(msg`You`) 92 + } 93 + 94 + if (!moderationOpts) return name 95 + const moderation = moderateProfile(profile, moderationOpts) 96 + 97 + return sanitizeDisplayName(name, moderation.ui('displayName')) 98 + }, [_, isMe, moderationOpts, profile]) 99 + 100 + return ( 101 + <Dialog.ScrollableInner 102 + label={_(msg`New user info dialog`)} 103 + style={web({maxWidth: 400})}> 104 + <View style={[a.gap_md]}> 105 + <View style={[a.align_center]}> 106 + <View 107 + style={[ 108 + { 109 + height: 60, 110 + width: 64, 111 + }, 112 + ]}> 113 + <Newskie 114 + width={64} 115 + height={64} 116 + fill="#FFC404" 117 + style={[a.absolute, a.inset_0]} 118 + /> 157 119 </View> 120 + <Text style={[a.font_bold, a.text_xl]}> 121 + {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>} 122 + </Text> 123 + </View> 124 + <Text style={[a.text_md, a.text_center, a.leading_snug]}> 125 + {profile.joinedViaStarterPack ? ( 126 + <Trans> 127 + {profileName} joined Bluesky using a starter pack{' '} 128 + {timeAgo(createdAt, now, {format: 'long'})} ago 129 + </Trans> 130 + ) : ( 131 + <Trans> 132 + {profileName} joined Bluesky{' '} 133 + {timeAgo(createdAt, now, {format: 'long'})} ago 134 + </Trans> 135 + )} 136 + </Text> 137 + {profile.joinedViaStarterPack ? ( 138 + <StarterPackCard.Link 139 + starterPack={profile.joinedViaStarterPack} 140 + onPress={() => control.close()}> 141 + <View 142 + style={[ 143 + a.w_full, 144 + a.mt_sm, 145 + a.p_lg, 146 + a.border, 147 + a.rounded_sm, 148 + t.atoms.border_contrast_low, 149 + ]}> 150 + <StarterPackCard.Card 151 + starterPack={profile.joinedViaStarterPack} 152 + /> 153 + </View> 154 + </StarterPackCard.Link> 155 + ) : null} 158 156 159 - <Dialog.Close /> 160 - </Dialog.ScrollableInner> 161 - </Dialog.Outer> 162 - </View> 157 + {isNative && ( 158 + <Button 159 + label={_(msg`Close`)} 160 + color="secondary" 161 + size="small" 162 + style={[a.mt_sm]} 163 + onPress={() => control.close()}> 164 + <ButtonText> 165 + <Trans>Close</Trans> 166 + </ButtonText> 167 + </Button> 168 + )} 169 + </View> 170 + 171 + <Dialog.Close /> 172 + </Dialog.ScrollableInner> 163 173 ) 164 174 }
+2 -2
src/components/Pills.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {BSKY_LABELER_DID, ModerationCause} from '@atproto/api' 3 + import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api' 4 4 import {Trans} from '@lingui/macro' 5 5 6 6 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 7 7 import {UserAvatar} from '#/view/com/util/UserAvatar' 8 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 8 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 9 9 import {Button} from '#/components/Button' 10 10 import { 11 11 ModerationDetailsDialog,
+1 -1
src/components/Portal.tsx
··· 10 10 useState, 11 11 } from 'react' 12 12 13 - type Component = React.ReactElement 13 + type Component = React.ReactElement<any> 14 14 15 15 type ContextType = { 16 16 outlet: Component | null
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
··· 139 139 playlist: string 140 140 setHasSubtitleTrack: (v: boolean) => void 141 141 setError: (v: Error | null) => void 142 - videoRef: React.RefObject<HTMLVideoElement> 142 + videoRef: React.RefObject<HTMLVideoElement | null> 143 143 setHlsLoading: (v: boolean) => void 144 144 }) { 145 145 const [Hls, setHls] = useState<typeof HlsTypes.default | undefined>(
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
··· 1 1 import {View} from 'react-native' 2 2 import {msg, Trans} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 - import type React from 'react' 5 4 6 5 import {atoms as a, useTheme} from '#/alf' 7 6 import {Button, ButtonText} from '#/components/Button'
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
··· 1 1 import {type SvgProps} from 'react-native-svg' 2 - import type React from 'react' 3 2 4 3 import {PressableWithHover} from '#/view/com/util/PressableWithHover' 5 4 import {atoms as a, useTheme, web} from '#/alf'
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
··· 2 2 import {View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 - import type React from 'react' 6 5 7 6 import {isFirefox, isTouchDevice} from '#/lib/browser' 8 7 import {clamp} from '#/lib/numbers'
+5 -5
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 46 46 hlsLoading, 47 47 hasSubtitleTrack, 48 48 }: { 49 - videoRef: React.RefObject<HTMLVideoElement> 50 - hlsRef: React.RefObject<Hls | undefined> 49 + videoRef: React.RefObject<HTMLVideoElement | null> 50 + hlsRef: React.RefObject<Hls | undefined | null> 51 51 active: boolean 52 52 setActive: () => void 53 53 focused: boolean 54 54 setFocused: (focused: boolean) => void 55 55 onScreen: boolean 56 - fullscreenRef: React.RefObject<HTMLDivElement> 56 + fullscreenRef: React.RefObject<HTMLDivElement | null> 57 57 hlsLoading: boolean 58 58 hasSubtitleTrack: boolean 59 59 }) { ··· 232 232 }, [onSeek, videoRef]) 233 233 234 234 const [showCursor, setShowCursor] = useState(true) 235 - const cursorTimeoutRef = useRef<ReturnType<typeof setTimeout>>() 235 + const cursorTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined) 236 236 const onPointerMoveEmptySpace = useCallback(() => { 237 237 setShowCursor(true) 238 238 if (cursorTimeoutRef.current) { ··· 264 264 [hovered], 265 265 ) 266 266 267 - const timeoutRef = useRef<ReturnType<typeof setTimeout>>() 267 + const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined) 268 268 269 269 const onHoverWithTimeout = useCallback(() => { 270 270 onHover()
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
··· 3 3 import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 - import type React from 'react' 7 6 8 7 import {isSafari, isTouchDevice} from '#/lib/browser' 9 8 import {atoms as a} from '#/alf'
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
··· 4 4 import {logger} from '#/logger' 5 5 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 6 6 7 - export function useVideoElement(ref: RefObject<HTMLVideoElement>) { 7 + export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) { 8 8 const [playing, setPlaying] = useState(false) 9 9 const [muted, setMuted] = useState(true) 10 10 const [currentTime, setCurrentTime] = useState(0)
+1 -2
src/components/Post/Embed/VideoEmbed/index.web.tsx
··· 10 10 import {type AppBskyEmbedVideo} from '@atproto/api' 11 11 import {msg} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 - import type React from 'react' 14 13 15 14 import {isFirefox} from '#/lib/browser' 16 15 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' ··· 38 37 useActiveVideoWeb() 39 38 const [onScreen, setOnScreen] = useState(false) 40 39 const [isFullscreen] = useFullscreen() 41 - const lastKnownTime = useRef<number | undefined>() 40 + const lastKnownTime = useRef<number | undefined>(undefined) 42 41 43 42 useEffect(() => { 44 43 if (!ref.current) return
-1
src/components/PostControls/PostMenu/index.tsx
··· 8 8 } from '@atproto/api' 9 9 import {msg} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 - import type React from 'react' 12 11 13 12 import {type Shadow} from '#/state/cache/post-shadow' 14 13 import {EventStopper} from '#/view/com/util/EventStopper'
-1
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 - import type React from 'react' 7 6 8 7 import {makeProfileLink} from '#/lib/routes/links' 9 8 import {type NavigationProp} from '#/lib/routes/types'
-1
src/components/PostControls/ShareMenu/index.tsx
··· 9 9 } from '@atproto/api' 10 10 import {msg} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 - import type React from 'react' 13 12 14 13 import {makeProfileLink} from '#/lib/routes/links' 15 14 import {shareUrl} from '#/lib/sharing'
-2
src/components/ProfileHoverCard/types.ts
··· 1 - import type React from 'react' 2 - 3 1 import {type ViewStyleProp} from '#/alf' 4 2 5 3 export type ProfileHoverCardProps = ViewStyleProp & {
+3 -3
src/components/ProgressGuide/FollowDialog.tsx
··· 293 293 interestsDisplayNames, 294 294 }: { 295 295 guide: Follow10ProgressGuide 296 - inputRef: React.RefObject<TextInput> 297 - listRef: React.RefObject<ListMethods> 296 + inputRef: React.RefObject<TextInput | null> 297 + listRef: React.RefObject<ListMethods | null> 298 298 onSelectTab: (v: string) => void 299 299 searchText: string 300 300 setHeaderHeight: (v: number) => void ··· 565 565 }: { 566 566 onChangeText: (text: string) => void 567 567 onEscape: () => void 568 - inputRef: React.RefObject<TextInput> 568 + inputRef: React.RefObject<TextInput | null> 569 569 defaultValue: string 570 570 }) { 571 571 const t = useTheme()
+1 -1
src/components/ProgressGuide/List.tsx
··· 1 - import {StyleProp, View, ViewStyle} from 'react-native' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 2 import {msg, Trans} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4
+2 -2
src/components/ProgressGuide/Toast.tsx
··· 14 14 import {isWeb} from '#/platform/detection' 15 15 import {atoms as a, useTheme} from '#/alf' 16 16 import {Portal} from '#/components/Portal' 17 - import {AnimatedCheck, AnimatedCheckRef} from '../anim/AnimatedCheck' 17 + import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 18 18 import {Text} from '../Typography' 19 19 20 20 export interface ProgressGuideToastRef { ··· 39 39 const translateY = useSharedValue(0) 40 40 const opacity = useSharedValue(0) 41 41 const animatedCheckRef = React.useRef<AnimatedCheckRef | null>(null) 42 - const timeoutRef = React.useRef<NodeJS.Timeout | undefined>() 42 + const timeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined) 43 43 const winDim = useWindowDimensions() 44 44 45 45 /**
+2 -2
src/components/ReportDialog/SelectLabelerView.tsx
··· 1 1 import {View} from 'react-native' 2 - import {AppBskyLabelerDefs} from '@atproto/api' 2 + import {type AppBskyLabelerDefs} from '@atproto/api' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 ··· 9 9 import {Divider} from '#/components/Divider' 10 10 import * as LabelingServiceCard from '#/components/LabelingServiceCard' 11 11 import {Text} from '#/components/Typography' 12 - import {ReportDialogProps} from './types' 12 + import {type ReportDialogProps} from './types' 13 13 14 14 export function SelectLabelerView({ 15 15 ...props
+6 -3
src/components/ReportDialog/SelectReportOptionView.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 3 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {ReportOption, useReportOptions} from '#/lib/moderation/useReportOptions' 7 + import { 8 + type ReportOption, 9 + useReportOptions, 10 + } from '#/lib/moderation/useReportOptions' 8 11 import {Link} from '#/components/Link' 9 12 import {DMCA_LINK} from '#/components/ReportDialog/const' 10 13 export {useDialogControl as useReportDialogControl} from '#/components/Dialog' ··· 23 26 } from '#/components/icons/Chevron' 24 27 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight' 25 28 import {Text} from '#/components/Typography' 26 - import {ReportDialogProps} from './types' 29 + import {type ReportDialogProps} from './types' 27 30 28 31 export function SelectReportOptionView(props: { 29 32 params: ReportDialogProps['params']
+3 -3
src/components/ReportDialog/SubmitView.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 3 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {getLabelingServiceTitle} from '#/lib/moderation' 8 - import {ReportOption} from '#/lib/moderation/useReportOptions' 8 + import {type ReportOption} from '#/lib/moderation/useReportOptions' 9 9 import {isAndroid} from '#/platform/detection' 10 10 import {useAgent} from '#/state/session' 11 11 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 19 19 import {PaperPlane_Stroke2_Corner0_Rounded as SendIcon} from '#/components/icons/PaperPlane' 20 20 import {Loader} from '#/components/Loader' 21 21 import {Text} from '#/components/Typography' 22 - import {ReportDialogProps} from './types' 22 + import {type ReportDialogProps} from './types' 23 23 24 24 export function SubmitView({ 25 25 params,
+4 -4
src/components/ReportDialog/index.tsx
··· 1 1 import React from 'react' 2 2 import {Pressable, View} from 'react-native' 3 - import {ScrollView} from 'react-native-gesture-handler' 3 + import {type ScrollView} from 'react-native-gesture-handler' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {ReportOption} from '#/lib/moderation/useReportOptions' 7 + import {type ReportOption} from '#/lib/moderation/useReportOptions' 8 8 import {useMyLabelersQuery} from '#/state/queries/preferences' 9 9 export {useDialogControl as useReportDialogControl} from '#/components/Dialog' 10 10 11 - import {AppBskyLabelerDefs} from '@atproto/api' 11 + import {type AppBskyLabelerDefs} from '@atproto/api' 12 12 13 13 import {atoms as a} from '#/alf' 14 14 import * as Dialog from '#/components/Dialog' ··· 18 18 import {SelectLabelerView} from './SelectLabelerView' 19 19 import {SelectReportOptionView} from './SelectReportOptionView' 20 20 import {SubmitView} from './SubmitView' 21 - import {ReportDialogProps} from './types' 21 + import {type ReportDialogProps} from './types' 22 22 23 23 export function ReportDialog(props: ReportDialogProps) { 24 24 return (
+1 -1
src/components/ReportDialog/types.ts
··· 1 - import * as Dialog from '#/components/Dialog' 1 + import type * as Dialog from '#/components/Dialog' 2 2 3 3 export type ReportDialogProps = { 4 4 control: Dialog.DialogOuterProps['control']
+2 -2
src/components/RichTextTag.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, Text as RNText, TextStyle} from 'react-native' 2 + import {type StyleProp, Text as RNText, type TextStyle} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 - import {NavigationProp} from '#/lib/routes/types' 7 + import {type NavigationProp} from '#/lib/routes/types' 8 8 import {isInvalidHandle} from '#/lib/strings/handles' 9 9 import {isNative, isWeb} from '#/platform/detection' 10 10 import {
+1 -1
src/components/Select/types.ts
··· 160 160 item: T, 161 161 index: number, 162 162 selectedValue?: string | null, 163 - ) => React.ReactElement 163 + ) => React.ReactElement<any> 164 164 /* 165 165 * Extracts the value from an item. Defaults to `item => item.value` 166 166 */
+3 -3
src/components/StarterPack/Main/PostsList.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {isNative} from '#/platform/detection' 7 - import {FeedDescriptor} from '#/state/queries/post-feed' 7 + import {type FeedDescriptor} from '#/state/queries/post-feed' 8 8 import {PostFeed} from '#/view/com/posts/PostFeed' 9 9 import {EmptyState} from '#/view/com/util/EmptyState' 10 - import {ListRef} from '#/view/com/util/List' 11 - import {SectionRef} from '#/screens/Profile/Sections/types' 10 + import {type ListRef} from '#/view/com/util/List' 11 + import {type SectionRef} from '#/screens/Profile/Sections/types' 12 12 13 13 interface ProfilesListProps { 14 14 listUri: string
+10 -7
src/components/StarterPack/Main/ProfilesList.tsx
··· 1 1 import React, {useCallback} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 - AppBskyGraphGetList, 4 + type AppBskyActorDefs, 5 + type AppBskyGraphGetList, 6 6 AtUri, 7 - ModerationOpts, 7 + type ModerationOpts, 8 8 } from '@atproto/api' 9 - import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' 9 + import { 10 + type InfiniteData, 11 + type UseInfiniteQueryResult, 12 + } from '@tanstack/react-query' 10 13 11 14 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 12 15 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' ··· 14 17 import {isNative, isWeb} from '#/platform/detection' 15 18 import {useAllListMembersQuery} from '#/state/queries/list-members' 16 19 import {useSession} from '#/state/session' 17 - import {List, ListRef} from '#/view/com/util/List' 18 - import {SectionRef} from '#/screens/Profile/Sections/types' 20 + import {List, type ListRef} from '#/view/com/util/List' 21 + import {type SectionRef} from '#/screens/Profile/Sections/types' 19 22 import {atoms as a, useTheme} from '#/alf' 20 23 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 21 24 import {Default as ProfileCard} from '#/components/ProfileCard'
+11 -11
src/components/StarterPack/QrCode.tsx
··· 1 - import React from 'react' 1 + import {lazy} from 'react' 2 2 import {View} from 'react-native' 3 3 // @ts-expect-error missing types 4 4 import QRCode from 'react-native-qrcode-styled' 5 5 import type ViewShot from 'react-native-view-shot' 6 - import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 6 + import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 7 import {Trans} from '@lingui/macro' 8 8 9 9 import {isWeb} from '#/platform/detection' ··· 15 15 import {Text} from '#/components/Typography' 16 16 import * as bsky from '#/types/bsky' 17 17 18 - const LazyViewShot = React.lazy( 18 + const LazyViewShot = lazy( 19 19 // @ts-expect-error dynamic import 20 20 () => import('react-native-view-shot/src/index'), 21 21 ) 22 22 23 - interface Props { 23 + export function QrCode({ 24 + starterPack, 25 + link, 26 + ref, 27 + }: { 24 28 starterPack: AppBskyGraphDefs.StarterPackView 25 29 link: string 26 - } 27 - 28 - export const QrCode = React.forwardRef<ViewShot, Props>(function QrCode( 29 - {starterPack, link}, 30 - ref, 31 - ) { 30 + ref: React.Ref<ViewShot> 31 + }) { 32 32 const {record} = starterPack 33 33 34 34 if ( ··· 93 93 </LinearGradientBackground> 94 94 </LazyViewShot> 95 95 ) 96 - }) 96 + } 97 97 98 98 export function QrCodeInner({link}: {link: string}) { 99 99 const t = useTheme()
+62 -51
src/components/StarterPack/QrCodeDialog.tsx
··· 1 - import React from 'react' 1 + import {Suspense, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import type ViewShot from 'react-native-view-shot' 4 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' ··· 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 - import {logEvent} from '#/lib/statsig/statsig' 12 11 import {logger} from '#/logger' 13 12 import {isNative, isWeb} from '#/platform/detection' 14 - import * as Toast from '#/view/com/util/Toast' 15 - import {atoms as a} from '#/alf' 16 - import {Button, ButtonText} from '#/components/Button' 13 + import {atoms as a, useBreakpoints} from '#/alf' 14 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 17 15 import * as Dialog from '#/components/Dialog' 18 16 import {type DialogControlProps} from '#/components/Dialog' 17 + import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 18 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 19 + import {FloppyDisk_Stroke2_Corner0_Rounded as FloppyDiskIcon} from '#/components/icons/FloppyDisk' 19 20 import {Loader} from '#/components/Loader' 20 21 import {QrCode} from '#/components/StarterPack/QrCode' 22 + import * as Toast from '#/components/Toast' 21 23 import * as bsky from '#/types/bsky' 22 24 23 25 export function QrCodeDialog({ ··· 30 32 control: DialogControlProps 31 33 }) { 32 34 const {_} = useLingui() 33 - const [isProcessing, setIsProcessing] = React.useState(false) 35 + const {gtMobile} = useBreakpoints() 36 + const [isSaveProcessing, setIsSaveProcessing] = useState(false) 37 + const [isCopyProcessing, setIsCopyProcessing] = useState(false) 34 38 35 - const ref = React.useRef<ViewShot>(null) 39 + const ref = useRef<ViewShot>(null) 36 40 37 41 const getCanvas = (base64: string): Promise<HTMLCanvasElement> => { 38 42 return new Promise(resolve => { ··· 68 72 try { 69 73 await createAssetAsync(`file://${uri}`) 70 74 } catch (e: unknown) { 71 - Toast.show( 72 - _(msg`An error occurred while saving the QR code!`), 73 - 'xmark', 74 - ) 75 + Toast.show(_(msg`An error occurred while saving the QR code!`), { 76 + type: 'error', 77 + }) 75 78 logger.error('Failed to save QR code', {error: e}) 76 79 return 77 80 } 78 81 } else { 79 - setIsProcessing(true) 82 + setIsSaveProcessing(true) 80 83 81 84 if ( 82 85 !bsky.validate( ··· 101 104 link.click() 102 105 } 103 106 104 - logEvent('starterPack:share', { 107 + logger.metric('starterPack:share', { 105 108 starterPack: starterPack.uri, 106 109 shareType: 'qrcode', 107 110 qrShareType: 'save', 108 111 }) 109 - setIsProcessing(false) 112 + setIsSaveProcessing(false) 110 113 Toast.show( 111 114 isWeb 112 115 ? _(msg`QR code has been downloaded!`) ··· 117 120 } 118 121 119 122 const onCopyPress = async () => { 120 - setIsProcessing(true) 123 + setIsCopyProcessing(true) 121 124 ref.current?.capture?.().then(async (uri: string) => { 122 125 const canvas = await getCanvas(uri) 123 126 // @ts-expect-error web only ··· 126 129 navigator.clipboard.write([item]) 127 130 }) 128 131 129 - logEvent('starterPack:share', { 132 + logger.metric('starterPack:share', { 130 133 starterPack: starterPack.uri, 131 134 shareType: 'qrcode', 132 135 qrShareType: 'copy', 133 136 }) 134 137 Toast.show(_(msg`QR code copied to your clipboard!`)) 135 - setIsProcessing(false) 138 + setIsCopyProcessing(false) 136 139 control.close() 137 140 }) 138 141 } ··· 142 145 control.close(() => { 143 146 Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( 144 147 () => { 145 - logEvent('starterPack:share', { 148 + logger.metric('starterPack:share', { 146 149 starterPack: starterPack.uri, 147 150 shareType: 'qrcode', 148 151 qrShareType: 'share', ··· 154 157 } 155 158 156 159 return ( 157 - <Dialog.Outer control={control}> 160 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 158 161 <Dialog.Handle /> 159 162 <Dialog.ScrollableInner 160 163 label={_(msg`Create a QR code for a starter pack`)}> 161 164 <View style={[a.flex_1, a.align_center, a.gap_5xl]}> 162 - <React.Suspense fallback={<Loading />}> 165 + <Suspense fallback={<Loading />}> 163 166 {!link ? ( 164 167 <Loading /> 165 168 ) : ( 166 169 <> 167 170 <QrCode starterPack={starterPack} link={link} ref={ref} /> 168 - {isProcessing ? ( 169 - <View> 170 - <Loader size="xl" /> 171 - </View> 172 - ) : ( 173 - <View 174 - style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}> 175 - <Button 176 - label={_(msg`Copy QR code`)} 177 - variant="solid" 178 - color="secondary" 179 - size="small" 180 - onPress={isWeb ? onCopyPress : onSharePress}> 181 - <ButtonText> 182 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 183 - </ButtonText> 184 - </Button> 185 - <Button 186 - label={_(msg`Save QR code`)} 187 - variant="solid" 188 - color="secondary" 189 - size="small" 190 - onPress={onSavePress}> 191 - <ButtonText> 192 - <Trans>Save</Trans> 193 - </ButtonText> 194 - </Button> 195 - </View> 196 - )} 171 + <View 172 + style={[ 173 + a.w_full, 174 + a.gap_md, 175 + gtMobile && [a.flex_row, a.justify_center, a.flex_wrap], 176 + ]}> 177 + <Button 178 + label={_(msg`Copy QR code`)} 179 + color="primary_subtle" 180 + size="large" 181 + onPress={isWeb ? onCopyPress : onSharePress}> 182 + <ButtonIcon 183 + icon={ 184 + isCopyProcessing 185 + ? Loader 186 + : isWeb 187 + ? ChainLinkIcon 188 + : ShareIcon 189 + } 190 + /> 191 + <ButtonText> 192 + {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 + </ButtonText> 194 + </Button> 195 + <Button 196 + label={_(msg`Save QR code`)} 197 + color="secondary" 198 + size="large" 199 + onPress={onSavePress}> 200 + <ButtonIcon 201 + icon={isSaveProcessing ? Loader : FloppyDiskIcon} 202 + /> 203 + <ButtonText> 204 + <Trans>Save</Trans> 205 + </ButtonText> 206 + </Button> 207 + </View> 197 208 </> 198 209 )} 199 - </React.Suspense> 210 + </Suspense> 200 211 </View> 201 212 <Dialog.Close /> 202 213 </Dialog.ScrollableInner> ··· 206 217 207 218 function Loading() { 208 219 return ( 209 - <View style={[a.align_center, a.p_xl]}> 220 + <View style={[a.align_center, a.justify_center, {minHeight: 400}]}> 210 221 <Loader size="xl" /> 211 222 </View> 212 223 )
+30 -24
src/components/StarterPack/ShareDialog.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 7 import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' 9 8 import {shareUrl} from '#/lib/sharing' 10 - import {logEvent} from '#/lib/statsig/statsig' 11 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 + import {logger} from '#/logger' 12 11 import {isNative, isWeb} from '#/platform/detection' 13 - import {atoms as a, useTheme} from '#/alf' 14 - import {Button, ButtonText} from '#/components/Button' 12 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import {type DialogControlProps} from '#/components/Dialog' 16 15 import * as Dialog from '#/components/Dialog' 16 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 17 + import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' 18 + import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 17 19 import {Loader} from '#/components/Loader' 18 20 import {Text} from '#/components/Typography' 19 21 ··· 27 29 28 30 export function ShareDialog(props: Props) { 29 31 return ( 30 - <Dialog.Outer control={props.control}> 32 + <Dialog.Outer 33 + control={props.control} 34 + nativeOptions={{preventExpansion: true}}> 31 35 <Dialog.Handle /> 32 36 <ShareDialogInner {...props} /> 33 37 </Dialog.Outer> ··· 43 47 }: Props) { 44 48 const {_} = useLingui() 45 49 const t = useTheme() 46 - const {isTabletOrDesktop} = useWebMediaQueries() 50 + const {gtMobile} = useBreakpoints() 47 51 48 52 const imageUrl = getStarterPackOgCard(starterPack) 49 53 50 54 const onShareLink = async () => { 51 55 if (!link) return 52 56 shareUrl(link) 53 - logEvent('starterPack:share', { 57 + logger.metric('starterPack:share', { 54 58 starterPack: starterPack.uri, 55 59 shareType: 'link', 56 60 }) ··· 67 71 <> 68 72 <Dialog.ScrollableInner label={_(msg`Share link dialog`)}> 69 73 {!imageLoaded || !link ? ( 70 - <View style={[a.p_xl, a.align_center]}> 74 + <View style={[a.align_center, a.justify_center, {minHeight: 350}]}> 71 75 <Loader size="xl" /> 72 76 </View> 73 77 ) : ( 74 - <View style={[!isTabletOrDesktop && a.gap_lg]}> 75 - <View style={[a.gap_sm, isTabletOrDesktop && a.pb_lg]}> 78 + <View style={[!gtMobile && a.gap_lg]}> 79 + <View style={[a.gap_sm, gtMobile && a.pb_lg]}> 76 80 <Text style={[a.font_bold, a.text_2xl]}> 77 81 <Trans>Invite people to this starter pack!</Trans> 78 82 </Text> ··· 89 93 a.rounded_sm, 90 94 { 91 95 aspectRatio: 1200 / 630, 92 - transform: [{scale: isTabletOrDesktop ? 0.85 : 1}], 93 - marginTop: isTabletOrDesktop ? -20 : 0, 96 + transform: [{scale: gtMobile ? 0.85 : 1}], 97 + marginTop: gtMobile ? -20 : 0, 94 98 }, 95 99 ]} 96 100 accessibilityIgnoresInvertColors={true} ··· 98 102 <View 99 103 style={[ 100 104 a.gap_md, 101 - isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}], 105 + gtMobile && [ 106 + a.gap_sm, 107 + a.justify_center, 108 + a.flex_row, 109 + a.flex_wrap, 110 + ], 102 111 ]}> 103 112 <Button 104 113 label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 105 - variant="solid" 106 - color="secondary" 107 - size="small" 108 - style={[isWeb && a.self_center]} 114 + color="primary_subtle" 115 + size="large" 109 116 onPress={onShareLink}> 117 + <ButtonIcon icon={ChainLinkIcon} /> 110 118 <ButtonText> 111 119 {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 112 120 </ButtonText> 113 121 </Button> 114 122 <Button 115 123 label={_(msg`Share QR code`)} 116 - variant="solid" 117 - color="secondary" 118 - size="small" 119 - style={[isWeb && a.self_center]} 124 + color="primary_subtle" 125 + size="large" 120 126 onPress={() => { 121 127 control.close(() => { 122 128 qrDialogControl.open() 123 129 }) 124 130 }}> 131 + <ButtonIcon icon={QrCodeIcon} /> 125 132 <ButtonText> 126 133 <Trans>Share QR code</Trans> 127 134 </ButtonText> ··· 129 136 {isNative && ( 130 137 <Button 131 138 label={_(msg`Save image`)} 132 - variant="ghost" 133 139 color="secondary" 134 - size="small" 135 - style={[isWeb && a.self_center]} 140 + size="large" 136 141 onPress={onSave}> 142 + <ButtonIcon icon={DownloadIcon} /> 137 143 <ButtonText> 138 144 <Trans>Save image</Trans> 139 145 </ButtonText>
+2 -2
src/components/StarterPack/Wizard/ScreenTransition.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import Animated, { 4 3 FadeIn, 5 4 FadeOut, 6 5 SlideInLeft, 7 6 SlideInRight, 8 7 } from 'react-native-reanimated' 8 + import type React from 'react' 9 9 10 10 import {isWeb} from '#/platform/detection' 11 11
+1 -1
src/components/SubtleWebHover.tsx
··· 1 - import {ViewStyleProp} from '#/alf' 1 + import {type ViewStyleProp} from '#/alf' 2 2 3 3 export function SubtleWebHover({}: ViewStyleProp & {hover: boolean}) { 4 4 return null
+4 -4
src/components/TrendingTopics.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AtUri} from '@atproto/api' 3 + import {type AtUri} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 10 10 // import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' 11 11 // import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote' 12 12 // import {UserAvatar} from '#/view/com/util/UserAvatar' 13 - import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' 14 - import {atoms as a, native, useTheme, ViewStyleProp} from '#/alf' 13 + import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics' 14 + import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf' 15 15 import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack' 16 - import {Link as InternalLink, LinkProps} from '#/components/Link' 16 + import {Link as InternalLink, type LinkProps} from '#/components/Link' 17 17 import {Text} from '#/components/Typography' 18 18 19 19 export function TrendingTopic({
+1 -1
src/components/anim/AnimatedCheck.tsx
··· 8 8 } from 'react-native-reanimated' 9 9 import Svg, {Circle, Path} from 'react-native-svg' 10 10 11 - import {Props, useCommonSVGProps} from '#/components/icons/common' 11 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 12 12 13 13 const AnimatedPath = Animated.createAnimatedComponent(Path) 14 14 const AnimatedCircle = Animated.createAnimatedComponent(Circle)
+1 -1
src/components/dialogs/Embed.tsx
··· 1 1 import {memo, useEffect, useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, AppBskyFeedPost, AtUri} from '@atproto/api' 3 + import {type AppBskyActorDefs, type AppBskyFeedPost, AtUri} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6
+3 -3
src/components/dialogs/EmbedConsent.tsx
··· 10 10 } from '#/lib/strings/embed-player' 11 11 import {useSetExternalEmbedPref} from '#/state/preferences' 12 12 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonText} from '#/components/Button' 13 14 import * as Dialog from '#/components/Dialog' 14 - import {Button, ButtonText} from '../Button' 15 - import {Text} from '../Typography' 15 + import {Text} from '#/components/Typography' 16 16 17 17 export function EmbedConsentDialog({ 18 18 control, ··· 48 48 }, [control, setExternalEmbedPref, source]) 49 49 50 50 return ( 51 - <Dialog.Outer control={control}> 51 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 52 52 <Dialog.Handle /> 53 53 <Dialog.ScrollableInner 54 54 label={_(msg`External Media`)}
+1 -1
src/components/dialogs/GifSelect.tsx
··· 37 37 onClose, 38 38 onSelectGif: onSelectGifProp, 39 39 }: { 40 - controlRef: React.RefObject<{open: () => void}> 40 + controlRef: React.RefObject<{open: () => void} | null> 41 41 onClose?: () => void 42 42 onSelectGif: (gif: Gif) => void 43 43 }) {
+2 -2
src/components/dialogs/MutedWords.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' 3 + import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 16 16 native, 17 17 useBreakpoints, 18 18 useTheme, 19 - ViewStyleProp, 19 + type ViewStyleProp, 20 20 web, 21 21 } from '#/alf' 22 22 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+1 -1
src/components/dialogs/SearchablePeopleList.tsx
··· 484 484 value: string 485 485 onChangeText: (text: string) => void 486 486 onEscape: () => void 487 - inputRef: React.RefObject<TextInput> 487 + inputRef: React.RefObject<TextInput | null> 488 488 }) { 489 489 const t = useTheme() 490 490 const {_} = useLingui()
-1
src/components/dms/ActionsWrapper.web.tsx
··· 3 3 import {type ChatBskyConvoDefs} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 - import type React from 'react' 7 6 8 7 import {useConvoActive} from '#/state/messages/convo' 9 8 import {useSession} from '#/state/session'
+2 -2
src/components/dms/BlockedByListDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ModerationCause} from '@atproto/api' 3 + import {type ModerationCause} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {listUriToHref} from '#/lib/strings/url-helpers' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import * as Dialog from '#/components/Dialog' 10 - import {DialogControlProps} from '#/components/Dialog' 10 + import {type DialogControlProps} from '#/components/Dialog' 11 11 import {InlineLinkText} from '#/components/Link' 12 12 import * as Prompt from '#/components/Prompt' 13 13 import {Text} from '#/components/Typography'
+2 -2
src/components/dms/LeaveConvoPrompt.tsx
··· 2 2 import {useLingui} from '@lingui/react' 3 3 import {StackActions, useNavigation} from '@react-navigation/native' 4 4 5 - import {NavigationProp} from '#/lib/routes/types' 5 + import {type NavigationProp} from '#/lib/routes/types' 6 6 import {isNative} from '#/platform/detection' 7 7 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 8 8 import * as Toast from '#/view/com/util/Toast' 9 - import {DialogOuterProps} from '#/components/Dialog' 9 + import {type DialogOuterProps} from '#/components/Dialog' 10 10 import * as Prompt from '#/components/Prompt' 11 11 12 12 export function LeaveConvoPrompt({
+2 -2
src/components/dms/MessagesListBlockedFooter.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ModerationDecision} from '@atproto/api' 3 + import {type ModerationDecision} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 14 14 import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 15 15 import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' 16 16 import {Text} from '#/components/Typography' 17 - import * as bsky from '#/types/bsky' 17 + import type * as bsky from '#/types/bsky' 18 18 19 19 export function MessagesListBlockedFooter({ 20 20 recipient: initialRecipient,
+1 -1
src/components/dms/ReportConversationPrompt.tsx
··· 1 1 import {msg} from '@lingui/macro' 2 2 import {useLingui} from '@lingui/react' 3 3 4 - import {DialogControlProps} from '#/components/Dialog' 4 + import {type DialogControlProps} from '#/components/Dialog' 5 5 import * as Prompt from '#/components/Prompt' 6 6 7 7 export function ReportConversationPrompt({
+2 -2
src/components/feeds/PostFeedVideoGridRow.tsx
··· 2 2 import {AppBskyEmbedVideo} from '@atproto/api' 3 3 4 4 import {logEvent} from '#/lib/statsig/statsig' 5 - import {FeedPostSliceItem} from '#/state/queries/post-feed' 6 - import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' 5 + import {type FeedPostSliceItem} from '#/state/queries/post-feed' 6 + import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' 7 7 import {atoms as a, useGutters} from '#/alf' 8 8 import * as Grid from '#/components/Grid' 9 9 import {
+2 -2
src/components/forms/DateField/index.web.tsx
··· 1 1 import React from 'react' 2 - import {StyleSheet, TextInput, TextInputProps} from 'react-native' 2 + import {StyleSheet, type TextInput, type TextInputProps} from 'react-native' 3 3 // @ts-expect-error untyped 4 4 import {unstable_createElement} from 'react-native-web' 5 5 6 - import {DateFieldProps} from '#/components/forms/DateField/types' 6 + import {type DateFieldProps} from '#/components/forms/DateField/types' 7 7 import {toSimpleDateString} from '#/components/forms/DateField/utils' 8 8 import * as TextField from '#/components/forms/TextField' 9 9 import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
+5 -2
src/components/forms/InputGroup.tsx
··· 23 23 {React.cloneElement(child, { 24 24 // @ts-ignore 25 25 style: [ 26 + // @ts-ignore 26 27 ...(Array.isArray(child.props?.style) 27 - ? child.props.style 28 - : [child.props.style || {}]), 28 + ? // @ts-ignore 29 + child.props.style 30 + : // @ts-ignore 31 + [child.props.style || {}]), 29 32 { 30 33 borderTopLeftRadius: i > 0 ? 0 : undefined, 31 34 borderTopRightRadius: i > 0 ? 0 : undefined,
+1 -1
src/components/forms/SearchInput.tsx
··· 1 1 import React from 'react' 2 - import {TextInput, View} from 'react-native' 2 + import {type TextInput, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/components/forms/TextField.tsx
··· 28 28 import {Text} from '#/components/Typography' 29 29 30 30 const Context = createContext<{ 31 - inputRef: React.RefObject<TextInput> | null 31 + inputRef: React.RefObject<TextInput | null> | null 32 32 isInvalid: boolean 33 33 hovered: boolean 34 34 onHoverIn: () => void ··· 152 152 value?: string 153 153 onChangeText?: (value: string) => void 154 154 isInvalid?: boolean 155 - inputRef?: React.RefObject<TextInput> | React.ForwardedRef<TextInput> 155 + inputRef?: React.RefObject<TextInput | null> | React.ForwardedRef<TextInput> 156 156 } 157 157 158 158 export function createInput(Component: typeof TextInput) {
+7 -2
src/components/forms/ToggleButton.tsx
··· 1 1 import React from 'react' 2 - import {AccessibilityProps, TextStyle, View, ViewStyle} from 'react-native' 2 + import { 3 + type AccessibilityProps, 4 + type TextStyle, 5 + View, 6 + type ViewStyle, 7 + } from 'react-native' 3 8 4 9 import {atoms as a, native, useTheme} from '#/alf' 5 10 import * as Toggle from '#/components/forms/Toggle' ··· 7 12 8 13 type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> & 9 14 AccessibilityProps & { 10 - children: React.ReactElement 15 + children: React.ReactElement<any> 11 16 testID?: string 12 17 } 13 18
+3 -3
src/components/hooks/useFollowMethods.ts
··· 2 2 import {msg} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4 5 - import {LogEvents} from '#/lib/statsig/statsig' 5 + import {type LogEvents} from '#/lib/statsig/statsig' 6 6 import {logger} from '#/logger' 7 - import {Shadow} from '#/state/cache/types' 7 + import {type Shadow} from '#/state/cache/types' 8 8 import {useProfileFollowMutationQueue} from '#/state/queries/profile' 9 9 import {useRequireAuth} from '#/state/session' 10 10 import * as Toast from '#/view/com/util/Toast' 11 - import * as bsky from '#/types/bsky' 11 + import type * as bsky from '#/types/bsky' 12 12 13 13 export function useFollowMethods({ 14 14 profile,
+1 -1
src/components/hooks/useFullscreen.ts
··· 14 14 return () => document.removeEventListener('fullscreenchange', onChange) 15 15 } 16 16 17 - export function useFullscreen(ref?: React.RefObject<HTMLElement>) { 17 + export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) { 18 18 if (!isWeb) throw new Error("'useFullscreen' is a web-only hook") 19 19 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => 20 20 Boolean(document.fullscreenElement),
+1 -1
src/components/icons/TEMPLATE.tsx
··· 1 1 import React from 'react' 2 2 import Svg, {Path} from 'react-native-svg' 3 3 4 - import {Props, useCommonSVGProps} from '#/components/icons/common' 4 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 6 6 export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef( 7 7 function LogoImpl(props: Props, ref) {
+1 -1
src/components/intents/VerifyEmailIntentDialog.tsx
··· 8 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 10 import * as Dialog from '#/components/Dialog' 11 - import {DialogControlProps} from '#/components/Dialog' 11 + import {type DialogControlProps} from '#/components/Dialog' 12 12 import {Divider} from '#/components/Divider' 13 13 import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Resend} from '#/components/icons/ArrowRotateCounterClockwise' 14 14 import {useIntentDialogs} from '#/components/intents/IntentDialogs'
-1
src/components/moderation/LabelPreference.tsx
··· 5 5 } from '@atproto/api' 6 6 import {msg, Trans} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' 8 - import type React from 'react' 9 8 10 9 import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' 11 10 import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
+8 -3
src/components/moderation/LabelsOnMe.tsx
··· 1 - import {StyleProp, View, ViewStyle} from 'react-native' 2 - import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 + import {type AppBskyFeedDefs, type ComAtprotoLabelDefs} from '@atproto/api' 3 3 import {msg, Plural, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {useSession} from '#/state/session' 7 7 import {atoms as a} from '#/alf' 8 - import {Button, ButtonIcon, ButtonSize, ButtonText} from '#/components/Button' 8 + import { 9 + Button, 10 + ButtonIcon, 11 + type ButtonSize, 12 + ButtonText, 13 + } from '#/components/Button' 9 14 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 10 15 import { 11 16 LabelsOnMeDialog,
+1 -1
src/components/moderation/LabelsOnMeDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' 3 + import {type ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {useMutation} from '@tanstack/react-query'
+2 -2
src/components/moderation/ModerationDetailsDialog.tsx
··· 1 1 import {View} from 'react-native' 2 - import {ModerationCause} from '@atproto/api' 2 + import {type ModerationCause} from '@atproto/api' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 ··· 12 12 import {atoms as a, useGutters, useTheme} from '#/alf' 13 13 import * as Dialog from '#/components/Dialog' 14 14 import {InlineLinkText} from '#/components/Link' 15 - import {AppModerationCause} from '#/components/Pills' 15 + import {type AppModerationCause} from '#/components/Pills' 16 16 import {Text} from '#/components/Typography' 17 17 18 18 export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog'
+2 -2
src/components/moderation/PostAlerts.tsx
··· 1 - import {StyleProp, ViewStyle} from 'react-native' 2 - import {ModerationCause, ModerationUI} from '@atproto/api' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 2 + import {type ModerationCause, type ModerationUI} from '@atproto/api' 3 3 4 4 import {getModerationCauseKey, unique} from '#/lib/moderation' 5 5 import * as Pills from '#/components/Pills'
+2 -2
src/components/moderation/ProfileHeaderAlerts.tsx
··· 1 - import {StyleProp, ViewStyle} from 'react-native' 2 - import {ModerationDecision} from '@atproto/api' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 2 + import {type ModerationDecision} from '@atproto/api' 3 3 4 4 import {getModerationCauseKey, unique} from '#/lib/moderation' 5 5 import * as Pills from '#/components/Pills'
+5 -5
src/components/moderation/ReportDialog/action.ts
··· 1 1 import { 2 - $Typed, 3 - ChatBskyConvoDefs, 4 - ComAtprotoModerationCreateReport, 2 + type $Typed, 3 + type ChatBskyConvoDefs, 4 + type ComAtprotoModerationCreateReport, 5 5 } from '@atproto/api' 6 6 import {msg} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' ··· 9 9 10 10 import {logger} from '#/logger' 11 11 import {useAgent} from '#/state/session' 12 - import {ReportState} from './state' 13 - import {ParsedReportSubject} from './types' 12 + import {type ReportState} from './state' 13 + import {type ParsedReportSubject} from './types' 14 14 15 15 export function useSubmitReportMutation() { 16 16 const {_} = useLingui()
+1 -1
src/components/moderation/ReportDialog/copy.ts
··· 2 2 import {msg} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4 5 - import {ParsedReportSubject} from './types' 5 + import {type ParsedReportSubject} from './types' 6 6 7 7 export function useCopyForSubject(subject: ParsedReportSubject) { 8 8 const {_} = useLingui()
+2 -2
src/components/moderation/ReportDialog/state.ts
··· 1 - import {AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api' 1 + import {type AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api' 2 2 3 - import {ReportOption} from './utils/useReportOptions' 3 + import {type ReportOption} from './utils/useReportOptions' 4 4 5 5 export type ReportState = { 6 6 selectedOption?: ReportOption
+6 -6
src/components/moderation/ReportDialog/types.ts
··· 1 1 import { 2 - $Typed, 3 - AppBskyActorDefs, 4 - AppBskyFeedDefs, 5 - AppBskyGraphDefs, 6 - ChatBskyConvoDefs, 2 + type $Typed, 3 + type AppBskyActorDefs, 4 + type AppBskyFeedDefs, 5 + type AppBskyGraphDefs, 6 + type ChatBskyConvoDefs, 7 7 } from '@atproto/api' 8 8 9 - import * as Dialog from '#/components/Dialog' 9 + import type * as Dialog from '#/components/Dialog' 10 10 11 11 export type ReportSubject = 12 12 | $Typed<AppBskyActorDefs.ProfileViewBasic>
+2 -2
src/components/moderation/ReportDialog/utils/parseReportSubject.ts
··· 6 6 } from '@atproto/api' 7 7 8 8 import { 9 - ParsedReportSubject, 10 - ReportSubject, 9 + type ParsedReportSubject, 10 + type ReportSubject, 11 11 } from '#/components/moderation/ReportDialog/types' 12 12 import * as bsky from '#/types/bsky' 13 13
+4 -4
src/components/moderation/ScreenHider.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - StyleProp, 3 + type StyleProp, 4 4 TouchableWithoutFeedback, 5 5 View, 6 - ViewStyle, 6 + type ViewStyle, 7 7 } from 'react-native' 8 - import {ModerationUI} from '@atproto/api' 8 + import {type ModerationUI} from '@atproto/api' 9 9 import {msg, Trans} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 11 import {useNavigation} from '@react-navigation/native' 12 12 13 13 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 14 14 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 15 - import {NavigationProp} from '#/lib/routes/types' 15 + import {type NavigationProp} from '#/lib/routes/types' 16 16 import {CenteredView} from '#/view/com/util/Views' 17 17 import {atoms as a, useTheme, web} from '#/alf' 18 18 import {Button, ButtonText} from '#/components/Button'
+1 -1
src/components/verification/VerificationCheckButton.tsx
··· 85 85 if (size === 'lg') { 86 86 dimensions = gtPhone ? 20 : 18 87 87 } else if (size === 'md') { 88 - dimensions = 16 88 + dimensions = 14 89 89 } 90 90 91 91 const verifiedByHidden = !state.profile.showBadge && state.profile.isViewer
+3 -3
src/lib/api/feed/custom.ts
··· 1 1 import { 2 - AppBskyFeedDefs, 3 - AppBskyFeedGetFeed as GetCustomFeed, 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetFeed as GetCustomFeed, 4 4 BskyAgent, 5 5 jsonStringToLex, 6 6 } from '@atproto/api' ··· 9 9 getAppLanguageAsContentLanguage, 10 10 getContentLanguages, 11 11 } from '#/state/preferences/languages' 12 - import {FeedAPI, FeedAPIResponse} from './types' 12 + import {type FeedAPI, type FeedAPIResponse} from './types' 13 13 import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' 14 14 15 15 export class CustomFeedAPI implements FeedAPI {
+2 -2
src/lib/api/feed/following.ts
··· 1 - import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' 1 + import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' 2 2 3 - import {FeedAPI, FeedAPIResponse} from './types' 3 + import {type FeedAPI, type FeedAPIResponse} from './types' 4 4 5 5 export class FollowingFeedAPI implements FeedAPI { 6 6 agent: BskyAgent
+4 -4
src/lib/api/feed/likes.ts
··· 1 1 import { 2 - AppBskyFeedDefs, 3 - AppBskyFeedGetActorLikes as GetActorLikes, 4 - BskyAgent, 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetActorLikes as GetActorLikes, 4 + type BskyAgent, 5 5 } from '@atproto/api' 6 6 7 - import {FeedAPI, FeedAPIResponse} from './types' 7 + import {type FeedAPI, type FeedAPIResponse} from './types' 8 8 9 9 export class LikesFeedAPI implements FeedAPI { 10 10 agent: BskyAgent
+12 -4
src/lib/api/feed/merge.ts
··· 1 - import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api' 1 + import { 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetTimeline, 4 + type BskyAgent, 5 + } from '@atproto/api' 2 6 import shuffle from 'lodash.shuffle' 3 7 4 8 import {bundleAsync} from '#/lib/async/bundle' 5 9 import {timeout} from '#/lib/async/timeout' 6 10 import {feedUriToHref} from '#/lib/strings/url-helpers' 7 11 import {getContentLanguages} from '#/state/preferences/languages' 8 - import {FeedParams} from '#/state/queries/post-feed' 12 + import {type FeedParams} from '#/state/queries/post-feed' 9 13 import {FeedTuner} from '../feed-manip' 10 - import {FeedTunerFn} from '../feed-manip' 11 - import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types' 14 + import {type FeedTunerFn} from '../feed-manip' 15 + import { 16 + type FeedAPI, 17 + type FeedAPIResponse, 18 + type ReasonFeedSource, 19 + } from './types' 12 20 import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' 13 21 14 22 const REQUEST_WAIT_MS = 500 // 500ms
+1 -1
src/lib/api/feed/types.ts
··· 1 - import {AppBskyFeedDefs} from '@atproto/api' 1 + import {type AppBskyFeedDefs} from '@atproto/api' 2 2 3 3 export interface FeedAPIResponse { 4 4 cursor?: string
+1 -1
src/lib/api/feed/utils.ts
··· 2 2 3 3 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' 4 4 import {isWeb} from '#/platform/detection' 5 - import {UsePreferencesQueryResponse} from '#/state/queries/preferences' 5 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 6 6 7 7 let debugTopics = '' 8 8 if (isWeb && typeof window !== 'undefined') {
+1 -1
src/lib/api/upload-blob.ts
··· 1 1 import {copyAsync} from 'expo-file-system' 2 - import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api' 2 + import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' 3 3 4 4 import {safeDeleteAsync} from '#/lib/media/manip' 5 5
+1 -1
src/lib/api/upload-blob.web.ts
··· 1 - import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api' 1 + import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' 2 2 3 3 /** 4 4 * @note It is recommended, on web, to use the `file` instance of the file
+1 -1
src/lib/assets.native.ts
··· 1 - import {ImageRequireSource} from 'react-native' 1 + import {type ImageRequireSource} from 'react-native' 2 2 3 3 export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.png') 4 4 export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/splash.png')
+1 -1
src/lib/assets.ts
··· 1 - import {ImageRequireSource} from 'react-native' 1 + import {type ImageRequireSource} from 'react-native' 2 2 3 3 // @ts-ignore we need to pretend -prf 4 4 export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.png'}
+1 -1
src/lib/custom-animations/GestureActionView.web.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 3 export function GestureActionView({children}: {children: React.ReactNode}) { 4 4 return children
+6 -1
src/lib/custom-animations/PressableScale.tsx
··· 1 - import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 1 + import { 2 + Pressable, 3 + type PressableProps, 4 + type StyleProp, 5 + type ViewStyle, 6 + } from 'react-native' 2 7 import Animated, { 3 8 cancelAnimation, 4 9 useAnimatedStyle,
+2 -2
src/lib/hooks/useAccountSwitcher.ts
··· 4 4 5 5 import {logger} from '#/logger' 6 6 import {isWeb} from '#/platform/detection' 7 - import {SessionAccount, useSessionApi} from '#/state/session' 7 + import {type SessionAccount, useSessionApi} from '#/state/session' 8 8 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 9 9 import * as Toast from '#/view/com/util/Toast' 10 10 import {logEvent} from '../statsig/statsig' 11 - import {LogEvents} from '../statsig/statsig' 11 + import {type LogEvents} from '../statsig/statsig' 12 12 13 13 export function useAccountSwitcher() { 14 14 const [pendingDid, setPendingDid] = useState<string | null>(null)
+1 -1
src/lib/hooks/useAnimatedValue.ts
··· 2 2 import {Animated} from 'react-native' 3 3 4 4 export function useAnimatedValue(initialValue: number) { 5 - const lazyRef = React.useRef<Animated.Value>() 5 + const lazyRef = React.useRef<Animated.Value>(undefined) 6 6 7 7 if (lazyRef.current === undefined) { 8 8 lazyRef.current = new Animated.Value(initialValue)
+1 -1
src/lib/hooks/useGoBack.ts
··· 1 1 import {StackActions, useNavigation} from '@react-navigation/native' 2 2 3 - import {NavigationProp} from '#/lib/routes/types' 3 + import {type NavigationProp} from '#/lib/routes/types' 4 4 import {router} from '#/routes' 5 5 6 6 export function useGoBack(onGoBack?: () => unknown) {
+1 -1
src/lib/hooks/useOTAUpdates.ts
··· 127 127 const appState = React.useRef<AppStateStatus>('active') 128 128 const lastMinimize = React.useRef(0) 129 129 const ranInitialCheck = React.useRef(false) 130 - const timeout = React.useRef<NodeJS.Timeout>() 130 + const timeout = React.useRef<NodeJS.Timeout>(undefined) 131 131 const {currentlyRunning, isUpdatePending} = useUpdates() 132 132 const currentChannel = currentlyRunning?.channel 133 133
+1 -1
src/lib/hooks/useSetTitle.ts
··· 1 1 import {useEffect} from 'react' 2 2 import {useNavigation} from '@react-navigation/native' 3 3 4 - import {NavigationProp} from '#/lib/routes/types' 4 + import {type NavigationProp} from '#/lib/routes/types' 5 5 import {bskyTitle} from '#/lib/strings/headings' 6 6 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 7 7
+1 -1
src/lib/hooks/useTimeAgo.ts
··· 1 1 import {useCallback} from 'react' 2 - import {I18n} from '@lingui/core' 2 + import {type I18n} from '@lingui/core' 3 3 import {defineMessage, msg, plural} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {differenceInSeconds} from 'date-fns'
+1 -1
src/lib/hooks/useWebScrollRestoration.ts
··· 1 1 import {useEffect, useMemo, useState} from 'react' 2 - import {EventArg, useNavigation} from '@react-navigation/core' 2 + import {type EventArg, useNavigation} from '@react-navigation/core' 3 3 4 4 if ('scrollRestoration' in history) { 5 5 // Tell the brower not to mess with the scroll.
+2 -2
src/lib/media/video/compress.web.ts
··· 1 - import {ImagePickerAsset} from 'expo-image-picker' 1 + import {type ImagePickerAsset} from 'expo-image-picker' 2 2 3 3 import {VIDEO_MAX_SIZE} from '#/lib/constants' 4 4 import {VideoTooLargeError} from '#/lib/media/video/errors' 5 - import {CompressedVideo} from './types' 5 + import {type CompressedVideo} from './types' 6 6 7 7 // doesn't actually compress, converts to ArrayBuffer 8 8 export async function compressVideo(
+2 -2
src/lib/media/video/upload.shared.ts
··· 1 - import {BskyAgent} from '@atproto/api' 2 - import {I18n} from '@lingui/core' 1 + import {type BskyAgent} from '@atproto/api' 2 + import {type I18n} from '@lingui/core' 3 3 import {msg} from '@lingui/macro' 4 4 5 5 import {VIDEO_SERVICE_DID} from '#/lib/constants'
+3 -3
src/lib/media/video/upload.ts
··· 1 1 import {createUploadTask, FileSystemUploadType} from 'expo-file-system' 2 - import {AppBskyVideoDefs, BskyAgent} from '@atproto/api' 3 - import {I18n} from '@lingui/core' 2 + import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api' 3 + import {type I18n} from '@lingui/core' 4 4 import {msg} from '@lingui/macro' 5 5 import {nanoid} from 'nanoid/non-secure' 6 6 7 7 import {AbortError} from '#/lib/async/cancelable' 8 8 import {ServerError} from '#/lib/media/video/errors' 9 - import {CompressedVideo} from '#/lib/media/video/types' 9 + import {type CompressedVideo} from '#/lib/media/video/types' 10 10 import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' 11 11 import {createVideoEndpointUrl, mimeToExt} from './util' 12 12
+4 -4
src/lib/media/video/upload.web.ts
··· 1 - import {AppBskyVideoDefs} from '@atproto/api' 2 - import {BskyAgent} from '@atproto/api' 3 - import {I18n} from '@lingui/core' 1 + import {type AppBskyVideoDefs} from '@atproto/api' 2 + import {type BskyAgent} from '@atproto/api' 3 + import {type I18n} from '@lingui/core' 4 4 import {msg} from '@lingui/macro' 5 5 import {nanoid} from 'nanoid/non-secure' 6 6 7 7 import {AbortError} from '#/lib/async/cancelable' 8 8 import {ServerError} from '#/lib/media/video/errors' 9 - import {CompressedVideo} from '#/lib/media/video/types' 9 + import {type CompressedVideo} from '#/lib/media/video/types' 10 10 import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' 11 11 import {createVideoEndpointUrl, mimeToExt} from './util' 12 12
+1 -1
src/lib/media/video/util.ts
··· 1 1 import {AtpAgent} from '@atproto/api' 2 2 3 - import {SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants' 3 + import {type SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants' 4 4 5 5 export const createVideoEndpointUrl = ( 6 6 route: string,
+1 -1
src/lib/merge-refs.ts
··· 13 13 * returns a ref callback function that can be used to merge multiple refs into a single ref. 14 14 */ 15 15 export function mergeRefs<T = any>( 16 - refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>, 16 + refs: Array<React.MutableRefObject<T> | React.Ref<T>>, 17 17 ): React.RefCallback<T> { 18 18 return value => { 19 19 refs.forEach(ref => {
+1 -1
src/lib/moderation/blocked-and-muted.ts
··· 1 - import * as bsky from '#/types/bsky' 1 + import type * as bsky from '#/types/bsky' 2 2 3 3 export function isBlockedOrBlocking(profile: bsky.profile.AnyProfileView) { 4 4 return profile.viewer?.blockedBy || profile.viewer?.blocking
+4 -1
src/lib/moderation/useLabelBehaviorDescription.ts
··· 1 - import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api' 1 + import { 2 + type InterpretedLabelValueDefinition, 3 + type LabelPreference, 4 + } from '@atproto/api' 2 5 import {msg} from '@lingui/macro' 3 6 import {useLingui} from '@lingui/react' 4 7
+4 -4
src/lib/moderation/useLabelInfo.ts
··· 1 1 import { 2 - AppBskyLabelerDefs, 3 - ComAtprotoLabelDefs, 4 - InterpretedLabelValueDefinition, 2 + type AppBskyLabelerDefs, 3 + type ComAtprotoLabelDefs, 4 + type InterpretedLabelValueDefinition, 5 5 interpretLabelValueDefinition, 6 6 LABELS, 7 7 } from '@atproto/api' ··· 9 9 import * as bcp47Match from 'bcp-47-match' 10 10 11 11 import { 12 - GlobalLabelStrings, 12 + type GlobalLabelStrings, 13 13 useGlobalLabelStrings, 14 14 } from '#/lib/moderation/useGlobalLabelStrings' 15 15 import {useLabelDefinitions} from '#/state/preferences'
+4 -3
src/lib/react-query.tsx
··· 1 - import React, {useRef, useState} from 'react' 2 - import {AppState, AppStateStatus} from 'react-native' 1 + import {useRef, useState} from 'react' 2 + import {AppState, type AppStateStatus} from 'react-native' 3 3 import AsyncStorage from '@react-native-async-storage/async-storage' 4 4 import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' 5 5 import {focusManager, onlineManager, QueryClient} from '@tanstack/react-query' 6 6 import { 7 7 PersistQueryClientProvider, 8 - PersistQueryClientProviderProps, 8 + type PersistQueryClientProviderProps, 9 9 } from '@tanstack/react-query-persist-client' 10 + import type React from 'react' 10 11 11 12 import {isNative} from '#/platform/detection' 12 13 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events'
+2 -2
src/lib/routes/helpers.ts
··· 1 - import {NavigationProp} from '@react-navigation/native' 1 + import {type NavigationProp} from '@react-navigation/native' 2 2 3 - import {RouteParams, State} from './types' 3 + import {type RouteParams, type State} from './types' 4 4 5 5 export function getRootNavigation<T extends {}>( 6 6 nav: NavigationProp<T>,
+1 -1
src/lib/strings/display-names.ts
··· 1 - import {ModerationUI} from '@atproto/api' 1 + import {type ModerationUI} from '@atproto/api' 2 2 3 3 // \u2705 = ✅ 4 4 // \u2713 = ✓
+1 -1
src/lib/strings/rich-text-helpers.ts
··· 1 - import {AppBskyRichtextFacet, RichText} from '@atproto/api' 1 + import {AppBskyRichtextFacet, type RichText} from '@atproto/api' 2 2 3 3 import {linkRequiresWarning} from './url-helpers' 4 4
+1 -1
src/lib/strings/rich-text-manip.ts
··· 1 - import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api' 1 + import {AppBskyRichtextFacet, type RichText, UnicodeString} from '@atproto/api' 2 2 3 3 import {toShortUrl} from './url-helpers' 4 4
+1 -1
src/lib/strings/time.ts
··· 1 - import {I18n} from '@lingui/core' 1 + import {type I18n} from '@lingui/core' 2 2 3 3 export function niceDate(i18n: I18n, date: number | string | Date) { 4 4 const d = new Date(date)
+1 -1
src/lib/themes.ts
··· 4 4 import {darkPalette, dimPalette, lightPalette} from '#/alf/themes' 5 5 import {fontWeight} from '#/alf/tokens' 6 6 import {colors} from './styles' 7 - import type {Theme} from './ThemeContext' 7 + import {type Theme} from './ThemeContext' 8 8 9 9 export const defaultTheme: Theme = { 10 10 colorScheme: 'light',
+1 -1
src/locale/deviceLocales.ts
··· 1 - import {getLocales as defaultGetLocales, Locale} from 'expo-localization' 1 + import {getLocales as defaultGetLocales, type Locale} from 'expo-localization' 2 2 3 3 import {dedupArray} from '#/lib/functions' 4 4
+1 -1
src/locale/i18nProvider.tsx
··· 1 - import React from 'react' 2 1 import {i18n} from '@lingui/core' 3 2 import {I18nProvider as DefaultI18nProvider} from '@lingui/react' 3 + import type React from 'react' 4 4 5 5 import {useLocaleLanguage} from './i18n' 6 6
+285 -246
src/locale/locales/en/messages.po
··· 30 30 msgid "{0, plural, one {# day} other {# days}}" 31 31 msgstr "" 32 32 33 - #: src/screens/Profile/ProfileFollowers.tsx:40 33 + #: src/screens/Profile/ProfileFollowers.tsx:43 34 34 msgid "{0, plural, one {# follower} other {# followers}}" 35 35 msgstr "" 36 36 37 - #: src/screens/Profile/ProfileFollows.tsx:40 37 + #: src/screens/Profile/ProfileFollows.tsx:43 38 38 msgid "{0, plural, one {# following} other {# following}}" 39 39 msgstr "" 40 40 ··· 42 42 msgid "{0, plural, one {# hour} other {# hours}}" 43 43 msgstr "" 44 44 45 - #: src/components/moderation/LabelsOnMe.tsx:53 45 + #: src/components/moderation/LabelsOnMe.tsx:58 46 46 msgid "{0, plural, one {# label has} other {# labels have}} been placed on this account" 47 47 msgstr "" 48 48 49 - #: src/components/moderation/LabelsOnMe.tsx:62 49 + #: src/components/moderation/LabelsOnMe.tsx:67 50 50 msgid "{0, plural, one {# label has} other {# labels have}} been placed on this content" 51 51 msgstr "" 52 52 53 - #: src/screens/Post/PostLikedBy.tsx:41 53 + #: src/screens/Post/PostLikedBy.tsx:44 54 54 msgid "{0, plural, one {# like} other {# likes}}" 55 55 msgstr "" 56 56 ··· 62 62 msgid "{0, plural, one {# month} other {# months}}" 63 63 msgstr "" 64 64 65 - #: src/screens/Post/PostQuotes.tsx:41 65 + #: src/screens/Post/PostQuotes.tsx:44 66 66 msgid "{0, plural, one {# quote} other {# quotes}}" 67 67 msgstr "" 68 68 69 - #: src/screens/Post/PostRepostedBy.tsx:41 69 + #: src/screens/Post/PostRepostedBy.tsx:44 70 70 msgid "{0, plural, one {# repost} other {# reposts}}" 71 71 msgstr "" 72 72 ··· 158 158 msgid "{0} joined this week" 159 159 msgstr "" 160 160 161 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:204 161 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:203 162 162 msgid "{0} of {1}" 163 163 msgstr "" 164 164 ··· 183 183 msgid "{0}, a list by {1}" 184 184 msgstr "" 185 185 186 - #: src/view/com/util/UserAvatar.tsx:572 187 - #: src/view/com/util/UserAvatar.tsx:590 186 + #: src/view/com/util/UserAvatar.tsx:577 187 + #: src/view/com/util/UserAvatar.tsx:595 188 188 msgid "{0}'s avatar" 189 189 msgstr "" 190 190 ··· 422 422 msgid "{notificationCount, plural, one {# unread item} other {# unread items}}" 423 423 msgstr "" 424 424 425 - #: src/components/NewskieDialog.tsx:116 425 + #: src/components/NewskieDialog.tsx:131 426 426 msgid "{profileName} joined Bluesky {0} ago" 427 427 msgstr "" 428 428 429 - #: src/components/NewskieDialog.tsx:111 429 + #: src/components/NewskieDialog.tsx:126 430 430 msgid "{profileName} joined Bluesky using a starter pack {0} ago" 431 431 msgstr "" 432 432 ··· 677 677 msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event." 678 678 msgstr "" 679 679 680 - #: src/view/screens/ProfileList.tsx:924 681 - #: src/view/screens/ProfileList.tsx:942 680 + #: src/screens/ProfileList/AboutSection.tsx:62 681 + #: src/screens/ProfileList/AboutSection.tsx:80 682 682 msgid "Add a user to this list" 683 683 msgstr "" 684 684 ··· 748 748 msgid "Add muted words and tags" 749 749 msgstr "" 750 750 751 - #: src/view/screens/ProfileList.tsx:932 752 - #: src/view/screens/ProfileList.tsx:950 751 + #: src/screens/ProfileList/AboutSection.tsx:70 752 + #: src/screens/ProfileList/AboutSection.tsx:88 753 753 msgid "Add people" 754 754 msgstr "" 755 755 ··· 769 769 msgid "Add some feeds to your starter pack!" 770 770 msgstr "" 771 771 772 - #: src/screens/Feeds/NoFollowingFeed.tsx:41 772 + #: src/screens/Feeds/NoFollowingFeed.tsx:39 773 773 msgid "Add the default feed of only people you follow" 774 774 msgstr "" 775 775 ··· 832 832 msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." 833 833 msgstr "" 834 834 835 - #: src/components/moderation/LabelPreference.tsx:247 835 + #: src/components/moderation/LabelPreference.tsx:246 836 836 msgid "Adult content is disabled." 837 837 msgstr "" 838 838 ··· 995 995 msgid "An error occurred while loading the video. Please try again later." 996 996 msgstr "" 997 997 998 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:227 998 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:226 999 999 msgid "An error occurred while loading the video. Please try again." 1000 1000 msgstr "" 1001 1001 1002 - #: src/components/StarterPack/QrCodeDialog.tsx:72 1002 + #: src/components/StarterPack/QrCodeDialog.tsx:75 1003 1003 msgid "An error occurred while saving the QR code!" 1004 1004 msgstr "" 1005 1005 ··· 1173 1173 msgid "Appearance" 1174 1174 msgstr "" 1175 1175 1176 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47 1176 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:51 1177 1177 #: src/screens/Home/NoFeedsPinned.tsx:93 1178 1178 msgid "Apply default recommended feeds" 1179 1179 msgstr "" ··· 1232 1232 msgid "Are you sure?" 1233 1233 msgstr "" 1234 1234 1235 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:87 1235 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:89 1236 1236 msgid "Are you writing in <0>{suggestedLanguageName}</0>?" 1237 1237 msgstr "" 1238 1238 ··· 1366 1366 msgid "Block Account?" 1367 1367 msgstr "" 1368 1368 1369 - #: src/view/screens/ProfileList.tsx:669 1369 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:97 1370 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:100 1370 1371 msgid "Block accounts" 1371 1372 msgstr "" 1372 1373 ··· 1378 1379 msgid "Block and/or delete this conversation" 1379 1380 msgstr "" 1380 1381 1381 - #: src/view/screens/ProfileList.tsx:789 1382 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:125 1382 1383 msgid "Block list" 1383 1384 msgstr "" 1384 1385 ··· 1386 1387 msgid "Block or report" 1387 1388 msgstr "" 1388 1389 1389 - #: src/view/screens/ProfileList.tsx:784 1390 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:120 1390 1391 msgid "Block these accounts?" 1391 1392 msgstr "" 1392 1393 ··· 1425 1426 msgid "Blocking does not prevent this labeler from placing labels on your account." 1426 1427 msgstr "" 1427 1428 1428 - #: src/view/screens/ProfileList.tsx:786 1429 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:122 1429 1430 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1430 1431 msgstr "" 1431 1432 ··· 1496 1497 msgid "Bluesky+ icons" 1497 1498 msgstr "" 1498 1499 1499 - #: src/lib/moderation/useLabelBehaviorDescription.ts:53 1500 + #: src/lib/moderation/useLabelBehaviorDescription.ts:56 1500 1501 msgid "Blur images" 1501 1502 msgstr "" 1502 1503 1503 - #: src/lib/moderation/useLabelBehaviorDescription.ts:51 1504 + #: src/lib/moderation/useLabelBehaviorDescription.ts:54 1504 1505 msgid "Blur images and filter from feeds" 1505 1506 msgstr "" 1506 1507 ··· 1711 1712 msgid "Change password dialog" 1712 1713 msgstr "" 1713 1714 1714 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:98 1715 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:100 1715 1716 msgid "Change post language to {suggestedLanguageName}" 1716 1717 msgstr "" 1717 1718 ··· 1875 1876 msgid "Click for information" 1876 1877 msgstr "" 1877 1878 1878 - #: src/view/screens/Support.tsx:41 1879 + #: src/view/screens/Support.tsx:44 1879 1880 msgid "click here" 1880 1881 msgstr "" 1881 1882 ··· 1929 1930 #: src/components/dms/ReportDialog.tsx:395 1930 1931 #: src/components/live/EditLiveDialog.tsx:229 1931 1932 #: src/components/live/EditLiveDialog.tsx:235 1932 - #: src/components/NewskieDialog.tsx:146 1933 - #: src/components/NewskieDialog.tsx:153 1933 + #: src/components/NewskieDialog.tsx:159 1934 + #: src/components/NewskieDialog.tsx:165 1934 1935 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:197 1935 1936 #: src/components/ProgressGuide/FollowDialog.tsx:379 1936 1937 #: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:118 ··· 1956 1957 msgid "Close alert" 1957 1958 msgstr "" 1958 1959 1959 - #: src/view/com/util/BottomSheetCustomBackdrop.tsx:36 1960 + #: src/view/com/util/BottomSheetCustomBackdrop.tsx:37 1960 1961 msgid "Close bottom drawer" 1961 1962 msgstr "" 1962 1963 ··· 2052 2053 2053 2054 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:45 2054 2055 #: src/Navigation.tsx:341 2055 - #: src/view/screens/CommunityGuidelines.tsx:34 2056 + #: src/view/screens/CommunityGuidelines.tsx:37 2056 2057 msgid "Community Guidelines" 2057 2058 msgstr "" 2058 2059 ··· 2081 2082 msgid "Compressing video..." 2082 2083 msgstr "" 2083 2084 2084 - #: src/components/moderation/LabelPreference.tsx:88 2085 + #: src/components/moderation/LabelPreference.tsx:87 2085 2086 msgid "Configure content filtering setting for category: {name}" 2086 2087 msgstr "" 2087 2088 2088 - #: src/components/moderation/LabelPreference.tsx:249 2089 + #: src/components/moderation/LabelPreference.tsx:248 2089 2090 msgid "Configured in <0>moderation settings</0>." 2090 2091 msgstr "" 2091 2092 ··· 2281 2282 msgid "Copies build version to clipboard" 2282 2283 msgstr "" 2283 2284 2284 - #: src/components/StarterPack/QrCodeDialog.tsx:182 2285 + #: src/components/StarterPack/QrCodeDialog.tsx:192 2285 2286 msgid "Copy" 2286 2287 msgstr "" 2287 2288 ··· 2294 2295 msgid "Copy at:// URI" 2295 2296 msgstr "" 2296 2297 2297 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:153 2298 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:156 2298 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:152 2299 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:155 2299 2300 msgid "Copy author DID" 2300 2301 msgstr "" 2301 2302 ··· 2314 2315 msgid "Copy host" 2315 2316 msgstr "" 2316 2317 2317 - #: src/components/StarterPack/ShareDialog.tsx:104 2318 + #: src/components/StarterPack/ShareDialog.tsx:113 2318 2319 #: src/screens/StarterPack/StarterPackScreen.tsx:617 2319 2320 msgid "Copy link" 2320 2321 msgstr "" 2321 2322 2322 - #: src/components/StarterPack/ShareDialog.tsx:111 2323 + #: src/components/StarterPack/ShareDialog.tsx:119 2323 2324 msgid "Copy Link" 2324 2325 msgstr "" 2325 2326 2326 - #: src/view/screens/ProfileList.tsx:513 2327 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172 2328 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:176 2327 2329 msgid "Copy link to list" 2328 2330 msgstr "" 2329 2331 2330 2332 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:127 2331 2333 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:130 2332 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:87 2333 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:90 2334 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:86 2335 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:89 2334 2336 msgid "Copy link to post" 2335 2337 msgstr "" 2336 2338 ··· 2348 2350 msgid "Copy message text" 2349 2351 msgstr "" 2350 2352 2351 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:144 2352 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:147 2353 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:143 2354 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:146 2353 2355 msgid "Copy post at:// URI" 2354 2356 msgstr "" 2355 2357 ··· 2358 2360 msgid "Copy post text" 2359 2361 msgstr "" 2360 2362 2361 - #: src/components/StarterPack/QrCodeDialog.tsx:176 2363 + #: src/components/StarterPack/QrCodeDialog.tsx:178 2362 2364 msgid "Copy QR code" 2363 2365 msgstr "" 2364 2366 ··· 2369 2371 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:40 2370 2372 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:107 2371 2373 #: src/Navigation.tsx:346 2372 - #: src/view/screens/CopyrightPolicy.tsx:31 2374 + #: src/view/screens/CopyrightPolicy.tsx:34 2373 2375 msgid "Copyright Policy" 2374 2376 msgstr "" 2375 2377 ··· 2394 2396 msgid "Could not load feed" 2395 2397 msgstr "" 2396 2398 2397 - #: src/view/screens/ProfileList.tsx:1029 2399 + #: src/screens/ProfileList/components/ErrorScreen.tsx:26 2400 + #: src/screens/ProfileList/index.tsx:79 2401 + #: src/screens/ProfileList/index.tsx:101 2398 2402 msgid "Could not load list" 2399 2403 msgstr "" 2400 2404 ··· 2422 2426 msgid "Create" 2423 2427 msgstr "" 2424 2428 2425 - #: src/components/StarterPack/QrCodeDialog.tsx:160 2429 + #: src/components/StarterPack/QrCodeDialog.tsx:163 2426 2430 msgid "Create a QR code for a starter pack" 2427 2431 msgstr "" 2428 2432 ··· 2480 2484 msgstr "" 2481 2485 2482 2486 #: src/components/moderation/ReportDialog/index.tsx:585 2483 - #: src/components/ReportDialog/SelectReportOptionView.tsx:99 2487 + #: src/components/ReportDialog/SelectReportOptionView.tsx:102 2484 2488 msgid "Create report for {0}" 2485 2489 msgstr "" 2486 2490 ··· 2568 2572 #: src/components/dms/MessageContextMenu.tsx:185 2569 2573 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:704 2570 2574 #: src/screens/Messages/components/ChatStatusInfo.tsx:55 2575 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:285 2571 2576 #: src/screens/Settings/AppPasswords.tsx:212 2572 2577 #: src/screens/StarterPack/StarterPackScreen.tsx:599 2573 2578 #: src/screens/StarterPack/StarterPackScreen.tsx:688 2574 2579 #: src/screens/StarterPack/StarterPackScreen.tsx:760 2575 - #: src/view/screens/ProfileList.tsx:768 2576 2580 msgid "Delete" 2577 2581 msgstr "" 2578 2582 ··· 2617 2621 msgid "Delete for me" 2618 2622 msgstr "" 2619 2623 2620 - #: src/view/screens/ProfileList.tsx:556 2624 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:211 2625 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:214 2621 2626 msgid "Delete list" 2622 2627 msgstr "" 2623 2628 ··· 2648 2653 msgid "Delete starter pack?" 2649 2654 msgstr "" 2650 2655 2651 - #: src/view/screens/ProfileList.tsx:763 2656 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:280 2652 2657 msgid "Delete this list?" 2653 2658 msgstr "" 2654 2659 ··· 2737 2742 msgid "Disable subtitles" 2738 2743 msgstr "" 2739 2744 2740 - #: src/lib/moderation/useLabelBehaviorDescription.ts:32 2741 - #: src/lib/moderation/useLabelBehaviorDescription.ts:42 2742 - #: src/lib/moderation/useLabelBehaviorDescription.ts:68 2745 + #: src/lib/moderation/useLabelBehaviorDescription.ts:35 2746 + #: src/lib/moderation/useLabelBehaviorDescription.ts:45 2747 + #: src/lib/moderation/useLabelBehaviorDescription.ts:71 2743 2748 #: src/screens/Messages/Settings.tsx:144 2744 2749 #: src/screens/Messages/Settings.tsx:147 2745 2750 #: src/screens/Moderation/index.tsx:413 ··· 2898 2903 msgid "Download Bluesky" 2899 2904 msgstr "" 2900 2905 2901 - #: src/screens/Settings/components/ExportCarDialog.tsx:79 2902 - #: src/screens/Settings/components/ExportCarDialog.tsx:84 2906 + #: src/screens/Settings/components/ExportCarDialog.tsx:78 2907 + #: src/screens/Settings/components/ExportCarDialog.tsx:83 2903 2908 msgid "Download CAR file" 2904 2909 msgstr "" 2905 2910 ··· 2986 2991 msgid "Edit interests" 2987 2992 msgstr "" 2988 2993 2989 - #: src/view/screens/ProfileList.tsx:544 2994 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:203 2995 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:206 2990 2996 msgid "Edit list details" 2991 2997 msgstr "" 2992 2998 ··· 3094 3100 3095 3101 #: src/components/dialogs/Embed.tsx:104 3096 3102 #: src/components/dialogs/Embed.tsx:108 3097 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:119 3098 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:124 3103 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:118 3104 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:123 3099 3105 msgid "Embed post" 3100 3106 msgstr "" 3101 3107 ··· 3422 3428 msgid "Failed to accept chat" 3423 3429 msgstr "" 3424 3430 3425 - #: src/components/dms/ActionsWrapper.web.tsx:67 3431 + #: src/components/dms/ActionsWrapper.web.tsx:66 3426 3432 #: src/components/dms/MessageContextMenu.tsx:99 3427 3433 msgid "Failed to add emoji reaction" 3428 3434 msgstr "" ··· 3532 3538 msgid "Failed to pin post" 3533 3539 msgstr "" 3534 3540 3535 - #: src/components/dms/ActionsWrapper.web.tsx:61 3541 + #: src/components/dms/ActionsWrapper.web.tsx:60 3536 3542 #: src/components/dms/MessageContextMenu.tsx:93 3537 3543 msgid "Failed to remove emoji reaction" 3538 3544 msgstr "" ··· 3578 3584 3579 3585 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:219 3580 3586 msgid "Failed to toggle thread mute, please try again" 3587 + msgstr "" 3588 + 3589 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:111 3590 + msgid "Failed to unpin list" 3581 3591 msgstr "" 3582 3592 3583 3593 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:149 ··· 3658 3668 msgstr "" 3659 3669 3660 3670 #: src/Navigation.tsx:574 3671 + #: src/screens/SavedFeeds.tsx:108 3661 3672 #: src/screens/Search/SearchResults.tsx:73 3662 3673 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3663 3674 #: src/view/screens/Feeds.tsx:511 3664 3675 #: src/view/screens/Profile.tsx:230 3665 - #: src/view/screens/SavedFeeds.tsx:104 3666 3676 #: src/view/shell/desktop/LeftNav.tsx:727 3667 3677 #: src/view/shell/Drawer.tsx:530 3668 3678 msgid "Feeds" 3669 3679 msgstr "" 3670 3680 3671 - #: src/view/screens/SavedFeeds.tsx:206 3672 - msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." 3681 + #: src/screens/SavedFeeds.tsx:215 3682 + msgid "Feeds are custom algorithms that users build with a little coding expertise. <0>See this guide</0> for more information." 3673 3683 msgstr "" 3674 3684 3675 3685 #: src/components/FeedCard.tsx:282 3676 - #: src/view/screens/SavedFeeds.tsx:86 3686 + #: src/screens/SavedFeeds.tsx:90 3677 3687 msgctxt "toast" 3678 3688 msgid "Feeds updated!" 3679 3689 msgstr "" ··· 3690 3700 msgid "File saved successfully!" 3691 3701 msgstr "" 3692 3702 3693 - #: src/lib/moderation/useLabelBehaviorDescription.ts:66 3703 + #: src/lib/moderation/useLabelBehaviorDescription.ts:69 3694 3704 msgid "Filter from feeds" 3695 3705 msgstr "" 3696 3706 ··· 3850 3860 msgid "Followers of @{0} that you know" 3851 3861 msgstr "" 3852 3862 3853 - #: src/screens/Profile/KnownFollowers.tsx:104 3854 - #: src/screens/Profile/KnownFollowers.tsx:121 3863 + #: src/screens/Profile/KnownFollowers.tsx:107 3864 + #: src/screens/Profile/KnownFollowers.tsx:124 3855 3865 msgid "Followers you know" 3856 3866 msgstr "" 3857 3867 ··· 3865 3875 msgid "Following" 3866 3876 msgstr "" 3867 3877 3878 + #: src/screens/SavedFeeds.tsx:410 3868 3879 #: src/view/screens/Feeds.tsx:603 3869 - #: src/view/screens/SavedFeeds.tsx:420 3870 3880 msgctxt "feed-name" 3871 3881 msgid "Following" 3872 3882 msgstr "" ··· 4068 4078 #: src/components/moderation/ScreenHider.tsx:163 4069 4079 #: src/screens/Messages/Inbox.tsx:249 4070 4080 #: src/screens/Profile/ProfileFeed/index.tsx:92 4081 + #: src/screens/ProfileList/components/ErrorScreen.tsx:34 4082 + #: src/screens/ProfileList/components/ErrorScreen.tsx:40 4071 4083 #: src/screens/VideoFeed/components/Header.tsx:163 4072 4084 #: src/screens/VideoFeed/index.tsx:1146 4073 4085 #: src/screens/VideoFeed/index.tsx:1150 4074 4086 #: src/view/com/auth/LoggedOut.tsx:72 4075 4087 #: src/view/screens/NotFound.tsx:57 4076 - #: src/view/screens/ProfileList.tsx:1038 4077 4088 msgid "Go back" 4078 4089 msgstr "" 4079 4090 ··· 4084 4095 #: src/screens/Profile/ProfileFeed/index.tsx:97 4085 4096 #: src/screens/StarterPack/StarterPackScreen.tsx:773 4086 4097 #: src/view/screens/NotFound.tsx:56 4087 - #: src/view/screens/ProfileList.tsx:1043 4088 4098 msgid "Go Back" 4089 4099 msgstr "" 4090 4100 4091 4101 #: src/components/dms/ReportDialog.tsx:197 4092 - #: src/components/ReportDialog/SelectReportOptionView.tsx:78 4102 + #: src/components/ReportDialog/SelectReportOptionView.tsx:81 4093 4103 #: src/components/ReportDialog/SubmitView.tsx:110 4094 4104 #: src/screens/Onboarding/Layout.tsx:121 4095 4105 #: src/screens/Onboarding/Layout.tsx:214 ··· 4249 4259 #: src/components/interstitials/Trending.tsx:131 4250 4260 #: src/components/interstitials/TrendingVideos.tsx:138 4251 4261 #: src/components/moderation/ContentHider.tsx:203 4252 - #: src/components/moderation/LabelPreference.tsx:141 4262 + #: src/components/moderation/LabelPreference.tsx:140 4253 4263 #: src/components/moderation/PostHider.tsx:134 4254 4264 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:715 4255 - #: src/lib/moderation/useLabelBehaviorDescription.ts:15 4256 - #: src/lib/moderation/useLabelBehaviorDescription.ts:20 4257 - #: src/lib/moderation/useLabelBehaviorDescription.ts:25 4258 - #: src/lib/moderation/useLabelBehaviorDescription.ts:30 4265 + #: src/lib/moderation/useLabelBehaviorDescription.ts:18 4266 + #: src/lib/moderation/useLabelBehaviorDescription.ts:23 4267 + #: src/lib/moderation/useLabelBehaviorDescription.ts:28 4268 + #: src/lib/moderation/useLabelBehaviorDescription.ts:33 4259 4269 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:111 4260 4270 msgid "Hide" 4261 4271 msgstr "" ··· 4406 4416 msgid "If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf." 4407 4417 msgstr "" 4408 4418 4409 - #: src/view/screens/ProfileList.tsx:765 4419 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:282 4410 4420 msgid "If you delete this list, you won't be able to recover it." 4411 4421 msgstr "" 4412 4422 ··· 4447 4457 msgid "Illegal and Urgent" 4448 4458 msgstr "" 4449 4459 4450 - #: src/view/com/util/images/Gallery.tsx:71 4460 + #: src/view/com/util/images/Gallery.tsx:70 4451 4461 msgid "Image" 4452 4462 msgstr "" 4453 4463 ··· 4593 4603 msgid "Invite codes: 1 available" 4594 4604 msgstr "" 4595 4605 4596 - #: src/components/StarterPack/ShareDialog.tsx:77 4606 + #: src/components/StarterPack/ShareDialog.tsx:81 4597 4607 msgid "Invite people to this starter pack!" 4598 4608 msgstr "" 4599 4609 ··· 4875 4885 msgid "Liked by" 4876 4886 msgstr "" 4877 4887 4878 - #: src/screens/Post/PostLikedBy.tsx:38 4879 - #: src/screens/Profile/ProfileLabelerLikedBy.tsx:29 4880 - #: src/view/screens/ProfileFeedLikedBy.tsx:30 4888 + #: src/screens/Post/PostLikedBy.tsx:41 4889 + #: src/screens/Profile/ProfileLabelerLikedBy.tsx:32 4890 + #: src/view/screens/ProfileFeedLikedBy.tsx:33 4881 4891 msgid "Liked By" 4882 4892 msgstr "" 4883 4893 ··· 4927 4937 msgid "List Avatar" 4928 4938 msgstr "" 4929 4939 4930 - #: src/view/screens/ProfileList.tsx:438 4940 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:50 4931 4941 msgctxt "toast" 4932 4942 msgid "List blocked" 4933 4943 msgstr "" ··· 4949 4959 msgid "List creator" 4950 4960 msgstr "" 4951 4961 4952 - #: src/view/screens/ProfileList.tsx:485 4962 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:97 4953 4963 msgctxt "toast" 4954 4964 msgid "List deleted" 4955 4965 msgstr "" ··· 4958 4968 msgid "List has been hidden" 4959 4969 msgstr "" 4960 4970 4961 - #: src/view/screens/ProfileList.tsx:176 4971 + #: src/screens/ProfileList/index.tsx:172 4962 4972 msgid "List Hidden" 4963 4973 msgstr "" 4964 4974 4965 - #: src/view/screens/ProfileList.tsx:402 4975 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:31 4966 4976 msgctxt "toast" 4967 4977 msgid "List muted" 4968 4978 msgstr "" ··· 4971 4981 msgid "List Name" 4972 4982 msgstr "" 4973 4983 4974 - #: src/view/screens/ProfileList.tsx:456 4984 + #: src/screens/ProfileList/components/Header.tsx:116 4985 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:138 4975 4986 msgctxt "toast" 4976 4987 msgid "List unblocked" 4977 4988 msgstr "" 4978 4989 4979 - #: src/view/screens/ProfileList.tsx:420 4990 + #: src/screens/ProfileList/components/Header.tsx:98 4991 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:120 4980 4992 msgctxt "toast" 4981 4993 msgid "List unmuted" 4982 4994 msgstr "" ··· 5025 5037 5026 5038 #: src/screens/Profile/ProfileFeed/index.tsx:224 5027 5039 #: src/screens/Profile/Sections/Feed.tsx:98 5028 - #: src/view/com/feeds/FeedPage.tsx:162 5029 - #: src/view/screens/ProfileList.tsx:878 5040 + #: src/screens/ProfileList/FeedSection.tsx:105 5041 + #: src/view/com/feeds/FeedPage.tsx:169 5030 5042 msgid "Load new posts" 5031 5043 msgstr "" 5032 5044 ··· 5059 5071 msgid "Looks like XXXXX-XXXXX" 5060 5072 msgstr "" 5061 5073 5062 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:39 5074 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:43 5063 5075 msgid "Looks like you haven't saved any feeds! Use our recommendations or browse more below." 5064 5076 msgstr "" 5065 5077 ··· 5067 5079 msgid "Looks like you unpinned all your feeds. But don't worry, you can add some below 😄" 5068 5080 msgstr "" 5069 5081 5070 - #: src/screens/Feeds/NoFollowingFeed.tsx:37 5082 + #: src/screens/Feeds/NoFollowingFeed.tsx:35 5071 5083 msgid "Looks like you're missing a following feed. <0>Click here to add one.</0>" 5072 5084 msgstr "" 5073 5085 ··· 5249 5261 msgid "Moderation Lists" 5250 5262 msgstr "" 5251 5263 5252 - #: src/components/moderation/LabelPreference.tsx:252 5264 + #: src/components/moderation/LabelPreference.tsx:251 5253 5265 msgid "moderation settings" 5254 5266 msgstr "" 5255 5267 ··· 5276 5288 msgid "More languages..." 5277 5289 msgstr "" 5278 5290 5291 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:156 5279 5292 #: src/view/com/profile/ProfileMenu.tsx:223 5280 5293 #: src/view/com/profile/ProfileMenu.tsx:229 5281 - #: src/view/screens/ProfileList.tsx:750 5282 5294 msgid "More options" 5283 5295 msgstr "" 5284 5296 5297 + #: src/screens/SavedFeeds.tsx:329 5298 + msgid "Move feed down" 5299 + msgstr "" 5300 + 5301 + #: src/screens/SavedFeeds.tsx:320 5302 + msgid "Move feed up" 5303 + msgstr "" 5304 + 5285 5305 #: src/screens/Onboarding/state.ts:113 5286 5306 msgid "Movies" 5287 5307 msgstr "" ··· 5291 5311 msgstr "" 5292 5312 5293 5313 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 5294 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 5314 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95 5295 5315 msgctxt "video" 5296 5316 msgid "Mute" 5297 5317 msgstr "" ··· 5308 5328 msgid "Mute account" 5309 5329 msgstr "" 5310 5330 5311 - #: src/view/screens/ProfileList.tsx:657 5331 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:89 5332 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:92 5312 5333 msgid "Mute accounts" 5313 5334 msgstr "" 5314 5335 ··· 5321 5342 msgid "Mute in:" 5322 5343 msgstr "" 5323 5344 5324 - #: src/view/screens/ProfileList.tsx:779 5345 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:115 5325 5346 msgid "Mute list" 5326 5347 msgstr "" 5327 5348 5328 - #: src/view/screens/ProfileList.tsx:774 5349 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:110 5329 5350 msgid "Mute these accounts?" 5330 5351 msgstr "" 5331 5352 ··· 5384 5405 msgid "Muted words & tags" 5385 5406 msgstr "" 5386 5407 5387 - #: src/view/screens/ProfileList.tsx:776 5408 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:112 5388 5409 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." 5389 5410 msgstr "" 5390 5411 ··· 5444 5465 msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?" 5445 5466 msgstr "" 5446 5467 5447 - #: src/components/ReportDialog/SelectReportOptionView.tsx:128 5468 + #: src/components/ReportDialog/SelectReportOptionView.tsx:131 5448 5469 msgid "Need to report a copyright violation?" 5449 5470 msgstr "" 5450 5471 ··· 5526 5547 msgstr "" 5527 5548 5528 5549 #: src/screens/Profile/ProfileFeed/index.tsx:241 5550 + #: src/screens/ProfileList/index.tsx:246 5551 + #: src/screens/ProfileList/index.tsx:284 5529 5552 #: src/view/screens/Feeds.tsx:552 5530 5553 #: src/view/screens/Notifications.tsx:167 5531 5554 #: src/view/screens/Profile.tsx:510 5532 - #: src/view/screens/ProfileList.tsx:250 5533 - #: src/view/screens/ProfileList.tsx:288 5534 5555 msgid "New post" 5535 5556 msgstr "" 5536 5557 5537 - #: src/view/com/feeds/FeedPage.tsx:173 5558 + #: src/view/com/feeds/FeedPage.tsx:180 5538 5559 msgctxt "action" 5539 5560 msgid "New post" 5540 5561 msgstr "" ··· 5556 5577 msgid "New starter pack" 5557 5578 msgstr "" 5558 5579 5559 - #: src/components/NewskieDialog.tsx:83 5580 + #: src/components/NewskieDialog.tsx:102 5560 5581 msgid "New user info dialog" 5561 5582 msgstr "" 5562 5583 ··· 5697 5718 msgid "No results for \"{0}\"." 5698 5719 msgstr "" 5699 5720 5700 - #: src/components/Lists.tsx:190 5721 + #: src/components/Lists.tsx:189 5701 5722 msgid "No results found" 5702 5723 msgstr "" 5703 5724 ··· 5774 5795 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." 5775 5796 msgstr "" 5776 5797 5777 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:134 5798 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:133 5778 5799 msgid "Note: This post is only visible to logged-in users." 5779 5800 msgstr "" 5780 5801 ··· 5845 5866 msgid "Nudity or adult content not labeled as such" 5846 5867 msgstr "" 5847 5868 5848 - #: src/lib/moderation/useLabelBehaviorDescription.ts:11 5869 + #: src/lib/moderation/useLabelBehaviorDescription.ts:14 5849 5870 #: src/screens/Settings/NotificationSettings/index.tsx:291 5850 5871 msgid "Off" 5851 5872 msgstr "" ··· 5926 5947 msgid "Only image files are supported" 5927 5948 msgstr "" 5928 5949 5929 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:40 5950 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:41 5930 5951 msgid "Only WebVTT (.vtt) files are supported" 5931 5952 msgstr "" 5932 5953 5933 - #: src/components/Lists.tsx:95 5954 + #: src/components/Lists.tsx:94 5934 5955 msgid "Oops, something went wrong!" 5935 5956 msgstr "" 5936 5957 5937 - #: src/components/Lists.tsx:174 5958 + #: src/components/Lists.tsx:173 5938 5959 #: src/components/StarterPack/ProfileStarterPacks.tsx:332 5939 5960 #: src/components/StarterPack/ProfileStarterPacks.tsx:341 5940 5961 #: src/screens/Settings/AppPasswords.tsx:59 ··· 5998 6019 msgid "Open pack" 5999 6020 msgstr "" 6000 6021 6001 - #: src/components/PostControls/PostMenu/index.tsx:65 6022 + #: src/components/PostControls/PostMenu/index.tsx:64 6002 6023 msgid "Open post options menu" 6003 6024 msgstr "" 6004 6025 ··· 6007 6028 msgid "Open profile" 6008 6029 msgstr "" 6009 6030 6010 - #: src/components/PostControls/ShareMenu/index.tsx:90 6031 + #: src/components/PostControls/ShareMenu/index.tsx:89 6011 6032 msgid "Open share menu" 6012 6033 msgstr "" 6013 6034 ··· 6091 6112 msgid "Opens list of invite codes" 6092 6113 msgstr "" 6093 6114 6094 - #: src/view/com/util/UserAvatar.tsx:576 6115 + #: src/view/com/util/UserAvatar.tsx:581 6095 6116 msgid "Opens live status dialog" 6096 6117 msgstr "" 6097 6118 ··· 6104 6125 msgstr "" 6105 6126 6106 6127 #: src/view/com/notifications/NotificationFeedItem.tsx:906 6107 - #: src/view/com/util/UserAvatar.tsx:594 6128 + #: src/view/com/util/UserAvatar.tsx:599 6108 6129 msgid "Opens this profile" 6109 6130 msgstr "" 6110 6131 ··· 6167 6188 msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." 6168 6189 msgstr "" 6169 6190 6170 - #: src/components/Lists.tsx:191 6191 + #: src/components/Lists.tsx:190 6171 6192 #: src/view/screens/NotFound.tsx:47 6172 6193 msgid "Page not found" 6173 6194 msgstr "" ··· 6211 6232 msgid "Pause video" 6212 6233 msgstr "" 6213 6234 6235 + #: src/screens/ProfileList/index.tsx:166 6214 6236 #: src/screens/Search/SearchResults.tsx:67 6215 6237 #: src/screens/StarterPack/StarterPackScreen.tsx:189 6216 - #: src/view/screens/ProfileList.tsx:170 6217 6238 msgid "People" 6218 6239 msgstr "" 6219 6240 ··· 6253 6274 6254 6275 #: src/screens/Profile/components/ProfileFeedHeader.tsx:523 6255 6276 #: src/screens/Profile/components/ProfileFeedHeader.tsx:530 6277 + #: src/screens/SavedFeeds.tsx:351 6256 6278 msgid "Pin feed" 6257 6279 msgstr "" 6258 6280 ··· 6260 6282 msgid "Pin Feed" 6261 6283 msgstr "" 6262 6284 6263 - #: src/view/screens/ProfileList.tsx:714 6285 + #: src/screens/ProfileList/components/Header.tsx:156 6286 + #: src/screens/ProfileList/components/Header.tsx:163 6264 6287 msgid "Pin to home" 6265 6288 msgstr "" 6266 6289 ··· 6282 6305 msgid "Pinned {0} to Home" 6283 6306 msgstr "" 6284 6307 6285 - #: src/view/screens/SavedFeeds.tsx:131 6308 + #: src/screens/SavedFeeds.tsx:142 6286 6309 msgid "Pinned Feeds" 6287 6310 msgstr "" 6288 6311 6289 - #: src/view/screens/ProfileList.tsx:361 6312 + #: src/screens/ProfileList/components/Header.tsx:74 6290 6313 msgid "Pinned to your feeds" 6291 6314 msgstr "" 6292 6315 ··· 6562 6585 6563 6586 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:250 6564 6587 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:262 6588 + #: src/screens/ProfileList/index.tsx:166 6565 6589 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:213 6566 6590 #: src/screens/StarterPack/StarterPackScreen.tsx:191 6567 6591 #: src/view/screens/Profile.tsx:225 6568 - #: src/view/screens/ProfileList.tsx:170 6569 6592 msgid "Posts" 6570 6593 msgstr "" 6571 6594 ··· 6594 6617 msgstr "" 6595 6618 6596 6619 #: src/components/Error.tsx:60 6597 - #: src/components/Lists.tsx:100 6620 + #: src/components/Lists.tsx:99 6598 6621 #: src/screens/Messages/components/MessageListError.tsx:24 6599 6622 #: src/screens/Signup/BackNextButtons.tsx:47 6600 6623 msgid "Press to retry" ··· 6643 6666 #: src/Navigation.tsx:331 6644 6667 #: src/screens/Settings/AboutSettings.tsx:92 6645 6668 #: src/screens/Settings/AboutSettings.tsx:95 6646 - #: src/view/screens/PrivacyPolicy.tsx:31 6669 + #: src/view/screens/PrivacyPolicy.tsx:34 6647 6670 #: src/view/shell/Drawer.tsx:704 6648 6671 #: src/view/shell/Drawer.tsx:705 6649 6672 msgid "Privacy Policy" ··· 6723 6746 msgid "Push, People you follow" 6724 6747 msgstr "" 6725 6748 6726 - #: src/components/StarterPack/QrCodeDialog.tsx:134 6749 + #: src/components/StarterPack/QrCodeDialog.tsx:137 6727 6750 msgid "QR code copied to your clipboard!" 6728 6751 msgstr "" 6729 6752 6730 - #: src/components/StarterPack/QrCodeDialog.tsx:112 6753 + #: src/components/StarterPack/QrCodeDialog.tsx:115 6731 6754 msgid "QR code has been downloaded!" 6732 6755 msgstr "" 6733 6756 6734 - #: src/components/StarterPack/QrCodeDialog.tsx:113 6757 + #: src/components/StarterPack/QrCodeDialog.tsx:116 6735 6758 msgid "QR code saved to your camera roll!" 6736 6759 msgstr "" 6737 6760 ··· 6766 6789 msgstr "" 6767 6790 6768 6791 #: src/lib/hooks/useNotificationHandler.ts:154 6769 - #: src/screens/Post/PostQuotes.tsx:38 6792 + #: src/screens/Post/PostQuotes.tsx:41 6770 6793 #: src/screens/Settings/NotificationSettings/index.tsx:170 6771 6794 #: src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx:41 6772 6795 msgid "Quotes" ··· 6929 6952 msgid "Remove Banner" 6930 6953 msgstr "" 6931 6954 6932 - #: src/screens/Messages/components/MessageInputEmbed.tsx:209 6955 + #: src/screens/Messages/components/MessageInputEmbed.tsx:212 6933 6956 msgid "Remove embed" 6934 6957 msgstr "" 6935 6958 ··· 6945 6968 6946 6969 #: src/screens/Profile/components/ProfileFeedHeader.tsx:319 6947 6970 #: src/screens/Profile/components/ProfileFeedHeader.tsx:325 6948 - #: src/view/screens/ProfileList.tsx:528 6949 - #: src/view/screens/SavedFeeds.tsx:350 6971 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:188 6972 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:191 6973 + #: src/screens/SavedFeeds.tsx:340 6950 6974 msgid "Remove from my feeds" 6951 6975 msgstr "" 6952 6976 ··· 7040 7064 msgstr "" 7041 7065 7042 7066 #: src/screens/Profile/components/ProfileFeedHeader.tsx:122 7067 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:74 7043 7068 #: src/view/com/posts/FeedShutdownMsg.tsx:44 7044 - #: src/view/screens/ProfileList.tsx:392 7045 7069 msgid "Removed from your feeds" 7046 7070 msgstr "" 7047 7071 ··· 7167 7191 msgid "Report feed" 7168 7192 msgstr "" 7169 7193 7170 - #: src/view/screens/ProfileList.tsx:570 7194 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:222 7195 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:225 7171 7196 msgid "Report list" 7172 7197 msgstr "" 7173 7198 ··· 7189 7214 msgid "Report submitted" 7190 7215 msgstr "" 7191 7216 7192 - #: src/components/ReportDialog/SelectReportOptionView.tsx:41 7217 + #: src/components/ReportDialog/SelectReportOptionView.tsx:44 7193 7218 msgid "Report this content" 7194 7219 msgstr "" 7195 7220 7196 7221 #: src/components/moderation/ReportDialog/copy.ts:31 7197 - #: src/components/ReportDialog/SelectReportOptionView.tsx:54 7222 + #: src/components/ReportDialog/SelectReportOptionView.tsx:57 7198 7223 msgid "Report this feed" 7199 7224 msgstr "" 7200 7225 7201 7226 #: src/components/moderation/ReportDialog/copy.ts:25 7202 - #: src/components/ReportDialog/SelectReportOptionView.tsx:51 7227 + #: src/components/ReportDialog/SelectReportOptionView.tsx:54 7203 7228 msgid "Report this list" 7204 7229 msgstr "" 7205 7230 7206 7231 #: src/components/dms/ReportDialog.tsx:61 7207 7232 #: src/components/dms/ReportDialog.tsx:185 7208 7233 #: src/components/moderation/ReportDialog/copy.ts:43 7209 - #: src/components/ReportDialog/SelectReportOptionView.tsx:60 7234 + #: src/components/ReportDialog/SelectReportOptionView.tsx:63 7210 7235 msgid "Report this message" 7211 7236 msgstr "" 7212 7237 7213 7238 #: src/components/moderation/ReportDialog/copy.ts:19 7214 - #: src/components/ReportDialog/SelectReportOptionView.tsx:48 7239 + #: src/components/ReportDialog/SelectReportOptionView.tsx:51 7215 7240 msgid "Report this post" 7216 7241 msgstr "" 7217 7242 7218 7243 #: src/components/moderation/ReportDialog/copy.ts:37 7219 - #: src/components/ReportDialog/SelectReportOptionView.tsx:57 7244 + #: src/components/ReportDialog/SelectReportOptionView.tsx:60 7220 7245 msgid "Report this starter pack" 7221 7246 msgstr "" 7222 7247 7223 7248 #: src/components/moderation/ReportDialog/copy.ts:13 7224 - #: src/components/ReportDialog/SelectReportOptionView.tsx:45 7249 + #: src/components/ReportDialog/SelectReportOptionView.tsx:48 7225 7250 msgid "Report this user" 7226 7251 msgstr "" 7227 7252 ··· 7249 7274 msgid "Repost or quote post" 7250 7275 msgstr "" 7251 7276 7252 - #: src/screens/Post/PostRepostedBy.tsx:38 7277 + #: src/screens/Post/PostRepostedBy.tsx:41 7253 7278 msgid "Reposted By" 7254 7279 msgstr "" 7255 7280 ··· 7354 7379 7355 7380 #: src/components/dms/MessageItem.tsx:322 7356 7381 #: src/components/Error.tsx:65 7357 - #: src/components/Lists.tsx:111 7382 + #: src/components/Lists.tsx:110 7358 7383 #: src/components/moderation/ReportDialog/index.tsx:229 7359 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55 7360 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:57 7384 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:54 7385 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:56 7361 7386 #: src/components/StarterPack/ProfileStarterPacks.tsx:346 7362 7387 #: src/screens/Login/LoginForm.tsx:323 7363 7388 #: src/screens/Login/LoginForm.tsx:330 ··· 7391 7416 msgstr "" 7392 7417 7393 7418 #: src/screens/Profile/ProfileFeed/index.tsx:93 7419 + #: src/screens/ProfileList/components/ErrorScreen.tsx:35 7394 7420 #: src/screens/Settings/components/ChangeHandleDialog.tsx:569 7395 7421 #: src/screens/VideoFeed/index.tsx:1147 7396 7422 #: src/view/screens/NotFound.tsx:60 7397 - #: src/view/screens/ProfileList.tsx:1039 7398 7423 msgid "Returns to previous page" 7399 7424 msgstr "" 7400 7425 ··· 7407 7432 #: src/components/dialogs/PostInteractionSettingsDialog.tsx:489 7408 7433 #: src/components/live/EditLiveDialog.tsx:216 7409 7434 #: src/components/live/EditLiveDialog.tsx:223 7410 - #: src/components/StarterPack/QrCodeDialog.tsx:192 7435 + #: src/components/StarterPack/QrCodeDialog.tsx:204 7411 7436 #: src/screens/Profile/Header/EditProfileDialog.tsx:238 7412 7437 #: src/screens/Profile/Header/EditProfileDialog.tsx:252 7438 + #: src/screens/SavedFeeds.tsx:120 7413 7439 #: src/screens/Settings/components/ChangeHandleDialog.tsx:267 7414 7440 #: src/view/com/composer/GifAltText.tsx:193 7415 7441 #: src/view/com/composer/GifAltText.tsx:202 ··· 7418 7444 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:152 7419 7445 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:162 7420 7446 #: src/view/com/modals/CreateOrEditList.tsx:315 7421 - #: src/view/screens/SavedFeeds.tsx:117 7422 7447 msgid "Save" 7423 7448 msgstr "" 7424 7449 ··· 7434 7459 7435 7460 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:191 7436 7461 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:200 7437 - #: src/view/screens/SavedFeeds.tsx:113 7438 - #: src/view/screens/SavedFeeds.tsx:117 7462 + #: src/screens/SavedFeeds.tsx:116 7463 + #: src/screens/SavedFeeds.tsx:120 7439 7464 msgid "Save changes" 7440 7465 msgstr "" 7441 7466 7442 - #: src/components/StarterPack/ShareDialog.tsx:131 7443 7467 #: src/components/StarterPack/ShareDialog.tsx:138 7468 + #: src/components/StarterPack/ShareDialog.tsx:144 7444 7469 msgid "Save image" 7445 7470 msgstr "" 7446 7471 ··· 7452 7477 msgid "Save new handle" 7453 7478 msgstr "" 7454 7479 7455 - #: src/components/StarterPack/QrCodeDialog.tsx:186 7480 + #: src/components/StarterPack/QrCodeDialog.tsx:196 7456 7481 msgid "Save QR code" 7457 7482 msgstr "" 7458 7483 ··· 7467 7492 msgid "Saved" 7468 7493 msgstr "" 7469 7494 7470 - #: src/view/screens/SavedFeeds.tsx:172 7495 + #: src/screens/SavedFeeds.tsx:184 7471 7496 msgid "Saved Feeds" 7472 7497 msgstr "" 7473 7498 ··· 7478 7503 msgstr "" 7479 7504 7480 7505 #: src/screens/Profile/components/ProfileFeedHeader.tsx:132 7481 - #: src/view/screens/ProfileList.tsx:372 7506 + #: src/screens/ProfileList/components/Header.tsx:85 7482 7507 msgid "Saved to your feeds" 7483 7508 msgstr "" 7484 7509 ··· 7487 7512 msgstr "" 7488 7513 7489 7514 #: src/components/dms/ChatEmptyPill.tsx:33 7490 - #: src/components/NewskieDialog.tsx:105 7515 + #: src/components/NewskieDialog.tsx:121 7491 7516 #: src/view/com/notifications/NotificationFeedItem.tsx:751 7492 7517 #: src/view/com/notifications/NotificationFeedItem.tsx:776 7493 7518 msgid "Say hello!" ··· 7506 7531 msgid "Scroll right" 7507 7532 msgstr "" 7508 7533 7509 - #: src/view/screens/ProfileList.tsx:996 7534 + #: src/screens/ProfileList/AboutSection.tsx:130 7510 7535 msgid "Scroll to top" 7511 7536 msgstr "" 7512 7537 ··· 7639 7664 msgid "See more suggested profiles on the Explore page" 7640 7665 msgstr "" 7641 7666 7642 - #: src/view/screens/SavedFeeds.tsx:213 7667 + #: src/screens/SavedFeeds.tsx:220 7643 7668 msgid "See this guide" 7644 7669 msgstr "" 7645 7670 7646 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:197 7671 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:196 7647 7672 msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause" 7648 7673 msgstr "" 7649 7674 ··· 7743 7768 msgid "Select primary language" 7744 7769 msgstr "" 7745 7770 7746 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:59 7747 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:66 7771 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:60 7772 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:67 7748 7773 msgid "Select subtitle file (.vtt)" 7749 7774 msgstr "" 7750 7775 ··· 7849 7874 7850 7875 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:101 7851 7876 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:107 7852 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:104 7853 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:110 7877 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:103 7878 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:109 7854 7879 msgid "Send via direct message" 7855 7880 msgstr "" 7856 7881 ··· 7946 7971 msgid "Sexually Suggestive" 7947 7972 msgstr "" 7948 7973 7949 - #: src/components/StarterPack/QrCodeDialog.tsx:182 7974 + #: src/components/StarterPack/QrCodeDialog.tsx:192 7950 7975 #: src/screens/Hashtag.tsx:126 7951 7976 #: src/screens/StarterPack/StarterPackScreen.tsx:433 7952 7977 #: src/screens/Topic.tsx:102 7953 - #: src/view/screens/ProfileList.tsx:513 7954 7978 msgid "Share" 7955 7979 msgstr "" 7956 7980 ··· 7978 8002 7979 8003 #: src/components/dialogs/LinkWarning.tsx:96 7980 8004 #: src/components/dialogs/LinkWarning.tsx:104 7981 - #: src/components/StarterPack/ShareDialog.tsx:104 7982 - #: src/components/StarterPack/ShareDialog.tsx:111 8005 + #: src/components/StarterPack/ShareDialog.tsx:113 8006 + #: src/components/StarterPack/ShareDialog.tsx:119 7983 8007 msgid "Share link" 7984 8008 msgstr "" 7985 8009 7986 - #: src/components/StarterPack/ShareDialog.tsx:68 8010 + #: src/components/StarterPack/ShareDialog.tsx:72 7987 8011 msgid "Share link dialog" 7988 8012 msgstr "" 7989 8013 ··· 7992 8016 msgid "Share post at:// URI" 7993 8017 msgstr "" 7994 8018 7995 - #: src/components/StarterPack/ShareDialog.tsx:115 7996 - #: src/components/StarterPack/ShareDialog.tsx:126 8019 + #: src/components/StarterPack/ShareDialog.tsx:123 8020 + #: src/components/StarterPack/ShareDialog.tsx:133 7997 8021 msgid "Share QR code" 7998 8022 msgstr "" 7999 8023 ··· 8005 8029 msgid "Share this starter pack" 8006 8030 msgstr "" 8007 8031 8008 - #: src/components/StarterPack/ShareDialog.tsx:80 8032 + #: src/components/StarterPack/ShareDialog.tsx:84 8009 8033 msgid "Share this starter pack and help people join your community on Bluesky." 8010 8034 msgstr "" 8011 8035 8012 8036 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:117 8013 8037 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:120 8038 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172 8039 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:178 8014 8040 #: src/screens/StarterPack/StarterPackScreen.tsx:611 8015 8041 #: src/screens/StarterPack/StarterPackScreen.tsx:619 8016 8042 #: src/view/com/profile/ProfileMenu.tsx:246 ··· 8027 8053 msgstr "" 8028 8054 8029 8055 #: src/components/moderation/ContentHider.tsx:203 8030 - #: src/components/moderation/LabelPreference.tsx:143 8056 + #: src/components/moderation/LabelPreference.tsx:142 8031 8057 #: src/components/moderation/PostHider.tsx:134 8032 8058 msgid "Show" 8033 8059 msgstr "" ··· 8044 8070 msgid "Show anyway" 8045 8071 msgstr "" 8046 8072 8047 - #: src/lib/moderation/useLabelBehaviorDescription.ts:27 8048 - #: src/lib/moderation/useLabelBehaviorDescription.ts:63 8073 + #: src/lib/moderation/useLabelBehaviorDescription.ts:30 8074 + #: src/lib/moderation/useLabelBehaviorDescription.ts:66 8049 8075 msgid "Show badge" 8050 8076 msgstr "" 8051 8077 8052 - #: src/lib/moderation/useLabelBehaviorDescription.ts:61 8078 + #: src/lib/moderation/useLabelBehaviorDescription.ts:64 8053 8079 msgid "Show badge and filter from feeds" 8054 8080 msgstr "" 8055 8081 ··· 8116 8142 msgid "Show samples of your saved feeds in your Following feed" 8117 8143 msgstr "" 8118 8144 8119 - #: src/lib/moderation/useLabelBehaviorDescription.ts:58 8145 + #: src/lib/moderation/useLabelBehaviorDescription.ts:61 8120 8146 msgid "Show warning" 8121 8147 msgstr "" 8122 8148 8123 - #: src/lib/moderation/useLabelBehaviorDescription.ts:56 8149 + #: src/lib/moderation/useLabelBehaviorDescription.ts:59 8124 8150 msgid "Show warning and filter from feeds" 8125 8151 msgstr "" 8126 8152 ··· 8299 8325 msgid "Something went wrong, please try again." 8300 8326 msgstr "" 8301 8327 8302 - #: src/components/Lists.tsx:175 8328 + #: src/components/Lists.tsx:174 8303 8329 msgid "Something went wrong!" 8304 8330 msgstr "" 8305 8331 ··· 8363 8389 msgid "Start a new chat" 8364 8390 msgstr "" 8365 8391 8366 - #: src/view/screens/ProfileList.tsx:846 8367 - #: src/view/screens/ProfileList.tsx:967 8392 + #: src/screens/ProfileList/AboutSection.tsx:102 8393 + #: src/screens/ProfileList/FeedSection.tsx:74 8368 8394 msgid "Start adding people" 8369 8395 msgstr "" 8370 8396 8371 - #: src/view/screens/ProfileList.tsx:853 8372 - #: src/view/screens/ProfileList.tsx:974 8397 + #: src/screens/ProfileList/AboutSection.tsx:108 8398 + #: src/screens/ProfileList/FeedSection.tsx:80 8373 8399 msgid "Start adding people!" 8374 8400 msgstr "" 8375 8401 ··· 8450 8476 msgid "Submit report" 8451 8477 msgstr "" 8452 8478 8453 - #: src/view/screens/ProfileList.tsx:741 8479 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:81 8454 8480 msgid "Subscribe" 8455 8481 msgstr "" 8456 8482 ··· 8470 8496 msgid "Subscribe to this labeler" 8471 8497 msgstr "" 8472 8498 8473 - #: src/view/screens/ProfileList.tsx:737 8499 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:69 8474 8500 msgid "Subscribe to this list" 8475 8501 msgstr "" 8476 8502 ··· 8513 8539 msgstr "" 8514 8540 8515 8541 #: src/Navigation.tsx:326 8516 - #: src/view/screens/Support.tsx:31 8517 8542 #: src/view/screens/Support.tsx:34 8543 + #: src/view/screens/Support.tsx:37 8518 8544 msgid "Support" 8519 8545 msgstr "" 8520 8546 ··· 8622 8648 #: src/Navigation.tsx:336 8623 8649 #: src/screens/Settings/AboutSettings.tsx:84 8624 8650 #: src/screens/Settings/AboutSettings.tsx:87 8625 - #: src/view/screens/TermsOfService.tsx:31 8651 + #: src/view/screens/TermsOfService.tsx:34 8626 8652 #: src/view/shell/Drawer.tsx:697 8627 8653 #: src/view/shell/Drawer.tsx:699 8628 8654 msgid "Terms of Service" ··· 8717 8743 msgid "The Bluesky web application" 8718 8744 msgstr "" 8719 8745 8720 - #: src/view/screens/CommunityGuidelines.tsx:38 8746 + #: src/view/screens/CommunityGuidelines.tsx:41 8721 8747 msgid "The Community Guidelines have been moved to <0/>" 8722 8748 msgstr "" 8723 8749 8724 - #: src/view/screens/CopyrightPolicy.tsx:35 8750 + #: src/view/screens/CopyrightPolicy.tsx:38 8725 8751 msgid "The Copyright Policy has been moved to <0/>" 8726 8752 msgstr "" 8727 8753 ··· 8765 8791 msgid "The open social network." 8766 8792 msgstr "" 8767 8793 8768 - #: src/view/screens/PrivacyPolicy.tsx:35 8794 + #: src/view/screens/PrivacyPolicy.tsx:38 8769 8795 msgid "The Privacy Policy has been moved to <0/>" 8770 8796 msgstr "" 8771 8797 ··· 8783 8809 msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead." 8784 8810 msgstr "" 8785 8811 8786 - #: src/components/ContextMenu/index.tsx:433 8812 + #: src/components/ContextMenu/index.tsx:434 8787 8813 msgid "The subject of the context menu" 8788 8814 msgstr "" 8789 8815 8790 - #: src/view/screens/Support.tsx:37 8816 + #: src/view/screens/Support.tsx:40 8791 8817 msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." 8792 8818 msgstr "" 8793 8819 8794 - #: src/view/screens/TermsOfService.tsx:35 8820 + #: src/view/screens/TermsOfService.tsx:38 8795 8821 msgid "The Terms of Service have been moved to" 8796 8822 msgstr "" 8797 8823 ··· 8812 8838 msgstr "" 8813 8839 8814 8840 #: src/screens/Profile/components/ProfileFeedHeader.tsx:178 8815 - #: src/view/screens/ProfileList.tsx:375 8816 - #: src/view/screens/ProfileList.tsx:394 8817 - #: src/view/screens/SavedFeeds.tsx:93 8841 + #: src/screens/ProfileList/components/Header.tsx:88 8842 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:76 8843 + #: src/screens/SavedFeeds.tsx:97 8818 8844 msgid "There was an issue contacting the server" 8819 8845 msgstr "" 8820 8846 ··· 8828 8854 msgstr "" 8829 8855 8830 8856 #: src/screens/Search/Explore.tsx:986 8831 - #: src/view/com/posts/PostFeed.tsx:701 8857 + #: src/view/com/posts/PostFeed.tsx:709 8832 8858 msgid "There was an issue fetching posts. Tap here to try again." 8833 8859 msgstr "" 8834 8860 ··· 8885 8911 #: src/screens/List/ListHiddenScreen.tsx:63 8886 8912 #: src/screens/List/ListHiddenScreen.tsx:77 8887 8913 #: src/screens/List/ListHiddenScreen.tsx:99 8888 - #: src/view/screens/ProfileList.tsx:411 8889 - #: src/view/screens/ProfileList.tsx:429 8890 - #: src/view/screens/ProfileList.tsx:447 8891 - #: src/view/screens/ProfileList.tsx:465 8914 + #: src/screens/ProfileList/components/Header.tsx:107 8915 + #: src/screens/ProfileList/components/Header.tsx:125 8916 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:129 8917 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:147 8918 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:40 8919 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:59 8892 8920 msgid "There was an issue. Please check your internet connection and try again." 8893 8921 msgstr "" 8894 8922 ··· 8979 9007 msgid "This feature allows users to receive notifications for your new posts and replies. Who do you want to enable this for?" 8980 9008 msgstr "" 8981 9009 8982 - #: src/screens/Settings/components/ExportCarDialog.tsx:96 9010 + #: src/screens/Settings/components/ExportCarDialog.tsx:95 8983 9011 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost</0>." 8984 9012 msgstr "" 8985 9013 ··· 9001 9029 9002 9030 #: src/components/StarterPack/Main/PostsList.tsx:36 9003 9031 #: src/screens/Profile/ProfileFeed/index.tsx:192 9004 - #: src/view/screens/ProfileList.tsx:843 9032 + #: src/screens/ProfileList/FeedSection.tsx:71 9005 9033 msgid "This feed is empty." 9006 9034 msgstr "" 9007 9035 ··· 9046 9074 msgid "This list – created by you – contains possible violations of Bluesky's community guidelines in its name or description." 9047 9075 msgstr "" 9048 9076 9049 - #: src/view/screens/ProfileList.tsx:962 9077 + #: src/screens/ProfileList/AboutSection.tsx:98 9050 9078 msgid "This list is empty." 9051 9079 msgstr "" 9052 9080 ··· 9127 9155 msgid "This user is included in the <0>{0}</0> list which you have muted." 9128 9156 msgstr "" 9129 9157 9130 - #: src/components/NewskieDialog.tsx:65 9158 + #: src/components/NewskieDialog.tsx:47 9131 9159 msgid "This user is new here. Press for more info about when they joined." 9132 9160 msgstr "" 9133 9161 ··· 9315 9343 #: src/components/dms/MessagesListBlockedFooter.tsx:119 9316 9344 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:208 9317 9345 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:328 9346 + #: src/screens/ProfileList/components/Header.tsx:171 9347 + #: src/screens/ProfileList/components/Header.tsx:178 9318 9348 #: src/view/com/profile/ProfileMenu.tsx:490 9319 - #: src/view/screens/ProfileList.tsx:723 9320 9349 msgid "Unblock" 9321 9350 msgstr "" 9322 9351 ··· 9337 9366 msgid "Unblock Account?" 9338 9367 msgstr "" 9339 9368 9340 - #: src/view/screens/ProfileList.tsx:620 9369 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:254 9370 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:257 9341 9371 msgid "Unblock list" 9342 9372 msgstr "" 9343 9373 ··· 9396 9426 msgstr "" 9397 9427 9398 9428 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 9399 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95 9429 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:94 9400 9430 msgctxt "video" 9401 9431 msgid "Unmute" 9402 9432 msgstr "" 9403 9433 9404 - #: src/view/screens/ProfileList.tsx:730 9434 + #: src/screens/ProfileList/components/Header.tsx:185 9435 + #: src/screens/ProfileList/components/Header.tsx:192 9405 9436 msgid "Unmute" 9406 9437 msgstr "" 9407 9438 ··· 9421 9452 msgid "Unmute conversation" 9422 9453 msgstr "" 9423 9454 9424 - #: src/view/screens/ProfileList.tsx:605 9455 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:264 9456 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:267 9425 9457 msgid "Unmute list" 9426 9458 msgstr "" 9427 9459 ··· 9434 9466 msgid "Unmute video" 9435 9467 msgstr "" 9436 9468 9437 - #: src/view/screens/ProfileList.tsx:714 9469 + #: src/screens/ProfileList/components/Header.tsx:156 9470 + #: src/screens/ProfileList/components/Header.tsx:163 9438 9471 msgid "Unpin" 9439 9472 msgstr "" 9440 9473 9441 9474 #: src/screens/Profile/components/ProfileFeedHeader.tsx:523 9442 9475 #: src/screens/Profile/components/ProfileFeedHeader.tsx:530 9476 + #: src/screens/SavedFeeds.tsx:351 9443 9477 msgid "Unpin feed" 9444 9478 msgstr "" 9445 9479 ··· 9457 9491 msgid "Unpin from profile" 9458 9492 msgstr "" 9459 9493 9460 - #: src/view/screens/ProfileList.tsx:585 9494 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:237 9495 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:240 9461 9496 msgid "Unpin moderation list" 9462 9497 msgstr "" 9463 9498 ··· 9465 9500 msgid "Unpinned {0} from Home" 9466 9501 msgstr "" 9467 9502 9468 - #: src/view/screens/ProfileList.tsx:362 9503 + #: src/screens/ProfileList/components/Header.tsx:75 9469 9504 msgid "Unpinned from your feeds" 9505 + msgstr "" 9506 + 9507 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:109 9508 + msgid "Unpinned list" 9470 9509 msgstr "" 9471 9510 9472 9511 #: src/screens/Settings/Settings.tsx:474 ··· 9604 9643 msgid "Use my default browser" 9605 9644 msgstr "" 9606 9645 9607 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:53 9646 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:56 9608 9647 msgid "Use recommended" 9609 9648 msgstr "" 9610 9649 ··· 9831 9870 msgid "Video is playing" 9832 9871 msgstr "" 9833 9872 9834 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:220 9873 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:219 9835 9874 msgid "Video not found." 9836 9875 msgstr "" 9837 9876 ··· 9880 9919 msgid "View blocked user's profile" 9881 9920 msgstr "" 9882 9921 9883 - #: src/screens/Settings/components/ExportCarDialog.tsx:100 9922 + #: src/screens/Settings/components/ExportCarDialog.tsx:99 9884 9923 msgid "View blogpost for more details" 9885 9924 msgstr "" 9886 9925 ··· 9888 9927 msgid "View debug entry" 9889 9928 msgstr "" 9890 9929 9891 - #: src/components/ReportDialog/SelectReportOptionView.tsx:137 9930 + #: src/components/ReportDialog/SelectReportOptionView.tsx:140 9892 9931 #: src/screens/VideoFeed/index.tsx:659 9893 9932 #: src/screens/VideoFeed/index.tsx:677 9894 9933 msgid "View details" 9895 9934 msgstr "" 9896 9935 9897 - #: src/components/ReportDialog/SelectReportOptionView.tsx:132 9936 + #: src/components/ReportDialog/SelectReportOptionView.tsx:135 9898 9937 msgid "View details for reporting a copyright violation" 9899 9938 msgstr "" 9900 9939 ··· 9902 9941 msgid "View full thread" 9903 9942 msgstr "" 9904 9943 9905 - #: src/components/moderation/LabelsOnMe.tsx:46 9944 + #: src/components/moderation/LabelsOnMe.tsx:51 9906 9945 msgid "View information about these labels" 9907 9946 msgstr "" 9908 9947 ··· 9925 9964 #: src/components/ProfileHoverCard/index.web.tsx:486 9926 9965 #: src/components/ProfileHoverCard/index.web.tsx:513 9927 9966 #: src/view/com/posts/PostFeedErrorMessage.tsx:179 9928 - #: src/view/com/util/PostMeta.tsx:91 9929 - #: src/view/com/util/PostMeta.tsx:128 9967 + #: src/view/com/util/PostMeta.tsx:90 9968 + #: src/view/com/util/PostMeta.tsx:127 9930 9969 msgid "View profile" 9931 9970 msgstr "" 9932 9971 ··· 9958 9997 msgid "View your default post interaction settings" 9959 9998 msgstr "" 9960 9999 9961 - #: src/view/com/home/HomeHeaderLayout.web.tsx:56 9962 - #: src/view/com/home/HomeHeaderLayoutMobile.tsx:71 10000 + #: src/view/com/home/HomeHeaderLayout.web.tsx:57 10001 + #: src/view/com/home/HomeHeaderLayoutMobile.tsx:72 9963 10002 msgid "View your feeds and explore more" 9964 10003 msgstr "" 9965 10004 ··· 9993 10032 msgid "Visit your notification settings" 9994 10033 msgstr "" 9995 10034 9996 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:81 10035 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:80 9997 10036 msgid "Volume" 9998 10037 msgstr "" 9999 10038 10000 - #: src/components/moderation/LabelPreference.tsx:142 10001 - #: src/lib/moderation/useLabelBehaviorDescription.ts:17 10002 - #: src/lib/moderation/useLabelBehaviorDescription.ts:22 10039 + #: src/components/moderation/LabelPreference.tsx:141 10040 + #: src/lib/moderation/useLabelBehaviorDescription.ts:20 10041 + #: src/lib/moderation/useLabelBehaviorDescription.ts:25 10003 10042 msgid "Warn" 10004 10043 msgstr "" 10005 10044 10006 - #: src/lib/moderation/useLabelBehaviorDescription.ts:48 10045 + #: src/lib/moderation/useLabelBehaviorDescription.ts:51 10007 10046 msgid "Warn content" 10008 10047 msgstr "" 10009 10048 10010 - #: src/lib/moderation/useLabelBehaviorDescription.ts:46 10049 + #: src/lib/moderation/useLabelBehaviorDescription.ts:49 10011 10050 msgid "Warn content and filter from feeds" 10012 10051 msgstr "" 10013 10052 ··· 10133 10172 msgid "We're sorry, but based on your device's location, you are currently located in a region where we cannot provide access at this time." 10134 10173 msgstr "" 10135 10174 10136 - #: src/view/screens/ProfileList.tsx:117 10175 + #: src/screens/ProfileList/index.tsx:87 10137 10176 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." 10138 10177 msgstr "" 10139 10178 ··· 10149 10188 msgid "We're sorry! The post you are replying to has been deleted." 10150 10189 msgstr "" 10151 10190 10152 - #: src/components/Lists.tsx:195 10191 + #: src/components/Lists.tsx:194 10153 10192 #: src/view/screens/NotFound.tsx:50 10154 10193 msgid "We're sorry! We can't find the page you were looking for." 10155 10194 msgstr "" ··· 10182 10221 msgid "Welcome back!" 10183 10222 msgstr "" 10184 10223 10185 - #: src/components/NewskieDialog.tsx:103 10224 + #: src/components/NewskieDialog.tsx:121 10186 10225 msgid "Welcome, friend!" 10187 10226 msgstr "" 10188 10227 ··· 10235 10274 msgid "Why are you appealing?" 10236 10275 msgstr "" 10237 10276 10238 - #: src/components/ReportDialog/SelectReportOptionView.tsx:42 10277 + #: src/components/ReportDialog/SelectReportOptionView.tsx:45 10239 10278 msgid "Why should this content be reviewed?" 10240 10279 msgstr "" 10241 10280 10242 10281 #: src/components/moderation/ReportDialog/copy.ts:32 10243 - #: src/components/ReportDialog/SelectReportOptionView.tsx:55 10282 + #: src/components/ReportDialog/SelectReportOptionView.tsx:58 10244 10283 msgid "Why should this feed be reviewed?" 10245 10284 msgstr "" 10246 10285 10247 10286 #: src/components/moderation/ReportDialog/copy.ts:26 10248 - #: src/components/ReportDialog/SelectReportOptionView.tsx:52 10287 + #: src/components/ReportDialog/SelectReportOptionView.tsx:55 10249 10288 msgid "Why should this list be reviewed?" 10250 10289 msgstr "" 10251 10290 10252 10291 #: src/components/moderation/ReportDialog/copy.ts:44 10253 - #: src/components/ReportDialog/SelectReportOptionView.tsx:61 10292 + #: src/components/ReportDialog/SelectReportOptionView.tsx:64 10254 10293 msgid "Why should this message be reviewed?" 10255 10294 msgstr "" 10256 10295 10257 10296 #: src/components/moderation/ReportDialog/copy.ts:20 10258 - #: src/components/ReportDialog/SelectReportOptionView.tsx:49 10297 + #: src/components/ReportDialog/SelectReportOptionView.tsx:52 10259 10298 msgid "Why should this post be reviewed?" 10260 10299 msgstr "" 10261 10300 10262 10301 #: src/components/moderation/ReportDialog/copy.ts:38 10263 - #: src/components/ReportDialog/SelectReportOptionView.tsx:58 10302 + #: src/components/ReportDialog/SelectReportOptionView.tsx:61 10264 10303 msgid "Why should this starter pack be reviewed?" 10265 10304 msgstr "" 10266 10305 10267 10306 #: src/components/moderation/ReportDialog/copy.ts:14 10268 - #: src/components/ReportDialog/SelectReportOptionView.tsx:46 10307 + #: src/components/ReportDialog/SelectReportOptionView.tsx:49 10269 10308 msgid "Why should this user be reviewed?" 10270 10309 msgstr "" 10271 10310 ··· 10297 10336 msgid "www.mylivestream.tv" 10298 10337 msgstr "" 10299 10338 10300 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:100 10339 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:102 10301 10340 msgid "Yes" 10302 10341 msgstr "" 10303 10342 ··· 10326 10365 msgid "Yesterday" 10327 10366 msgstr "" 10328 10367 10329 - #: src/components/NewskieDialog.tsx:43 10368 + #: src/components/NewskieDialog.tsx:91 10330 10369 msgid "You" 10331 10370 msgstr "" 10332 10371 ··· 10448 10487 msgid "You do not have any followers." 10449 10488 msgstr "" 10450 10489 10451 - #: src/screens/Profile/KnownFollowers.tsx:109 10490 + #: src/screens/Profile/KnownFollowers.tsx:112 10452 10491 msgid "You don't follow any users who follow @{name}." 10453 10492 msgstr "" 10454 10493 ··· 10460 10499 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." 10461 10500 msgstr "" 10462 10501 10463 - #: src/view/screens/SavedFeeds.tsx:145 10502 + #: src/screens/SavedFeeds.tsx:149 10464 10503 msgid "You don't have any pinned feeds." 10465 10504 msgstr "" 10466 10505 10467 - #: src/view/screens/SavedFeeds.tsx:185 10506 + #: src/screens/SavedFeeds.tsx:191 10468 10507 msgid "You don't have any saved feeds." 10469 10508 msgstr "" 10470 10509 ··· 10530 10569 msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account." 10531 10570 msgstr "" 10532 10571 10533 - #: src/components/Lists.tsx:58 10572 + #: src/components/Lists.tsx:57 10534 10573 msgid "You have reached the end" 10535 10574 msgstr "" 10536 10575 ··· 10595 10634 msgid "You must complete age assurance in order to access this screen." 10596 10635 msgstr "" 10597 10636 10598 - #: src/components/StarterPack/QrCodeDialog.tsx:61 10637 + #: src/components/StarterPack/QrCodeDialog.tsx:65 10599 10638 msgid "You must grant access to your photo library to save a QR code" 10600 10639 msgstr "" 10601 10640 ··· 10771 10810 msgid "Your birth date" 10772 10811 msgstr "" 10773 10812 10774 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:224 10813 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:223 10775 10814 msgid "Your browser does not support the video format. Please try a different browser." 10776 10815 msgstr "" 10777 10816
+1 -1
src/logger/__tests__/logDump.test.ts
··· 1 1 import {expect, test} from '@jest/globals' 2 2 3 - import {add, ConsoleTransportEntry, getEntries} from '#/logger/logDump' 3 + import {add, type ConsoleTransportEntry, getEntries} from '#/logger/logDump' 4 4 import {LogContext, LogLevel} from '#/logger/types' 5 5 6 6 test('works', () => {
+1 -1
src/logger/logDump.ts
··· 1 - import type {LogContext, LogLevel, Metadata} from '#/logger/types' 1 + import {type LogContext, type LogLevel, type Metadata} from '#/logger/types' 2 2 3 3 export type ConsoleTransportEntry = { 4 4 id: string
+1 -1
src/logger/transports/bitdrift.ts
··· 1 1 import {debug, error, info, warn} from '#/logger/bitdrift/lib' 2 - import {LogLevel, Transport} from '#/logger/types' 2 + import {LogLevel, type Transport} from '#/logger/types' 3 3 import {prepareMetadata} from '#/logger/util' 4 4 5 5 const logFunctions = {
+1 -1
src/logger/transports/console.ts
··· 1 1 import format from 'date-fns/format' 2 2 3 - import {LogLevel, Transport} from '#/logger/types' 3 + import {LogLevel, type Transport} from '#/logger/types' 4 4 import {prepareMetadata} from '#/logger/util' 5 5 import {isWeb} from '#/platform/detection' 6 6
+1 -1
src/logger/util.ts
··· 1 - import {LogLevel, Metadata, Serializable} from '#/logger/types' 1 + import {LogLevel, type Metadata, type Serializable} from '#/logger/types' 2 2 3 3 export const enabledLogLevels: { 4 4 [key in LogLevel]: LogLevel[]
-49
src/platform/polyfills.ts
··· 1 1 import 'react-native-url-polyfill/auto' 2 2 import 'fast-text-encoding' 3 3 export {} 4 - 5 - /** 6 - https://github.com/MaxArt2501/base64-js 7 - The MIT License (MIT) 8 - Copyright (c) 2014 MaxArt2501 9 - */ 10 - 11 - const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 12 - // Regular expression to check formal correctness of base64 encoded strings 13 - const b64re = 14 - /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/ 15 - 16 - globalThis.atob = (str: string): string => { 17 - // atob can work with strings with whitespaces, even inside the encoded part, 18 - // but only \t, \n, \f, \r and ' ', which can be stripped. 19 - str = String(str).replace(/[\t\n\f\r ]+/g, '') 20 - if (!b64re.test(str)) { 21 - throw new TypeError( 22 - "Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.", 23 - ) 24 - } 25 - 26 - // Adding the padding if missing, for simplicity 27 - str += '=='.slice(2 - (str.length & 3)) 28 - var bitmap, 29 - result = '', 30 - r1, 31 - r2, 32 - i = 0 33 - for (; i < str.length; ) { 34 - bitmap = 35 - (b64.indexOf(str.charAt(i++)) << 18) | 36 - (b64.indexOf(str.charAt(i++)) << 12) | 37 - ((r1 = b64.indexOf(str.charAt(i++))) << 6) | 38 - (r2 = b64.indexOf(str.charAt(i++))) 39 - 40 - result += 41 - r1 === 64 42 - ? String.fromCharCode((bitmap >> 16) & 255) 43 - : r2 === 64 44 - ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255) 45 - : String.fromCharCode( 46 - (bitmap >> 16) & 255, 47 - (bitmap >> 8) & 255, 48 - bitmap & 255, 49 - ) 50 - } 51 - return result 52 - }
+16 -18
src/screens/Feeds/NoFollowingFeed.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 1 + import {type GestureResponderEvent, View} from 'react-native' 3 2 import {msg, Trans} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 ··· 9 8 import {InlineLinkText} from '#/components/Link' 10 9 import {Text} from '#/components/Typography' 11 10 12 - export function NoFollowingFeed() { 11 + export function NoFollowingFeed({onAddFeed}: {onAddFeed?: () => void}) { 13 12 const t = useTheme() 14 13 const {_} = useLingui() 15 14 const {mutateAsync: addSavedFeeds} = useAddSavedFeedsMutation() 16 15 17 - const addRecommendedFeeds = React.useCallback( 18 - (e: any) => { 19 - e.preventDefault() 16 + const addRecommendedFeeds = (e: GestureResponderEvent) => { 17 + e.preventDefault() 20 18 21 - addSavedFeeds([ 22 - { 23 - ...TIMELINE_SAVED_FEED, 24 - pinned: true, 25 - }, 26 - ]) 19 + addSavedFeeds([ 20 + { 21 + ...TIMELINE_SAVED_FEED, 22 + pinned: true, 23 + }, 24 + ]) 27 25 28 - // prevent navigation 29 - return false 30 - }, 31 - [addSavedFeeds], 32 - ) 26 + onAddFeed?.() 27 + 28 + // prevent navigation 29 + return false as const 30 + } 33 31 34 32 return ( 35 33 <View style={[a.flex_row, a.flex_wrap, a.align_center, a.py_md, a.px_lg]}> ··· 37 35 <Trans> 38 36 Looks like you're missing a following feed.{' '} 39 37 <InlineLinkText 40 - to="/" 38 + to="#" 41 39 label={_(msg`Add the default feed of only people you follow`)} 42 40 onPress={addRecommendedFeeds} 43 41 style={[a.leading_snug]}>
+10 -7
src/screens/Feeds/NoSavedFeedsOfAnyType.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 2 import {TID} from '@atproto/common-web' 4 3 import {msg, Trans} from '@lingui/macro' ··· 16 15 * feeds if pressed. It should only be presented to the user if they actually 17 16 * have no other feeds saved. 18 17 */ 19 - export function NoSavedFeedsOfAnyType() { 18 + export function NoSavedFeedsOfAnyType({ 19 + onAddRecommendedFeeds, 20 + }: { 21 + onAddRecommendedFeeds?: () => void 22 + }) { 20 23 const t = useTheme() 21 24 const {_} = useLingui() 22 25 const {isPending, mutateAsync: overwriteSavedFeeds} = 23 26 useOverwriteSavedFeedsMutation() 24 27 25 - const addRecommendedFeeds = React.useCallback(async () => { 28 + const addRecommendedFeeds = async () => { 29 + onAddRecommendedFeeds?.() 26 30 await overwriteSavedFeeds( 27 31 RECOMMENDED_SAVED_FEEDS.map(f => ({ 28 32 ...f, 29 33 id: TID.nextStr(), 30 34 })), 31 35 ) 32 - }, [overwriteSavedFeeds]) 36 + } 33 37 34 38 return ( 35 39 <View ··· 46 50 disabled={isPending} 47 51 label={_(msg`Apply default recommended feeds`)} 48 52 size="small" 49 - variant="solid" 50 - color="primary" 53 + color="primary_subtle" 51 54 onPress={addRecommendedFeeds}> 52 - <ButtonIcon icon={Plus} position="left" /> 55 + <ButtonIcon icon={Plus} /> 53 56 <ButtonText>{_(msg`Use recommended`)}</ButtonText> 54 57 </Button> 55 58 </View>
+1 -1
src/screens/Home/NoFeedsPinned.tsx
··· 6 6 7 7 import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' 8 8 import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' 9 - import {UsePreferencesQueryResponse} from '#/state/queries/preferences' 9 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 10 10 import {CenteredView} from '#/view/com/util/Views' 11 11 import {atoms as a} from '#/alf' 12 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+1 -1
src/screens/List/ListHiddenScreen.tsx
··· 11 11 import {RQKEY_ROOT as listQueryRoot} from '#/state/queries/list' 12 12 import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 13 13 import { 14 - UsePreferencesQueryResponse, 14 + type UsePreferencesQueryResponse, 15 15 useRemoveFeedMutation, 16 16 } from '#/state/queries/preferences' 17 17 import {useSession} from '#/state/session'
+1 -1
src/screens/Login/ChooseAccountForm.tsx
··· 5 5 6 6 import {logEvent} from '#/lib/statsig/statsig' 7 7 import {logger} from '#/logger' 8 - import {SessionAccount, useSession, useSessionApi} from '#/state/session' 8 + import {type SessionAccount, useSession, useSessionApi} from '#/state/session' 9 9 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 10 10 import * as Toast from '#/view/com/util/Toast' 11 11 import {atoms as a} from '#/alf'
+1 -1
src/screens/Login/FormContainer.tsx
··· 1 - import React from 'react' 2 1 import {type StyleProp, View, type ViewStyle} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 5 import {Text} from '#/components/Typography'
+2 -2
src/screens/Login/ScreenTransition.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import Animated, {FadeInRight, FadeOutLeft} from 'react-native-reanimated' 3 + import type React from 'react' 4 4 5 5 export function ScreenTransition({ 6 6 style,
+1 -1
src/screens/Messages/components/ChatStatusInfo.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {ActiveConvoStates} from '#/state/messages/convo' 6 + import {type ActiveConvoStates} from '#/state/messages/convo' 7 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 8 8 import {useSession} from '#/state/session' 9 9 import {atoms as a, useTheme} from '#/alf'
+5 -2
src/screens/Messages/components/MessageInputEmbed.tsx
··· 9 9 } from '@atproto/api' 10 10 import {msg} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 - import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' 12 + import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' 13 13 14 14 import {makeProfileLink} from '#/lib/routes/links' 15 - import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' 15 + import { 16 + type CommonNavigatorParams, 17 + type NavigationProp, 18 + } from '#/lib/routes/types' 16 19 import { 17 20 convertBskyAppUrlIfNeeded, 18 21 isBskyPostUrl,
+1 -1
src/screens/Messages/components/MessageListError.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {ConvoItem, ConvoItemError} from '#/state/messages/convo/types' 6 + import {type ConvoItem, ConvoItemError} from '#/state/messages/convo/types' 7 7 import {atoms as a, useTheme} from '#/alf' 8 8 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 9 9 import {InlineLinkText} from '#/components/Link'
+1 -1
src/screens/ModerationInteractionSettings/index.tsx
··· 9 9 import {createPostgateRecord} from '#/state/queries/postgate/util' 10 10 import { 11 11 usePreferencesQuery, 12 - UsePreferencesQueryResponse, 12 + type UsePreferencesQueryResponse, 13 13 } from '#/state/queries/preferences' 14 14 import { 15 15 threadgateAllowUISettingToAllowRecordValue,
+1 -1
src/screens/Onboarding/StepInterests/InterestButton.tsx
··· 1 1 import React from 'react' 2 - import {TextStyle, View, ViewStyle} from 'react-native' 2 + import {type TextStyle, View, type ViewStyle} from 'react-native' 3 3 4 4 import {capitalize} from '#/lib/strings/capitalize' 5 5 import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
+1 -1
src/screens/Onboarding/StepProfile/AvatarCreatorCircle.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 4 - import {Avatar} from '#/screens/Onboarding/StepProfile/index' 4 + import {type Avatar} from '#/screens/Onboarding/StepProfile/index' 5 5 import {atoms as a, useTheme} from '#/alf' 6 6 7 7 export function AvatarCreatorCircle({
+3 -3
src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {Avatar} from '#/screens/Onboarding/StepProfile/index' 6 + import {type Avatar} from '#/screens/Onboarding/StepProfile/index' 7 7 import { 8 - AvatarColor, 8 + type AvatarColor, 9 9 avatarColors, 10 10 emojiItems, 11 - EmojiName, 11 + type EmojiName, 12 12 emojiNames, 13 13 } from '#/screens/Onboarding/StepProfile/types' 14 14 import {atoms as a, useTheme} from '#/alf'
+4 -1
src/screens/Post/PostLikedBy.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+4 -1
src/screens/Post/PostQuotes.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+4 -1
src/screens/Post/PostRepostedBy.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+1 -1
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 354 354 )} 355 355 </Text> 356 356 357 - <View style={[{paddingLeft: 3, top: -1}]}> 357 + <View style={[a.pl_xs]}> 358 358 <VerificationCheckButton profile={authorShadow} size="md" /> 359 359 </View> 360 360 </View>
+1 -1
src/screens/Profile/ErrorState.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 - import {NavigationProp} from '#/lib/routes/types' 7 + import {type NavigationProp} from '#/lib/routes/types' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import {Button, ButtonText} from '#/components/Button' 10 10 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+3 -3
src/screens/Profile/Header/GrowableAvatar.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, View, ViewStyle} from 'react-native' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 3 2 import Animated, { 4 3 Extrapolation, 5 4 interpolate, 6 - SharedValue, 5 + type SharedValue, 7 6 useAnimatedStyle, 8 7 } from 'react-native-reanimated' 8 + import type React from 'react' 9 9 10 10 import {isIOS} from '#/platform/detection' 11 11 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext'
+3 -2
src/screens/Profile/Header/GrowableBanner.tsx
··· 1 - import React, {useEffect, useState} from 'react' 1 + import {useEffect, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {ActivityIndicator} from 'react-native' 4 4 import Animated, { 5 5 Extrapolation, 6 6 interpolate, 7 7 runOnJS, 8 - SharedValue, 8 + type SharedValue, 9 9 useAnimatedProps, 10 10 useAnimatedReaction, 11 11 useAnimatedStyle, ··· 13 13 import {useSafeAreaInsets} from 'react-native-safe-area-context' 14 14 import {BlurView} from 'expo-blur' 15 15 import {useIsFetching} from '@tanstack/react-query' 16 + import type React from 'react' 16 17 17 18 import {isIOS} from '#/platform/detection' 18 19 import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs'
+4 -1
src/screens/Profile/Header/StatusBarShadow.tsx
··· 1 - import Animated, {SharedValue, useAnimatedStyle} from 'react-native-reanimated' 1 + import Animated, { 2 + type SharedValue, 3 + useAnimatedStyle, 4 + } from 'react-native-reanimated' 2 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 3 6 import {LinearGradient} from 'expo-linear-gradient' 4 7
+5 -5
src/screens/Profile/Header/index.tsx
··· 1 1 import React, {memo, useState} from 'react' 2 - import {LayoutChangeEvent, StyleSheet, View} from 'react-native' 2 + import {type LayoutChangeEvent, StyleSheet, View} from 'react-native' 3 3 import Animated, { 4 4 runOnJS, 5 5 useAnimatedReaction, ··· 8 8 } from 'react-native-reanimated' 9 9 import {useSafeAreaInsets} from 'react-native-safe-area-context' 10 10 import { 11 - AppBskyActorDefs, 12 - AppBskyLabelerDefs, 13 - ModerationOpts, 14 - RichText as RichTextAPI, 11 + type AppBskyActorDefs, 12 + type AppBskyLabelerDefs, 13 + type ModerationOpts, 14 + type RichText as RichTextAPI, 15 15 } from '@atproto/api' 16 16 import {useIsFocused} from '@react-navigation/native' 17 17
+5 -2
src/screens/Profile/KnownFollowers.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {cleanError} from '#/lib/strings/errors' 10 13 import {logger} from '#/logger' 11 14 import {useProfileKnownFollowersQuery} from '#/state/queries/known-followers'
+4 -1
src/screens/Profile/ProfileFollowers.tsx
··· 2 2 import {Plural} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {sanitizeDisplayName} from '#/lib/strings/display-names' 7 10 import {useProfileQuery} from '#/state/queries/profile' 8 11 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
+4 -1
src/screens/Profile/ProfileFollows.tsx
··· 2 2 import {Plural} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {sanitizeDisplayName} from '#/lib/strings/display-names' 7 10 import {useProfileQuery} from '#/state/queries/profile' 8 11 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
+4 -1
src/screens/Profile/ProfileLabelerLikedBy.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 6 + import { 7 + type CommonNavigatorParams, 8 + type NativeStackScreenProps, 9 + } from '#/lib/routes/types' 7 10 import {makeRecordUri} from '#/lib/strings/url-helpers' 8 11 import {useSetMinimalShellMode} from '#/state/shell' 9 12 import {ViewHeader} from '#/view/com/util/ViewHeader'
+136
src/screens/ProfileList/AboutSection.tsx
··· 1 + import {useCallback, useImperativeHandle, useState} from 'react' 2 + import {View} from 'react-native' 3 + import {type AppBskyGraphDefs} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import {isNative} from '#/platform/detection' 8 + import {useSession} from '#/state/session' 9 + import {ListMembers} from '#/view/com/lists/ListMembers' 10 + import {EmptyState} from '#/view/com/util/EmptyState' 11 + import {type ListRef} from '#/view/com/util/List' 12 + import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 13 + import {atoms as a, useBreakpoints} from '#/alf' 14 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 + import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 16 + 17 + interface SectionRef { 18 + scrollToTop: () => void 19 + } 20 + 21 + interface AboutSectionProps { 22 + ref?: React.Ref<SectionRef> 23 + list: AppBskyGraphDefs.ListView 24 + onPressAddUser: () => void 25 + headerHeight: number 26 + scrollElRef: ListRef 27 + } 28 + 29 + export function AboutSection({ 30 + ref, 31 + list, 32 + onPressAddUser, 33 + headerHeight, 34 + scrollElRef, 35 + }: AboutSectionProps) { 36 + const {_} = useLingui() 37 + const {currentAccount} = useSession() 38 + const {gtMobile} = useBreakpoints() 39 + const [isScrolledDown, setIsScrolledDown] = useState(false) 40 + const isOwner = list.creator.did === currentAccount?.did 41 + 42 + const onScrollToTop = useCallback(() => { 43 + scrollElRef.current?.scrollToOffset({ 44 + animated: isNative, 45 + offset: -headerHeight, 46 + }) 47 + }, [scrollElRef, headerHeight]) 48 + 49 + useImperativeHandle(ref, () => ({ 50 + scrollToTop: onScrollToTop, 51 + })) 52 + 53 + const renderHeader = useCallback(() => { 54 + if (!isOwner) { 55 + return <View /> 56 + } 57 + if (!gtMobile) { 58 + return ( 59 + <View style={[a.px_sm, a.py_sm]}> 60 + <Button 61 + testID="addUserBtn" 62 + label={_(msg`Add a user to this list`)} 63 + onPress={onPressAddUser} 64 + color="primary" 65 + size="small" 66 + variant="outline" 67 + style={[a.py_md]}> 68 + <ButtonIcon icon={PersonPlusIcon} /> 69 + <ButtonText> 70 + <Trans>Add people</Trans> 71 + </ButtonText> 72 + </Button> 73 + </View> 74 + ) 75 + } 76 + return ( 77 + <View style={[a.px_lg, a.py_md, a.flex_row_reverse]}> 78 + <Button 79 + testID="addUserBtn" 80 + label={_(msg`Add a user to this list`)} 81 + onPress={onPressAddUser} 82 + color="primary" 83 + size="small" 84 + variant="ghost" 85 + style={[a.py_sm]}> 86 + <ButtonIcon icon={PersonPlusIcon} /> 87 + <ButtonText> 88 + <Trans>Add people</Trans> 89 + </ButtonText> 90 + </Button> 91 + </View> 92 + ) 93 + }, [isOwner, _, onPressAddUser, gtMobile]) 94 + 95 + const renderEmptyState = useCallback(() => { 96 + return ( 97 + <View style={[a.gap_xl, a.align_center]}> 98 + <EmptyState icon="users-slash" message={_(msg`This list is empty.`)} /> 99 + {isOwner && ( 100 + <Button 101 + testID="emptyStateAddUserBtn" 102 + label={_(msg`Start adding people`)} 103 + onPress={onPressAddUser} 104 + color="primary" 105 + size="small"> 106 + <ButtonIcon icon={PersonPlusIcon} /> 107 + <ButtonText> 108 + <Trans>Start adding people!</Trans> 109 + </ButtonText> 110 + </Button> 111 + )} 112 + </View> 113 + ) 114 + }, [_, onPressAddUser, isOwner]) 115 + 116 + return ( 117 + <View> 118 + <ListMembers 119 + testID="listItems" 120 + list={list.uri} 121 + scrollElRef={scrollElRef} 122 + renderHeader={renderHeader} 123 + renderEmptyState={renderEmptyState} 124 + headerOffset={headerHeight} 125 + onScrolledDownChange={setIsScrolledDown} 126 + /> 127 + {isScrolledDown && ( 128 + <LoadLatestBtn 129 + onPress={onScrollToTop} 130 + label={_(msg`Scroll to top`)} 131 + showIndicator={false} 132 + /> 133 + )} 134 + </View> 135 + ) 136 + }
+111
src/screens/ProfileList/FeedSection.tsx
··· 1 + import {useCallback, useEffect, useImperativeHandle, useState} from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import {useIsFocused} from '@react-navigation/native' 6 + import {useQueryClient} from '@tanstack/react-query' 7 + 8 + import {isNative} from '#/platform/detection' 9 + import {listenSoftReset} from '#/state/events' 10 + import {type FeedDescriptor} from '#/state/queries/post-feed' 11 + import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 12 + import {PostFeed} from '#/view/com/posts/PostFeed' 13 + import {EmptyState} from '#/view/com/util/EmptyState' 14 + import {type ListRef} from '#/view/com/util/List' 15 + import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 16 + import {atoms as a} from '#/alf' 17 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 + import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 19 + 20 + interface SectionRef { 21 + scrollToTop: () => void 22 + } 23 + 24 + interface FeedSectionProps { 25 + ref?: React.Ref<SectionRef> 26 + feed: FeedDescriptor 27 + headerHeight: number 28 + scrollElRef: ListRef 29 + isFocused: boolean 30 + isOwner: boolean 31 + onPressAddUser: () => void 32 + } 33 + 34 + export function FeedSection({ 35 + ref, 36 + feed, 37 + scrollElRef, 38 + headerHeight, 39 + isFocused, 40 + isOwner, 41 + onPressAddUser, 42 + }: FeedSectionProps) { 43 + const queryClient = useQueryClient() 44 + const [hasNew, setHasNew] = useState(false) 45 + const [isScrolledDown, setIsScrolledDown] = useState(false) 46 + const isScreenFocused = useIsFocused() 47 + const {_} = useLingui() 48 + 49 + const onScrollToTop = useCallback(() => { 50 + scrollElRef.current?.scrollToOffset({ 51 + animated: isNative, 52 + offset: -headerHeight, 53 + }) 54 + queryClient.resetQueries({queryKey: FEED_RQKEY(feed)}) 55 + setHasNew(false) 56 + }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) 57 + useImperativeHandle(ref, () => ({ 58 + scrollToTop: onScrollToTop, 59 + })) 60 + 61 + useEffect(() => { 62 + if (!isScreenFocused) { 63 + return 64 + } 65 + return listenSoftReset(onScrollToTop) 66 + }, [onScrollToTop, isScreenFocused]) 67 + 68 + const renderPostsEmpty = useCallback(() => { 69 + return ( 70 + <View style={[a.gap_xl, a.align_center]}> 71 + <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 72 + {isOwner && ( 73 + <Button 74 + label={_(msg`Start adding people`)} 75 + onPress={onPressAddUser} 76 + color="primary" 77 + size="small"> 78 + <ButtonIcon icon={PersonPlusIcon} /> 79 + <ButtonText> 80 + <Trans>Start adding people!</Trans> 81 + </ButtonText> 82 + </Button> 83 + )} 84 + </View> 85 + ) 86 + }, [_, onPressAddUser, isOwner]) 87 + 88 + return ( 89 + <View> 90 + <PostFeed 91 + testID="listFeed" 92 + enabled={isFocused} 93 + feed={feed} 94 + pollInterval={60e3} 95 + disablePoll={hasNew} 96 + scrollElRef={scrollElRef} 97 + onHasNew={setHasNew} 98 + onScrolledDownChange={setIsScrolledDown} 99 + renderEmptyState={renderPostsEmpty} 100 + headerOffset={headerHeight} 101 + /> 102 + {(isScrolledDown || hasNew) && ( 103 + <LoadLatestBtn 104 + onPress={onScrollToTop} 105 + label={_(msg`Load new posts`)} 106 + showIndicator={hasNew} 107 + /> 108 + )} 109 + </View> 110 + ) 111 + }
+46
src/screens/ProfileList/components/ErrorScreen.tsx
··· 1 + import {View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {useNavigation} from '@react-navigation/native' 5 + 6 + import {type NavigationProp} from '#/lib/routes/types' 7 + import {atoms as a, useTheme} from '#/alf' 8 + import {Button, ButtonText} from '#/components/Button' 9 + import {Text} from '#/components/Typography' 10 + 11 + export function ErrorScreen({error}: {error: React.ReactNode}) { 12 + const t = useTheme() 13 + const navigation = useNavigation<NavigationProp>() 14 + const {_} = useLingui() 15 + const onPressBack = () => { 16 + if (navigation.canGoBack()) { 17 + navigation.goBack() 18 + } else { 19 + navigation.navigate('Home') 20 + } 21 + } 22 + 23 + return ( 24 + <View style={[a.px_xl, a.py_md, a.gap_md]}> 25 + <Text style={[a.text_4xl, a.font_heavy]}> 26 + <Trans>Could not load list</Trans> 27 + </Text> 28 + <Text style={[a.text_md, t.atoms.text_contrast_high, a.leading_snug]}> 29 + {error} 30 + </Text> 31 + 32 + <View style={[a.flex_row, a.mt_lg]}> 33 + <Button 34 + label={_(msg`Go back`)} 35 + accessibilityHint={_(msg`Returns to previous page`)} 36 + onPress={onPressBack} 37 + size="small" 38 + color="secondary"> 39 + <ButtonText> 40 + <Trans>Go back</Trans> 41 + </ButtonText> 42 + </Button> 43 + </View> 44 + </View> 45 + ) 46 + }
+208
src/screens/ProfileList/components/Header.tsx
··· 1 + import {useMemo} from 'react' 2 + import {View} from 'react-native' 3 + import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import {useHaptics} from '#/lib/haptics' 8 + import {makeListLink} from '#/lib/routes/links' 9 + import {logger} from '#/logger' 10 + import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 11 + import { 12 + useAddSavedFeedsMutation, 13 + type UsePreferencesQueryResponse, 14 + useUpdateSavedFeedsMutation, 15 + } from '#/state/queries/preferences' 16 + import {useSession} from '#/state/session' 17 + import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader' 18 + import {atoms as a} from '#/alf' 19 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 21 + import {Loader} from '#/components/Loader' 22 + import {RichText} from '#/components/RichText' 23 + import * as Toast from '#/components/Toast' 24 + import {MoreOptionsMenu} from './MoreOptionsMenu' 25 + import {SubscribeMenu} from './SubscribeMenu' 26 + 27 + export function Header({ 28 + rkey, 29 + list, 30 + preferences, 31 + }: { 32 + rkey: string 33 + list: AppBskyGraphDefs.ListView 34 + preferences: UsePreferencesQueryResponse 35 + }) { 36 + const {_} = useLingui() 37 + const {currentAccount} = useSession() 38 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 39 + const isModList = list.purpose === AppBskyGraphDefs.MODLIST 40 + const isBlocking = !!list.viewer?.blocked 41 + const isMuting = !!list.viewer?.muted 42 + const playHaptic = useHaptics() 43 + 44 + const {mutateAsync: muteList, isPending: isMutePending} = 45 + useListMuteMutation() 46 + const {mutateAsync: blockList, isPending: isBlockPending} = 47 + useListBlockMutation() 48 + const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} = 49 + useAddSavedFeedsMutation() 50 + const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} = 51 + useUpdateSavedFeedsMutation() 52 + 53 + const isPending = isAddSavedFeedPending || isUpdatingSavedFeeds 54 + 55 + const savedFeedConfig = preferences?.savedFeeds?.find( 56 + f => f.value === list.uri, 57 + ) 58 + const isPinned = Boolean(savedFeedConfig?.pinned) 59 + 60 + const onTogglePinned = async () => { 61 + playHaptic() 62 + 63 + try { 64 + if (savedFeedConfig) { 65 + const pinned = !savedFeedConfig.pinned 66 + await updateSavedFeeds([ 67 + { 68 + ...savedFeedConfig, 69 + pinned, 70 + }, 71 + ]) 72 + Toast.show( 73 + pinned 74 + ? _(msg`Pinned to your feeds`) 75 + : _(msg`Unpinned from your feeds`), 76 + ) 77 + } else { 78 + await addSavedFeeds([ 79 + { 80 + type: 'list', 81 + value: list.uri, 82 + pinned: true, 83 + }, 84 + ]) 85 + Toast.show(_(msg`Saved to your feeds`)) 86 + } 87 + } catch (e) { 88 + Toast.show(_(msg`There was an issue contacting the server`), { 89 + type: 'error', 90 + }) 91 + logger.error('Failed to toggle pinned feed', {message: e}) 92 + } 93 + } 94 + 95 + const onUnsubscribeMute = async () => { 96 + try { 97 + await muteList({uri: list.uri, mute: false}) 98 + Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 99 + logger.metric( 100 + 'moderation:unsubscribedFromList', 101 + {listType: 'mute'}, 102 + {statsig: true}, 103 + ) 104 + } catch { 105 + Toast.show( 106 + _( 107 + msg`There was an issue. Please check your internet connection and try again.`, 108 + ), 109 + ) 110 + } 111 + } 112 + 113 + const onUnsubscribeBlock = async () => { 114 + try { 115 + await blockList({uri: list.uri, block: false}) 116 + Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 117 + logger.metric( 118 + 'moderation:unsubscribedFromList', 119 + {listType: 'block'}, 120 + {statsig: true}, 121 + ) 122 + } catch { 123 + Toast.show( 124 + _( 125 + msg`There was an issue. Please check your internet connection and try again.`, 126 + ), 127 + ) 128 + } 129 + } 130 + 131 + const descriptionRT = useMemo( 132 + () => 133 + list.description 134 + ? new RichTextAPI({ 135 + text: list.description, 136 + facets: list.descriptionFacets, 137 + }) 138 + : undefined, 139 + [list], 140 + ) 141 + 142 + return ( 143 + <> 144 + <ProfileSubpageHeader 145 + href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} 146 + title={list.name} 147 + avatar={list.avatar} 148 + isOwner={list.creator.did === currentAccount?.did} 149 + creator={list.creator} 150 + purpose={list.purpose} 151 + avatarType="list"> 152 + {isCurateList ? ( 153 + <Button 154 + testID={isPinned ? 'unpinBtn' : 'pinBtn'} 155 + color={isPinned ? 'secondary' : 'primary_subtle'} 156 + label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} 157 + onPress={onTogglePinned} 158 + disabled={isPending} 159 + size="small" 160 + style={[a.rounded_full]}> 161 + {!isPinned && <ButtonIcon icon={isPending ? Loader : PinIcon} />} 162 + <ButtonText> 163 + {isPinned ? <Trans>Unpin</Trans> : <Trans>Pin to home</Trans>} 164 + </ButtonText> 165 + </Button> 166 + ) : isModList ? ( 167 + isBlocking ? ( 168 + <Button 169 + testID="unblockBtn" 170 + color="secondary" 171 + label={_(msg`Unblock`)} 172 + onPress={onUnsubscribeBlock} 173 + size="small" 174 + style={[a.rounded_full]} 175 + disabled={isBlockPending}> 176 + {isBlockPending && <ButtonIcon icon={Loader} />} 177 + <ButtonText> 178 + <Trans>Unblock</Trans> 179 + </ButtonText> 180 + </Button> 181 + ) : isMuting ? ( 182 + <Button 183 + testID="unmuteBtn" 184 + color="secondary" 185 + label={_(msg`Unmute`)} 186 + onPress={onUnsubscribeMute} 187 + size="small" 188 + style={[a.rounded_full]} 189 + disabled={isMutePending}> 190 + {isMutePending && <ButtonIcon icon={Loader} />} 191 + <ButtonText> 192 + <Trans>Unmute</Trans> 193 + </ButtonText> 194 + </Button> 195 + ) : ( 196 + <SubscribeMenu list={list} /> 197 + ) 198 + ) : null} 199 + <MoreOptionsMenu list={list} /> 200 + </ProfileSubpageHeader> 201 + {descriptionRT ? ( 202 + <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> 203 + <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> 204 + </View> 205 + ) : null} 206 + </> 207 + ) 208 + }
+298
src/screens/ProfileList/components/MoreOptionsMenu.tsx
··· 1 + import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {useNavigation} from '@react-navigation/native' 5 + 6 + import {type NavigationProp} from '#/lib/routes/types' 7 + import {shareUrl} from '#/lib/sharing' 8 + import {toShareUrl} from '#/lib/strings/url-helpers' 9 + import {logger} from '#/logger' 10 + import {isWeb} from '#/platform/detection' 11 + import {useModalControls} from '#/state/modals' 12 + import { 13 + useListBlockMutation, 14 + useListDeleteMutation, 15 + useListMuteMutation, 16 + } from '#/state/queries/list' 17 + import {useRemoveFeedMutation} from '#/state/queries/preferences' 18 + import {useSession} from '#/state/session' 19 + import {Button, ButtonIcon} from '#/components/Button' 20 + import {useDialogControl} from '#/components/Dialog' 21 + import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 22 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLink} from '#/components/icons/ChainLink' 23 + import {DotGrid_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid' 24 + import {PencilLine_Stroke2_Corner0_Rounded as PencilLineIcon} from '#/components/icons/Pencil' 25 + import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheckIcon} from '#/components/icons/Person' 26 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 27 + import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 28 + import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 29 + import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 30 + import * as Menu from '#/components/Menu' 31 + import { 32 + ReportDialog, 33 + useReportDialogControl, 34 + } from '#/components/moderation/ReportDialog' 35 + import * as Prompt from '#/components/Prompt' 36 + import * as Toast from '#/components/Toast' 37 + 38 + export function MoreOptionsMenu({ 39 + list, 40 + savedFeedConfig, 41 + }: { 42 + list: AppBskyGraphDefs.ListView 43 + savedFeedConfig?: AppBskyActorDefs.SavedFeed 44 + }) { 45 + const {_} = useLingui() 46 + const {currentAccount} = useSession() 47 + const {openModal} = useModalControls() 48 + const deleteListPromptControl = useDialogControl() 49 + const reportDialogControl = useReportDialogControl() 50 + const navigation = useNavigation<NavigationProp>() 51 + 52 + const {mutateAsync: removeSavedFeed} = useRemoveFeedMutation() 53 + const {mutateAsync: deleteList} = useListDeleteMutation() 54 + const {mutateAsync: muteList} = useListMuteMutation() 55 + const {mutateAsync: blockList} = useListBlockMutation() 56 + 57 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 58 + const isModList = list.purpose === AppBskyGraphDefs.MODLIST 59 + const isBlocking = !!list.viewer?.blocked 60 + const isMuting = !!list.viewer?.muted 61 + const isPinned = Boolean(savedFeedConfig?.pinned) 62 + const isOwner = currentAccount?.did === list.creator.did 63 + 64 + const onPressShare = () => { 65 + const {rkey} = new AtUri(list.uri) 66 + const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) 67 + shareUrl(url) 68 + } 69 + 70 + const onRemoveFromSavedFeeds = async () => { 71 + if (!savedFeedConfig) return 72 + try { 73 + await removeSavedFeed(savedFeedConfig) 74 + Toast.show(_(msg`Removed from your feeds`)) 75 + } catch (e) { 76 + Toast.show(_(msg`There was an issue contacting the server`), { 77 + type: 'error', 78 + }) 79 + logger.error('Failed to remove pinned list', {message: e}) 80 + } 81 + } 82 + 83 + const onPressEdit = () => { 84 + openModal({ 85 + name: 'create-or-edit-list', 86 + list, 87 + }) 88 + } 89 + 90 + const onPressDelete = async () => { 91 + await deleteList({uri: list.uri}) 92 + 93 + if (savedFeedConfig) { 94 + await removeSavedFeed(savedFeedConfig) 95 + } 96 + 97 + Toast.show(_(msg({message: 'List deleted', context: 'toast'}))) 98 + if (navigation.canGoBack()) { 99 + navigation.goBack() 100 + } else { 101 + navigation.navigate('Home') 102 + } 103 + } 104 + 105 + const onUnpinModList = async () => { 106 + try { 107 + if (!savedFeedConfig) return 108 + await removeSavedFeed(savedFeedConfig) 109 + Toast.show(_(msg`Unpinned list`)) 110 + } catch { 111 + Toast.show(_(msg`Failed to unpin list`), { 112 + type: 'error', 113 + }) 114 + } 115 + } 116 + 117 + const onUnsubscribeMute = async () => { 118 + try { 119 + await muteList({uri: list.uri, mute: false}) 120 + Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 121 + logger.metric( 122 + 'moderation:unsubscribedFromList', 123 + {listType: 'mute'}, 124 + {statsig: true}, 125 + ) 126 + } catch { 127 + Toast.show( 128 + _( 129 + msg`There was an issue. Please check your internet connection and try again.`, 130 + ), 131 + ) 132 + } 133 + } 134 + 135 + const onUnsubscribeBlock = async () => { 136 + try { 137 + await blockList({uri: list.uri, block: false}) 138 + Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 139 + logger.metric( 140 + 'moderation:unsubscribedFromList', 141 + {listType: 'block'}, 142 + {statsig: true}, 143 + ) 144 + } catch { 145 + Toast.show( 146 + _( 147 + msg`There was an issue. Please check your internet connection and try again.`, 148 + ), 149 + ) 150 + } 151 + } 152 + 153 + return ( 154 + <> 155 + <Menu.Root> 156 + <Menu.Trigger label={_(msg`More options`)}> 157 + {({props}) => ( 158 + <Button 159 + label={props.accessibilityLabel} 160 + testID="moreOptionsBtn" 161 + size="small" 162 + color="secondary" 163 + shape="round" 164 + {...props}> 165 + <ButtonIcon icon={DotGridIcon} /> 166 + </Button> 167 + )} 168 + </Menu.Trigger> 169 + <Menu.Outer> 170 + <Menu.Group> 171 + <Menu.Item 172 + label={isWeb ? _(msg`Copy link to list`) : _(msg`Share via...`)} 173 + onPress={onPressShare}> 174 + <Menu.ItemText> 175 + {isWeb ? ( 176 + <Trans>Copy link to list</Trans> 177 + ) : ( 178 + <Trans>Share via...</Trans> 179 + )} 180 + </Menu.ItemText> 181 + <Menu.ItemIcon 182 + position="right" 183 + icon={isWeb ? ChainLink : ShareIcon} 184 + /> 185 + </Menu.Item> 186 + {savedFeedConfig && ( 187 + <Menu.Item 188 + label={_(msg`Remove from my feeds`)} 189 + onPress={onRemoveFromSavedFeeds}> 190 + <Menu.ItemText> 191 + <Trans>Remove from my feeds</Trans> 192 + </Menu.ItemText> 193 + <Menu.ItemIcon position="right" icon={TrashIcon} /> 194 + </Menu.Item> 195 + )} 196 + </Menu.Group> 197 + 198 + <Menu.Divider /> 199 + 200 + {isOwner ? ( 201 + <Menu.Group> 202 + <Menu.Item 203 + label={_(msg`Edit list details`)} 204 + onPress={onPressEdit}> 205 + <Menu.ItemText> 206 + <Trans>Edit list details</Trans> 207 + </Menu.ItemText> 208 + <Menu.ItemIcon position="right" icon={PencilLineIcon} /> 209 + </Menu.Item> 210 + <Menu.Item 211 + label={_(msg`Delete list`)} 212 + onPress={deleteListPromptControl.open}> 213 + <Menu.ItemText> 214 + <Trans>Delete list</Trans> 215 + </Menu.ItemText> 216 + <Menu.ItemIcon position="right" icon={TrashIcon} /> 217 + </Menu.Item> 218 + </Menu.Group> 219 + ) : ( 220 + <Menu.Group> 221 + <Menu.Item 222 + label={_(msg`Report list`)} 223 + onPress={reportDialogControl.open}> 224 + <Menu.ItemText> 225 + <Trans>Report list</Trans> 226 + </Menu.ItemText> 227 + <Menu.ItemIcon position="right" icon={WarningIcon} /> 228 + </Menu.Item> 229 + </Menu.Group> 230 + )} 231 + 232 + {isModList && isPinned && ( 233 + <> 234 + <Menu.Divider /> 235 + <Menu.Group> 236 + <Menu.Item 237 + label={_(msg`Unpin moderation list`)} 238 + onPress={onUnpinModList}> 239 + <Menu.ItemText> 240 + <Trans>Unpin moderation list</Trans> 241 + </Menu.ItemText> 242 + <Menu.ItemIcon icon={PinIcon} /> 243 + </Menu.Item> 244 + </Menu.Group> 245 + </> 246 + )} 247 + 248 + {isCurateList && (isBlocking || isMuting) && ( 249 + <> 250 + <Menu.Divider /> 251 + <Menu.Group> 252 + {isBlocking && ( 253 + <Menu.Item 254 + label={_(msg`Unblock list`)} 255 + onPress={onUnsubscribeBlock}> 256 + <Menu.ItemText> 257 + <Trans>Unblock list</Trans> 258 + </Menu.ItemText> 259 + <Menu.ItemIcon icon={PersonCheckIcon} /> 260 + </Menu.Item> 261 + )} 262 + {isMuting && ( 263 + <Menu.Item 264 + label={_(msg`Unmute list`)} 265 + onPress={onUnsubscribeMute}> 266 + <Menu.ItemText> 267 + <Trans>Unmute list</Trans> 268 + </Menu.ItemText> 269 + <Menu.ItemIcon icon={UnmuteIcon} /> 270 + </Menu.Item> 271 + )} 272 + </Menu.Group> 273 + </> 274 + )} 275 + </Menu.Outer> 276 + </Menu.Root> 277 + 278 + <Prompt.Basic 279 + control={deleteListPromptControl} 280 + title={_(msg`Delete this list?`)} 281 + description={_( 282 + msg`If you delete this list, you won't be able to recover it.`, 283 + )} 284 + onConfirm={onPressDelete} 285 + confirmButtonCta={_(msg`Delete`)} 286 + confirmButtonColor="negative" 287 + /> 288 + 289 + <ReportDialog 290 + control={reportDialogControl} 291 + subject={{ 292 + ...list, 293 + $type: 'app.bsky.graph.defs#listView', 294 + }} 295 + /> 296 + </> 297 + ) 298 + }
+130
src/screens/ProfileList/components/SubscribeMenu.tsx
··· 1 + import {type AppBskyGraphDefs} from '@atproto/api' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {logger} from '#/logger' 6 + import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 7 + import {atoms as a} from '#/alf' 8 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 9 + import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 10 + import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person' 11 + import {Loader} from '#/components/Loader' 12 + import * as Menu from '#/components/Menu' 13 + import * as Prompt from '#/components/Prompt' 14 + import * as Toast from '#/components/Toast' 15 + 16 + export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) { 17 + const {_} = useLingui() 18 + const subscribeMutePromptControl = Prompt.usePromptControl() 19 + const subscribeBlockPromptControl = Prompt.usePromptControl() 20 + 21 + const {mutateAsync: muteList, isPending: isMutePending} = 22 + useListMuteMutation() 23 + const {mutateAsync: blockList, isPending: isBlockPending} = 24 + useListBlockMutation() 25 + 26 + const isPending = isMutePending || isBlockPending 27 + 28 + const onSubscribeMute = async () => { 29 + try { 30 + await muteList({uri: list.uri, mute: true}) 31 + Toast.show(_(msg({message: 'List muted', context: 'toast'}))) 32 + logger.metric( 33 + 'moderation:subscribedToList', 34 + {listType: 'mute'}, 35 + {statsig: true}, 36 + ) 37 + } catch { 38 + Toast.show( 39 + _( 40 + msg`There was an issue. Please check your internet connection and try again.`, 41 + ), 42 + {type: 'error'}, 43 + ) 44 + } 45 + } 46 + 47 + const onSubscribeBlock = async () => { 48 + try { 49 + await blockList({uri: list.uri, block: true}) 50 + Toast.show(_(msg({message: 'List blocked', context: 'toast'}))) 51 + logger.metric( 52 + 'moderation:subscribedToList', 53 + {listType: 'block'}, 54 + {statsig: true}, 55 + ) 56 + } catch { 57 + Toast.show( 58 + _( 59 + msg`There was an issue. Please check your internet connection and try again.`, 60 + ), 61 + {type: 'error'}, 62 + ) 63 + } 64 + } 65 + 66 + return ( 67 + <> 68 + <Menu.Root> 69 + <Menu.Trigger label={_(msg`Subscribe to this list`)}> 70 + {({props}) => ( 71 + <Button 72 + label={props.accessibilityLabel} 73 + testID="subscribeBtn" 74 + size="small" 75 + color="primary_subtle" 76 + style={[a.rounded_full]} 77 + disabled={isPending} 78 + {...props}> 79 + {isPending && <ButtonIcon icon={Loader} />} 80 + <ButtonText> 81 + <Trans>Subscribe</Trans> 82 + </ButtonText> 83 + </Button> 84 + )} 85 + </Menu.Trigger> 86 + <Menu.Outer showCancel> 87 + <Menu.Group> 88 + <Menu.Item 89 + label={_(msg`Mute accounts`)} 90 + onPress={subscribeMutePromptControl.open}> 91 + <Menu.ItemText> 92 + <Trans>Mute accounts</Trans> 93 + </Menu.ItemText> 94 + <Menu.ItemIcon position="right" icon={MuteIcon} /> 95 + </Menu.Item> 96 + <Menu.Item 97 + label={_(msg`Block accounts`)} 98 + onPress={subscribeBlockPromptControl.open}> 99 + <Menu.ItemText> 100 + <Trans>Block accounts</Trans> 101 + </Menu.ItemText> 102 + <Menu.ItemIcon position="right" icon={PersonXIcon} /> 103 + </Menu.Item> 104 + </Menu.Group> 105 + </Menu.Outer> 106 + </Menu.Root> 107 + 108 + <Prompt.Basic 109 + control={subscribeMutePromptControl} 110 + title={_(msg`Mute these accounts?`)} 111 + description={_( 112 + msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, 113 + )} 114 + onConfirm={onSubscribeMute} 115 + confirmButtonCta={_(msg`Mute list`)} 116 + /> 117 + 118 + <Prompt.Basic 119 + control={subscribeBlockPromptControl} 120 + title={_(msg`Block these accounts?`)} 121 + description={_( 122 + msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 123 + )} 124 + onConfirm={onSubscribeBlock} 125 + confirmButtonCta={_(msg`Block list`)} 126 + confirmButtonColor="negative" 127 + /> 128 + </> 129 + ) 130 + }
+296
src/screens/ProfileList/index.tsx
··· 1 + import {useCallback, useMemo, useRef} from 'react' 2 + import {View} from 'react-native' 3 + import {useAnimatedRef} from 'react-native-reanimated' 4 + import { 5 + AppBskyGraphDefs, 6 + AtUri, 7 + moderateUserList, 8 + type ModerationOpts, 9 + } from '@atproto/api' 10 + import {msg, Trans} from '@lingui/macro' 11 + import {useLingui} from '@lingui/react' 12 + import {useFocusEffect, useIsFocused} from '@react-navigation/native' 13 + import {useQueryClient} from '@tanstack/react-query' 14 + 15 + import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 16 + import {useSetTitle} from '#/lib/hooks/useSetTitle' 17 + import {ComposeIcon2} from '#/lib/icons' 18 + import { 19 + type CommonNavigatorParams, 20 + type NativeStackScreenProps, 21 + } from '#/lib/routes/types' 22 + import {cleanError} from '#/lib/strings/errors' 23 + import {useModerationOpts} from '#/state/preferences/moderation-opts' 24 + import {useListQuery} from '#/state/queries/list' 25 + import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 26 + import { 27 + usePreferencesQuery, 28 + type UsePreferencesQueryResponse, 29 + } from '#/state/queries/preferences' 30 + import {useResolveUriQuery} from '#/state/queries/resolve-uri' 31 + import {truncateAndInvalidate} from '#/state/queries/util' 32 + import {useSession} from '#/state/session' 33 + import {useSetMinimalShellMode} from '#/state/shell' 34 + import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' 35 + import {FAB} from '#/view/com/util/fab/FAB' 36 + import {type ListRef} from '#/view/com/util/List' 37 + import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 38 + import {atoms as a, platform} from '#/alf' 39 + import {useDialogControl} from '#/components/Dialog' 40 + import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 41 + import * as Layout from '#/components/Layout' 42 + import {Loader} from '#/components/Loader' 43 + import * as Hider from '#/components/moderation/Hider' 44 + import {AboutSection} from './AboutSection' 45 + import {ErrorScreen} from './components/ErrorScreen' 46 + import {Header} from './components/Header' 47 + import {FeedSection} from './FeedSection' 48 + 49 + interface SectionRef { 50 + scrollToTop: () => void 51 + } 52 + 53 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'> 54 + export function ProfileListScreen(props: Props) { 55 + return ( 56 + <Layout.Screen testID="profileListScreen"> 57 + <ProfileListScreenInner {...props} /> 58 + </Layout.Screen> 59 + ) 60 + } 61 + 62 + function ProfileListScreenInner(props: Props) { 63 + const {_} = useLingui() 64 + const {name: handleOrDid, rkey} = props.route.params 65 + const {data: resolvedUri, error: resolveError} = useResolveUriQuery( 66 + AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), 67 + ) 68 + const {data: preferences} = usePreferencesQuery() 69 + const {data: list, error: listError} = useListQuery(resolvedUri?.uri) 70 + const moderationOpts = useModerationOpts() 71 + 72 + if (resolveError) { 73 + return ( 74 + <> 75 + <Layout.Header.Outer> 76 + <Layout.Header.BackButton /> 77 + <Layout.Header.Content> 78 + <Layout.Header.TitleText> 79 + <Trans>Could not load list</Trans> 80 + </Layout.Header.TitleText> 81 + </Layout.Header.Content> 82 + <Layout.Header.Slot /> 83 + </Layout.Header.Outer> 84 + <Layout.Content centerContent> 85 + <ErrorScreen 86 + error={_( 87 + msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`, 88 + )} 89 + /> 90 + </Layout.Content> 91 + </> 92 + ) 93 + } 94 + if (listError) { 95 + return ( 96 + <> 97 + <Layout.Header.Outer> 98 + <Layout.Header.BackButton /> 99 + <Layout.Header.Content> 100 + <Layout.Header.TitleText> 101 + <Trans>Could not load list</Trans> 102 + </Layout.Header.TitleText> 103 + </Layout.Header.Content> 104 + <Layout.Header.Slot /> 105 + </Layout.Header.Outer> 106 + <Layout.Content centerContent> 107 + <ErrorScreen error={cleanError(listError)} /> 108 + </Layout.Content> 109 + </> 110 + ) 111 + } 112 + 113 + return resolvedUri && list && moderationOpts && preferences ? ( 114 + <ProfileListScreenLoaded 115 + {...props} 116 + uri={resolvedUri.uri} 117 + list={list} 118 + moderationOpts={moderationOpts} 119 + preferences={preferences} 120 + /> 121 + ) : ( 122 + <> 123 + <Layout.Header.Outer> 124 + <Layout.Header.BackButton /> 125 + <Layout.Header.Content /> 126 + <Layout.Header.Slot /> 127 + </Layout.Header.Outer> 128 + <Layout.Content 129 + centerContent 130 + contentContainerStyle={platform({ 131 + web: [a.mx_auto], 132 + native: [a.align_center], 133 + })}> 134 + <Loader size="2xl" /> 135 + </Layout.Content> 136 + </> 137 + ) 138 + } 139 + 140 + function ProfileListScreenLoaded({ 141 + route, 142 + uri, 143 + list, 144 + moderationOpts, 145 + preferences, 146 + }: Props & { 147 + uri: string 148 + list: AppBskyGraphDefs.ListView 149 + moderationOpts: ModerationOpts 150 + preferences: UsePreferencesQueryResponse 151 + }) { 152 + const {_} = useLingui() 153 + const queryClient = useQueryClient() 154 + const {openComposer} = useOpenComposer() 155 + const setMinimalShellMode = useSetMinimalShellMode() 156 + const {currentAccount} = useSession() 157 + const {rkey} = route.params 158 + const feedSectionRef = useRef<SectionRef>(null) 159 + const aboutSectionRef = useRef<SectionRef>(null) 160 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 161 + const isScreenFocused = useIsFocused() 162 + const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 163 + const isOwner = currentAccount?.did === list.creator.did 164 + const scrollElRef = useAnimatedRef() 165 + const addUserDialogControl = useDialogControl() 166 + const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 167 + 168 + const moderation = useMemo(() => { 169 + return moderateUserList(list, moderationOpts) 170 + }, [list, moderationOpts]) 171 + 172 + useSetTitle(isHidden ? _(msg`List Hidden`) : list.name) 173 + 174 + useFocusEffect( 175 + useCallback(() => { 176 + setMinimalShellMode(false) 177 + }, [setMinimalShellMode]), 178 + ) 179 + 180 + const onChangeMembers = () => { 181 + if (isCurateList) { 182 + truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`)) 183 + } 184 + } 185 + 186 + const onCurrentPageSelected = useCallback( 187 + (index: number) => { 188 + if (index === 0) { 189 + feedSectionRef.current?.scrollToTop() 190 + } else if (index === 1) { 191 + aboutSectionRef.current?.scrollToTop() 192 + } 193 + }, 194 + [feedSectionRef], 195 + ) 196 + 197 + const renderHeader = useCallback(() => { 198 + return <Header rkey={rkey} list={list} preferences={preferences} /> 199 + }, [rkey, list, preferences]) 200 + 201 + if (isCurateList) { 202 + return ( 203 + <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 204 + <Hider.Mask> 205 + <ListHiddenScreen list={list} preferences={preferences} /> 206 + </Hider.Mask> 207 + <Hider.Content> 208 + <View style={[a.util_screen_outer]}> 209 + <PagerWithHeader 210 + items={sectionTitlesCurate} 211 + isHeaderReady={true} 212 + renderHeader={renderHeader} 213 + onCurrentPageSelected={onCurrentPageSelected}> 214 + {({headerHeight, scrollElRef, isFocused}) => ( 215 + <FeedSection 216 + ref={feedSectionRef} 217 + feed={`list|${uri}`} 218 + scrollElRef={scrollElRef as ListRef} 219 + headerHeight={headerHeight} 220 + isFocused={isScreenFocused && isFocused} 221 + isOwner={isOwner} 222 + onPressAddUser={addUserDialogControl.open} 223 + /> 224 + )} 225 + {({headerHeight, scrollElRef}) => ( 226 + <AboutSection 227 + ref={aboutSectionRef} 228 + scrollElRef={scrollElRef as ListRef} 229 + list={list} 230 + onPressAddUser={addUserDialogControl.open} 231 + headerHeight={headerHeight} 232 + /> 233 + )} 234 + </PagerWithHeader> 235 + <FAB 236 + testID="composeFAB" 237 + onPress={() => openComposer({})} 238 + icon={ 239 + <ComposeIcon2 240 + strokeWidth={1.5} 241 + size={29} 242 + style={{color: 'white'}} 243 + /> 244 + } 245 + accessibilityRole="button" 246 + accessibilityLabel={_(msg`New post`)} 247 + accessibilityHint="" 248 + /> 249 + </View> 250 + <ListAddRemoveUsersDialog 251 + control={addUserDialogControl} 252 + list={list} 253 + onChange={onChangeMembers} 254 + /> 255 + </Hider.Content> 256 + </Hider.Outer> 257 + ) 258 + } 259 + return ( 260 + <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 261 + <Hider.Mask> 262 + <ListHiddenScreen list={list} preferences={preferences} /> 263 + </Hider.Mask> 264 + <Hider.Content> 265 + <View style={[a.util_screen_outer]}> 266 + <Layout.Center>{renderHeader()}</Layout.Center> 267 + <AboutSection 268 + list={list} 269 + scrollElRef={scrollElRef as ListRef} 270 + onPressAddUser={addUserDialogControl.open} 271 + headerHeight={0} 272 + /> 273 + <FAB 274 + testID="composeFAB" 275 + onPress={() => openComposer({})} 276 + icon={ 277 + <ComposeIcon2 278 + strokeWidth={1.5} 279 + size={29} 280 + style={{color: 'white'}} 281 + /> 282 + } 283 + accessibilityRole="button" 284 + accessibilityLabel={_(msg`New post`)} 285 + accessibilityHint="" 286 + /> 287 + </View> 288 + <ListAddRemoveUsersDialog 289 + control={addUserDialogControl} 290 + list={list} 291 + onChange={onChangeMembers} 292 + /> 293 + </Hider.Content> 294 + </Hider.Outer> 295 + ) 296 + }
+415
src/screens/SavedFeeds.tsx
··· 1 + import {useCallback, useState} from 'react' 2 + import {View} from 'react-native' 3 + import Animated, {LinearTransition} from 'react-native-reanimated' 4 + import {type AppBskyActorDefs} from '@atproto/api' 5 + import {TID} from '@atproto/common-web' 6 + import {msg, Trans} from '@lingui/macro' 7 + import {useLingui} from '@lingui/react' 8 + import {useFocusEffect} from '@react-navigation/native' 9 + import {useNavigation} from '@react-navigation/native' 10 + import {type NativeStackScreenProps} from '@react-navigation/native-stack' 11 + 12 + import {RECOMMENDED_SAVED_FEEDS, TIMELINE_SAVED_FEED} from '#/lib/constants' 13 + import {useHaptics} from '#/lib/haptics' 14 + import { 15 + type CommonNavigatorParams, 16 + type NavigationProp, 17 + } from '#/lib/routes/types' 18 + import {logger} from '#/logger' 19 + import { 20 + useOverwriteSavedFeedsMutation, 21 + usePreferencesQuery, 22 + } from '#/state/queries/preferences' 23 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 24 + import {useSetMinimalShellMode} from '#/state/shell' 25 + import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' 26 + import * as Toast from '#/view/com/util/Toast' 27 + import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' 28 + import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' 29 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 30 + import {Admonition} from '#/components/Admonition' 31 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 32 + import { 33 + ArrowBottom_Stroke2_Corner0_Rounded as ArrowDownIcon, 34 + ArrowTop_Stroke2_Corner0_Rounded as ArrowUpIcon, 35 + } from '#/components/icons/Arrow' 36 + import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 37 + import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 38 + import {Pin_Filled_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 39 + import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 40 + import * as Layout from '#/components/Layout' 41 + import {InlineLinkText} from '#/components/Link' 42 + import {Loader} from '#/components/Loader' 43 + import {Text} from '#/components/Typography' 44 + 45 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 46 + export function SavedFeeds({}: Props) { 47 + const {data: preferences} = usePreferencesQuery() 48 + if (!preferences) { 49 + return <View /> 50 + } 51 + return <SavedFeedsInner preferences={preferences} /> 52 + } 53 + 54 + function SavedFeedsInner({ 55 + preferences, 56 + }: { 57 + preferences: UsePreferencesQueryResponse 58 + }) { 59 + const t = useTheme() 60 + const {_} = useLingui() 61 + const {gtMobile} = useBreakpoints() 62 + const setMinimalShellMode = useSetMinimalShellMode() 63 + const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} = 64 + useOverwriteSavedFeedsMutation() 65 + const navigation = useNavigation<NavigationProp>() 66 + 67 + /* 68 + * Use optimistic data if exists and no error, otherwise fallback to remote 69 + * data 70 + */ 71 + const [currentFeeds, setCurrentFeeds] = useState( 72 + () => preferences.savedFeeds || [], 73 + ) 74 + const hasUnsavedChanges = currentFeeds !== preferences.savedFeeds 75 + const pinnedFeeds = currentFeeds.filter(f => f.pinned) 76 + const unpinnedFeeds = currentFeeds.filter(f => !f.pinned) 77 + const noSavedFeedsOfAnyType = pinnedFeeds.length + unpinnedFeeds.length === 0 78 + const noFollowingFeed = 79 + currentFeeds.every(f => f.type !== 'timeline') && !noSavedFeedsOfAnyType 80 + 81 + useFocusEffect( 82 + useCallback(() => { 83 + setMinimalShellMode(false) 84 + }, [setMinimalShellMode]), 85 + ) 86 + 87 + const onSaveChanges = async () => { 88 + try { 89 + await overwriteSavedFeeds(currentFeeds) 90 + Toast.show(_(msg({message: 'Feeds updated!', context: 'toast'}))) 91 + if (navigation.canGoBack()) { 92 + navigation.goBack() 93 + } else { 94 + navigation.navigate('Feeds') 95 + } 96 + } catch (e) { 97 + Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 98 + logger.error('Failed to toggle pinned feed', {message: e}) 99 + } 100 + } 101 + 102 + return ( 103 + <Layout.Screen> 104 + <Layout.Header.Outer> 105 + <Layout.Header.BackButton /> 106 + <Layout.Header.Content align="left"> 107 + <Layout.Header.TitleText> 108 + <Trans>Feeds</Trans> 109 + </Layout.Header.TitleText> 110 + </Layout.Header.Content> 111 + <Button 112 + testID="saveChangesBtn" 113 + size="small" 114 + color={hasUnsavedChanges ? 'primary' : 'secondary'} 115 + onPress={onSaveChanges} 116 + label={_(msg`Save changes`)} 117 + disabled={isOverwritePending || !hasUnsavedChanges}> 118 + <ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} /> 119 + <ButtonText> 120 + {gtMobile ? <Trans>Save changes</Trans> : <Trans>Save</Trans>} 121 + </ButtonText> 122 + </Button> 123 + </Layout.Header.Outer> 124 + 125 + <Layout.Content> 126 + {noSavedFeedsOfAnyType && ( 127 + <View style={[t.atoms.border_contrast_low, a.border_b]}> 128 + <NoSavedFeedsOfAnyType 129 + onAddRecommendedFeeds={() => 130 + setCurrentFeeds( 131 + RECOMMENDED_SAVED_FEEDS.map(f => ({ 132 + ...f, 133 + id: TID.nextStr(), 134 + })), 135 + ) 136 + } 137 + /> 138 + </View> 139 + )} 140 + 141 + <SectionHeaderText> 142 + <Trans>Pinned Feeds</Trans> 143 + </SectionHeaderText> 144 + 145 + {preferences ? ( 146 + !pinnedFeeds.length ? ( 147 + <View style={[a.flex_1, a.p_lg]}> 148 + <Admonition type="info"> 149 + <Trans>You don't have any pinned feeds.</Trans> 150 + </Admonition> 151 + </View> 152 + ) : ( 153 + pinnedFeeds.map(f => ( 154 + <ListItem 155 + key={f.id} 156 + feed={f} 157 + isPinned 158 + currentFeeds={currentFeeds} 159 + setCurrentFeeds={setCurrentFeeds} 160 + preferences={preferences} 161 + /> 162 + )) 163 + ) 164 + ) : ( 165 + <View style={[a.w_full, a.py_2xl, a.align_center]}> 166 + <Loader size="xl" /> 167 + </View> 168 + )} 169 + 170 + {noFollowingFeed && ( 171 + <View style={[t.atoms.border_contrast_low, a.border_b]}> 172 + <NoFollowingFeed 173 + onAddFeed={() => 174 + setCurrentFeeds(feeds => [ 175 + ...feeds, 176 + {...TIMELINE_SAVED_FEED, id: TID.next().toString()}, 177 + ]) 178 + } 179 + /> 180 + </View> 181 + )} 182 + 183 + <SectionHeaderText> 184 + <Trans>Saved Feeds</Trans> 185 + </SectionHeaderText> 186 + 187 + {preferences ? ( 188 + !unpinnedFeeds.length ? ( 189 + <View style={[a.flex_1, a.p_lg]}> 190 + <Admonition type="info"> 191 + <Trans>You don't have any saved feeds.</Trans> 192 + </Admonition> 193 + </View> 194 + ) : ( 195 + unpinnedFeeds.map(f => ( 196 + <ListItem 197 + key={f.id} 198 + feed={f} 199 + isPinned={false} 200 + currentFeeds={currentFeeds} 201 + setCurrentFeeds={setCurrentFeeds} 202 + preferences={preferences} 203 + /> 204 + )) 205 + ) 206 + ) : ( 207 + <View style={[a.w_full, a.py_2xl, a.align_center]}> 208 + <Loader size="xl" /> 209 + </View> 210 + )} 211 + 212 + <View style={[a.px_lg, a.py_xl]}> 213 + <Text 214 + style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]}> 215 + <Trans> 216 + Feeds are custom algorithms that users build with a little coding 217 + expertise.{' '} 218 + <InlineLinkText 219 + to="https://github.com/bluesky-social/feed-generator" 220 + label={_(msg`See this guide`)} 221 + disableMismatchWarning 222 + style={[a.leading_snug]}> 223 + See this guide 224 + </InlineLinkText>{' '} 225 + for more information. 226 + </Trans> 227 + </Text> 228 + </View> 229 + </Layout.Content> 230 + </Layout.Screen> 231 + ) 232 + } 233 + 234 + function ListItem({ 235 + feed, 236 + isPinned, 237 + currentFeeds, 238 + setCurrentFeeds, 239 + }: { 240 + feed: AppBskyActorDefs.SavedFeed 241 + isPinned: boolean 242 + currentFeeds: AppBskyActorDefs.SavedFeed[] 243 + setCurrentFeeds: React.Dispatch<AppBskyActorDefs.SavedFeed[]> 244 + preferences: UsePreferencesQueryResponse 245 + }) { 246 + const {_} = useLingui() 247 + const t = useTheme() 248 + const playHaptic = useHaptics() 249 + const feedUri = feed.value 250 + 251 + const onTogglePinned = async () => { 252 + playHaptic() 253 + setCurrentFeeds( 254 + currentFeeds.map(f => 255 + f.id === feed.id ? {...feed, pinned: !feed.pinned} : f, 256 + ), 257 + ) 258 + } 259 + 260 + const onPressUp = async () => { 261 + if (!isPinned) return 262 + 263 + const nextFeeds = currentFeeds.slice() 264 + const ids = currentFeeds.map(f => f.id) 265 + const index = ids.indexOf(feed.id) 266 + const nextIndex = index - 1 267 + 268 + if (index === -1 || index === 0) return 269 + ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 270 + nextFeeds[nextIndex], 271 + nextFeeds[index], 272 + ] 273 + 274 + setCurrentFeeds(nextFeeds) 275 + } 276 + 277 + const onPressDown = async () => { 278 + if (!isPinned) return 279 + 280 + const nextFeeds = currentFeeds.slice() 281 + const ids = currentFeeds.map(f => f.id) 282 + const index = ids.indexOf(feed.id) 283 + const nextIndex = index + 1 284 + 285 + if (index === -1 || index >= nextFeeds.filter(f => f.pinned).length - 1) 286 + return 287 + ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 288 + nextFeeds[nextIndex], 289 + nextFeeds[index], 290 + ] 291 + 292 + setCurrentFeeds(nextFeeds) 293 + } 294 + 295 + const onPressRemove = async () => { 296 + playHaptic() 297 + setCurrentFeeds(currentFeeds.filter(f => f.id !== feed.id)) 298 + } 299 + 300 + return ( 301 + <Animated.View 302 + style={[a.flex_row, a.border_b, t.atoms.border_contrast_low]} 303 + layout={LinearTransition.duration(100)}> 304 + {feed.type === 'timeline' ? ( 305 + <FollowingFeedCard /> 306 + ) : ( 307 + <FeedSourceCard 308 + key={feedUri} 309 + feedUri={feedUri} 310 + style={[isPinned && a.pr_sm]} 311 + showMinimalPlaceholder 312 + hideTopBorder={true} 313 + /> 314 + )} 315 + <View style={[a.pr_lg, a.flex_row, a.align_center, a.gap_sm]}> 316 + {isPinned ? ( 317 + <> 318 + <Button 319 + testID={`feed-${feed.type}-moveUp`} 320 + label={_(msg`Move feed up`)} 321 + onPress={onPressUp} 322 + size="small" 323 + color="secondary" 324 + shape="square"> 325 + <ButtonIcon icon={ArrowUpIcon} /> 326 + </Button> 327 + <Button 328 + testID={`feed-${feed.type}-moveDown`} 329 + label={_(msg`Move feed down`)} 330 + onPress={onPressDown} 331 + size="small" 332 + color="secondary" 333 + shape="square"> 334 + <ButtonIcon icon={ArrowDownIcon} /> 335 + </Button> 336 + </> 337 + ) : ( 338 + <Button 339 + testID={`feed-${feedUri}-toggleSave`} 340 + label={_(msg`Remove from my feeds`)} 341 + onPress={onPressRemove} 342 + size="small" 343 + color="secondary" 344 + variant="ghost" 345 + shape="square"> 346 + <ButtonIcon icon={TrashIcon} /> 347 + </Button> 348 + )} 349 + <Button 350 + testID={`feed-${feed.type}-togglePin`} 351 + label={isPinned ? _(msg`Unpin feed`) : _(msg`Pin feed`)} 352 + onPress={onTogglePinned} 353 + size="small" 354 + color={isPinned ? 'primary_subtle' : 'secondary'} 355 + shape="square"> 356 + <ButtonIcon icon={PinIcon} /> 357 + </Button> 358 + </View> 359 + </Animated.View> 360 + ) 361 + } 362 + 363 + function SectionHeaderText({children}: {children: React.ReactNode}) { 364 + const t = useTheme() 365 + // eslint-disable-next-line bsky-internal/avoid-unwrapped-text 366 + return ( 367 + <View 368 + style={[ 369 + a.flex_row, 370 + a.flex_1, 371 + a.px_lg, 372 + a.pt_2xl, 373 + a.pb_md, 374 + a.border_b, 375 + t.atoms.border_contrast_low, 376 + ]}> 377 + <Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>{children}</Text> 378 + </View> 379 + ) 380 + } 381 + 382 + function FollowingFeedCard() { 383 + const t = useTheme() 384 + return ( 385 + <View style={[a.flex_row, a.align_center, a.flex_1, a.p_lg]}> 386 + <View 387 + style={[ 388 + a.align_center, 389 + a.justify_center, 390 + a.rounded_sm, 391 + a.mr_md, 392 + { 393 + width: 36, 394 + height: 36, 395 + backgroundColor: t.palette.primary_500, 396 + }, 397 + ]}> 398 + <FilterTimeline 399 + style={[ 400 + { 401 + width: 22, 402 + height: 22, 403 + }, 404 + ]} 405 + fill={t.palette.white} 406 + /> 407 + </View> 408 + <View style={[a.flex_1, a.flex_row, a.gap_sm, a.align_center]}> 409 + <Text style={[a.text_sm, a.font_bold, a.leading_snug]}> 410 + <Trans context="feed-name">Following</Trans> 411 + </Text> 412 + </View> 413 + </View> 414 + ) 415 + }
+1 -1
src/screens/Settings/AppIconSettings/AppIconImage.tsx
··· 1 1 import {Image} from 'expo-image' 2 2 3 - import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' 3 + import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 4 4 import {atoms as a, platform, useTheme} from '#/alf' 5 5 6 6 export function AppIconImage({
+1 -1
src/screens/Settings/components/AddAppPasswordDialog.tsx
··· 8 8 SlideInRight, 9 9 SlideOutLeft, 10 10 } from 'react-native-reanimated' 11 - import {ComAtprotoServerCreateAppPassword} from '@atproto/api' 11 + import {type ComAtprotoServerCreateAppPassword} from '@atproto/api' 12 12 import {msg, Trans} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react' 14 14 import {useMutation} from '@tanstack/react-query'
+2 -2
src/screens/Settings/components/CopyButton.tsx
··· 1 1 import {useCallback, useEffect, useState} from 'react' 2 - import {GestureResponderEvent, View} from 'react-native' 2 + import {type GestureResponderEvent, View} from 'react-native' 3 3 import Animated, { 4 4 FadeOutUp, 5 5 useReducedMotion, ··· 9 9 import {Trans} from '@lingui/macro' 10 10 11 11 import {atoms as a, useTheme} from '#/alf' 12 - import {Button, ButtonProps} from '#/components/Button' 12 + import {Button, type ButtonProps} from '#/components/Button' 13 13 import {Text} from '#/components/Typography' 14 14 15 15 export function CopyButton({
+1 -1
src/screens/Settings/components/DeactivateAccountDialog.tsx
··· 7 7 import {useAgent, useSessionApi} from '#/state/session' 8 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import {DialogOuterProps} from '#/components/Dialog' 10 + import {type DialogOuterProps} from '#/components/Dialog' 11 11 import {Divider} from '#/components/Divider' 12 12 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 13 13 import {Loader} from '#/components/Loader'
+6 -7
src/screens/Settings/components/ExportCarDialog.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 18 18 export function ExportCarDialog({ 19 19 control, 20 20 }: { 21 - control: Dialog.DialogOuterProps['control'] 21 + control: Dialog.DialogControlProps 22 22 }) { 23 23 const {_} = useLingui() 24 24 const t = useTheme() 25 25 const agent = useAgent() 26 - const [loading, setLoading] = React.useState(false) 26 + const [loading, setLoading] = useState(false) 27 27 28 - const download = React.useCallback(async () => { 28 + const download = useCallback(async () => { 29 29 if (!agent.session) { 30 30 return // shouldnt ever happen 31 31 } ··· 52 52 }, [_, control, agent]) 53 53 54 54 return ( 55 - <Dialog.Outer control={control}> 55 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 56 56 <Dialog.Handle /> 57 57 <Dialog.ScrollableInner 58 58 accessibilityDescribedBy="dialog-description" ··· 63 63 </Text> 64 64 <Text 65 65 nativeID="dialog-description" 66 - style={[a.text_sm, a.leading_normal, t.atoms.text_contrast_high]}> 66 + style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_high]}> 67 67 <Trans> 68 68 Your account repository, containing all public data records, can 69 69 be downloaded as a "CAR" file. This file does not include media ··· 73 73 </Text> 74 74 75 75 <Button 76 - variant="solid" 77 76 color="primary" 78 77 size="large" 79 78 label={_(msg`Download CAR file`)}
+1 -1
src/screens/Settings/components/PwiOptOut.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {$Typed, ComAtprotoLabelDefs} from '@atproto/api' 3 + import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6
+1 -1
src/screens/Signup/StepCaptcha/CaptchaWebView.tsx
··· 31 31 onError: (error: unknown) => void 32 32 }) { 33 33 const startedAt = useRef(Date.now()) 34 - const successTo = useRef<NodeJS.Timeout>() 34 + const successTo = useRef<NodeJS.Timeout>(undefined) 35 35 36 36 useEffect(() => { 37 37 return () => {
+1 -1
src/screens/Signup/StepInfo/Policies.tsx
··· 72 72 ) 73 73 } 74 74 75 - let els: ReactElement 75 + let els: ReactElement<any> 76 76 if (tos && pp) { 77 77 els = ( 78 78 <Trans>
+1 -1
src/screens/Signup/StepInfo/index.tsx
··· 68 68 69 69 const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false) 70 70 71 - const tldtsRef = React.useRef<typeof tldts>() 71 + const tldtsRef = React.useRef<typeof tldts>(undefined) 72 72 React.useEffect(() => { 73 73 // @ts-expect-error - valid path 74 74 import('tldts/dist/index.cjs.min.js').then(tldts => {
+2 -2
src/screens/StarterPack/Wizard/StepFeeds.tsx
··· 1 1 import {useState} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 4 - import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' 4 + import {type AppBskyFeedDefs, type ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 7 import {DISCOVER_FEED_URI} from '#/lib/constants'
+3 -3
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 1 1 import {useState} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 4 - import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' 4 + import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 7 import {isNative} from '#/platform/detection' ··· 16 16 import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' 17 17 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 18 18 import {Text} from '#/components/Typography' 19 - import * as bsky from '#/types/bsky' 19 + import type * as bsky from '#/types/bsky' 20 20 21 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { 22 22 return item?.did ?? ''
+4 -4
src/screens/VideoFeed/components/Header.tsx
··· 1 1 import {useCallback} from 'react' 2 - import {GestureResponderEvent, View} from 'react-native' 2 + import {type GestureResponderEvent, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 7 import {HITSLOP_30} from '#/lib/constants' 8 - import {NavigationProp} from '#/lib/routes/types' 8 + import {type NavigationProp} from '#/lib/routes/types' 9 9 import {sanitizeHandle} from '#/lib/strings/handles' 10 10 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 11 11 import {UserAvatar} from '#/view/com/util/UserAvatar' 12 - import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' 12 + import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' 13 13 import {atoms as a, useBreakpoints} from '#/alf' 14 - import {Button, ButtonProps} from '#/components/Button' 14 + import {Button, type ButtonProps} from '#/components/Button' 15 15 import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow' 16 16 import * as Layout from '#/components/Layout' 17 17 import {BUTTON_VISUAL_ALIGNMENT_OFFSET} from '#/components/Layout/const'
+1 -1
src/screens/VideoFeed/types.ts
··· 1 - import {AuthorFilter} from '#/state/queries/post-feed' 1 + import {type AuthorFilter} from '#/state/queries/post-feed' 2 2 3 3 /** 4 4 * Kind of like `FeedDescriptor` but not
+5 -5
src/state/messages/convo/util.ts
··· 1 1 import { 2 - ConvoState, 3 - ConvoStateBackgrounded, 4 - ConvoStateDisabled, 5 - ConvoStateReady, 6 - ConvoStateSuspended, 2 + type ConvoState, 3 + type ConvoStateBackgrounded, 4 + type ConvoStateDisabled, 5 + type ConvoStateReady, 6 + type ConvoStateSuspended, 7 7 ConvoStatus, 8 8 } from './types' 9 9
+1 -1
src/state/messages/events/types.ts
··· 1 - import {BskyAgent, ChatBskyConvoGetLog} from '@atproto/api' 1 + import {type BskyAgent, type ChatBskyConvoGetLog} from '@atproto/api' 2 2 3 3 export type MessagesEventBusParams = { 4 4 agent: BskyAgent
+1 -1
src/state/messages/index.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 3 import {CurrentConvoIdProvider} from '#/state/messages/current-convo-id' 4 4 import {MessagesEventBusProvider} from '#/state/messages/events'
+2 -2
src/state/persisted/index.web.ts
··· 4 4 import {logger} from '#/logger' 5 5 import { 6 6 defaults, 7 - Schema, 7 + type Schema, 8 8 tryParse, 9 9 tryStringify, 10 10 } from '#/state/persisted/schema' 11 - import {PersistedApi} from './types' 11 + import {type PersistedApi} from './types' 12 12 import {normalizeData} from './util' 13 13 14 14 export type {PersistedAccount, Schema} from '#/state/persisted/schema'
+1 -1
src/state/persisted/types.ts
··· 1 - import type {Schema} from './schema' 1 + import {type Schema} from './schema' 2 2 3 3 export type PersistedApi = { 4 4 init(): Promise<void>
+1 -1
src/state/persisted/util.ts
··· 2 2 3 3 import {dedupArray} from '#/lib/functions' 4 4 import {logger} from '#/logger' 5 - import {Schema} from '#/state/persisted/schema' 5 + import {type Schema} from '#/state/persisted/schema' 6 6 7 7 export function normalizeData(data: Schema) { 8 8 const next = {...data}
+5 -1
src/state/queries/actor-autocomplete.ts
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' 2 + import { 3 + type AppBskyActorDefs, 4 + moderateProfile, 5 + type ModerationOpts, 6 + } from '@atproto/api' 3 7 import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query' 4 8 5 9 import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
+1 -1
src/state/queries/app-passwords.ts
··· 1 - import {ComAtprotoServerCreateAppPassword} from '@atproto/api' 1 + import {type ComAtprotoServerCreateAppPassword} from '@atproto/api' 2 2 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 3 3 4 4 import {STALE} from '#/state/queries'
+1 -1
src/state/queries/invites.ts
··· 1 - import {ComAtprotoServerDefs} from '@atproto/api' 1 + import {type ComAtprotoServerDefs} from '@atproto/api' 2 2 import {useQuery} from '@tanstack/react-query' 3 3 4 4 import {cleanError} from '#/lib/strings/errors'
+7 -4
src/state/queries/known-followers.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetKnownFollowers} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyGraphGetKnownFollowers, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+4 -4
src/state/queries/my-blocked-accounts.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetBlocks} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetBlocks} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+2 -2
src/state/queries/my-lists.ts
··· 1 - import {AppBskyGraphDefs} from '@atproto/api' 2 - import {QueryClient, useQuery} from '@tanstack/react-query' 1 + import {type AppBskyGraphDefs} from '@atproto/api' 2 + import {type QueryClient, useQuery} from '@tanstack/react-query' 3 3 4 4 import {accumulate} from '#/lib/async/accumulate' 5 5 import {STALE} from '#/state/queries'
+4 -4
src/state/queries/my-muted-accounts.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetMutes} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetMutes} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+1 -1
src/state/queries/nuxs/types.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import {type AppBskyActorDefs} from '@atproto/api' 2 2 3 3 export type Data = Record<string, unknown> | undefined 4 4
+3 -3
src/state/queries/nuxs/util.ts
··· 1 - import {AppBskyActorDefs, nuxSchema} from '@atproto/api' 1 + import {type AppBskyActorDefs, nuxSchema} from '@atproto/api' 2 2 3 3 import { 4 - AppNux, 5 - Nux, 4 + type AppNux, 5 + type Nux, 6 6 nuxNames, 7 7 NuxSchemas, 8 8 } from '#/state/queries/nuxs/definitions'
+1 -1
src/state/queries/post-interaction-settings.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import {type AppBskyActorDefs} from '@atproto/api' 2 2 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 3 4 4 import {preferencesQueryKey} from '#/state/queries/preferences'
+4 -4
src/state/queries/post-liked-by.ts
··· 1 - import {AppBskyActorDefs, AppBskyFeedGetLikes} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyFeedGetLikes} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+6 -6
src/state/queries/post-quotes.ts
··· 1 1 import { 2 - AppBskyActorDefs, 2 + type AppBskyActorDefs, 3 3 AppBskyEmbedRecord, 4 - AppBskyFeedDefs, 5 - AppBskyFeedGetQuotes, 4 + type AppBskyFeedDefs, 5 + type AppBskyFeedGetQuotes, 6 6 AtUri, 7 7 } from '@atproto/api' 8 8 import { 9 - InfiniteData, 10 - QueryClient, 11 - QueryKey, 9 + type InfiniteData, 10 + type QueryClient, 11 + type QueryKey, 12 12 useInfiniteQuery, 13 13 } from '@tanstack/react-query' 14 14
+7 -4
src/state/queries/post-reposted-by.ts
··· 1 - import {AppBskyActorDefs, AppBskyFeedGetRepostedBy} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyFeedGetRepostedBy, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+1 -1
src/state/queries/postgate/index.ts
··· 173 173 const agent = useAgent() 174 174 const queryClient = useQueryClient() 175 175 const getPosts = useGetPosts() 176 - const prevEmbed = React.useRef<AppBskyFeedDefs.PostView['embed']>() 176 + const prevEmbed = React.useRef<AppBskyFeedDefs.PostView['embed']>(undefined) 177 177 178 178 return useMutation({ 179 179 mutationFn: async ({
+3 -3
src/state/queries/preferences/types.ts
··· 1 1 import { 2 - BskyFeedViewPreference, 3 - BskyPreferences, 4 - BskyThreadViewPreference, 2 + type BskyFeedViewPreference, 3 + type BskyPreferences, 4 + type BskyThreadViewPreference, 5 5 } from '@atproto/api' 6 6 7 7 export type UsePreferencesQueryResponse = Omit<
+9 -2
src/state/queries/profile-feedgens.ts
··· 1 - import {AppBskyFeedGetActorFeeds, moderateFeedGenerator} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import { 2 + type AppBskyFeedGetActorFeeds, 3 + moderateFeedGenerator, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryKey, 8 + useInfiniteQuery, 9 + } from '@tanstack/react-query' 3 10 4 11 import {useAgent} from '#/state/session' 5 12 import {useModerationOpts} from '../preferences/moderation-opts'
+7 -4
src/state/queries/profile-followers.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetFollowers} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyGraphGetFollowers, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+4 -4
src/state/queries/profile-follows.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetFollows} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetFollows} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+6 -2
src/state/queries/profile-lists.ts
··· 1 - import {AppBskyGraphGetLists, moderateUserList} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import {type AppBskyGraphGetLists, moderateUserList} from '@atproto/api' 2 + import { 3 + type InfiniteData, 4 + type QueryKey, 5 + useInfiniteQuery, 6 + } from '@tanstack/react-query' 3 7 4 8 import {useAgent} from '#/state/session' 5 9 import {useModerationOpts} from '../preferences/moderation-opts'
+4 -4
src/state/queries/resolve-link.ts
··· 1 - import {QueryClient, useQuery} from '@tanstack/react-query' 1 + import {type QueryClient, useQuery} from '@tanstack/react-query' 2 2 3 3 import {STALE} from '#/state/queries/index' 4 4 import {useAgent} from '../session' ··· 9 9 const RQKEY_GIF_ROOT = 'resolve-gif' 10 10 export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url] 11 11 12 - import {BskyAgent} from '@atproto/api' 12 + import {type BskyAgent} from '@atproto/api' 13 13 14 - import {ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' 15 - import {Gif} from './tenor' 14 + import {type ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' 15 + import {type Gif} from './tenor' 16 16 17 17 export function useResolveLinkQuery(url: string) { 18 18 const agent = useAgent()
+5 -1
src/state/queries/resolve-uri.ts
··· 1 1 import {AtUri} from '@atproto/api' 2 - import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query' 2 + import { 3 + type QueryClient, 4 + useQuery, 5 + type UseQueryResult, 6 + } from '@tanstack/react-query' 3 7 4 8 import {STALE} from '#/state/queries' 5 9 import {useAgent} from '#/state/session'
+6 -6
src/state/queries/search-posts.ts
··· 1 1 import React from 'react' 2 2 import { 3 - AppBskyActorDefs, 4 - AppBskyFeedDefs, 5 - AppBskyFeedSearchPosts, 3 + type AppBskyActorDefs, 4 + type AppBskyFeedDefs, 5 + type AppBskyFeedSearchPosts, 6 6 AtUri, 7 7 moderatePost, 8 8 } from '@atproto/api' 9 9 import { 10 - InfiniteData, 11 - QueryClient, 12 - QueryKey, 10 + type InfiniteData, 11 + type QueryClient, 12 + type QueryKey, 13 13 useInfiniteQuery, 14 14 } from '@tanstack/react-query' 15 15
+6 -2
src/state/queries/suggested-feeds.ts
··· 1 - import {AppBskyFeedGetSuggestedFeeds} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import {type AppBskyFeedGetSuggestedFeeds} from '@atproto/api' 2 + import { 3 + type InfiniteData, 4 + type QueryKey, 5 + useInfiniteQuery, 6 + } from '@tanstack/react-query' 3 7 4 8 import {STALE} from '#/state/queries' 5 9 import {useAgent} from '#/state/session'
+3 -3
src/state/queries/threadgate/index.ts
··· 1 1 import { 2 2 AppBskyFeedDefs, 3 - AppBskyFeedGetPostThread, 3 + type AppBskyFeedGetPostThread, 4 4 AppBskyFeedThreadgate, 5 5 AtUri, 6 - BskyAgent, 6 + type BskyAgent, 7 7 } from '@atproto/api' 8 8 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 9 9 ··· 11 11 import {until} from '#/lib/async/until' 12 12 import {STALE} from '#/state/queries' 13 13 import {RQKEY_ROOT as postThreadQueryKeyRoot} from '#/state/queries/post-thread' 14 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 14 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 15 15 import { 16 16 createThreadgateRecord, 17 17 mergeThreadgateRecords,
+2 -2
src/state/queries/threadgate/util.ts
··· 1 - import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' 1 + import {type AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' 2 2 3 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 3 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 4 4 import * as bsky from '#/types/bsky' 5 5 6 6 export function threadgateViewToAllowUISetting(
+2 -2
src/state/queries/unstable-profile-cache.ts
··· 1 1 import {useCallback} from 'react' 2 - import {QueryClient, useQueryClient} from '@tanstack/react-query' 2 + import {type QueryClient, useQueryClient} from '@tanstack/react-query' 3 3 4 - import * as bsky from '#/types/bsky' 4 + import type * as bsky from '#/types/bsky' 5 5 6 6 const unstableProfileViewCacheQueryKeyRoot = 'unstableProfileViewCache' 7 7 export const unstableProfileViewCacheQueryKey = (didOrHandle: string) => [
+8 -4
src/state/queries/util.ts
··· 1 1 import { 2 - AppBskyActorDefs, 2 + type AppBskyActorDefs, 3 3 AppBskyEmbedRecord, 4 4 AppBskyEmbedRecordWithMedia, 5 - AppBskyFeedDefs, 5 + type AppBskyFeedDefs, 6 6 AppBskyFeedPost, 7 - AtUri, 7 + type AtUri, 8 8 } from '@atproto/api' 9 - import {InfiniteData, QueryClient, QueryKey} from '@tanstack/react-query' 9 + import { 10 + type InfiniteData, 11 + type QueryClient, 12 + type QueryKey, 13 + } from '@tanstack/react-query' 10 14 11 15 import * as bsky from '#/types/bsky' 12 16
+1 -1
src/state/session/__tests__/session-test.ts
··· 2 2 import {describe, expect, it, jest} from '@jest/globals' 3 3 4 4 import {agentToSessionAccountOrThrow} from '../agent' 5 - import {Action, getInitialState, reducer, State} from '../reducer' 5 + import {type Action, getInitialState, reducer, type State} from '../reducer' 6 6 7 7 jest.mock('jwt-decode', () => ({ 8 8 jwtDecode(_token: string) {
+1 -1
src/state/session/util.ts
··· 3 3 import {hasProp} from '#/lib/type-guards' 4 4 import {logger} from '#/logger' 5 5 import * as persisted from '#/state/persisted' 6 - import {SessionAccount} from './types' 6 + import {type SessionAccount} from './types' 7 7 8 8 export function readLastActiveAccount() { 9 9 const {currentAccount, accounts} = persisted.get('session')
-2
src/state/shell/index.tsx
··· 1 - import type React from 'react' 2 - 3 1 import {Provider as ColorModeProvider} from './color-mode' 4 2 import {Provider as DrawerOpenProvider} from './drawer-open' 5 3 import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
+1 -1
src/state/shell/reminders.ts
··· 1 1 import {simpleAreDatesEqual} from '#/lib/strings/time' 2 2 import {logger} from '#/logger' 3 3 import * as persisted from '#/state/persisted' 4 - import {SessionAccount} from '../session' 4 + import {type SessionAccount} from '../session' 5 5 import {isOnboardingActive} from './onboarding' 6 6 7 7 export function shouldRequestEmailConfirmation(account: SessionAccount) {
+1 -1
src/types/bsky/index.ts
··· 1 - import {ValidationResult} from '@atproto/lexicon' 1 + import {type ValidationResult} from '@atproto/lexicon' 2 2 3 3 export * as post from '#/types/bsky/post' 4 4 export * as profile from '#/types/bsky/profile'
+1 -1
src/types/bsky/post.ts
··· 1 1 import { 2 - $Typed, 2 + type $Typed, 3 3 AppBskyEmbedExternal, 4 4 AppBskyEmbedImages, 5 5 AppBskyEmbedRecord,
+1 -1
src/view/com/composer/AltTextCounterWrapper.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {MAX_ALT_TEXT} from '#/lib/constants' 5 5 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
+1 -1
src/view/com/composer/Composer.tsx
··· 174 174 videoUri: initVideoUri, 175 175 cancelRef, 176 176 }: Props & { 177 - cancelRef?: React.RefObject<CancelRef> 177 + cancelRef?: React.RefObject<CancelRef | null> 178 178 }) => { 179 179 const {currentAccount} = useSession() 180 180 const agent = useAgent()
+1 -1
src/view/com/composer/KeyboardAccessory.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 2 import {KeyboardStickyView} from 'react-native-keyboard-controller' 4 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 + import type React from 'react' 5 5 6 6 import {isWeb} from '#/platform/detection' 7 7 import {atoms as a, useTheme} from '#/alf'
+6 -1
src/view/com/composer/char-progress/CharProgress.tsx
··· 1 - import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' 1 + import { 2 + type StyleProp, 3 + type TextStyle, 4 + View, 5 + type ViewStyle, 6 + } from 'react-native' 2 7 // @ts-ignore no type definition -prf 3 8 import ProgressCircle from 'react-native-progress/Circle' 4 9 // @ts-ignore no type definition -prf
-2
src/view/com/composer/photos/EditImageDialog.tsx
··· 1 - import type React from 'react' 2 - 3 1 import {type ComposerImage} from '#/state/gallery' 4 2 import type * as Dialog from '#/components/Dialog' 5 3
+1 -1
src/view/com/composer/photos/EditImageDialog.web.tsx
··· 112 112 aspectRatio, 113 113 }: Required<Pick<EditImageDialogProps, 'image'>> & 114 114 Omit<EditImageDialogProps, 'control' | 'image'> & { 115 - saveRef: React.RefObject<{save: () => Promise<void>}> 115 + saveRef: React.RefObject<{save: () => Promise<void>} | null> 116 116 }) { 117 117 const t = useTheme() 118 118 const [isDragging, setIsDragging] = useState(false)
+1 -1
src/view/com/composer/photos/SelectGifBtn.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {logEvent} from '#/lib/statsig/statsig' 7 - import {Gif} from '#/state/queries/tenor' 7 + import {type Gif} from '#/state/queries/tenor' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import {Button} from '#/components/Button' 10 10 import {GifSelectDialog} from '#/components/dialogs/GifSelect'
+11 -1
src/view/com/composer/select-language/SuggestedLanguage.tsx
··· 1 1 import {useEffect, useState} from 'react' 2 2 import {View} from 'react-native' 3 + import {parseLanguage} from '@atproto/api' 3 4 import {msg, Trans} from '@lingui/macro' 4 5 import {useLingui} from '@lingui/react' 5 6 import lande from 'lande' ··· 21 22 22 23 export function SuggestedLanguage({ 23 24 text, 24 - replyToLanguage, 25 + replyToLanguage: replyToLanguageProp, 25 26 }: { 26 27 text: string 27 28 replyToLanguage?: string 28 29 }) { 30 + const replyToLanguage = cleanUpLanguage(replyToLanguageProp) 29 31 const [suggestedLanguage, setSuggestedLanguage] = useState< 30 32 string | undefined 31 33 >(text.length === 0 ? replyToLanguage : undefined) ··· 125 127 } 126 128 return code3ToCode2Strict(lang) 127 129 } 130 + 131 + function cleanUpLanguage(text: string | undefined): string | undefined { 132 + if (!text) { 133 + return undefined 134 + } 135 + 136 + return parseLanguage(text)?.language 137 + }
+1 -1
src/view/com/composer/text-input/text-input-util.ts
··· 1 - import {AppBskyRichtextFacet, RichText} from '@atproto/api' 1 + import {type AppBskyRichtextFacet, type RichText} from '@atproto/api' 2 2 3 3 export type LinkFacetMatch = { 4 4 rt: RichText
+1 -1
src/view/com/composer/text-input/web/LinkDecorator.ts
··· 16 16 17 17 import {URL_REGEX} from '@atproto/api' 18 18 import {Mark} from '@tiptap/core' 19 - import {Node as ProsemirrorNode} from '@tiptap/pm/model' 19 + import {type Node as ProsemirrorNode} from '@tiptap/pm/model' 20 20 import {Plugin, PluginKey} from '@tiptap/pm/state' 21 21 import {Decoration, DecorationSet} from '@tiptap/pm/view' 22 22
+1 -1
src/view/com/composer/text-input/web/TagDecorator.ts
··· 16 16 17 17 import {TAG_REGEX, TRAILING_PUNCTUATION_REGEX} from '@atproto/api' 18 18 import {Mark} from '@tiptap/core' 19 - import {Node as ProsemirrorNode} from '@tiptap/pm/model' 19 + import {type Node as ProsemirrorNode} from '@tiptap/pm/model' 20 20 import {Plugin, PluginKey} from '@tiptap/pm/state' 21 21 import {Decoration, DecorationSet} from '@tiptap/pm/view' 22 22
+4 -4
src/view/com/composer/threadgate/ThreadgateBtn.tsx
··· 1 - import {Keyboard, StyleProp, ViewStyle} from 'react-native' 2 - import {AnimatedStyle} from 'react-native-reanimated' 3 - import {AppBskyFeedPostgate} from '@atproto/api' 1 + import {Keyboard, type StyleProp, type ViewStyle} from 'react-native' 2 + import {type AnimatedStyle} from 'react-native-reanimated' 3 + import {type AppBskyFeedPostgate} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {isNative} from '#/platform/detection' 8 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate' 8 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate' 9 9 import {native} from '#/alf' 10 10 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 11 import * as Dialog from '#/components/Dialog'
+2 -1
src/view/com/composer/videos/SubtitleFilePicker.tsx
··· 1 - import React, {useRef} from 'react' 1 + import {useRef} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 5 6 6 7 import {logger} from '#/logger' 7 8 import * as Toast from '#/view/com/util/Toast'
+2 -2
src/view/com/composer/videos/VideoPreview.web.tsx
··· 1 1 import {View} from 'react-native' 2 - import {ImagePickerAsset} from 'expo-image-picker' 2 + import {type ImagePickerAsset} from 'expo-image-picker' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {CompressedVideo} from '#/lib/media/video/types' 6 + import {type CompressedVideo} from '#/lib/media/video/types' 7 7 import {clamp} from '#/lib/numbers' 8 8 import {useAutoplayDisabled} from '#/state/preferences' 9 9 import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
+1 -1
src/view/com/composer/videos/VideoTranscodeProgress.tsx
··· 1 1 import {View} from 'react-native' 2 2 // @ts-expect-error no type definition 3 3 import ProgressPie from 'react-native-progress/Pie' 4 - import {ImagePickerAsset} from 'expo-image-picker' 4 + import {type ImagePickerAsset} from 'expo-image-picker' 5 5 6 6 import {clamp} from '#/lib/numbers' 7 7 import {isWeb} from '#/platform/detection'
+1 -1
src/view/com/composer/videos/pickVideo.web.ts
··· 1 - import {ImagePickerAsset, ImagePickerResult} from 'expo-image-picker' 1 + import {type ImagePickerAsset, type ImagePickerResult} from 'expo-image-picker' 2 2 3 3 import {SUPPORTED_MIME_TYPES} from '#/lib/constants' 4 4
+8 -1
src/view/com/feeds/FeedPage.tsx
··· 1 - import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import { 2 + type JSX, 3 + useCallback, 4 + useEffect, 5 + useMemo, 6 + useRef, 7 + useState, 8 + } from 'react' 2 9 import {View} from 'react-native' 3 10 import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' 4 11 import {msg} from '@lingui/macro'
+2 -1
src/view/com/home/HomeHeaderLayout.web.tsx
··· 1 - import React from 'react' 1 + import {type JSX} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 5 6 6 7 import {useKawaiiMode} from '#/state/preferences/kawaii' 7 8 import {useSession} from '#/state/session'
+1
src/view/com/home/HomeHeaderLayoutMobile.tsx
··· 1 + import {type JSX} from 'react' 1 2 import {View} from 'react-native' 2 3 import Animated from 'react-native-reanimated' 3 4 import {msg} from '@lingui/macro'
+2 -2
src/view/com/lightbox/ImageViewing/@types/index.ts
··· 6 6 * 7 7 */ 8 8 9 - import {TransformsStyle} from 'react-native' 10 - import {MeasuredDimensions} from 'react-native-reanimated' 9 + import {type TransformsStyle} from 'react-native' 10 + import {type MeasuredDimensions} from 'react-native-reanimated' 11 11 12 12 export type Dimensions = { 13 13 width: number
+1 -1
src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx
··· 5 5 * LICENSE file in the root directory of this source tree. 6 6 * 7 7 */ 8 - import {StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' 8 + import {StyleSheet, TouchableOpacity, type ViewStyle} from 'react-native' 9 9 import {SafeAreaView} from 'react-native-safe-area-context' 10 10 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 11 11 import {msg} from '@lingui/macro'
+7 -7
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
··· 3 3 import { 4 4 Gesture, 5 5 GestureDetector, 6 - PanGesture, 6 + type PanGesture, 7 7 } from 'react-native-gesture-handler' 8 8 import Animated, { 9 9 runOnJS, 10 - SharedValue, 10 + type SharedValue, 11 11 useAnimatedReaction, 12 12 useAnimatedRef, 13 13 useAnimatedStyle, ··· 16 16 } from 'react-native-reanimated' 17 17 import {Image} from 'expo-image' 18 18 19 - import type { 20 - Dimensions as ImageDimensions, 21 - ImageSource, 22 - Transform, 19 + import { 20 + type Dimensions as ImageDimensions, 21 + type ImageSource, 22 + type Transform, 23 23 } from '../../@types' 24 24 import { 25 25 applyRounding, ··· 28 28 prependPinch, 29 29 prependTransform, 30 30 readTransform, 31 - TransformMatrix, 31 + type TransformMatrix, 32 32 } from '../../transforms' 33 33 34 34 const MIN_SCREEN_ZOOM = 2
+5 -5
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
··· 11 11 import { 12 12 Gesture, 13 13 GestureDetector, 14 - PanGesture, 14 + type PanGesture, 15 15 } from 'react-native-gesture-handler' 16 16 import Animated, { 17 17 runOnJS, 18 - SharedValue, 18 + type SharedValue, 19 19 useAnimatedProps, 20 20 useAnimatedReaction, 21 21 useAnimatedRef, ··· 27 27 28 28 import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' 29 29 import { 30 - Dimensions as ImageDimensions, 31 - ImageSource, 32 - Transform, 30 + type Dimensions as ImageDimensions, 31 + type ImageSource, 32 + type Transform, 33 33 } from '../../@types' 34 34 35 35 const MAX_ORIGINAL_IMAGE_ZOOM = 2
+6 -6
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
··· 2 2 3 3 import React from 'react' 4 4 import {View} from 'react-native' 5 - import {PanGesture} from 'react-native-gesture-handler' 6 - import {SharedValue} from 'react-native-reanimated' 5 + import {type PanGesture} from 'react-native-gesture-handler' 6 + import {type SharedValue} from 'react-native-reanimated' 7 7 8 - import {Dimensions} from '#/lib/media/types' 8 + import {type Dimensions} from '#/lib/media/types' 9 9 import { 10 - Dimensions as ImageDimensions, 11 - ImageSource, 12 - Transform, 10 + type Dimensions as ImageDimensions, 11 + type ImageSource, 12 + type Transform, 13 13 } from '../../@types' 14 14 15 15 type Props = {
+1 -1
src/view/com/lightbox/ImageViewing/transforms.ts
··· 1 - import type {Position} from './@types' 1 + import {type Position} from './@types' 2 2 3 3 export type TransformMatrix = [ 4 4 number,
+1 -1
src/view/com/lists/ListMembers.tsx
··· 1 - import React, {useCallback} from 'react' 1 + import React, {type JSX, useCallback} from 'react' 2 2 import { 3 3 Dimensions, 4 4 type GestureResponderEvent,
+5 -5
src/view/com/lists/MyLists.tsx
··· 1 - import React from 'react' 1 + import React, {type JSX} from 'react' 2 2 import { 3 3 ActivityIndicator, 4 4 FlatList as RNFlatList, 5 5 RefreshControl, 6 - StyleProp, 6 + type StyleProp, 7 7 View, 8 - ViewStyle, 8 + type ViewStyle, 9 9 } from 'react-native' 10 - import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' 10 + import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' 11 11 import {msg} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 13 ··· 16 16 import {s} from '#/lib/styles' 17 17 import {logger} from '#/logger' 18 18 import {useModerationOpts} from '#/state/preferences/moderation-opts' 19 - import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 19 + import {type MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 20 20 import {atoms as a, useTheme} from '#/alf' 21 21 import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 22 22 import * as ListCard from '#/components/ListCard'
+3 -3
src/view/com/modals/InviteCodes.tsx
··· 6 6 View, 7 7 } from 'react-native' 8 8 import {setStringAsync} from 'expo-clipboard' 9 - import {ComAtprotoServerDefs} from '@atproto/api' 9 + import {type ComAtprotoServerDefs} from '@atproto/api' 10 10 import { 11 11 FontAwesomeIcon, 12 - FontAwesomeIconStyle, 12 + type FontAwesomeIconStyle, 13 13 } from '@fortawesome/react-native-fontawesome' 14 14 import {msg, Trans} from '@lingui/macro' 15 15 import {useLingui} from '@lingui/react' ··· 22 22 import {useInvitesAPI, useInvitesState} from '#/state/invites' 23 23 import {useModalControls} from '#/state/modals' 24 24 import { 25 - InviteCodesQueryResponse, 25 + type InviteCodesQueryResponse, 26 26 useInviteCodesQuery, 27 27 } from '#/state/queries/invites' 28 28 import {ErrorMessage} from '../util/error/ErrorMessage'
+2 -2
src/view/com/modals/UserAddRemoveLists.tsx
··· 5 5 useWindowDimensions, 6 6 View, 7 7 } from 'react-native' 8 - import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' 8 + import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' 9 9 import {msg, Trans} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 11 ··· 18 18 import {useModalControls} from '#/state/modals' 19 19 import { 20 20 getMembership, 21 - ListMembersip, 21 + type ListMembersip, 22 22 useDangerousListMembershipsQuery, 23 23 useListMembershipAddMutation, 24 24 useListMembershipRemoveMutation,
+1 -1
src/view/com/notifications/NotificationFeedItem.tsx
··· 248 248 : '' 249 249 250 250 let a11yLabel = '' 251 - let notificationContent: ReactElement 251 + let notificationContent: ReactElement<any> 252 252 let icon = ( 253 253 <HeartIconFilled 254 254 size="xl"
+1
src/view/com/pager/Pager.tsx
··· 1 1 import { 2 + type JSX, 2 3 memo, 3 4 useCallback, 4 5 useContext,
+1
src/view/com/pager/Pager.web.tsx
··· 1 1 import { 2 2 Children, 3 + type JSX, 3 4 useCallback, 4 5 useImperativeHandle, 5 6 useRef,
+1 -1
src/view/com/pager/PagerWithHeader.tsx
··· 1 - import {memo, useCallback, useEffect, useRef, useState} from 'react' 1 + import {type JSX, memo, useCallback, useEffect, useRef, useState} from 'react' 2 2 import { 3 3 type LayoutChangeEvent, 4 4 type NativeScrollEvent,
+8 -3
src/view/com/pager/PagerWithHeader.web.tsx
··· 1 1 import * as React from 'react' 2 - import {ScrollView, View} from 'react-native' 2 + import {type JSX} from 'react' 3 + import {type ScrollView, View} from 'react-native' 3 4 import {useAnimatedRef} from 'react-native-reanimated' 4 5 5 - import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager' 6 + import { 7 + Pager, 8 + type PagerRef, 9 + type RenderTabBarFnProps, 10 + } from '#/view/com/pager/Pager' 6 11 import {atoms as a, web} from '#/alf' 7 12 import * as Layout from '#/components/Layout' 8 - import {ListMethods} from '../util/List' 13 + import {type ListMethods} from '../util/List' 9 14 import {TabBar} from './TabBar' 10 15 11 16 export interface PagerWithHeaderChildParams {
+3 -1
src/view/com/pager/TabBar.web.tsx
··· 106 106 <PressableWithHover 107 107 testID={`${testID}-selector-${i}`} 108 108 key={`${item}-${i}`} 109 - ref={node => (itemRefs.current[i] = node as any)} 109 + ref={node => { 110 + itemRefs.current[i] = node as any 111 + }} 110 112 style={styles.item} 111 113 hoverStyle={t.atoms.bg_contrast_25} 112 114 onPress={() => onPressItem(i)}
+1 -1
src/view/com/post-thread/PostLikedBy.tsx
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 - import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 2 + import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/view/com/post-thread/PostQuotes.tsx
··· 1 1 import {useCallback, useState} from 'react' 2 2 import { 3 - AppBskyFeedDefs, 3 + type AppBskyFeedDefs, 4 4 AppBskyFeedPost, 5 5 moderatePost, 6 - ModerationDecision, 6 + type ModerationDecision, 7 7 } from '@atproto/api' 8 8 import {msg} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/post-thread/PostRepostedBy.tsx
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/view/com/posts/CustomFeedEmptyState.tsx
··· 2 2 import {StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {isWeb} from '#/platform/detection' 15 15 import {Button} from '../util/forms/Button'
+2 -2
src/view/com/posts/FollowingEmptyState.tsx
··· 2 2 import {StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {isWeb} from '#/platform/detection' 15 15 import {Button} from '../util/forms/Button'
+2 -2
src/view/com/posts/FollowingEndOfFeed.tsx
··· 2 2 import {Dimensions, StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 - import {NavigationProp} from '#/lib/routes/types' 11 + import {type NavigationProp} from '#/lib/routes/types' 12 12 import {s} from '#/lib/styles' 13 13 import {isWeb} from '#/platform/detection' 14 14 import {Button} from '../util/forms/Button'
+9 -1
src/view/com/posts/PostFeed.tsx
··· 1 - import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import { 2 + type JSX, 3 + memo, 4 + useCallback, 5 + useEffect, 6 + useMemo, 7 + useRef, 8 + useState, 9 + } from 'react' 2 10 import { 3 11 ActivityIndicator, 4 12 AppState,
+1 -1
src/view/com/profile/ProfileFollowers.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+1 -1
src/view/com/profile/ProfileFollows.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+1 -1
src/view/com/util/Alert.web.tsx
··· 1 - import {AlertButton, AlertStatic} from 'react-native' 1 + import {type AlertButton, type AlertStatic} from 'react-native' 2 2 3 3 class WebAlert implements Pick<AlertStatic, 'alert'> { 4 4 public alert(title: string, message?: string, buttons?: AlertButton[]): void {
+3 -2
src/view/com/util/BottomSheetCustomBackdrop.tsx
··· 1 - import React, {useMemo} from 'react' 1 + import {useMemo} from 'react' 2 2 import {TouchableWithoutFeedback} from 'react-native' 3 3 import Animated, { 4 4 Extrapolation, 5 5 interpolate, 6 6 useAnimatedStyle, 7 7 } from 'react-native-reanimated' 8 - import {BottomSheetBackdropProps} from '@discord/bottom-sheet/src' 8 + import {type BottomSheetBackdropProps} from '@discord/bottom-sheet/src' 9 9 import {msg} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 + import type React from 'react' 11 12 12 13 export function createCustomBackdrop( 13 14 onClose?: (() => void) | undefined,
+3 -3
src/view/com/util/EmptyState.tsx
··· 1 - import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 2 - import {IconProp} from '@fortawesome/fontawesome-svg-core' 1 + import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 2 + import {type IconProp} from '@fortawesome/fontawesome-svg-core' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 8 8 import {usePalette} from '#/lib/hooks/usePalette'
+2 -2
src/view/com/util/EmptyStateWithButton.tsx
··· 1 1 import {StyleSheet, View} from 'react-native' 2 - import {IconProp} from '@fortawesome/fontawesome-svg-core' 2 + import {type IconProp} from '@fortawesome/fontawesome-svg-core' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 8 8 import {usePalette} from '#/lib/hooks/usePalette'
+2 -2
src/view/com/util/ErrorBoundary.tsx
··· 1 - import {Component, ErrorInfo, ReactNode} from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {Component, type ErrorInfo, type ReactNode} from 'react' 2 + import {type StyleProp, type ViewStyle} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
-1
src/view/com/util/EventStopper.tsx
··· 1 1 import {View, type ViewStyle} from 'react-native' 2 - import type React from 'react' 3 2 4 3 /** 5 4 * This utility function captures events and stops
+2 -2
src/view/com/util/FeedInfoText.tsx
··· 1 - import {StyleProp, StyleSheet, TextStyle} from 'react-native' 1 + import {type StyleProp, StyleSheet, type TextStyle} from 'react-native' 2 2 3 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 4 - import {TypographyVariant} from '#/lib/ThemeContext' 4 + import {type TypographyVariant} from '#/lib/ThemeContext' 5 5 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 6 6 import {TextLinkOnWebOnly} from './Link' 7 7 import {LoadingPlaceholder} from './LoadingPlaceholder'
+1 -1
src/view/com/util/Link.tsx
··· 1 - import {memo, useCallback, useMemo} from 'react' 1 + import {type JSX, memo, useCallback, useMemo} from 'react' 2 2 import { 3 3 type GestureResponderEvent, 4 4 Platform,
+1 -1
src/view/com/util/List.tsx
··· 57 57 ...props 58 58 }, 59 59 ref, 60 - ): React.ReactElement => { 60 + ): React.ReactElement<any> => { 61 61 const isScrolledDown = useSharedValue(false) 62 62 const t = useTheme() 63 63 const dedupe = useDedupe(400)
+13 -6
src/view/com/util/List.web.tsx
··· 1 - import React, {isValidElement, memo, startTransition, useRef} from 'react' 1 + import React, { 2 + isValidElement, 3 + type JSX, 4 + memo, 5 + startTransition, 6 + useRef, 7 + } from 'react' 2 8 import { 3 9 type FlatListProps, 4 10 StyleSheet, ··· 202 208 behavior: animated ? 'smooth' : 'instant', 203 209 }) 204 210 }, 211 + 205 212 scrollToEnd({animated = true}: {animated?: boolean}) { 206 213 const element = getScrollableNode() 207 214 element?.scrollTo({ ··· 382 389 containerRef, 383 390 onVisibleChange, 384 391 }: { 385 - root?: React.RefObject<HTMLDivElement> | null 392 + root?: React.RefObject<HTMLDivElement | null> | null 386 393 topMargin?: string 387 394 bottomMargin?: string 388 - containerRef: React.RefObject<Element> 395 + containerRef: React.RefObject<Element | null> 389 396 onVisibleChange: (isVisible: boolean) => void 390 397 }) { 391 398 const [containerHeight, setContainerHeight] = React.useState(0) ··· 404 411 } 405 412 406 413 function useResizeObserver( 407 - ref: React.RefObject<Element>, 414 + ref: React.RefObject<Element | null>, 408 415 onResize: undefined | ((w: number, h: number) => void), 409 416 ) { 410 417 const handleResize = useNonReactiveCallback(onResize ?? (() => {})) ··· 509 516 onVisibleChange, 510 517 style, 511 518 }: { 512 - root?: React.RefObject<HTMLDivElement> | null 519 + root?: React.RefObject<HTMLDivElement | null> | null 513 520 topMargin?: string 514 521 bottomMargin?: string 515 522 onVisibleChange: (isVisible: boolean) => void ··· 551 558 552 559 export const List = memo(React.forwardRef(ListImpl)) as <ItemT>( 553 560 props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>}, 554 - ) => React.ReactElement 561 + ) => React.ReactElement<any> 555 562 556 563 // https://stackoverflow.com/questions/7944460/detect-safari-browser 557 564
+1 -1
src/view/com/util/LoadMoreRetryBtn.tsx
··· 1 1 import {StyleSheet} from 'react-native' 2 2 import { 3 3 FontAwesomeIcon, 4 - FontAwesomeIconStyle, 4 + type FontAwesomeIconStyle, 5 5 } from '@fortawesome/react-native-fontawesome' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette'
-17
src/view/com/util/LoadingScreen.tsx
··· 1 - import {ActivityIndicator, View} from 'react-native' 2 - 3 - import {s} from '#/lib/styles' 4 - import * as Layout from '#/components/Layout' 5 - 6 - /** 7 - * @deprecated use Layout compoenents directly 8 - */ 9 - export function LoadingScreen() { 10 - return ( 11 - <Layout.Content> 12 - <View style={s.p20}> 13 - <ActivityIndicator size="large" /> 14 - </View> 15 - </Layout.Content> 16 - ) 17 - }
+1 -1
src/view/com/util/MainScrollProvider.tsx
··· 1 1 import React, {useCallback, useEffect} from 'react' 2 - import {NativeScrollEvent} from 'react-native' 2 + import {type NativeScrollEvent} from 'react-native' 3 3 import {interpolate, useSharedValue, withSpring} from 'react-native-reanimated' 4 4 import EventEmitter from 'eventemitter3' 5 5
-1
src/view/com/util/PostMeta.tsx
··· 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {useQueryClient} from '@tanstack/react-query' 7 - import type React from 'react' 8 7 9 8 import {useActorStatus} from '#/lib/actor-status' 10 9 import {makeProfileLink} from '#/lib/routes/links'
+8 -3
src/view/com/util/PressableWithHover.tsx
··· 1 - import {forwardRef, PropsWithChildren} from 'react' 2 - import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 3 - import {View} from 'react-native' 1 + import {forwardRef, type PropsWithChildren} from 'react' 2 + import { 3 + Pressable, 4 + type PressableProps, 5 + type StyleProp, 6 + type ViewStyle, 7 + } from 'react-native' 8 + import {type View} from 'react-native' 4 9 5 10 import {addStyle} from '#/lib/styles' 6 11 import {useInteractionState} from '#/components/hooks/useInteractionState'
+2 -2
src/view/com/util/TimeElapsed.tsx
··· 1 - import React from 'react' 2 - import {I18n} from '@lingui/core' 1 + import React, {type JSX} from 'react' 2 + import {type I18n} from '@lingui/core' 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
+13 -7
src/view/com/util/UserAvatar.tsx
··· 1 - import React, {memo, useCallback, useMemo, useState} from 'react' 1 + import {memo, useCallback, useMemo, useState} from 'react' 2 2 import { 3 3 Image, 4 4 Pressable, ··· 375 375 } 376 376 }, [circular, size]) 377 377 378 - const onOpenCamera = React.useCallback(async () => { 378 + const onOpenCamera = useCallback(async () => { 379 379 if (!(await requestCameraAccessIfNeeded())) { 380 380 return 381 381 } ··· 389 389 ) 390 390 }, [onSelectNewAvatar, requestCameraAccessIfNeeded]) 391 391 392 - const onOpenLibrary = React.useCallback(async () => { 392 + const onOpenLibrary = useCallback(async () => { 393 393 if (!(await requestPhotoAccessIfNeeded())) { 394 394 return 395 395 } ··· 433 433 circular, 434 434 ]) 435 435 436 - const onRemoveAvatar = React.useCallback(() => { 436 + const onRemoveAvatar = useCallback(() => { 437 437 onSelectNewAvatar(null) 438 438 }, [onSelectNewAvatar]) 439 439 ··· 542 542 disableNavigation, 543 543 onBeforePress, 544 544 live, 545 - ...rest 545 + ...props 546 546 }: PreviewableUserAvatarProps): React.ReactNode => { 547 547 const {_} = useLingui() 548 548 const queryClient = useQueryClient() ··· 571 571 moderation={moderation} 572 572 type={profile.associated?.labeler ? 'labeler' : 'user'} 573 573 live={status.isActive || live} 574 - {...rest} 574 + {...props} 575 575 /> 576 576 ) 577 + 578 + const linkStyle = 579 + props.type !== 'algo' && props.type !== 'list' 580 + ? a.rounded_full 581 + : {borderRadius: props.size > 32 ? 8 : 3} 577 582 578 583 return ( 579 584 <ProfileHoverCard did={profile.did} disable={disableHoverCard}> ··· 610 615 did: profile.did, 611 616 handle: profile.handle, 612 617 })} 613 - onPress={onPress}> 618 + onPress={onPress} 619 + style={linkStyle}> 614 620 {avatarEl} 615 621 </Link> 616 622 )}
+2
src/view/com/util/ViewHeader.tsx
··· 1 + import {type JSX} from 'react' 2 + 1 3 import {Header} from '#/components/Layout' 2 4 3 5 /**
+4 -4
src/view/com/util/ViewSelector.tsx
··· 1 - import React, {useEffect, useState} from 'react' 1 + import React, {type JSX, useEffect, useState} from 'react' 2 2 import { 3 - NativeScrollEvent, 4 - NativeSyntheticEvent, 3 + type NativeScrollEvent, 4 + type NativeSyntheticEvent, 5 5 Pressable, 6 6 RefreshControl, 7 7 ScrollView, ··· 36 36 renderItem: (item: any) => JSX.Element 37 37 ListFooterComponent?: 38 38 | React.ComponentType<any> 39 - | React.ReactElement 39 + | React.ReactElement<any> 40 40 | null 41 41 | undefined 42 42 onSelectView?: (viewIndex: number) => void
+3 -3
src/view/com/util/Views.tsx
··· 1 1 import {forwardRef} from 'react' 2 - import {FlatListComponent} from 'react-native' 3 - import {View, ViewProps} from 'react-native' 2 + import {type FlatListComponent} from 'react-native' 3 + import {View, type ViewProps} from 'react-native' 4 4 import Animated from 'react-native-reanimated' 5 - import {FlatListPropsWithLayout} from 'react-native-reanimated' 5 + import {type FlatListPropsWithLayout} from 'react-native-reanimated' 6 6 7 7 // If you explode these into functions, don't forget to forwardRef! 8 8
+1 -1
src/view/com/util/WebAuxClickWrapper.tsx
··· 1 - import React from 'react' 2 1 import {Platform} from 'react-native' 2 + import type React from 'react' 3 3 4 4 const onMouseUp = (e: React.MouseEvent & {target: HTMLElement}) => { 5 5 // Only handle whenever it is the middle button
+3 -3
src/view/com/util/error/ErrorMessage.tsx
··· 1 1 import { 2 - StyleProp, 2 + type StyleProp, 3 3 StyleSheet, 4 4 TouchableOpacity, 5 5 View, 6 - ViewStyle, 6 + type ViewStyle, 7 7 } from 'react-native' 8 8 import { 9 9 FontAwesomeIcon, 10 - FontAwesomeIconStyle, 10 + type FontAwesomeIconStyle, 11 11 } from '@fortawesome/react-native-fontawesome' 12 12 import {msg} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/util/error/ErrorScreen.tsx
··· 1 1 import {View} from 'react-native' 2 2 import { 3 3 FontAwesomeIcon, 4 - FontAwesomeIconStyle, 4 + type FontAwesomeIconStyle, 5 5 } from '@fortawesome/react-native-fontawesome' 6 6 import {msg, Trans} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/util/fab/FAB.web.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 3 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 - import {FABInner, FABProps} from './FABInner' 4 + import {FABInner, type FABProps} from './FABInner' 5 5 6 6 export const FAB = (_opts: FABProps) => { 7 7 const {isDesktop} = useWebMediaQueries()
+12 -5
src/view/com/util/fab/FABInner.tsx
··· 1 - import {ComponentProps} from 'react' 2 - import {StyleSheet, TouchableWithoutFeedback} from 'react-native' 1 + import {type ComponentProps, type JSX} from 'react' 2 + import { 3 + type Pressable, 4 + type StyleProp, 5 + StyleSheet, 6 + type ViewStyle, 7 + } from 'react-native' 3 8 import Animated from 'react-native-reanimated' 4 9 import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 10 import {LinearGradient} from 'expo-linear-gradient' ··· 12 17 import {gradients} from '#/lib/styles' 13 18 import {isWeb} from '#/platform/detection' 14 19 import {ios} from '#/alf' 20 + import {atoms as a} from '#/alf' 15 21 16 - export interface FABProps 17 - extends ComponentProps<typeof TouchableWithoutFeedback> { 22 + export interface FABProps extends ComponentProps<typeof Pressable> { 18 23 testID?: string 19 24 icon: JSX.Element 25 + style?: StyleProp<ViewStyle> 20 26 } 21 27 22 - export function FABInner({testID, icon, onPress, ...props}: FABProps) { 28 + export function FABInner({testID, icon, onPress, style, ...props}: FABProps) { 23 29 const insets = useSafeAreaInsets() 24 30 const {isMobile, isTablet} = useWebMediaQueries() 25 31 const playHaptic = useHaptics() ··· 51 57 playHaptic('Heavy') 52 58 })} 53 59 targetScale={0.9} 60 + style={[a.rounded_full, style]} 54 61 {...props}> 55 62 <LinearGradient 56 63 colors={[gradients.blueLight.start, gradients.blueLight.end]}
+1 -1
src/view/com/util/forms/NativeDropdown.web.tsx
··· 161 161 menuRef, 162 162 }: { 163 163 items: DropdownItem[] 164 - menuRef: React.RefObject<HTMLDivElement> 164 + menuRef: React.RefObject<HTMLDivElement | null> 165 165 }) { 166 166 const pal = usePalette('default') 167 167 const theme = useTheme()
-1
src/view/com/util/images/Gallery.tsx
··· 4 4 import {type AppBskyEmbedImages} from '@atproto/api' 5 5 import {msg} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 - import type React from 'react' 8 7 9 8 import {type Dimensions} from '#/lib/media/types' 10 9 import {
+1 -1
src/view/com/util/images/Image.tsx
··· 1 - import {Image, ImageProps, ImageSource} from 'expo-image' 1 + import {Image, type ImageProps, type ImageSource} from 'expo-image' 2 2 3 3 interface HighPriorityImageProps extends ImageProps { 4 4 source: ImageSource
+1 -1
src/view/com/util/layouts/LoggedOutLayout.tsx
··· 1 - import React from 'react' 2 1 import {ScrollView, StyleSheet, View} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' 5 5 import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
+1 -1
src/view/icons/Logomark.tsx
··· 1 - import Svg, {Path, PathProps, SvgProps} from 'react-native-svg' 1 + import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg' 2 2 3 3 import {usePalette} from '#/lib/hooks/usePalette' 4 4
+4 -1
src/view/screens/CommunityGuidelines.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+4 -1
src/view/screens/CopyrightPolicy.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+1 -1
src/view/screens/NotFound.tsx
··· 9 9 } from '@react-navigation/native' 10 10 11 11 import {usePalette} from '#/lib/hooks/usePalette' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {useSetMinimalShellMode} from '#/state/shell' 15 15 import {Button} from '#/view/com/util/forms/Button'
+4 -1
src/view/screens/ProfileFeedLikedBy.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 6 + import { 7 + type CommonNavigatorParams, 8 + type NativeStackScreenProps, 9 + } from '#/lib/routes/types' 7 10 import {makeRecordUri} from '#/lib/strings/url-helpers' 8 11 import {useSetMinimalShellMode} from '#/state/shell' 9 12 import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
-1079
src/view/screens/ProfileList.tsx
··· 1 - import React, {useCallback, useMemo} from 'react' 2 - import {StyleSheet, View} from 'react-native' 3 - import {useAnimatedRef} from 'react-native-reanimated' 4 - import { 5 - AppBskyGraphDefs, 6 - AtUri, 7 - moderateUserList, 8 - type ModerationOpts, 9 - RichText as RichTextAPI, 10 - } from '@atproto/api' 11 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 12 - import {msg, Trans} from '@lingui/macro' 13 - import {useLingui} from '@lingui/react' 14 - import {useFocusEffect, useIsFocused} from '@react-navigation/native' 15 - import {useNavigation} from '@react-navigation/native' 16 - import {useQueryClient} from '@tanstack/react-query' 17 - 18 - import {useHaptics} from '#/lib/haptics' 19 - import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 20 - import {usePalette} from '#/lib/hooks/usePalette' 21 - import {useSetTitle} from '#/lib/hooks/useSetTitle' 22 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 23 - import {ComposeIcon2} from '#/lib/icons' 24 - import {makeListLink} from '#/lib/routes/links' 25 - import { 26 - type CommonNavigatorParams, 27 - type NativeStackScreenProps, 28 - } from '#/lib/routes/types' 29 - import {type NavigationProp} from '#/lib/routes/types' 30 - import {shareUrl} from '#/lib/sharing' 31 - import {cleanError} from '#/lib/strings/errors' 32 - import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers' 33 - import {s} from '#/lib/styles' 34 - import {logger} from '#/logger' 35 - import {isNative, isWeb} from '#/platform/detection' 36 - import {listenSoftReset} from '#/state/events' 37 - import {useModalControls} from '#/state/modals' 38 - import {useModerationOpts} from '#/state/preferences/moderation-opts' 39 - import { 40 - useListBlockMutation, 41 - useListDeleteMutation, 42 - useListMuteMutation, 43 - useListQuery, 44 - } from '#/state/queries/list' 45 - import {type FeedDescriptor} from '#/state/queries/post-feed' 46 - import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 47 - import { 48 - useAddSavedFeedsMutation, 49 - usePreferencesQuery, 50 - type UsePreferencesQueryResponse, 51 - useRemoveFeedMutation, 52 - useUpdateSavedFeedsMutation, 53 - } from '#/state/queries/preferences' 54 - import {useResolveUriQuery} from '#/state/queries/resolve-uri' 55 - import {truncateAndInvalidate} from '#/state/queries/util' 56 - import {useSession} from '#/state/session' 57 - import {useSetMinimalShellMode} from '#/state/shell' 58 - import {ListMembers} from '#/view/com/lists/ListMembers' 59 - import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' 60 - import {PostFeed} from '#/view/com/posts/PostFeed' 61 - import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader' 62 - import {EmptyState} from '#/view/com/util/EmptyState' 63 - import {FAB} from '#/view/com/util/fab/FAB' 64 - import {Button} from '#/view/com/util/forms/Button' 65 - import { 66 - type DropdownItem, 67 - NativeDropdown, 68 - } from '#/view/com/util/forms/NativeDropdown' 69 - import {type ListRef} from '#/view/com/util/List' 70 - import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 71 - import {LoadingScreen} from '#/view/com/util/LoadingScreen' 72 - import {Text} from '#/view/com/util/text/Text' 73 - import * as Toast from '#/view/com/util/Toast' 74 - import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 75 - import {atoms as a} from '#/alf' 76 - import {Button as NewButton, ButtonIcon, ButtonText} from '#/components/Button' 77 - import {useDialogControl} from '#/components/Dialog' 78 - import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 79 - import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 80 - import * as Layout from '#/components/Layout' 81 - import * as Hider from '#/components/moderation/Hider' 82 - import { 83 - ReportDialog, 84 - useReportDialogControl, 85 - } from '#/components/moderation/ReportDialog' 86 - import * as Prompt from '#/components/Prompt' 87 - import {RichText} from '#/components/RichText' 88 - 89 - interface SectionRef { 90 - scrollToTop: () => void 91 - } 92 - 93 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'> 94 - export function ProfileListScreen(props: Props) { 95 - return ( 96 - <Layout.Screen testID="profileListScreen"> 97 - <ProfileListScreenInner {...props} /> 98 - </Layout.Screen> 99 - ) 100 - } 101 - 102 - function ProfileListScreenInner(props: Props) { 103 - const {_} = useLingui() 104 - const {name: handleOrDid, rkey} = props.route.params 105 - const {data: resolvedUri, error: resolveError} = useResolveUriQuery( 106 - AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), 107 - ) 108 - const {data: preferences} = usePreferencesQuery() 109 - const {data: list, error: listError} = useListQuery(resolvedUri?.uri) 110 - const moderationOpts = useModerationOpts() 111 - 112 - if (resolveError) { 113 - return ( 114 - <Layout.Content> 115 - <ErrorScreen 116 - error={_( 117 - msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`, 118 - )} 119 - /> 120 - </Layout.Content> 121 - ) 122 - } 123 - if (listError) { 124 - return ( 125 - <Layout.Content> 126 - <ErrorScreen error={cleanError(listError)} /> 127 - </Layout.Content> 128 - ) 129 - } 130 - 131 - return resolvedUri && list && moderationOpts && preferences ? ( 132 - <ProfileListScreenLoaded 133 - {...props} 134 - uri={resolvedUri.uri} 135 - list={list} 136 - moderationOpts={moderationOpts} 137 - preferences={preferences} 138 - /> 139 - ) : ( 140 - <LoadingScreen /> 141 - ) 142 - } 143 - 144 - function ProfileListScreenLoaded({ 145 - route, 146 - uri, 147 - list, 148 - moderationOpts, 149 - preferences, 150 - }: Props & { 151 - uri: string 152 - list: AppBskyGraphDefs.ListView 153 - moderationOpts: ModerationOpts 154 - preferences: UsePreferencesQueryResponse 155 - }) { 156 - const {_} = useLingui() 157 - const queryClient = useQueryClient() 158 - const {openComposer} = useOpenComposer() 159 - const setMinimalShellMode = useSetMinimalShellMode() 160 - const {currentAccount} = useSession() 161 - const {rkey} = route.params 162 - const feedSectionRef = React.useRef<SectionRef>(null) 163 - const aboutSectionRef = React.useRef<SectionRef>(null) 164 - const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 165 - const isScreenFocused = useIsFocused() 166 - const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 167 - const isOwner = currentAccount?.did === list.creator.did 168 - const scrollElRef = useAnimatedRef() 169 - const addUserDialogControl = useDialogControl() 170 - const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 171 - 172 - const moderation = React.useMemo(() => { 173 - return moderateUserList(list, moderationOpts) 174 - }, [list, moderationOpts]) 175 - 176 - useSetTitle(isHidden ? _(msg`List Hidden`) : list.name) 177 - 178 - useFocusEffect( 179 - useCallback(() => { 180 - setMinimalShellMode(false) 181 - }, [setMinimalShellMode]), 182 - ) 183 - 184 - const onChangeMembers = useCallback(() => { 185 - if (isCurateList) { 186 - truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`)) 187 - } 188 - }, [list.uri, isCurateList, queryClient]) 189 - 190 - const onCurrentPageSelected = React.useCallback( 191 - (index: number) => { 192 - if (index === 0) { 193 - feedSectionRef.current?.scrollToTop() 194 - } else if (index === 1) { 195 - aboutSectionRef.current?.scrollToTop() 196 - } 197 - }, 198 - [feedSectionRef], 199 - ) 200 - 201 - const renderHeader = useCallback(() => { 202 - return <Header rkey={rkey} list={list} preferences={preferences} /> 203 - }, [rkey, list, preferences]) 204 - 205 - if (isCurateList) { 206 - return ( 207 - <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 208 - <Hider.Mask> 209 - <ListHiddenScreen list={list} preferences={preferences} /> 210 - </Hider.Mask> 211 - <Hider.Content> 212 - <View style={s.hContentRegion}> 213 - <PagerWithHeader 214 - items={sectionTitlesCurate} 215 - isHeaderReady={true} 216 - renderHeader={renderHeader} 217 - onCurrentPageSelected={onCurrentPageSelected}> 218 - {({headerHeight, scrollElRef, isFocused}) => ( 219 - <FeedSection 220 - ref={feedSectionRef} 221 - feed={`list|${uri}`} 222 - scrollElRef={scrollElRef as ListRef} 223 - headerHeight={headerHeight} 224 - isFocused={isScreenFocused && isFocused} 225 - isOwner={isOwner} 226 - onPressAddUser={addUserDialogControl.open} 227 - /> 228 - )} 229 - {({headerHeight, scrollElRef}) => ( 230 - <AboutSection 231 - ref={aboutSectionRef} 232 - scrollElRef={scrollElRef as ListRef} 233 - list={list} 234 - onPressAddUser={addUserDialogControl.open} 235 - headerHeight={headerHeight} 236 - /> 237 - )} 238 - </PagerWithHeader> 239 - <FAB 240 - testID="composeFAB" 241 - onPress={() => openComposer({})} 242 - icon={ 243 - <ComposeIcon2 244 - strokeWidth={1.5} 245 - size={29} 246 - style={{color: 'white'}} 247 - /> 248 - } 249 - accessibilityRole="button" 250 - accessibilityLabel={_(msg`New post`)} 251 - accessibilityHint="" 252 - /> 253 - </View> 254 - <ListAddRemoveUsersDialog 255 - control={addUserDialogControl} 256 - list={list} 257 - onChange={onChangeMembers} 258 - /> 259 - </Hider.Content> 260 - </Hider.Outer> 261 - ) 262 - } 263 - return ( 264 - <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 265 - <Hider.Mask> 266 - <ListHiddenScreen list={list} preferences={preferences} /> 267 - </Hider.Mask> 268 - <Hider.Content> 269 - <View style={s.hContentRegion}> 270 - <Layout.Center>{renderHeader()}</Layout.Center> 271 - <AboutSection 272 - list={list} 273 - scrollElRef={scrollElRef as ListRef} 274 - onPressAddUser={addUserDialogControl.open} 275 - headerHeight={0} 276 - /> 277 - <FAB 278 - testID="composeFAB" 279 - onPress={() => openComposer({})} 280 - icon={ 281 - <ComposeIcon2 282 - strokeWidth={1.5} 283 - size={29} 284 - style={{color: 'white'}} 285 - /> 286 - } 287 - accessibilityRole="button" 288 - accessibilityLabel={_(msg`New post`)} 289 - accessibilityHint="" 290 - /> 291 - </View> 292 - <ListAddRemoveUsersDialog 293 - control={addUserDialogControl} 294 - list={list} 295 - onChange={onChangeMembers} 296 - /> 297 - </Hider.Content> 298 - </Hider.Outer> 299 - ) 300 - } 301 - 302 - function Header({ 303 - rkey, 304 - list, 305 - preferences, 306 - }: { 307 - rkey: string 308 - list: AppBskyGraphDefs.ListView 309 - preferences: UsePreferencesQueryResponse 310 - }) { 311 - const pal = usePalette('default') 312 - const palInverted = usePalette('inverted') 313 - const {_} = useLingui() 314 - const navigation = useNavigation<NavigationProp>() 315 - const {currentAccount} = useSession() 316 - const reportDialogControl = useReportDialogControl() 317 - const {openModal} = useModalControls() 318 - const listMuteMutation = useListMuteMutation() 319 - const listBlockMutation = useListBlockMutation() 320 - const listDeleteMutation = useListDeleteMutation() 321 - const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist' 322 - const isModList = list.purpose === 'app.bsky.graph.defs#modlist' 323 - const isBlocking = !!list.viewer?.blocked 324 - const isMuting = !!list.viewer?.muted 325 - const isOwner = list.creator.did === currentAccount?.did 326 - const playHaptic = useHaptics() 327 - 328 - const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} = 329 - useAddSavedFeedsMutation() 330 - const {mutateAsync: removeSavedFeed, isPending: isRemovePending} = 331 - useRemoveFeedMutation() 332 - const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} = 333 - useUpdateSavedFeedsMutation() 334 - 335 - const isPending = 336 - isAddSavedFeedPending || isRemovePending || isUpdatingSavedFeeds 337 - 338 - const deleteListPromptControl = useDialogControl() 339 - const subscribeMutePromptControl = useDialogControl() 340 - const subscribeBlockPromptControl = useDialogControl() 341 - 342 - const savedFeedConfig = preferences?.savedFeeds?.find( 343 - f => f.value === list.uri, 344 - ) 345 - const isPinned = Boolean(savedFeedConfig?.pinned) 346 - 347 - const onTogglePinned = React.useCallback(async () => { 348 - playHaptic() 349 - 350 - try { 351 - if (savedFeedConfig) { 352 - const pinned = !savedFeedConfig.pinned 353 - await updateSavedFeeds([ 354 - { 355 - ...savedFeedConfig, 356 - pinned, 357 - }, 358 - ]) 359 - Toast.show( 360 - pinned 361 - ? _(msg`Pinned to your feeds`) 362 - : _(msg`Unpinned from your feeds`), 363 - ) 364 - } else { 365 - await addSavedFeeds([ 366 - { 367 - type: 'list', 368 - value: list.uri, 369 - pinned: true, 370 - }, 371 - ]) 372 - Toast.show(_(msg`Saved to your feeds`)) 373 - } 374 - } catch (e) { 375 - Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 376 - logger.error('Failed to toggle pinned feed', {message: e}) 377 - } 378 - }, [ 379 - playHaptic, 380 - addSavedFeeds, 381 - updateSavedFeeds, 382 - list.uri, 383 - _, 384 - savedFeedConfig, 385 - ]) 386 - 387 - const onRemoveFromSavedFeeds = React.useCallback(async () => { 388 - playHaptic() 389 - if (!savedFeedConfig) return 390 - try { 391 - await removeSavedFeed(savedFeedConfig) 392 - Toast.show(_(msg`Removed from your feeds`)) 393 - } catch (e) { 394 - Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 395 - logger.error('Failed to remove pinned list', {message: e}) 396 - } 397 - }, [playHaptic, removeSavedFeed, _, savedFeedConfig]) 398 - 399 - const onSubscribeMute = useCallback(async () => { 400 - try { 401 - await listMuteMutation.mutateAsync({uri: list.uri, mute: true}) 402 - Toast.show(_(msg({message: 'List muted', context: 'toast'}))) 403 - logger.metric( 404 - 'moderation:subscribedToList', 405 - {listType: 'mute'}, 406 - {statsig: true}, 407 - ) 408 - } catch { 409 - Toast.show( 410 - _( 411 - msg`There was an issue. Please check your internet connection and try again.`, 412 - ), 413 - ) 414 - } 415 - }, [list, listMuteMutation, _]) 416 - 417 - const onUnsubscribeMute = useCallback(async () => { 418 - try { 419 - await listMuteMutation.mutateAsync({uri: list.uri, mute: false}) 420 - Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 421 - logger.metric( 422 - 'moderation:unsubscribedFromList', 423 - {listType: 'mute'}, 424 - {statsig: true}, 425 - ) 426 - } catch { 427 - Toast.show( 428 - _( 429 - msg`There was an issue. Please check your internet connection and try again.`, 430 - ), 431 - ) 432 - } 433 - }, [list, listMuteMutation, _]) 434 - 435 - const onSubscribeBlock = useCallback(async () => { 436 - try { 437 - await listBlockMutation.mutateAsync({uri: list.uri, block: true}) 438 - Toast.show(_(msg({message: 'List blocked', context: 'toast'}))) 439 - logger.metric( 440 - 'moderation:subscribedToList', 441 - {listType: 'block'}, 442 - {statsig: true}, 443 - ) 444 - } catch { 445 - Toast.show( 446 - _( 447 - msg`There was an issue. Please check your internet connection and try again.`, 448 - ), 449 - ) 450 - } 451 - }, [list, listBlockMutation, _]) 452 - 453 - const onUnsubscribeBlock = useCallback(async () => { 454 - try { 455 - await listBlockMutation.mutateAsync({uri: list.uri, block: false}) 456 - Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 457 - logger.metric( 458 - 'moderation:unsubscribedFromList', 459 - {listType: 'block'}, 460 - {statsig: true}, 461 - ) 462 - } catch { 463 - Toast.show( 464 - _( 465 - msg`There was an issue. Please check your internet connection and try again.`, 466 - ), 467 - ) 468 - } 469 - }, [list, listBlockMutation, _]) 470 - 471 - const onPressEdit = useCallback(() => { 472 - openModal({ 473 - name: 'create-or-edit-list', 474 - list, 475 - }) 476 - }, [openModal, list]) 477 - 478 - const onPressDelete = useCallback(async () => { 479 - await listDeleteMutation.mutateAsync({uri: list.uri}) 480 - 481 - if (savedFeedConfig) { 482 - await removeSavedFeed(savedFeedConfig) 483 - } 484 - 485 - Toast.show(_(msg({message: 'List deleted', context: 'toast'}))) 486 - if (navigation.canGoBack()) { 487 - navigation.goBack() 488 - } else { 489 - navigation.navigate('Home') 490 - } 491 - }, [ 492 - list, 493 - listDeleteMutation, 494 - navigation, 495 - _, 496 - removeSavedFeed, 497 - savedFeedConfig, 498 - ]) 499 - 500 - const onPressReport = useCallback(() => { 501 - reportDialogControl.open() 502 - }, [reportDialogControl]) 503 - 504 - const onPressShare = useCallback(() => { 505 - const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) 506 - shareUrl(url) 507 - }, [list, rkey]) 508 - 509 - const onPressShareBsky = useCallback(() => { 510 - const url = toShareUrlBsky(`/profile/${list.creator.did}/lists/${rkey}`) 511 - shareUrl(url) 512 - }, [list, rkey]) 513 - 514 - const dropdownItems: DropdownItem[] = useMemo(() => { 515 - let items: DropdownItem[] = [ 516 - { 517 - testID: 'listHeaderDropdownShareBtn', 518 - label: isWeb ? _(msg`Copy link to list`) : _(msg`Share`), 519 - onPress: onPressShare, 520 - icon: { 521 - ios: { 522 - name: 'square.and.arrow.up', 523 - }, 524 - android: '', 525 - web: 'share', 526 - }, 527 - }, 528 - { 529 - testID: 'listHeaderDropdownShareBtn', 530 - label: isWeb ? _(msg`Copy via bsky.app`) : _(msg`Share`), 531 - onPress: onPressShareBsky, 532 - icon: { 533 - ios: { 534 - name: 'square.and.arrow.up', 535 - }, 536 - android: '', 537 - web: 'share', 538 - }, 539 - }, 540 - ] 541 - 542 - if (savedFeedConfig) { 543 - items.push({ 544 - testID: 'listHeaderDropdownRemoveFromFeedsBtn', 545 - label: _(msg`Remove from my feeds`), 546 - onPress: onRemoveFromSavedFeeds, 547 - icon: { 548 - ios: { 549 - name: 'trash', 550 - }, 551 - android: '', 552 - web: ['far', 'trash-can'], 553 - }, 554 - }) 555 - } 556 - 557 - if (isOwner) { 558 - items.push({label: 'separator'}) 559 - items.push({ 560 - testID: 'listHeaderDropdownEditBtn', 561 - label: _(msg`Edit list details`), 562 - onPress: onPressEdit, 563 - icon: { 564 - ios: { 565 - name: 'pencil', 566 - }, 567 - android: '', 568 - web: 'pen', 569 - }, 570 - }) 571 - items.push({ 572 - testID: 'listHeaderDropdownDeleteBtn', 573 - label: _(msg`Delete list`), 574 - onPress: deleteListPromptControl.open, 575 - icon: { 576 - ios: { 577 - name: 'trash', 578 - }, 579 - android: '', 580 - web: ['far', 'trash-can'], 581 - }, 582 - }) 583 - } else { 584 - items.push({label: 'separator'}) 585 - items.push({ 586 - testID: 'listHeaderDropdownReportBtn', 587 - label: _(msg`Report list`), 588 - onPress: onPressReport, 589 - icon: { 590 - ios: { 591 - name: 'exclamationmark.triangle', 592 - }, 593 - android: '', 594 - web: 'circle-exclamation', 595 - }, 596 - }) 597 - } 598 - if (isModList && isPinned) { 599 - items.push({label: 'separator'}) 600 - items.push({ 601 - testID: 'listHeaderDropdownUnpinBtn', 602 - label: _(msg`Unpin moderation list`), 603 - onPress: 604 - isPending || !savedFeedConfig 605 - ? undefined 606 - : () => removeSavedFeed(savedFeedConfig), 607 - icon: { 608 - ios: { 609 - name: 'pin', 610 - }, 611 - android: '', 612 - web: 'thumbtack', 613 - }, 614 - }) 615 - } 616 - if (isCurateList && (isBlocking || isMuting)) { 617 - items.push({label: 'separator'}) 618 - 619 - if (isMuting) { 620 - items.push({ 621 - testID: 'listHeaderDropdownMuteBtn', 622 - label: _(msg`Unmute list`), 623 - onPress: onUnsubscribeMute, 624 - icon: { 625 - ios: { 626 - name: 'eye', 627 - }, 628 - android: '', 629 - web: 'eye', 630 - }, 631 - }) 632 - } 633 - 634 - if (isBlocking) { 635 - items.push({ 636 - testID: 'listHeaderDropdownBlockBtn', 637 - label: _(msg`Unblock list`), 638 - onPress: onUnsubscribeBlock, 639 - icon: { 640 - ios: { 641 - name: 'person.fill.xmark', 642 - }, 643 - android: '', 644 - web: 'user-slash', 645 - }, 646 - }) 647 - } 648 - } 649 - return items 650 - }, [ 651 - _, 652 - onPressShare, 653 - onPressShareBsky, 654 - savedFeedConfig, 655 - isOwner, 656 - isModList, 657 - isPinned, 658 - isCurateList, 659 - isBlocking, 660 - isMuting, 661 - onRemoveFromSavedFeeds, 662 - onPressEdit, 663 - deleteListPromptControl.open, 664 - onPressReport, 665 - isPending, 666 - removeSavedFeed, 667 - onUnsubscribeMute, 668 - onUnsubscribeBlock, 669 - ]) 670 - 671 - const subscribeDropdownItems: DropdownItem[] = useMemo(() => { 672 - return [ 673 - { 674 - testID: 'subscribeDropdownMuteBtn', 675 - label: _(msg`Mute accounts`), 676 - onPress: subscribeMutePromptControl.open, 677 - icon: { 678 - ios: { 679 - name: 'speaker.slash', 680 - }, 681 - android: '', 682 - web: 'user-slash', 683 - }, 684 - }, 685 - { 686 - testID: 'subscribeDropdownBlockBtn', 687 - label: _(msg`Block accounts`), 688 - onPress: subscribeBlockPromptControl.open, 689 - icon: { 690 - ios: { 691 - name: 'person.fill.xmark', 692 - }, 693 - android: '', 694 - web: 'ban', 695 - }, 696 - }, 697 - ] 698 - }, [_, subscribeMutePromptControl.open, subscribeBlockPromptControl.open]) 699 - 700 - const descriptionRT = useMemo( 701 - () => 702 - list.description 703 - ? new RichTextAPI({ 704 - text: list.description, 705 - facets: list.descriptionFacets, 706 - }) 707 - : undefined, 708 - [list], 709 - ) 710 - 711 - return ( 712 - <> 713 - <ProfileSubpageHeader 714 - href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} 715 - title={list.name} 716 - avatar={list.avatar} 717 - isOwner={list.creator.did === currentAccount?.did} 718 - creator={list.creator} 719 - purpose={list.purpose} 720 - avatarType="list"> 721 - <ReportDialog 722 - control={reportDialogControl} 723 - subject={{ 724 - ...list, 725 - $type: 'app.bsky.graph.defs#listView', 726 - }} 727 - /> 728 - {isCurateList ? ( 729 - <Button 730 - testID={isPinned ? 'unpinBtn' : 'pinBtn'} 731 - type={isPinned ? 'default' : 'inverted'} 732 - label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} 733 - onPress={onTogglePinned} 734 - disabled={isPending} 735 - /> 736 - ) : isModList ? ( 737 - isBlocking ? ( 738 - <Button 739 - testID="unblockBtn" 740 - type="default" 741 - label={_(msg`Unblock`)} 742 - onPress={onUnsubscribeBlock} 743 - /> 744 - ) : isMuting ? ( 745 - <Button 746 - testID="unmuteBtn" 747 - type="default" 748 - label={_(msg`Unmute`)} 749 - onPress={onUnsubscribeMute} 750 - /> 751 - ) : ( 752 - <NativeDropdown 753 - testID="subscribeBtn" 754 - items={subscribeDropdownItems} 755 - accessibilityLabel={_(msg`Subscribe to this list`)} 756 - accessibilityHint=""> 757 - <View style={[palInverted.view, styles.btn]}> 758 - <Text style={palInverted.text}> 759 - <Trans>Subscribe</Trans> 760 - </Text> 761 - </View> 762 - </NativeDropdown> 763 - ) 764 - ) : null} 765 - <NativeDropdown 766 - testID="headerDropdownBtn" 767 - items={dropdownItems} 768 - accessibilityLabel={_(msg`More options`)} 769 - accessibilityHint=""> 770 - <View style={[pal.viewLight, styles.btn]}> 771 - <FontAwesomeIcon 772 - icon="ellipsis" 773 - size={20} 774 - color={pal.colors.text} 775 - /> 776 - </View> 777 - </NativeDropdown> 778 - 779 - <Prompt.Basic 780 - control={deleteListPromptControl} 781 - title={_(msg`Delete this list?`)} 782 - description={_( 783 - msg`If you delete this list, you won't be able to recover it.`, 784 - )} 785 - onConfirm={onPressDelete} 786 - confirmButtonCta={_(msg`Delete`)} 787 - confirmButtonColor="negative" 788 - /> 789 - 790 - <Prompt.Basic 791 - control={subscribeMutePromptControl} 792 - title={_(msg`Mute these accounts?`)} 793 - description={_( 794 - msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, 795 - )} 796 - onConfirm={onSubscribeMute} 797 - confirmButtonCta={_(msg`Mute list`)} 798 - /> 799 - 800 - <Prompt.Basic 801 - control={subscribeBlockPromptControl} 802 - title={_(msg`Block these accounts?`)} 803 - description={_( 804 - msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 805 - )} 806 - onConfirm={onSubscribeBlock} 807 - confirmButtonCta={_(msg`Block list`)} 808 - confirmButtonColor="negative" 809 - /> 810 - </ProfileSubpageHeader> 811 - {descriptionRT ? ( 812 - <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> 813 - <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> 814 - </View> 815 - ) : null} 816 - </> 817 - ) 818 - } 819 - 820 - interface FeedSectionProps { 821 - feed: FeedDescriptor 822 - headerHeight: number 823 - scrollElRef: ListRef 824 - isFocused: boolean 825 - isOwner: boolean 826 - onPressAddUser: () => void 827 - } 828 - const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( 829 - function FeedSectionImpl( 830 - {feed, scrollElRef, headerHeight, isFocused, isOwner, onPressAddUser}, 831 - ref, 832 - ) { 833 - const queryClient = useQueryClient() 834 - const [hasNew, setHasNew] = React.useState(false) 835 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 836 - const isScreenFocused = useIsFocused() 837 - const {_} = useLingui() 838 - 839 - const onScrollToTop = useCallback(() => { 840 - scrollElRef.current?.scrollToOffset({ 841 - animated: isNative, 842 - offset: -headerHeight, 843 - }) 844 - queryClient.resetQueries({queryKey: FEED_RQKEY(feed)}) 845 - setHasNew(false) 846 - }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) 847 - React.useImperativeHandle(ref, () => ({ 848 - scrollToTop: onScrollToTop, 849 - })) 850 - 851 - React.useEffect(() => { 852 - if (!isScreenFocused) { 853 - return 854 - } 855 - return listenSoftReset(onScrollToTop) 856 - }, [onScrollToTop, isScreenFocused]) 857 - 858 - const renderPostsEmpty = useCallback(() => { 859 - return ( 860 - <View style={[a.gap_xl, a.align_center]}> 861 - <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 862 - {isOwner && ( 863 - <NewButton 864 - label={_(msg`Start adding people`)} 865 - onPress={onPressAddUser} 866 - color="primary" 867 - size="small" 868 - variant="solid"> 869 - <ButtonIcon icon={PersonPlusIcon} /> 870 - <ButtonText> 871 - <Trans>Start adding people!</Trans> 872 - </ButtonText> 873 - </NewButton> 874 - )} 875 - </View> 876 - ) 877 - }, [_, onPressAddUser, isOwner]) 878 - 879 - return ( 880 - <View> 881 - <PostFeed 882 - testID="listFeed" 883 - enabled={isFocused} 884 - feed={feed} 885 - pollInterval={60e3} 886 - disablePoll={hasNew} 887 - scrollElRef={scrollElRef} 888 - onHasNew={setHasNew} 889 - onScrolledDownChange={setIsScrolledDown} 890 - renderEmptyState={renderPostsEmpty} 891 - headerOffset={headerHeight} 892 - /> 893 - {(isScrolledDown || hasNew) && ( 894 - <LoadLatestBtn 895 - onPress={onScrollToTop} 896 - label={_(msg`Load new posts`)} 897 - showIndicator={hasNew} 898 - /> 899 - )} 900 - </View> 901 - ) 902 - }, 903 - ) 904 - 905 - interface AboutSectionProps { 906 - list: AppBskyGraphDefs.ListView 907 - onPressAddUser: () => void 908 - headerHeight: number 909 - scrollElRef: ListRef 910 - } 911 - const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( 912 - function AboutSectionImpl( 913 - {list, onPressAddUser, headerHeight, scrollElRef}, 914 - ref, 915 - ) { 916 - const {_} = useLingui() 917 - const {currentAccount} = useSession() 918 - const {isMobile} = useWebMediaQueries() 919 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 920 - const isOwner = list.creator.did === currentAccount?.did 921 - 922 - const onScrollToTop = useCallback(() => { 923 - scrollElRef.current?.scrollToOffset({ 924 - animated: isNative, 925 - offset: -headerHeight, 926 - }) 927 - }, [scrollElRef, headerHeight]) 928 - 929 - React.useImperativeHandle(ref, () => ({ 930 - scrollToTop: onScrollToTop, 931 - })) 932 - 933 - const renderHeader = React.useCallback(() => { 934 - if (!isOwner) { 935 - return <View /> 936 - } 937 - if (isMobile) { 938 - return ( 939 - <View style={[a.px_sm, a.py_sm]}> 940 - <NewButton 941 - testID="addUserBtn" 942 - label={_(msg`Add a user to this list`)} 943 - onPress={onPressAddUser} 944 - color="primary" 945 - size="small" 946 - variant="outline" 947 - style={[a.py_md]}> 948 - <ButtonIcon icon={PersonPlusIcon} /> 949 - <ButtonText> 950 - <Trans>Add people</Trans> 951 - </ButtonText> 952 - </NewButton> 953 - </View> 954 - ) 955 - } 956 - return ( 957 - <View style={[a.px_lg, a.py_md, a.flex_row_reverse]}> 958 - <NewButton 959 - testID="addUserBtn" 960 - label={_(msg`Add a user to this list`)} 961 - onPress={onPressAddUser} 962 - color="primary" 963 - size="small" 964 - variant="ghost" 965 - style={[a.py_sm]}> 966 - <ButtonIcon icon={PersonPlusIcon} /> 967 - <ButtonText> 968 - <Trans>Add people</Trans> 969 - </ButtonText> 970 - </NewButton> 971 - </View> 972 - ) 973 - }, [isOwner, _, onPressAddUser, isMobile]) 974 - 975 - const renderEmptyState = useCallback(() => { 976 - return ( 977 - <View style={[a.gap_xl, a.align_center]}> 978 - <EmptyState 979 - icon="users-slash" 980 - message={_(msg`This list is empty.`)} 981 - /> 982 - {isOwner && ( 983 - <NewButton 984 - testID="emptyStateAddUserBtn" 985 - label={_(msg`Start adding people`)} 986 - onPress={onPressAddUser} 987 - color="primary" 988 - size="small" 989 - variant="solid"> 990 - <ButtonIcon icon={PersonPlusIcon} /> 991 - <ButtonText> 992 - <Trans>Start adding people!</Trans> 993 - </ButtonText> 994 - </NewButton> 995 - )} 996 - </View> 997 - ) 998 - }, [_, onPressAddUser, isOwner]) 999 - 1000 - return ( 1001 - <View> 1002 - <ListMembers 1003 - testID="listItems" 1004 - list={list.uri} 1005 - scrollElRef={scrollElRef} 1006 - renderHeader={renderHeader} 1007 - renderEmptyState={renderEmptyState} 1008 - headerOffset={headerHeight} 1009 - onScrolledDownChange={setIsScrolledDown} 1010 - /> 1011 - {isScrolledDown && ( 1012 - <LoadLatestBtn 1013 - onPress={onScrollToTop} 1014 - label={_(msg`Scroll to top`)} 1015 - showIndicator={false} 1016 - /> 1017 - )} 1018 - </View> 1019 - ) 1020 - }, 1021 - ) 1022 - 1023 - function ErrorScreen({error}: {error: string}) { 1024 - const pal = usePalette('default') 1025 - const navigation = useNavigation<NavigationProp>() 1026 - const {_} = useLingui() 1027 - const onPressBack = useCallback(() => { 1028 - if (navigation.canGoBack()) { 1029 - navigation.goBack() 1030 - } else { 1031 - navigation.navigate('Home') 1032 - } 1033 - }, [navigation]) 1034 - 1035 - return ( 1036 - <View 1037 - style={[ 1038 - pal.view, 1039 - pal.border, 1040 - { 1041 - paddingHorizontal: 18, 1042 - paddingVertical: 14, 1043 - borderTopWidth: StyleSheet.hairlineWidth, 1044 - }, 1045 - ]}> 1046 - <Text type="title-lg" style={[pal.text, s.mb10]}> 1047 - <Trans>Could not load list</Trans> 1048 - </Text> 1049 - <Text type="md" style={[pal.text, s.mb20]}> 1050 - {error} 1051 - </Text> 1052 - 1053 - <View style={{flexDirection: 'row'}}> 1054 - <Button 1055 - type="default" 1056 - accessibilityLabel={_(msg`Go back`)} 1057 - accessibilityHint={_(msg`Returns to previous page`)} 1058 - onPress={onPressBack} 1059 - style={{flexShrink: 1}}> 1060 - <Text type="button" style={pal.text}> 1061 - <Trans>Go Back</Trans> 1062 - </Text> 1063 - </Button> 1064 - </View> 1065 - </View> 1066 - ) 1067 - } 1068 - 1069 - const styles = StyleSheet.create({ 1070 - btn: { 1071 - flexDirection: 'row', 1072 - alignItems: 'center', 1073 - gap: 6, 1074 - paddingVertical: 7, 1075 - paddingHorizontal: 14, 1076 - borderRadius: 50, 1077 - marginLeft: 6, 1078 - }, 1079 - })
-450
src/view/screens/SavedFeeds.tsx
··· 1 - import React from 'react' 2 - import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' 3 - import Animated, {LinearTransition} from 'react-native-reanimated' 4 - import {type AppBskyActorDefs} from '@atproto/api' 5 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6 - import {msg, Trans} from '@lingui/macro' 7 - import {useLingui} from '@lingui/react' 8 - import {useFocusEffect} from '@react-navigation/native' 9 - import {useNavigation} from '@react-navigation/native' 10 - import {type NativeStackScreenProps} from '@react-navigation/native-stack' 11 - 12 - import {useHaptics} from '#/lib/haptics' 13 - import {usePalette} from '#/lib/hooks/usePalette' 14 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 15 - import { 16 - type CommonNavigatorParams, 17 - type NavigationProp, 18 - } from '#/lib/routes/types' 19 - import {colors, s} from '#/lib/styles' 20 - import {logger} from '#/logger' 21 - import { 22 - useOverwriteSavedFeedsMutation, 23 - usePreferencesQuery, 24 - } from '#/state/queries/preferences' 25 - import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 26 - import {useSetMinimalShellMode} from '#/state/shell' 27 - import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' 28 - import {TextLink} from '#/view/com/util/Link' 29 - import {Text} from '#/view/com/util/text/Text' 30 - import * as Toast from '#/view/com/util/Toast' 31 - import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' 32 - import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' 33 - import {atoms as a, useTheme} from '#/alf' 34 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 35 - import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 36 - import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 37 - import * as Layout from '#/components/Layout' 38 - import {Loader} from '#/components/Loader' 39 - import {Text as NewText} from '#/components/Typography' 40 - 41 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 42 - export function SavedFeeds({}: Props) { 43 - const {data: preferences} = usePreferencesQuery() 44 - if (!preferences) { 45 - return <View /> 46 - } 47 - return <SavedFeedsInner preferences={preferences} /> 48 - } 49 - 50 - function SavedFeedsInner({ 51 - preferences, 52 - }: { 53 - preferences: UsePreferencesQueryResponse 54 - }) { 55 - const pal = usePalette('default') 56 - const {_} = useLingui() 57 - const {isMobile, isDesktop} = useWebMediaQueries() 58 - const setMinimalShellMode = useSetMinimalShellMode() 59 - const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} = 60 - useOverwriteSavedFeedsMutation() 61 - const navigation = useNavigation<NavigationProp>() 62 - 63 - /* 64 - * Use optimistic data if exists and no error, otherwise fallback to remote 65 - * data 66 - */ 67 - const [currentFeeds, setCurrentFeeds] = React.useState( 68 - () => preferences.savedFeeds || [], 69 - ) 70 - const hasUnsavedChanges = currentFeeds !== preferences.savedFeeds 71 - const pinnedFeeds = currentFeeds.filter(f => f.pinned) 72 - const unpinnedFeeds = currentFeeds.filter(f => !f.pinned) 73 - const noSavedFeedsOfAnyType = pinnedFeeds.length + unpinnedFeeds.length === 0 74 - const noFollowingFeed = 75 - currentFeeds.every(f => f.type !== 'timeline') && !noSavedFeedsOfAnyType 76 - 77 - useFocusEffect( 78 - React.useCallback(() => { 79 - setMinimalShellMode(false) 80 - }, [setMinimalShellMode]), 81 - ) 82 - 83 - const onSaveChanges = React.useCallback(async () => { 84 - try { 85 - await overwriteSavedFeeds(currentFeeds) 86 - Toast.show(_(msg({message: 'Feeds updated!', context: 'toast'}))) 87 - if (navigation.canGoBack()) { 88 - navigation.goBack() 89 - } else { 90 - navigation.navigate('Feeds') 91 - } 92 - } catch (e) { 93 - Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 94 - logger.error('Failed to toggle pinned feed', {message: e}) 95 - } 96 - }, [_, overwriteSavedFeeds, currentFeeds, navigation]) 97 - 98 - return ( 99 - <Layout.Screen> 100 - <Layout.Header.Outer> 101 - <Layout.Header.BackButton /> 102 - <Layout.Header.Content align="left"> 103 - <Layout.Header.TitleText> 104 - <Trans>Feeds</Trans> 105 - </Layout.Header.TitleText> 106 - </Layout.Header.Content> 107 - <Button 108 - testID="saveChangesBtn" 109 - size="small" 110 - variant={hasUnsavedChanges ? 'solid' : 'solid'} 111 - color={hasUnsavedChanges ? 'primary' : 'secondary'} 112 - onPress={onSaveChanges} 113 - label={_(msg`Save changes`)} 114 - disabled={isOverwritePending || !hasUnsavedChanges}> 115 - <ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} /> 116 - <ButtonText> 117 - {isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>} 118 - </ButtonText> 119 - </Button> 120 - </Layout.Header.Outer> 121 - 122 - <Layout.Content> 123 - {noSavedFeedsOfAnyType && ( 124 - <View style={[pal.border, a.border_b]}> 125 - <NoSavedFeedsOfAnyType /> 126 - </View> 127 - )} 128 - 129 - <View style={[pal.text, pal.border, styles.title]}> 130 - <Text type="title" style={pal.text}> 131 - <Trans>Pinned Feeds</Trans> 132 - </Text> 133 - </View> 134 - 135 - {preferences ? ( 136 - !pinnedFeeds.length ? ( 137 - <View 138 - style={[ 139 - pal.border, 140 - isMobile && s.flex1, 141 - pal.viewLight, 142 - styles.empty, 143 - ]}> 144 - <Text type="lg" style={[pal.text]}> 145 - <Trans>You don't have any pinned feeds.</Trans> 146 - </Text> 147 - </View> 148 - ) : ( 149 - pinnedFeeds.map(f => ( 150 - <ListItem 151 - key={f.id} 152 - feed={f} 153 - isPinned 154 - currentFeeds={currentFeeds} 155 - setCurrentFeeds={setCurrentFeeds} 156 - preferences={preferences} 157 - /> 158 - )) 159 - ) 160 - ) : ( 161 - <ActivityIndicator style={{marginTop: 20}} /> 162 - )} 163 - 164 - {noFollowingFeed && ( 165 - <View style={[pal.border, a.border_b]}> 166 - <NoFollowingFeed /> 167 - </View> 168 - )} 169 - 170 - <View style={[pal.text, pal.border, styles.title]}> 171 - <Text type="title" style={pal.text}> 172 - <Trans>Saved Feeds</Trans> 173 - </Text> 174 - </View> 175 - {preferences ? ( 176 - !unpinnedFeeds.length ? ( 177 - <View 178 - style={[ 179 - pal.border, 180 - isMobile && s.flex1, 181 - pal.viewLight, 182 - styles.empty, 183 - ]}> 184 - <Text type="lg" style={[pal.text]}> 185 - <Trans>You don't have any saved feeds.</Trans> 186 - </Text> 187 - </View> 188 - ) : ( 189 - unpinnedFeeds.map(f => ( 190 - <ListItem 191 - key={f.id} 192 - feed={f} 193 - isPinned={false} 194 - currentFeeds={currentFeeds} 195 - setCurrentFeeds={setCurrentFeeds} 196 - preferences={preferences} 197 - /> 198 - )) 199 - ) 200 - ) : ( 201 - <ActivityIndicator style={{marginTop: 20}} /> 202 - )} 203 - 204 - <View style={styles.footerText}> 205 - <Text type="sm" style={pal.textLight}> 206 - <Trans> 207 - Feeds are custom algorithms that users build with a little coding 208 - expertise.{' '} 209 - <TextLink 210 - type="sm" 211 - style={pal.link} 212 - href="https://github.com/bluesky-social/feed-generator" 213 - text={_(msg`See this guide`)} 214 - />{' '} 215 - for more information. 216 - </Trans> 217 - </Text> 218 - </View> 219 - </Layout.Content> 220 - </Layout.Screen> 221 - ) 222 - } 223 - 224 - function ListItem({ 225 - feed, 226 - isPinned, 227 - currentFeeds, 228 - setCurrentFeeds, 229 - }: { 230 - feed: AppBskyActorDefs.SavedFeed 231 - isPinned: boolean 232 - currentFeeds: AppBskyActorDefs.SavedFeed[] 233 - setCurrentFeeds: React.Dispatch<AppBskyActorDefs.SavedFeed[]> 234 - preferences: UsePreferencesQueryResponse 235 - }) { 236 - const {_} = useLingui() 237 - const pal = usePalette('default') 238 - const playHaptic = useHaptics() 239 - const feedUri = feed.value 240 - 241 - const onTogglePinned = React.useCallback(async () => { 242 - playHaptic() 243 - setCurrentFeeds( 244 - currentFeeds.map(f => 245 - f.id === feed.id ? {...feed, pinned: !feed.pinned} : f, 246 - ), 247 - ) 248 - }, [playHaptic, feed, currentFeeds, setCurrentFeeds]) 249 - 250 - const onPressUp = React.useCallback(async () => { 251 - if (!isPinned) return 252 - 253 - const nextFeeds = currentFeeds.slice() 254 - const ids = currentFeeds.map(f => f.id) 255 - const index = ids.indexOf(feed.id) 256 - const nextIndex = index - 1 257 - 258 - if (index === -1 || index === 0) return 259 - ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 260 - nextFeeds[nextIndex], 261 - nextFeeds[index], 262 - ] 263 - 264 - setCurrentFeeds(nextFeeds) 265 - }, [feed, isPinned, setCurrentFeeds, currentFeeds]) 266 - 267 - const onPressDown = React.useCallback(async () => { 268 - if (!isPinned) return 269 - 270 - const nextFeeds = currentFeeds.slice() 271 - const ids = currentFeeds.map(f => f.id) 272 - const index = ids.indexOf(feed.id) 273 - const nextIndex = index + 1 274 - 275 - if (index === -1 || index >= nextFeeds.filter(f => f.pinned).length - 1) 276 - return 277 - ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 278 - nextFeeds[nextIndex], 279 - nextFeeds[index], 280 - ] 281 - 282 - setCurrentFeeds(nextFeeds) 283 - }, [feed, isPinned, setCurrentFeeds, currentFeeds]) 284 - 285 - const onPressRemove = React.useCallback(async () => { 286 - playHaptic() 287 - setCurrentFeeds(currentFeeds.filter(f => f.id !== feed.id)) 288 - }, [playHaptic, feed, currentFeeds, setCurrentFeeds]) 289 - 290 - return ( 291 - <Animated.View 292 - style={[styles.itemContainer, pal.border]} 293 - layout={LinearTransition.duration(100)}> 294 - {feed.type === 'timeline' ? ( 295 - <FollowingFeedCard /> 296 - ) : ( 297 - <FeedSourceCard 298 - key={feedUri} 299 - feedUri={feedUri} 300 - style={[isPinned && a.pr_sm]} 301 - showMinimalPlaceholder 302 - hideTopBorder={true} 303 - /> 304 - )} 305 - {isPinned ? ( 306 - <> 307 - <Pressable 308 - accessibilityRole="button" 309 - onPress={onPressUp} 310 - hitSlop={5} 311 - style={state => ({ 312 - backgroundColor: pal.viewLight.backgroundColor, 313 - paddingHorizontal: 12, 314 - paddingVertical: 10, 315 - borderRadius: 4, 316 - marginRight: 8, 317 - opacity: state.hovered || state.pressed ? 0.5 : 1, 318 - })} 319 - testID={`feed-${feed.type}-moveUp`}> 320 - <FontAwesomeIcon 321 - icon="arrow-up" 322 - size={14} 323 - style={[pal.textLight]} 324 - /> 325 - </Pressable> 326 - <Pressable 327 - accessibilityRole="button" 328 - onPress={onPressDown} 329 - hitSlop={5} 330 - style={state => ({ 331 - backgroundColor: pal.viewLight.backgroundColor, 332 - paddingHorizontal: 12, 333 - paddingVertical: 10, 334 - borderRadius: 4, 335 - marginRight: 8, 336 - opacity: state.hovered || state.pressed ? 0.5 : 1, 337 - })} 338 - testID={`feed-${feed.type}-moveDown`}> 339 - <FontAwesomeIcon 340 - icon="arrow-down" 341 - size={14} 342 - style={[pal.textLight]} 343 - /> 344 - </Pressable> 345 - </> 346 - ) : ( 347 - <Pressable 348 - testID={`feed-${feedUri}-toggleSave`} 349 - accessibilityRole="button" 350 - accessibilityLabel={_(msg`Remove from my feeds`)} 351 - accessibilityHint="" 352 - onPress={onPressRemove} 353 - hitSlop={5} 354 - style={state => ({ 355 - marginRight: 8, 356 - paddingHorizontal: 12, 357 - paddingVertical: 10, 358 - borderRadius: 4, 359 - opacity: state.hovered || state.focused ? 0.5 : 1, 360 - })}> 361 - <FontAwesomeIcon 362 - icon={['far', 'trash-can']} 363 - size={19} 364 - color={pal.colors.icon} 365 - /> 366 - </Pressable> 367 - )} 368 - <View style={{paddingRight: 16}}> 369 - <Pressable 370 - accessibilityRole="button" 371 - hitSlop={5} 372 - onPress={onTogglePinned} 373 - style={state => ({ 374 - backgroundColor: pal.viewLight.backgroundColor, 375 - paddingHorizontal: 12, 376 - paddingVertical: 10, 377 - borderRadius: 4, 378 - opacity: state.hovered || state.focused ? 0.5 : 1, 379 - })} 380 - testID={`feed-${feed.type}-togglePin`}> 381 - <FontAwesomeIcon 382 - icon="thumb-tack" 383 - size={14} 384 - color={isPinned ? colors.blue3 : pal.colors.icon} 385 - /> 386 - </Pressable> 387 - </View> 388 - </Animated.View> 389 - ) 390 - } 391 - 392 - function FollowingFeedCard() { 393 - const t = useTheme() 394 - return ( 395 - <View style={[a.flex_row, a.align_center, a.flex_1, a.p_lg]}> 396 - <View 397 - style={[ 398 - a.align_center, 399 - a.justify_center, 400 - a.rounded_sm, 401 - a.mr_md, 402 - { 403 - width: 36, 404 - height: 36, 405 - backgroundColor: t.palette.primary_500, 406 - }, 407 - ]}> 408 - <FilterTimeline 409 - style={[ 410 - { 411 - width: 22, 412 - height: 22, 413 - }, 414 - ]} 415 - fill={t.palette.white} 416 - /> 417 - </View> 418 - <View style={[a.flex_1, a.flex_row, a.gap_sm, a.align_center]}> 419 - <NewText style={[a.text_sm, a.font_bold, a.leading_snug]}> 420 - <Trans context="feed-name">Following</Trans> 421 - </NewText> 422 - </View> 423 - </View> 424 - ) 425 - } 426 - 427 - const styles = StyleSheet.create({ 428 - empty: { 429 - paddingHorizontal: 20, 430 - paddingVertical: 20, 431 - borderRadius: 8, 432 - marginHorizontal: 10, 433 - marginTop: 10, 434 - }, 435 - title: { 436 - paddingHorizontal: 14, 437 - paddingTop: 20, 438 - paddingBottom: 10, 439 - borderBottomWidth: StyleSheet.hairlineWidth, 440 - }, 441 - itemContainer: { 442 - flexDirection: 'row', 443 - alignItems: 'center', 444 - borderBottomWidth: StyleSheet.hairlineWidth, 445 - }, 446 - footerText: { 447 - paddingHorizontal: 26, 448 - paddingVertical: 22, 449 - }, 450 - })
+1 -1
src/view/screens/Storybook/Dialogs.tsx
··· 22 22 React.useState<boolean>() 23 23 const [shouldRenderUnmountTest, setShouldRenderUnmountTest] = 24 24 React.useState(false) 25 - const unmountTestInterval = React.useRef<number>() 25 + const unmountTestInterval = React.useRef<number>(undefined) 26 26 27 27 const onUnmountTestStartPressWithClose = () => { 28 28 setShouldRenderUnmountTest(true)
+1 -1
src/view/screens/Storybook/ListContained.tsx
··· 2 2 import {View} from 'react-native' 3 3 4 4 import {ScrollProvider} from '#/lib/ScrollContext' 5 - import {List, ListMethods} from '#/view/com/util/List' 5 + import {List, type ListMethods} from '#/view/com/util/List' 6 6 import {Button, ButtonText} from '#/components/Button' 7 7 import * as Toggle from '#/components/forms/Toggle' 8 8 import {Text} from '#/components/Typography'
+4 -1
src/view/screens/Support.tsx
··· 5 5 6 6 import {HELP_DESK_URL} from '#/lib/constants' 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+1 -1
src/view/shell/Drawer.tsx
··· 1 - import React, {type ComponentProps} from 'react' 1 + import React, {type ComponentProps, type JSX} from 'react' 2 2 import {Linking, ScrollView, TouchableOpacity, View} from 'react-native' 3 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import {msg, Plural, plural, Trans} from '@lingui/macro'
+1 -1
src/view/shell/bottom-bar/BottomBar.tsx
··· 1 - import {useCallback} from 'react' 1 + import {type JSX, useCallback} from 'react' 2 2 import {type GestureResponderEvent, View} from 'react-native' 3 3 import Animated from 'react-native-reanimated' 4 4 import {useSafeAreaInsets} from 'react-native-safe-area-context'
+1 -1
src/view/shell/bottom-bar/BottomBarWeb.tsx
··· 230 230 } 231 231 232 232 const NavItem: React.FC<{ 233 - children: (props: {isActive: boolean}) => React.ReactChild 233 + children: (props: {isActive: boolean}) => React.ReactNode 234 234 href: string 235 235 routeName: string 236 236 hasNew?: boolean
+1 -1
src/view/shell/desktop/LeftNav.tsx
··· 1 - import {useCallback, useMemo, useState} from 'react' 1 + import {type JSX, useCallback, useMemo, useState} from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 3 import {type AppBskyActorDefs} from '@atproto/api' 4 4 import {msg, plural, Trans} from '@lingui/macro'
+22 -41
yarn.lock
··· 7589 7589 resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 7590 7590 integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 7591 7591 7592 - "@types/prop-types@*": 7593 - version "15.7.5" 7594 - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 7595 - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== 7596 - 7597 7592 "@types/psl@^1.1.1": 7598 7593 version "1.1.1" 7599 7594 resolved "https://registry.yarnpkg.com/@types/psl/-/psl-1.1.1.tgz#3ba9e6d4bd2a32652a639fd5df7e539151d0a3b2" ··· 7609 7604 resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" 7610 7605 integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== 7611 7606 7612 - "@types/react-dom@^19.1.2": 7613 - version "19.1.3" 7614 - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.3.tgz#3f0c60804441bf34d19f8dd0d44405c0c0e21bfa" 7615 - integrity sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg== 7607 + "@types/react-dom@^19.1.8": 7608 + version "19.1.9" 7609 + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.9.tgz#5ab695fce1e804184767932365ae6569c11b4b4b" 7610 + integrity sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ== 7616 7611 7617 - "@types/react-responsive@^8.0.5": 7618 - version "8.0.5" 7619 - resolved "https://registry.yarnpkg.com/@types/react-responsive/-/react-responsive-8.0.5.tgz#77769862d2a0711434feb972be08e3e6c334440a" 7620 - integrity sha512-k3gQJgI87oP5IrVZe//3LKJFnAeFaqqWmmtl5eoYL2H3HqFcIhUaE30kRK1CsW3DHdojZxcVj4ZNc2ClsEu2PA== 7612 + "@types/react@^19.1.12": 7613 + version "19.1.12" 7614 + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.12.tgz#7bfaa76aabbb0b4fe0493c21a3a7a93d33e8937b" 7615 + integrity sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w== 7621 7616 dependencies: 7622 - "@types/react" "*" 7623 - 7624 - "@types/react@*", "@types/react@^18": 7625 - version "18.2.20" 7626 - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.20.tgz#1605557a83df5c8a2cc4eeb743b3dfc0eb6aaeb2" 7627 - integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw== 7628 - dependencies: 7629 - "@types/prop-types" "*" 7630 - "@types/scheduler" "*" 7631 7617 csstype "^3.0.2" 7632 7618 7633 7619 "@types/retry@0.12.0": 7634 7620 version "0.12.0" 7635 7621 resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" 7636 7622 integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== 7637 - 7638 - "@types/scheduler@*": 7639 - version "0.16.3" 7640 - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" 7641 - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== 7642 7623 7643 7624 "@types/semver@^7.3.12": 7644 7625 version "7.5.0" ··· 14515 14496 resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" 14516 14497 integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== 14517 14498 14518 - matchmediaquery@^0.3.0: 14519 - version "0.3.1" 14520 - resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.1.tgz#8247edc47e499ebb7c58f62a9ff9ccf5b815c6d7" 14521 - integrity sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ== 14499 + matchmediaquery@^0.4.2: 14500 + version "0.4.2" 14501 + resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.4.2.tgz#22582bd4ae63ad9f54c53001bba80cbed0f7eafa" 14502 + integrity sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA== 14522 14503 dependencies: 14523 14504 css-mediaquery "^0.1.2" 14524 14505 ··· 17124 17105 use-callback-ref "^1.3.3" 17125 17106 use-sidecar "^1.1.3" 17126 17107 17127 - react-responsive@^9.0.2: 17128 - version "9.0.2" 17129 - resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-9.0.2.tgz#34531ca77a61e7a8775714016d21241df7e4205c" 17130 - integrity sha512-+4CCab7z8G8glgJoRjAwocsgsv6VA2w7JPxFWHRc7kvz8mec1/K5LutNC2MG28Mn8mu6+bu04XZxHv5gyfT7xQ== 17108 + react-responsive@^10.0.1: 17109 + version "10.0.1" 17110 + resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-10.0.1.tgz#293d4d2562da93409861216f0110d146c5676eb3" 17111 + integrity sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g== 17131 17112 dependencies: 17132 17113 hyphenate-style-name "^1.0.0" 17133 - matchmediaquery "^0.3.0" 17114 + matchmediaquery "^0.4.2" 17134 17115 prop-types "^15.6.1" 17135 - shallow-equal "^1.2.1" 17116 + shallow-equal "^3.1.0" 17136 17117 17137 17118 react-server-dom-webpack@~19.0.0: 17138 17119 version "19.0.0" ··· 17985 17966 resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-1.0.0.tgz#94e9210bf27e7583f9749a0d07bd4f4937ea488f" 17986 17967 integrity sha512-DkS7q3nN68dEMb4E18HFPDAvyrjDZK9YAQQF2QxeFu9gp2xRDXFMF8qLJ1EmQ/qeEGQmop4lmMM1WtYJTIcCMw== 17987 17968 17988 - shallow-equal@^1.2.1: 17989 - version "1.2.1" 17990 - resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" 17991 - integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== 17969 + shallow-equal@^3.1.0: 17970 + version "3.1.0" 17971 + resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-3.1.0.tgz#e7a54bac629c7f248eff6c2f5b63122ba4320bec" 17972 + integrity sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg== 17992 17973 17993 17974 sharp@^0.33.5: 17994 17975 version "0.33.5"