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