An ATproto social media client -- with an independent Appview.

Merge branch 'manual-upstream-sync/2025-09-11'

serenity 29913214 8f841c0c

+3339 -2908
+52 -1
bskyweb/cmd/bskyweb/server.go
··· 313 313 e.GET("/profile/:handleOrDID/known-followers", server.WebGeneric) 314 314 e.GET("/profile/:handleOrDID/search", server.WebGeneric) 315 315 e.GET("/profile/:handleOrDID/lists/:rkey", server.WebGeneric) 316 - e.GET("/profile/:handleOrDID/feed/:rkey", server.WebGeneric) 316 + e.GET("/profile/:handleOrDID/feed/:rkey", server.WebFeed) 317 317 e.GET("/profile/:handleOrDID/feed/:rkey/liked-by", server.WebGeneric) 318 318 e.GET("/profile/:handleOrDID/labeler/liked-by", server.WebGeneric) 319 319 ··· 601 601 data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 602 602 data["requestHost"] = req.Host 603 603 return c.Render(http.StatusOK, "profile.html", data) 604 + } 605 + 606 + func (srv *Server) WebFeed(c echo.Context) error { 607 + ctx := c.Request().Context() 608 + data := srv.NewTemplateContext() 609 + 610 + // sanity check arguments. don't 4xx, just let app handle if not expected format 611 + rkeyParam := c.Param("rkey") 612 + rkey, err := syntax.ParseRecordKey(rkeyParam) 613 + if err != nil { 614 + return c.Render(http.StatusOK, "feed.html", data) 615 + } 616 + handleOrDIDParam := c.Param("handleOrDID") 617 + handleOrDID, err := syntax.ParseAtIdentifier(handleOrDIDParam) 618 + if err != nil { 619 + return c.Render(http.StatusOK, "feed.html", data) 620 + } 621 + 622 + identifier := handleOrDID.Normalize().String() 623 + 624 + // requires two fetches: first fetch profile to get DID 625 + pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, identifier) 626 + if err != nil { 627 + log.Warnf("failed to fetch profile for: %s\t%v", identifier, err) 628 + return c.Render(http.StatusOK, "feed.html", data) 629 + } 630 + unauthedViewingOkay := true 631 + for _, label := range pv.Labels { 632 + if label.Src == pv.Did && label.Val == "!no-unauthenticated" { 633 + unauthedViewingOkay = false 634 + } 635 + } 636 + 637 + if !unauthedViewingOkay { 638 + return c.Render(http.StatusOK, "feed.html", data) 639 + } 640 + did := pv.Did 641 + data["did"] = did 642 + 643 + // then fetch the feed generator 644 + feedURI := fmt.Sprintf("at://%s/app.bsky.feed.generator/%s", did, rkey) 645 + fgv, err := appbsky.FeedGetFeedGenerator(ctx, srv.xrpcc, feedURI) 646 + if err != nil { 647 + log.Warnf("failed to fetch feed generator: %s\t%v", feedURI, err) 648 + return c.Render(http.StatusOK, "feed.html", data) 649 + } 650 + req := c.Request() 651 + data["feedView"] = fgv.View 652 + data["requestURI"] = fmt.Sprintf("https://%s%s", req.Host, req.URL.Path) 653 + 654 + return c.Render(http.StatusOK, "feed.html", data) 604 655 } 605 656 606 657 type IPCCRequest struct {
+54
bskyweb/templates/feed.html
··· 1 + {% extends "base.html" %} 2 + 3 + {% block head_title %} 4 + {%- if feedView -%} 5 + {{ feedView.DisplayName }} by @{{ feedView.Creator.Handle }} | Bluesky Feed 6 + {%- else -%} 7 + Bluesky 8 + {%- endif -%} 9 + {% endblock %} 10 + 11 + {% block html_head_extra -%} 12 + {%- if feedView -%} 13 + <meta property="og:site_name" content="Bluesky Social"> 14 + <meta property="og:type" content="website"> 15 + {%- if requestURI %} 16 + <meta property="og:url" content="{{ requestURI }}"> 17 + <link rel="canonical" href="{{ requestURI|canonicalize_url }}" /> 18 + {% endif -%} 19 + 20 + {%- if feedView.DisplayName %} 21 + <meta property="og:title" content="{{ feedView.DisplayName }} by @{{ feedView.Creator.Handle }}"> 22 + {% else %} 23 + <meta property="og:title" content="Feed by @{{ feedView.Creator.Handle }}"> 24 + {% endif -%} 25 + 26 + {%- if feedView.Description %} 27 + <meta name="description" content="{{ feedView.Description }}"> 28 + <meta property="og:description" content="{{ feedView.Description }}"> 29 + <meta property="twitter:description" content="{{ feedView.Description }}"> 30 + {% endif -%} 31 + 32 + {%- if feedView.Avatar %} 33 + <meta property="og:image" content="{{ feedView.Avatar }}"> 34 + <meta property="twitter:image" content="{{ feedView.Avatar }}"> 35 + <meta name="twitter:card" content="summary"> 36 + {% endif %} 37 + 38 + <meta name="twitter:label1" content="Created by"> 39 + <meta name="twitter:value1" content="@{{ feedView.Creator.Handle }}"> 40 + 41 + <link rel="alternate" href="{{ feedView.Uri }}" /> 42 + {% endif -%} 43 + {%- endblock %} 44 + 45 + {% block noscript_extra -%} 46 + {%- if feedView -%} 47 + <div id="bsky_feed_summary"> 48 + <h3>Feed</h3> 49 + <p id="bsky_feed_name">{{ feedView.DisplayName }}</p> 50 + <p id="bsky_feed_creator">{{ feedView.Creator.Handle }}</p> 51 + <p id="bsky_feed_description">{{ feedView.Description }}</p> 52 + </div> 53 + {% endif -%} 54 + {%- endblock %}
+3 -5
package.json
··· 123 123 "array.prototype.findlast": "^1.2.3", 124 124 "await-lock": "^2.2.2", 125 125 "babel-plugin-transform-remove-console": "^6.9.4", 126 - "base64-js": "^1.5.1", 127 126 "bcp-47": "^2.1.0", 128 127 "bcp-47-match": "^2.0.3", 129 128 "date-fns": "^2.30.0", ··· 213 212 "react-native-web-webview": "^1.0.2", 214 213 "react-native-webview": "^13.13.5", 215 214 "react-remove-scroll-bar": "^2.3.8", 216 - "react-responsive": "^9.0.2", 215 + "react-responsive": "^10.0.1", 217 216 "react-textarea-autosize": "^8.5.3", 218 217 "sonner": "^2.0.7", 219 218 "sonner-native": "^0.21.0", ··· 245 244 "@types/lodash.isequal": "^4.5.6", 246 245 "@types/lodash.shuffle": "^4.2.7", 247 246 "@types/psl": "^1.1.1", 248 - "@types/react-dom": "^19.1.2", 249 - "@types/react-responsive": "^8.0.5", 247 + "@types/react": "^19.1.12", 248 + "@types/react-dom": "^19.1.8", 250 249 "@typescript-eslint/eslint-plugin": "^7.18.0", 251 250 "@typescript-eslint/parser": "^7.18.0", 252 251 "babel-jest": "^29.7.0", ··· 284 283 "@expo/image-utils": "0.6.3", 285 284 "@react-native/babel-preset": "0.79.3", 286 285 "@react-native/normalize-colors": "0.79.3", 287 - "@types/react": "^18", 288 286 "**/expo-constants": "17.0.3", 289 287 "**/expo-device": "7.1.4", 290 288 "**/zod": "3.23.8",
+13
patches/@discord+bottom-sheet+4.6.1.patch
··· 1 + diff --git a/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts b/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 2 + index 1c788ab..d30f330 100644 3 + --- a/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 4 + +++ b/node_modules/@discord/bottom-sheet/src/hooks/useStableCallback.ts 5 + @@ -6,7 +6,7 @@ type Callback = (...args: any[]) => any; 6 + * https://gist.github.com/JakeCoxon/c7ebf6e6496f8468226fd36b596e1985 7 + */ 8 + export const useStableCallback = (callback: Callback) => { 9 + - const callbackRef = useRef<Callback>(); 10 + + const callbackRef = useRef<Callback>(undefined); 11 + const memoCallback = useCallback( 12 + (...args: any) => callbackRef.current && callbackRef.current(...args), 13 + []
+3 -3
src/Navigation.tsx
··· 1 - import {useCallback, useRef} from 'react' 1 + import {type JSX, useCallback, useRef} from 'react' 2 2 import {Linking} from 'react-native' 3 3 import * as Notifications from 'expo-notifications' 4 4 import {i18n, type MessageDescriptor} from '@lingui/core' ··· 64 64 import {PrivacyPolicyScreen} from '#/view/screens/PrivacyPolicy' 65 65 import {ProfileScreen} from '#/view/screens/Profile' 66 66 import {ProfileFeedLikedByScreen} from '#/view/screens/ProfileFeedLikedBy' 67 - import {ProfileListScreen} from '#/view/screens/ProfileList' 68 - import {SavedFeeds} from '#/view/screens/SavedFeeds' 69 67 import {Storybook} from '#/view/screens/Storybook' 70 68 import {SupportScreen} from '#/view/screens/Support' 71 69 import {TermsOfServiceScreen} from '#/view/screens/TermsOfService' ··· 92 90 import {ProfileFollowsScreen} from '#/screens/Profile/ProfileFollows' 93 91 import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy' 94 92 import {ProfileSearchScreen} from '#/screens/Profile/ProfileSearch' 93 + import {ProfileListScreen} from '#/screens/ProfileList' 94 + import {SavedFeeds} from '#/screens/SavedFeeds' 95 95 import {SearchScreen} from '#/screens/Search' 96 96 import {AboutSettingsScreen} from '#/screens/Settings/AboutSettings' 97 97 import {AccessibilitySettingsScreen} from '#/screens/Settings/AccessibilitySettings'
+1 -1
src/Splash.tsx
··· 15 15 withTiming, 16 16 } from 'react-native-reanimated' 17 17 import {useSafeAreaInsets} from 'react-native-safe-area-context' 18 - import Svg, {Path, SvgProps} from 'react-native-svg' 18 + import Svg, {Path, type SvgProps} from 'react-native-svg' 19 19 import {Image} from 'expo-image' 20 20 import * as SplashScreen from 'expo-splash-screen' 21 21
+1 -1
src/alf/themes.ts
··· 1 1 import {atoms} from '#/alf/atoms' 2 - import {Palette, Theme} from '#/alf/types' 2 + import {type Palette, type Theme} from '#/alf/types' 3 3 import { 4 4 BLUE_HUE, 5 5 defaultScale,
+1 -1
src/alf/types.ts
··· 1 - import {StyleProp, TextStyle, ViewStyle} from 'react-native' 1 + import {type StyleProp, type TextStyle, type ViewStyle} from 'react-native' 2 2 3 3 export type TextStyleProp = { 4 4 style?: StyleProp<TextStyle>
-1
src/alf/typography.tsx
··· 3 3 import {type StyleProp, type TextStyle} from 'react-native' 4 4 import {UITextView} from 'react-native-uitextview' 5 5 import createEmojiRegex from 'emoji-regex' 6 - import type React from 'react' 7 6 8 7 import {isNative} from '#/platform/detection' 9 8 import {isIOS} from '#/platform/detection'
+1 -1
src/alf/util/themeSelector.ts
··· 1 - import {ThemeName} from '#/alf/types' 1 + import {type ThemeName} from '#/alf/types' 2 2 3 3 export function select<T>(name: ThemeName, options: Record<ThemeName, T>) { 4 4 switch (name) {
+2 -2
src/alf/util/useColorModeTheme.ts
··· 1 1 import React from 'react' 2 - import {ColorSchemeName, useColorScheme} from 'react-native' 2 + import {type ColorSchemeName, useColorScheme} from 'react-native' 3 3 4 4 import {isWeb} from '#/platform/detection' 5 5 import {useThemePrefs} from '#/state/shell' 6 6 import {dark, dim, light} from '#/alf/themes' 7 - import {ThemeName} from '#/alf/types' 7 + import {type ThemeName} from '#/alf/types' 8 8 9 9 export function useColorModeTheme(): ThemeName { 10 10 const theme = useThemeName()
+1 -1
src/alf/util/useGutters.ts
··· 1 1 import React from 'react' 2 2 3 - import {Breakpoint, useBreakpoints} from '#/alf/breakpoints' 3 + import {type Breakpoint, useBreakpoints} from '#/alf/breakpoints' 4 4 import * as tokens from '#/alf/tokens' 5 5 6 6 type Gutter = 'compact' | 'base' | 'wide' | 0
+2 -2
src/components/Button.tsx
··· 71 71 export type ButtonContext = VariantProps & ButtonState 72 72 73 73 type NonTextElements = 74 - | React.ReactElement 75 - | Iterable<React.ReactElement | null | undefined | boolean> 74 + | React.ReactElement<any> 75 + | Iterable<React.ReactElement<any> | null | undefined | boolean> 76 76 77 77 export type ButtonProps = Pick< 78 78 PressableProps,
+2 -1
src/components/ContextMenu/index.tsx
··· 119 119 const hoverablesSV = useSharedValue< 120 120 Record<string, {id: string; rect: Measurement}> 121 121 >({}) 122 - const syncHoverablesThrottleRef = useRef<ReturnType<typeof setTimeout>>() 122 + const syncHoverablesThrottleRef = 123 + useRef<ReturnType<typeof setTimeout>>(undefined) 123 124 const [hoveredMenuItem, setHoveredMenuItem] = useState<string | null>(null) 124 125 125 126 const onHoverableTouchUp = useCallback((id: string) => {
-1
src/components/ContextMenu/types.ts
··· 5 5 type ViewStyle, 6 6 } from 'react-native' 7 7 import {type SharedValue} from 'react-native-reanimated' 8 - import type React from 'react' 9 8 10 9 import type * as Dialog from '#/components/Dialog' 11 10 import {
+1 -2
src/components/Dialog/types.ts
··· 5 5 type StyleProp, 6 6 type ViewStyle, 7 7 } from 'react-native' 8 - import type React from 'react' 9 8 10 9 import {type ViewStyleProp} from '#/alf' 11 10 import {type BottomSheetViewProps} from '../../../modules/bottom-sheet' ··· 34 33 */ 35 34 export type DialogControlProps = DialogControlRefProps & { 36 35 id: string 37 - ref: React.RefObject<DialogControlRefProps> 36 + ref: React.RefObject<DialogControlRefProps | null> 38 37 isOpen?: boolean 39 38 } 40 39
+1 -1
src/components/Dialog/utils.ts
··· 1 1 import React from 'react' 2 2 3 - import {DialogControlProps} from '#/components/Dialog/types' 3 + import {type DialogControlProps} from '#/components/Dialog/types' 4 4 5 5 export function useAutoOpen(control: DialogControlProps, showTimeout?: number) { 6 6 React.useEffect(() => {
+2 -2
src/components/Fill.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 2 + import type React from 'react' 3 3 4 - import {atoms as a, ViewStyleProp} from '#/alf' 4 + import {atoms as a, type ViewStyleProp} from '#/alf' 5 5 6 6 export function Fill({ 7 7 children,
+1 -1
src/components/GradientFill.tsx
··· 1 1 import {LinearGradient} from 'expo-linear-gradient' 2 2 3 - import {atoms as a, tokens, ViewStyleProp} from '#/alf' 3 + import {atoms as a, type tokens, type ViewStyleProp} from '#/alf' 4 4 5 5 export function GradientFill({ 6 6 gradient,
+4 -4
src/components/IconCircle.tsx
··· 3 3 import { 4 4 atoms as a, 5 5 flatten, 6 - TextStyleProp, 6 + type TextStyleProp, 7 7 useTheme, 8 - ViewStyleProp, 8 + type ViewStyleProp, 9 9 } from '#/alf' 10 - import {Props} from '#/components/icons/common' 11 - import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 10 + import {type Props} from '#/components/icons/common' 11 + import {type Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' 12 12 13 13 export function IconCircle({ 14 14 icon: Icon,
+4 -4
src/components/LabelingServiceCard/index.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 2 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 3 import {msg, Plural, Trans} from '@lingui/macro' 5 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 6 6 7 7 import {getLabelingServiceTitle} from '#/lib/moderation' 8 8 import {sanitizeHandle} from '#/lib/strings/handles' 9 9 import {useLabelerInfoQuery} from '#/state/queries/labeler' 10 10 import {UserAvatar} from '#/view/com/util/UserAvatar' 11 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 11 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 12 12 import {Flag_Stroke2_Corner0_Rounded as Flag} from '#/components/icons/Flag' 13 - import {Link as InternalLink, LinkProps} from '#/components/Link' 13 + import {Link as InternalLink, type LinkProps} from '#/components/Link' 14 14 import {RichText} from '#/components/RichText' 15 15 import {Text} from '#/components/Typography' 16 16 import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '../icons/Chevron'
+1 -1
src/components/LikedByList.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 2 + import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/components/LinearGradientBackground.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import {LinearGradient} from 'expo-linear-gradient' 3 + import type React from 'react' 4 4 5 5 import {gradients} from '#/alf/tokens' 6 6
-1
src/components/Lists.tsx
··· 2 2 import {type StyleProp, View, type ViewStyle} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 - import type React from 'react' 6 5 7 6 import {cleanError} from '#/lib/strings/errors' 8 7 import {CenteredView} from '#/view/com/util/Views'
+1 -1
src/components/Loader.tsx
··· 8 8 } from 'react-native-reanimated' 9 9 10 10 import {atoms as a, flatten, useTheme} from '#/alf' 11 - import {Props, useCommonSVGProps} from '#/components/icons/common' 11 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 12 12 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 13 13 14 14 export function Loader(props: Props) {
+1 -1
src/components/Loader.web.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 3 import {atoms as a, flatten, useTheme} from '#/alf' 4 - import {Props, useCommonSVGProps} from '#/components/icons/common' 4 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader' 6 6 7 7 export function Loader(props: Props) {
+2 -2
src/components/MediaInsetBorder.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 3 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 4 4 import {Fill} from '#/components/Fill' 5 5 6 6 /**
+8 -8
src/components/Menu/types.ts
··· 1 - import React from 'react' 2 1 import { 3 - AccessibilityProps, 4 - AccessibilityRole, 5 - GestureResponderEvent, 6 - PressableProps, 2 + type AccessibilityProps, 3 + type AccessibilityRole, 4 + type GestureResponderEvent, 5 + type PressableProps, 7 6 } from 'react-native' 7 + import type React from 'react' 8 8 9 - import {TextStyleProp, ViewStyleProp} from '#/alf' 10 - import * as Dialog from '#/components/Dialog' 11 - import {Props as SVGIconProps} from '#/components/icons/common' 9 + import {type TextStyleProp, type ViewStyleProp} from '#/alf' 10 + import type * as Dialog from '#/components/Dialog' 11 + import {type Props as SVGIconProps} from '#/components/icons/common' 12 12 13 13 export type ContextType = { 14 14 control: Dialog.DialogOuterProps['control']
+111 -101
src/components/NewskieDialog.tsx
··· 1 - import React from 'react' 1 + import {useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, moderateProfile} from '@atproto/api' 3 + import {type AppBskyActorDefs, moderateProfile} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {differenceInSeconds} from 'date-fns' ··· 27 27 disabled?: boolean 28 28 }) { 29 29 const {_} = useLingui() 30 - const t = useTheme() 31 - const moderationOpts = useModerationOpts() 32 - const {currentAccount} = useSession() 33 - const timeAgo = useGetTimeAgo() 34 30 const control = useDialogControl() 35 31 36 - const isMe = profile.did === currentAccount?.did 37 32 const createdAt = profile.createdAt as string | undefined 38 33 39 - const profileName = React.useMemo(() => { 40 - const name = profile.displayName || profile.handle 41 - 42 - if (isMe) { 43 - return _(msg`You`) 44 - } 45 - 46 - if (!moderationOpts) return name 47 - const moderation = moderateProfile(profile, moderationOpts) 48 - 49 - return sanitizeDisplayName(name, moderation.ui('displayName')) 50 - }, [_, isMe, moderationOpts, profile]) 51 - 52 - const [now] = React.useState(() => Date.now()) 53 - const daysOld = React.useMemo(() => { 34 + const [now] = useState(() => Date.now()) 35 + const daysOld = useMemo(() => { 54 36 if (!createdAt) return Infinity 55 37 return differenceInSeconds(now, new Date(createdAt)) / 86400 56 38 }, [createdAt, now]) ··· 77 59 )} 78 60 </Button> 79 61 80 - <Dialog.Outer control={control}> 62 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 81 63 <Dialog.Handle /> 82 - <Dialog.ScrollableInner 83 - label={_(msg`New user info dialog`)} 84 - style={web({width: 'auto', maxWidth: 400, minWidth: 200})}> 85 - <View style={[a.gap_md]}> 86 - <View style={[a.align_center]}> 87 - <View 88 - style={[ 89 - { 90 - height: 60, 91 - width: 64, 92 - }, 93 - ]}> 94 - <Newskie 95 - width={64} 96 - height={64} 97 - fill="#FFC404" 98 - style={[a.absolute, a.inset_0]} 99 - /> 100 - </View> 101 - <Text style={[a.font_bold, a.text_xl]}> 102 - {isMe ? ( 103 - <Trans>Welcome, friend!</Trans> 104 - ) : ( 105 - <Trans>Say hello!</Trans> 106 - )} 107 - </Text> 108 - </View> 109 - <Text style={[a.text_md, a.text_center, a.leading_snug]}> 110 - {profile.joinedViaStarterPack ? ( 111 - <Trans> 112 - {profileName} joined Bluesky using a starter pack{' '} 113 - {timeAgo(createdAt, now, {format: 'long'})} ago 114 - </Trans> 115 - ) : ( 116 - <Trans> 117 - {profileName} joined Bluesky{' '} 118 - {timeAgo(createdAt, now, {format: 'long'})} ago 119 - </Trans> 120 - )} 121 - </Text> 122 - {profile.joinedViaStarterPack ? ( 123 - <StarterPackCard.Link 124 - starterPack={profile.joinedViaStarterPack} 125 - onPress={() => { 126 - control.close() 127 - }}> 128 - <View 129 - style={[ 130 - a.w_full, 131 - a.mt_sm, 132 - a.p_lg, 133 - a.border, 134 - a.rounded_sm, 135 - t.atoms.border_contrast_low, 136 - ]}> 137 - <StarterPackCard.Card 138 - starterPack={profile.joinedViaStarterPack} 139 - /> 140 - </View> 141 - </StarterPackCard.Link> 142 - ) : null} 64 + <DialogInner profile={profile} createdAt={createdAt} now={now} /> 65 + </Dialog.Outer> 66 + </View> 67 + ) 68 + } 143 69 144 - {isNative && ( 145 - <Button 146 - label={_(msg`Close`)} 147 - variant="solid" 148 - color="secondary" 149 - size="small" 150 - style={[a.mt_sm]} 151 - onPress={() => control.close()}> 152 - <ButtonText> 153 - <Trans>Close</Trans> 154 - </ButtonText> 155 - </Button> 156 - )} 70 + function DialogInner({ 71 + profile, 72 + createdAt, 73 + now, 74 + }: { 75 + profile: AppBskyActorDefs.ProfileViewDetailed 76 + createdAt: string 77 + now: number 78 + }) { 79 + const control = Dialog.useDialogContext() 80 + const {_} = useLingui() 81 + const t = useTheme() 82 + const moderationOpts = useModerationOpts() 83 + const {currentAccount} = useSession() 84 + const timeAgo = useGetTimeAgo() 85 + const isMe = profile.did === currentAccount?.did 86 + 87 + const profileName = useMemo(() => { 88 + const name = profile.displayName || profile.handle 89 + 90 + if (isMe) { 91 + return _(msg`You`) 92 + } 93 + 94 + if (!moderationOpts) return name 95 + const moderation = moderateProfile(profile, moderationOpts) 96 + 97 + return sanitizeDisplayName(name, moderation.ui('displayName')) 98 + }, [_, isMe, moderationOpts, profile]) 99 + 100 + return ( 101 + <Dialog.ScrollableInner 102 + label={_(msg`New user info dialog`)} 103 + style={web({maxWidth: 400})}> 104 + <View style={[a.gap_md]}> 105 + <View style={[a.align_center]}> 106 + <View 107 + style={[ 108 + { 109 + height: 60, 110 + width: 64, 111 + }, 112 + ]}> 113 + <Newskie 114 + width={64} 115 + height={64} 116 + fill="#FFC404" 117 + style={[a.absolute, a.inset_0]} 118 + /> 157 119 </View> 120 + <Text style={[a.font_bold, a.text_xl]}> 121 + {isMe ? <Trans>Welcome, friend!</Trans> : <Trans>Say hello!</Trans>} 122 + </Text> 123 + </View> 124 + <Text style={[a.text_md, a.text_center, a.leading_snug]}> 125 + {profile.joinedViaStarterPack ? ( 126 + <Trans> 127 + {profileName} joined Bluesky using a starter pack{' '} 128 + {timeAgo(createdAt, now, {format: 'long'})} ago 129 + </Trans> 130 + ) : ( 131 + <Trans> 132 + {profileName} joined Bluesky{' '} 133 + {timeAgo(createdAt, now, {format: 'long'})} ago 134 + </Trans> 135 + )} 136 + </Text> 137 + {profile.joinedViaStarterPack ? ( 138 + <StarterPackCard.Link 139 + starterPack={profile.joinedViaStarterPack} 140 + onPress={() => control.close()}> 141 + <View 142 + style={[ 143 + a.w_full, 144 + a.mt_sm, 145 + a.p_lg, 146 + a.border, 147 + a.rounded_sm, 148 + t.atoms.border_contrast_low, 149 + ]}> 150 + <StarterPackCard.Card 151 + starterPack={profile.joinedViaStarterPack} 152 + /> 153 + </View> 154 + </StarterPackCard.Link> 155 + ) : null} 158 156 159 - <Dialog.Close /> 160 - </Dialog.ScrollableInner> 161 - </Dialog.Outer> 162 - </View> 157 + {isNative && ( 158 + <Button 159 + label={_(msg`Close`)} 160 + color="secondary" 161 + size="small" 162 + style={[a.mt_sm]} 163 + onPress={() => control.close()}> 164 + <ButtonText> 165 + <Trans>Close</Trans> 166 + </ButtonText> 167 + </Button> 168 + )} 169 + </View> 170 + 171 + <Dialog.Close /> 172 + </Dialog.ScrollableInner> 163 173 ) 164 174 }
+2 -2
src/components/Pills.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {BSKY_LABELER_DID, ModerationCause} from '@atproto/api' 3 + import {BSKY_LABELER_DID, type ModerationCause} from '@atproto/api' 4 4 import {Trans} from '@lingui/macro' 5 5 6 6 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 7 7 import {UserAvatar} from '#/view/com/util/UserAvatar' 8 - import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 8 + import {atoms as a, useTheme, type ViewStyleProp} from '#/alf' 9 9 import {Button} from '#/components/Button' 10 10 import { 11 11 ModerationDetailsDialog,
+1 -1
src/components/Portal.tsx
··· 10 10 useState, 11 11 } from 'react' 12 12 13 - type Component = React.ReactElement 13 + type Component = React.ReactElement<any> 14 14 15 15 type ContextType = { 16 16 outlet: Component | null
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
··· 139 139 playlist: string 140 140 setHasSubtitleTrack: (v: boolean) => void 141 141 setError: (v: Error | null) => void 142 - videoRef: React.RefObject<HTMLVideoElement> 142 + videoRef: React.RefObject<HTMLVideoElement | null> 143 143 setHlsLoading: (v: boolean) => void 144 144 }) { 145 145 const [Hls, setHls] = useState<typeof HlsTypes.default | undefined>(
+7 -6
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
··· 1 1 import {View} from 'react-native' 2 2 import {msg, Trans} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 - import type React from 'react' 5 4 6 5 import {atoms as a, useTheme} from '#/alf' 7 - import {Button, ButtonText} from '#/components/Button' 6 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 7 + import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateIcon} from '#/components/icons/ArrowRotateCounterClockwise' 8 + import {MediaInsetBorder} from '#/components/MediaInsetBorder' 8 9 import {Text as TypoText} from '#/components/Typography' 9 10 10 11 export function Container({children}: {children: React.ReactNode}) { ··· 17 18 a.justify_center, 18 19 a.align_center, 19 20 a.px_lg, 20 - a.border, 21 - t.atoms.border_contrast_low, 22 - a.rounded_sm, 21 + a.rounded_md, 22 + a.overflow_hidden, 23 23 a.gap_lg, 24 24 ]}> 25 25 {children} 26 + <MediaInsetBorder /> 26 27 </View> 27 28 ) 28 29 } ··· 51 52 onPress={onPress} 52 53 size="small" 53 54 color="secondary_inverted" 54 - variant="solid" 55 55 label={_(msg`Retry`)}> 56 + <ButtonIcon icon={ArrowRotateIcon} /> 56 57 <ButtonText> 57 58 <Trans>Retry</Trans> 58 59 </ButtonText>
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
··· 1 1 import {type SvgProps} from 'react-native-svg' 2 - import type React from 'react' 3 2 4 3 import {PressableWithHover} from '#/view/com/util/PressableWithHover' 5 4 import {atoms as a, useTheme, web} from '#/alf'
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
··· 2 2 import {View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 - import type React from 'react' 6 5 7 6 import {isFirefox, isTouchDevice} from '#/lib/browser' 8 7 import {clamp} from '#/lib/numbers'
+5 -5
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
··· 46 46 hlsLoading, 47 47 hasSubtitleTrack, 48 48 }: { 49 - videoRef: React.RefObject<HTMLVideoElement> 50 - hlsRef: React.RefObject<Hls | undefined> 49 + videoRef: React.RefObject<HTMLVideoElement | null> 50 + hlsRef: React.RefObject<Hls | undefined | null> 51 51 active: boolean 52 52 setActive: () => void 53 53 focused: boolean 54 54 setFocused: (focused: boolean) => void 55 55 onScreen: boolean 56 - fullscreenRef: React.RefObject<HTMLDivElement> 56 + fullscreenRef: React.RefObject<HTMLDivElement | null> 57 57 hlsLoading: boolean 58 58 hasSubtitleTrack: boolean 59 59 }) { ··· 232 232 }, [onSeek, videoRef]) 233 233 234 234 const [showCursor, setShowCursor] = useState(true) 235 - const cursorTimeoutRef = useRef<ReturnType<typeof setTimeout>>() 235 + const cursorTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined) 236 236 const onPointerMoveEmptySpace = useCallback(() => { 237 237 setShowCursor(true) 238 238 if (cursorTimeoutRef.current) { ··· 264 264 [hovered], 265 265 ) 266 266 267 - const timeoutRef = useRef<ReturnType<typeof setTimeout>>() 267 + const timeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined) 268 268 269 269 const onHoverWithTimeout = useCallback(() => { 270 270 onHover()
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
··· 3 3 import Animated, {FadeIn, FadeOut} from 'react-native-reanimated' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 - import type React from 'react' 7 6 8 7 import {isSafari, isTouchDevice} from '#/lib/browser' 9 8 import {atoms as a} from '#/alf'
+1 -1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
··· 4 4 import {logger} from '#/logger' 5 5 import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext' 6 6 7 - export function useVideoElement(ref: RefObject<HTMLVideoElement>) { 7 + export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) { 8 8 const [playing, setPlaying] = useState(false) 9 9 const [muted, setMuted] = useState(true) 10 10 const [currentTime, setCurrentTime] = useState(0)
+8 -3
src/components/Post/Embed/VideoEmbed/index.web.tsx
··· 10 10 import {type AppBskyEmbedVideo} from '@atproto/api' 11 11 import {msg} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 - import type React from 'react' 14 13 15 14 import {isFirefox} from '#/lib/browser' 16 15 import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' ··· 38 37 useActiveVideoWeb() 39 38 const [onScreen, setOnScreen] = useState(false) 40 39 const [isFullscreen] = useFullscreen() 41 - const lastKnownTime = useRef<number | undefined>() 40 + const lastKnownTime = useRef<number | undefined>(undefined) 42 41 43 42 useEffect(() => { 44 43 if (!ref.current) return ··· 87 86 const contents = ( 88 87 <div 89 88 ref={ref} 90 - style={{display: 'flex', flex: 1, cursor: 'default'}} 89 + style={{ 90 + display: 'flex', 91 + flex: 1, 92 + cursor: 'default', 93 + backgroundImage: `url(${embed.thumbnail})`, 94 + backgroundSize: 'cover', 95 + }} 91 96 onClick={evt => evt.stopPropagation()}> 92 97 <ErrorBoundary renderError={renderError} key={key}> 93 98 <OnlyNearScreen>
-1
src/components/PostControls/PostMenu/index.tsx
··· 8 8 } from '@atproto/api' 9 9 import {msg} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 - import type React from 'react' 12 11 13 12 import {type Shadow} from '#/state/cache/post-shadow' 14 13 import {EventStopper} from '#/view/com/util/EventStopper'
-1
src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 - import type React from 'react' 7 6 8 7 import {makeProfileLink} from '#/lib/routes/links' 9 8 import {type NavigationProp} from '#/lib/routes/types'
-1
src/components/PostControls/ShareMenu/index.tsx
··· 9 9 } from '@atproto/api' 10 10 import {msg} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 - import type React from 'react' 13 12 14 13 import {makeProfileLink} from '#/lib/routes/links' 15 14 import {shareUrl} from '#/lib/sharing'
-2
src/components/ProfileHoverCard/types.ts
··· 1 - import type React from 'react' 2 - 3 1 import {type ViewStyleProp} from '#/alf' 4 2 5 3 export type ProfileHoverCardProps = ViewStyleProp & {
+3 -3
src/components/ProgressGuide/FollowDialog.tsx
··· 293 293 interestsDisplayNames, 294 294 }: { 295 295 guide: Follow10ProgressGuide 296 - inputRef: React.RefObject<TextInput> 297 - listRef: React.RefObject<ListMethods> 296 + inputRef: React.RefObject<TextInput | null> 297 + listRef: React.RefObject<ListMethods | null> 298 298 onSelectTab: (v: string) => void 299 299 searchText: string 300 300 setHeaderHeight: (v: number) => void ··· 565 565 }: { 566 566 onChangeText: (text: string) => void 567 567 onEscape: () => void 568 - inputRef: React.RefObject<TextInput> 568 + inputRef: React.RefObject<TextInput | null> 569 569 defaultValue: string 570 570 }) { 571 571 const t = useTheme()
+1 -1
src/components/ProgressGuide/List.tsx
··· 1 - import {StyleProp, View, ViewStyle} from 'react-native' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 2 import {msg, Trans} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4
+2 -2
src/components/ProgressGuide/Toast.tsx
··· 14 14 import {isWeb} from '#/platform/detection' 15 15 import {atoms as a, useTheme} from '#/alf' 16 16 import {Portal} from '#/components/Portal' 17 - import {AnimatedCheck, AnimatedCheckRef} from '../anim/AnimatedCheck' 17 + import {AnimatedCheck, type AnimatedCheckRef} from '../anim/AnimatedCheck' 18 18 import {Text} from '../Typography' 19 19 20 20 export interface ProgressGuideToastRef { ··· 39 39 const translateY = useSharedValue(0) 40 40 const opacity = useSharedValue(0) 41 41 const animatedCheckRef = React.useRef<AnimatedCheckRef | null>(null) 42 - const timeoutRef = React.useRef<NodeJS.Timeout | undefined>() 42 + const timeoutRef = React.useRef<NodeJS.Timeout | undefined>(undefined) 43 43 const winDim = useWindowDimensions() 44 44 45 45 /**
+2 -2
src/components/ReportDialog/SelectLabelerView.tsx
··· 1 1 import {View} from 'react-native' 2 - import {AppBskyLabelerDefs} from '@atproto/api' 2 + import {type AppBskyLabelerDefs} from '@atproto/api' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 ··· 9 9 import {Divider} from '#/components/Divider' 10 10 import * as LabelingServiceCard from '#/components/LabelingServiceCard' 11 11 import {Text} from '#/components/Typography' 12 - import {ReportDialogProps} from './types' 12 + import {type ReportDialogProps} from './types' 13 13 14 14 export function SelectLabelerView({ 15 15 ...props
+6 -3
src/components/ReportDialog/SelectReportOptionView.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 3 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {ReportOption, useReportOptions} from '#/lib/moderation/useReportOptions' 7 + import { 8 + type ReportOption, 9 + useReportOptions, 10 + } from '#/lib/moderation/useReportOptions' 8 11 import {Link} from '#/components/Link' 9 12 import {DMCA_LINK} from '#/components/ReportDialog/const' 10 13 export {useDialogControl as useReportDialogControl} from '#/components/Dialog' ··· 23 26 } from '#/components/icons/Chevron' 24 27 import {SquareArrowTopRight_Stroke2_Corner0_Rounded as SquareArrowTopRight} from '#/components/icons/SquareArrowTopRight' 25 28 import {Text} from '#/components/Typography' 26 - import {ReportDialogProps} from './types' 29 + import {type ReportDialogProps} from './types' 27 30 28 31 export function SelectReportOptionView(props: { 29 32 params: ReportDialogProps['params']
+3 -3
src/components/ReportDialog/SubmitView.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyLabelerDefs} from '@atproto/api' 3 + import {type AppBskyLabelerDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {getLabelingServiceTitle} from '#/lib/moderation' 8 - import {ReportOption} from '#/lib/moderation/useReportOptions' 8 + import {type ReportOption} from '#/lib/moderation/useReportOptions' 9 9 import {isAndroid} from '#/platform/detection' 10 10 import {useAgent} from '#/state/session' 11 11 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress' ··· 19 19 import {PaperPlane_Stroke2_Corner0_Rounded as SendIcon} from '#/components/icons/PaperPlane' 20 20 import {Loader} from '#/components/Loader' 21 21 import {Text} from '#/components/Typography' 22 - import {ReportDialogProps} from './types' 22 + import {type ReportDialogProps} from './types' 23 23 24 24 export function SubmitView({ 25 25 params,
+4 -4
src/components/ReportDialog/index.tsx
··· 1 1 import React from 'react' 2 2 import {Pressable, View} from 'react-native' 3 - import {ScrollView} from 'react-native-gesture-handler' 3 + import {type ScrollView} from 'react-native-gesture-handler' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {ReportOption} from '#/lib/moderation/useReportOptions' 7 + import {type ReportOption} from '#/lib/moderation/useReportOptions' 8 8 import {useMyLabelersQuery} from '#/state/queries/preferences' 9 9 export {useDialogControl as useReportDialogControl} from '#/components/Dialog' 10 10 11 - import {AppBskyLabelerDefs} from '@atproto/api' 11 + import {type AppBskyLabelerDefs} from '@atproto/api' 12 12 13 13 import {atoms as a} from '#/alf' 14 14 import * as Dialog from '#/components/Dialog' ··· 18 18 import {SelectLabelerView} from './SelectLabelerView' 19 19 import {SelectReportOptionView} from './SelectReportOptionView' 20 20 import {SubmitView} from './SubmitView' 21 - import {ReportDialogProps} from './types' 21 + import {type ReportDialogProps} from './types' 22 22 23 23 export function ReportDialog(props: ReportDialogProps) { 24 24 return (
+1 -1
src/components/ReportDialog/types.ts
··· 1 - import * as Dialog from '#/components/Dialog' 1 + import type * as Dialog from '#/components/Dialog' 2 2 3 3 export type ReportDialogProps = { 4 4 control: Dialog.DialogOuterProps['control']
+2 -2
src/components/RichTextTag.tsx
··· 1 1 import React from 'react' 2 - import {StyleProp, Text as RNText, TextStyle} from 'react-native' 2 + import {type StyleProp, Text as RNText, type TextStyle} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 - import {NavigationProp} from '#/lib/routes/types' 7 + import {type NavigationProp} from '#/lib/routes/types' 8 8 import {isInvalidHandle} from '#/lib/strings/handles' 9 9 import {isNative, isWeb} from '#/platform/detection' 10 10 import {
+1 -1
src/components/Select/types.ts
··· 160 160 item: T, 161 161 index: number, 162 162 selectedValue?: string | null, 163 - ) => React.ReactElement 163 + ) => React.ReactElement<any> 164 164 /* 165 165 * Extracts the value from an item. Defaults to `item => item.value` 166 166 */
+3 -3
src/components/StarterPack/Main/PostsList.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {isNative} from '#/platform/detection' 7 - import {FeedDescriptor} from '#/state/queries/post-feed' 7 + import {type FeedDescriptor} from '#/state/queries/post-feed' 8 8 import {PostFeed} from '#/view/com/posts/PostFeed' 9 9 import {EmptyState} from '#/view/com/util/EmptyState' 10 - import {ListRef} from '#/view/com/util/List' 11 - import {SectionRef} from '#/screens/Profile/Sections/types' 10 + import {type ListRef} from '#/view/com/util/List' 11 + import {type SectionRef} from '#/screens/Profile/Sections/types' 12 12 13 13 interface ProfilesListProps { 14 14 listUri: string
+10 -7
src/components/StarterPack/Main/ProfilesList.tsx
··· 1 1 import React, {useCallback} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import { 4 - AppBskyActorDefs, 5 - AppBskyGraphGetList, 4 + type AppBskyActorDefs, 5 + type AppBskyGraphGetList, 6 6 AtUri, 7 - ModerationOpts, 7 + type ModerationOpts, 8 8 } from '@atproto/api' 9 - import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query' 9 + import { 10 + type InfiniteData, 11 + type UseInfiniteQueryResult, 12 + } from '@tanstack/react-query' 10 13 11 14 import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset' 12 15 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' ··· 14 17 import {isNative, isWeb} from '#/platform/detection' 15 18 import {useAllListMembersQuery} from '#/state/queries/list-members' 16 19 import {useSession} from '#/state/session' 17 - import {List, ListRef} from '#/view/com/util/List' 18 - import {SectionRef} from '#/screens/Profile/Sections/types' 20 + import {List, type ListRef} from '#/view/com/util/List' 21 + import {type SectionRef} from '#/screens/Profile/Sections/types' 19 22 import {atoms as a, useTheme} from '#/alf' 20 23 import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' 21 24 import {Default as ProfileCard} from '#/components/ProfileCard'
+20 -24
src/components/StarterPack/ProfileStarterPacks.tsx
··· 1 - import React, { 2 - useCallback, 3 - useEffect, 4 - useImperativeHandle, 5 - useState, 6 - } from 'react' 1 + import {useCallback, useEffect, useImperativeHandle, useState} from 'react' 7 2 import { 8 3 findNodeHandle, 9 4 type ListRenderItemInfo, 10 5 type StyleProp, 6 + useWindowDimensions, 11 7 View, 12 8 type ViewStyle, 13 9 } from 'react-native' ··· 42 38 } 43 39 44 40 interface ProfileFeedgensProps { 41 + ref?: React.Ref<SectionRef> 45 42 scrollElRef: ListRef 46 43 did: string 47 44 headerOffset: number ··· 56 53 return item.uri 57 54 } 58 55 59 - export const ProfileStarterPacks = React.forwardRef< 60 - SectionRef, 61 - ProfileFeedgensProps 62 - >(function ProfileFeedgensImpl( 63 - { 64 - scrollElRef, 65 - did, 66 - headerOffset, 67 - enabled, 68 - style, 69 - testID, 70 - setScrollViewTag, 71 - isMe, 72 - }, 56 + export function ProfileStarterPacks({ 73 57 ref, 74 - ) { 58 + scrollElRef, 59 + did, 60 + headerOffset, 61 + enabled, 62 + style, 63 + testID, 64 + setScrollViewTag, 65 + isMe, 66 + }: ProfileFeedgensProps) { 75 67 const t = useTheme() 76 68 const bottomBarOffset = useBottomBarOffset(100) 69 + const {height} = useWindowDimensions() 77 70 const [isPTRing, setIsPTRing] = useState(false) 78 71 const { 79 72 data, ··· 101 94 setIsPTRing(false) 102 95 }, [refetch, setIsPTRing]) 103 96 104 - const onEndReached = React.useCallback(async () => { 97 + const onEndReached = useCallback(async () => { 105 98 if (isFetchingNextPage || !hasNextPage || isError) return 106 99 try { 107 100 await fetchNextPage() ··· 144 137 refreshing={isPTRing} 145 138 headerOffset={headerOffset} 146 139 progressViewOffset={ios(0)} 147 - contentContainerStyle={{paddingBottom: headerOffset + bottomBarOffset}} 140 + contentContainerStyle={{ 141 + minHeight: height + headerOffset, 142 + paddingBottom: bottomBarOffset, 143 + }} 148 144 removeClippedSubviews={true} 149 145 desktopFixedHeight 150 146 onEndReached={onEndReached} ··· 158 154 /> 159 155 </View> 160 156 ) 161 - }) 157 + } 162 158 163 159 function CreateAnother() { 164 160 const {_} = useLingui()
+11 -11
src/components/StarterPack/QrCode.tsx
··· 1 - import React from 'react' 1 + import {lazy} from 'react' 2 2 import {View} from 'react-native' 3 3 // @ts-expect-error missing types 4 4 import QRCode from 'react-native-qrcode-styled' 5 5 import type ViewShot from 'react-native-view-shot' 6 - import {AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 6 + import {type AppBskyGraphDefs, AppBskyGraphStarterpack} from '@atproto/api' 7 7 import {Trans} from '@lingui/macro' 8 8 9 9 import {isWeb} from '#/platform/detection' ··· 15 15 import {Text} from '#/components/Typography' 16 16 import * as bsky from '#/types/bsky' 17 17 18 - const LazyViewShot = React.lazy( 18 + const LazyViewShot = lazy( 19 19 // @ts-expect-error dynamic import 20 20 () => import('react-native-view-shot/src/index'), 21 21 ) 22 22 23 - interface Props { 23 + export function QrCode({ 24 + starterPack, 25 + link, 26 + ref, 27 + }: { 24 28 starterPack: AppBskyGraphDefs.StarterPackView 25 29 link: string 26 - } 27 - 28 - export const QrCode = React.forwardRef<ViewShot, Props>(function QrCode( 29 - {starterPack, link}, 30 - ref, 31 - ) { 30 + ref: React.Ref<ViewShot> 31 + }) { 32 32 const {record} = starterPack 33 33 34 34 if ( ··· 93 93 </LinearGradientBackground> 94 94 </LazyViewShot> 95 95 ) 96 - }) 96 + } 97 97 98 98 export function QrCodeInner({link}: {link: string}) { 99 99 const t = useTheme()
+62 -51
src/components/StarterPack/QrCodeDialog.tsx
··· 1 - import React from 'react' 1 + import {Suspense, useRef, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import type ViewShot from 'react-native-view-shot' 4 4 import {requestMediaLibraryPermissionsAsync} from 'expo-image-picker' ··· 8 8 import {msg, Trans} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react' 10 10 11 - import {logEvent} from '#/lib/statsig/statsig' 12 11 import {logger} from '#/logger' 13 12 import {isNative, isWeb} from '#/platform/detection' 14 - import * as Toast from '#/view/com/util/Toast' 15 - import {atoms as a} from '#/alf' 16 - import {Button, ButtonText} from '#/components/Button' 13 + import {atoms as a, useBreakpoints} from '#/alf' 14 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 17 15 import * as Dialog from '#/components/Dialog' 18 16 import {type DialogControlProps} from '#/components/Dialog' 17 + import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 18 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 19 + import {FloppyDisk_Stroke2_Corner0_Rounded as FloppyDiskIcon} from '#/components/icons/FloppyDisk' 19 20 import {Loader} from '#/components/Loader' 20 21 import {QrCode} from '#/components/StarterPack/QrCode' 22 + import * as Toast from '#/components/Toast' 21 23 import * as bsky from '#/types/bsky' 22 24 23 25 export function QrCodeDialog({ ··· 30 32 control: DialogControlProps 31 33 }) { 32 34 const {_} = useLingui() 33 - const [isProcessing, setIsProcessing] = React.useState(false) 35 + const {gtMobile} = useBreakpoints() 36 + const [isSaveProcessing, setIsSaveProcessing] = useState(false) 37 + const [isCopyProcessing, setIsCopyProcessing] = useState(false) 34 38 35 - const ref = React.useRef<ViewShot>(null) 39 + const ref = useRef<ViewShot>(null) 36 40 37 41 const getCanvas = (base64: string): Promise<HTMLCanvasElement> => { 38 42 return new Promise(resolve => { ··· 68 72 try { 69 73 await createAssetAsync(`file://${uri}`) 70 74 } catch (e: unknown) { 71 - Toast.show( 72 - _(msg`An error occurred while saving the QR code!`), 73 - 'xmark', 74 - ) 75 + Toast.show(_(msg`An error occurred while saving the QR code!`), { 76 + type: 'error', 77 + }) 75 78 logger.error('Failed to save QR code', {error: e}) 76 79 return 77 80 } 78 81 } else { 79 - setIsProcessing(true) 82 + setIsSaveProcessing(true) 80 83 81 84 if ( 82 85 !bsky.validate( ··· 101 104 link.click() 102 105 } 103 106 104 - logEvent('starterPack:share', { 107 + logger.metric('starterPack:share', { 105 108 starterPack: starterPack.uri, 106 109 shareType: 'qrcode', 107 110 qrShareType: 'save', 108 111 }) 109 - setIsProcessing(false) 112 + setIsSaveProcessing(false) 110 113 Toast.show( 111 114 isWeb 112 115 ? _(msg`QR code has been downloaded!`) ··· 117 120 } 118 121 119 122 const onCopyPress = async () => { 120 - setIsProcessing(true) 123 + setIsCopyProcessing(true) 121 124 ref.current?.capture?.().then(async (uri: string) => { 122 125 const canvas = await getCanvas(uri) 123 126 // @ts-expect-error web only ··· 126 129 navigator.clipboard.write([item]) 127 130 }) 128 131 129 - logEvent('starterPack:share', { 132 + logger.metric('starterPack:share', { 130 133 starterPack: starterPack.uri, 131 134 shareType: 'qrcode', 132 135 qrShareType: 'copy', 133 136 }) 134 137 Toast.show(_(msg`QR code copied to your clipboard!`)) 135 - setIsProcessing(false) 138 + setIsCopyProcessing(false) 136 139 control.close() 137 140 }) 138 141 } ··· 142 145 control.close(() => { 143 146 Sharing.shareAsync(uri, {mimeType: 'image/png', UTI: 'image/png'}).then( 144 147 () => { 145 - logEvent('starterPack:share', { 148 + logger.metric('starterPack:share', { 146 149 starterPack: starterPack.uri, 147 150 shareType: 'qrcode', 148 151 qrShareType: 'share', ··· 154 157 } 155 158 156 159 return ( 157 - <Dialog.Outer control={control}> 160 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 158 161 <Dialog.Handle /> 159 162 <Dialog.ScrollableInner 160 163 label={_(msg`Create a QR code for a starter pack`)}> 161 164 <View style={[a.flex_1, a.align_center, a.gap_5xl]}> 162 - <React.Suspense fallback={<Loading />}> 165 + <Suspense fallback={<Loading />}> 163 166 {!link ? ( 164 167 <Loading /> 165 168 ) : ( 166 169 <> 167 170 <QrCode starterPack={starterPack} link={link} ref={ref} /> 168 - {isProcessing ? ( 169 - <View> 170 - <Loader size="xl" /> 171 - </View> 172 - ) : ( 173 - <View 174 - style={[a.w_full, a.gap_md, isWeb && [a.flex_row_reverse]]}> 175 - <Button 176 - label={_(msg`Copy QR code`)} 177 - variant="solid" 178 - color="secondary" 179 - size="small" 180 - onPress={isWeb ? onCopyPress : onSharePress}> 181 - <ButtonText> 182 - {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 183 - </ButtonText> 184 - </Button> 185 - <Button 186 - label={_(msg`Save QR code`)} 187 - variant="solid" 188 - color="secondary" 189 - size="small" 190 - onPress={onSavePress}> 191 - <ButtonText> 192 - <Trans>Save</Trans> 193 - </ButtonText> 194 - </Button> 195 - </View> 196 - )} 171 + <View 172 + style={[ 173 + a.w_full, 174 + a.gap_md, 175 + gtMobile && [a.flex_row, a.justify_center, a.flex_wrap], 176 + ]}> 177 + <Button 178 + label={_(msg`Copy QR code`)} 179 + color="primary_subtle" 180 + size="large" 181 + onPress={isWeb ? onCopyPress : onSharePress}> 182 + <ButtonIcon 183 + icon={ 184 + isCopyProcessing 185 + ? Loader 186 + : isWeb 187 + ? ChainLinkIcon 188 + : ShareIcon 189 + } 190 + /> 191 + <ButtonText> 192 + {isWeb ? <Trans>Copy</Trans> : <Trans>Share</Trans>} 193 + </ButtonText> 194 + </Button> 195 + <Button 196 + label={_(msg`Save QR code`)} 197 + color="secondary" 198 + size="large" 199 + onPress={onSavePress}> 200 + <ButtonIcon 201 + icon={isSaveProcessing ? Loader : FloppyDiskIcon} 202 + /> 203 + <ButtonText> 204 + <Trans>Save</Trans> 205 + </ButtonText> 206 + </Button> 207 + </View> 197 208 </> 198 209 )} 199 - </React.Suspense> 210 + </Suspense> 200 211 </View> 201 212 <Dialog.Close /> 202 213 </Dialog.ScrollableInner> ··· 206 217 207 218 function Loading() { 208 219 return ( 209 - <View style={[a.align_center, a.p_xl]}> 220 + <View style={[a.align_center, a.justify_center, {minHeight: 400}]}> 210 221 <Loader size="xl" /> 211 222 </View> 212 223 )
+30 -24
src/components/StarterPack/ShareDialog.tsx
··· 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 8 7 import {useSaveImageToMediaLibrary} from '#/lib/media/save-image' 9 8 import {shareUrl} from '#/lib/sharing' 10 - import {logEvent} from '#/lib/statsig/statsig' 11 9 import {getStarterPackOgCard} from '#/lib/strings/starter-pack' 10 + import {logger} from '#/logger' 12 11 import {isNative, isWeb} from '#/platform/detection' 13 - import {atoms as a, useTheme} from '#/alf' 14 - import {Button, ButtonText} from '#/components/Button' 12 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 14 import {type DialogControlProps} from '#/components/Dialog' 16 15 import * as Dialog from '#/components/Dialog' 16 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 17 + import {Download_Stroke2_Corner0_Rounded as DownloadIcon} from '#/components/icons/Download' 18 + import {QrCode_Stroke2_Corner0_Rounded as QrCodeIcon} from '#/components/icons/QrCode' 17 19 import {Loader} from '#/components/Loader' 18 20 import {Text} from '#/components/Typography' 19 21 ··· 27 29 28 30 export function ShareDialog(props: Props) { 29 31 return ( 30 - <Dialog.Outer control={props.control}> 32 + <Dialog.Outer 33 + control={props.control} 34 + nativeOptions={{preventExpansion: true}}> 31 35 <Dialog.Handle /> 32 36 <ShareDialogInner {...props} /> 33 37 </Dialog.Outer> ··· 43 47 }: Props) { 44 48 const {_} = useLingui() 45 49 const t = useTheme() 46 - const {isTabletOrDesktop} = useWebMediaQueries() 50 + const {gtMobile} = useBreakpoints() 47 51 48 52 const imageUrl = getStarterPackOgCard(starterPack) 49 53 50 54 const onShareLink = async () => { 51 55 if (!link) return 52 56 shareUrl(link) 53 - logEvent('starterPack:share', { 57 + logger.metric('starterPack:share', { 54 58 starterPack: starterPack.uri, 55 59 shareType: 'link', 56 60 }) ··· 67 71 <> 68 72 <Dialog.ScrollableInner label={_(msg`Share link dialog`)}> 69 73 {!imageLoaded || !link ? ( 70 - <View style={[a.p_xl, a.align_center]}> 74 + <View style={[a.align_center, a.justify_center, {minHeight: 350}]}> 71 75 <Loader size="xl" /> 72 76 </View> 73 77 ) : ( 74 - <View style={[!isTabletOrDesktop && a.gap_lg]}> 75 - <View style={[a.gap_sm, isTabletOrDesktop && a.pb_lg]}> 78 + <View style={[!gtMobile && a.gap_lg]}> 79 + <View style={[a.gap_sm, gtMobile && a.pb_lg]}> 76 80 <Text style={[a.font_bold, a.text_2xl]}> 77 81 <Trans>Invite people to this starter pack!</Trans> 78 82 </Text> ··· 89 93 a.rounded_sm, 90 94 { 91 95 aspectRatio: 1200 / 630, 92 - transform: [{scale: isTabletOrDesktop ? 0.85 : 1}], 93 - marginTop: isTabletOrDesktop ? -20 : 0, 96 + transform: [{scale: gtMobile ? 0.85 : 1}], 97 + marginTop: gtMobile ? -20 : 0, 94 98 }, 95 99 ]} 96 100 accessibilityIgnoresInvertColors={true} ··· 98 102 <View 99 103 style={[ 100 104 a.gap_md, 101 - isWeb && [a.gap_sm, a.flex_row_reverse, {marginLeft: 'auto'}], 105 + gtMobile && [ 106 + a.gap_sm, 107 + a.justify_center, 108 + a.flex_row, 109 + a.flex_wrap, 110 + ], 102 111 ]}> 103 112 <Button 104 113 label={isWeb ? _(msg`Copy link`) : _(msg`Share link`)} 105 - variant="solid" 106 - color="secondary" 107 - size="small" 108 - style={[isWeb && a.self_center]} 114 + color="primary_subtle" 115 + size="large" 109 116 onPress={onShareLink}> 117 + <ButtonIcon icon={ChainLinkIcon} /> 110 118 <ButtonText> 111 119 {isWeb ? <Trans>Copy Link</Trans> : <Trans>Share link</Trans>} 112 120 </ButtonText> 113 121 </Button> 114 122 <Button 115 123 label={_(msg`Share QR code`)} 116 - variant="solid" 117 - color="secondary" 118 - size="small" 119 - style={[isWeb && a.self_center]} 124 + color="primary_subtle" 125 + size="large" 120 126 onPress={() => { 121 127 control.close(() => { 122 128 qrDialogControl.open() 123 129 }) 124 130 }}> 131 + <ButtonIcon icon={QrCodeIcon} /> 125 132 <ButtonText> 126 133 <Trans>Share QR code</Trans> 127 134 </ButtonText> ··· 129 136 {isNative && ( 130 137 <Button 131 138 label={_(msg`Save image`)} 132 - variant="ghost" 133 139 color="secondary" 134 - size="small" 135 - style={[isWeb && a.self_center]} 140 + size="large" 136 141 onPress={onSave}> 142 + <ButtonIcon icon={DownloadIcon} /> 137 143 <ButtonText> 138 144 <Trans>Save image</Trans> 139 145 </ButtonText>
+2 -2
src/components/StarterPack/Wizard/ScreenTransition.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import Animated, { 4 3 FadeIn, 5 4 FadeOut, 6 5 SlideInLeft, 7 6 SlideInRight, 8 7 } from 'react-native-reanimated' 8 + import type React from 'react' 9 9 10 10 import {isWeb} from '#/platform/detection' 11 11
+1 -1
src/components/SubtleWebHover.tsx
··· 1 - import {ViewStyleProp} from '#/alf' 1 + import {type ViewStyleProp} from '#/alf' 2 2 3 3 export function SubtleWebHover({}: ViewStyleProp & {hover: boolean}) { 4 4 return null
+4 -4
src/components/TrendingTopics.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AtUri} from '@atproto/api' 3 + import {type AtUri} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 10 10 // import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag' 11 11 // import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote' 12 12 // import {UserAvatar} from '#/view/com/util/UserAvatar' 13 - import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics' 14 - import {atoms as a, native, useTheme, ViewStyleProp} from '#/alf' 13 + import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics' 14 + import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf' 15 15 import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack' 16 - import {Link as InternalLink, LinkProps} from '#/components/Link' 16 + import {Link as InternalLink, type LinkProps} from '#/components/Link' 17 17 import {Text} from '#/components/Typography' 18 18 19 19 export function TrendingTopic({
+4 -4
src/components/VideoPostCard.tsx
··· 3 3 import {Image} from 'expo-image' 4 4 import {LinearGradient} from 'expo-linear-gradient' 5 5 import { 6 - AppBskyActorDefs, 6 + type AppBskyActorDefs, 7 7 AppBskyEmbedVideo, 8 - AppBskyFeedDefs, 8 + type AppBskyFeedDefs, 9 9 AppBskyFeedPost, 10 - ModerationDecision, 10 + type ModerationDecision, 11 11 } from '@atproto/api' 12 12 import {msg} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react' ··· 15 15 import {sanitizeHandle} from '#/lib/strings/handles' 16 16 import {formatCount} from '#/view/com/util/numeric/format' 17 17 import {UserAvatar} from '#/view/com/util/UserAvatar' 18 - import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' 18 + import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' 19 19 import {atoms as a, useTheme} from '#/alf' 20 20 import {BLUE_HUE} from '#/alf/util/colorGeneration' 21 21 import {select} from '#/alf/util/themeSelector'
+1 -1
src/components/anim/AnimatedCheck.tsx
··· 8 8 } from 'react-native-reanimated' 9 9 import Svg, {Circle, Path} from 'react-native-svg' 10 10 11 - import {Props, useCommonSVGProps} from '#/components/icons/common' 11 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 12 12 13 13 const AnimatedPath = Animated.createAnimatedComponent(Path) 14 14 const AnimatedCircle = Animated.createAnimatedComponent(Circle)
+1 -1
src/components/dialogs/Embed.tsx
··· 1 1 import {memo, useEffect, useMemo, useState} from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, AppBskyFeedPost, AtUri} from '@atproto/api' 3 + import {type AppBskyActorDefs, type AppBskyFeedPost, AtUri} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6
+3 -3
src/components/dialogs/EmbedConsent.tsx
··· 10 10 } from '#/lib/strings/embed-player' 11 11 import {useSetExternalEmbedPref} from '#/state/preferences' 12 12 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 13 + import {Button, ButtonText} from '#/components/Button' 13 14 import * as Dialog from '#/components/Dialog' 14 - import {Button, ButtonText} from '../Button' 15 - import {Text} from '../Typography' 15 + import {Text} from '#/components/Typography' 16 16 17 17 export function EmbedConsentDialog({ 18 18 control, ··· 48 48 }, [control, setExternalEmbedPref, source]) 49 49 50 50 return ( 51 - <Dialog.Outer control={control}> 51 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 52 52 <Dialog.Handle /> 53 53 <Dialog.ScrollableInner 54 54 label={_(msg`External Media`)}
+1 -1
src/components/dialogs/GifSelect.tsx
··· 37 37 onClose, 38 38 onSelectGif: onSelectGifProp, 39 39 }: { 40 - controlRef: React.RefObject<{open: () => void}> 40 + controlRef: React.RefObject<{open: () => void} | null> 41 41 onClose?: () => void 42 42 onSelectGif: (gif: Gif) => void 43 43 }) {
+2 -2
src/components/dialogs/MutedWords.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' 3 + import {type AppBskyActorDefs, sanitizeMutedWordValue} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 16 16 native, 17 17 useBreakpoints, 18 18 useTheme, 19 - ViewStyleProp, 19 + type ViewStyleProp, 20 20 web, 21 21 } from '#/alf' 22 22 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+1 -1
src/components/dialogs/SearchablePeopleList.tsx
··· 484 484 value: string 485 485 onChangeText: (text: string) => void 486 486 onEscape: () => void 487 - inputRef: React.RefObject<TextInput> 487 + inputRef: React.RefObject<TextInput | null> 488 488 }) { 489 489 const t = useTheme() 490 490 const {_} = useLingui()
-1
src/components/dms/ActionsWrapper.web.tsx
··· 3 3 import {type ChatBskyConvoDefs} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 - import type React from 'react' 7 6 8 7 import {useConvoActive} from '#/state/messages/convo' 9 8 import {useSession} from '#/state/session'
+2 -2
src/components/dms/BlockedByListDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ModerationCause} from '@atproto/api' 3 + import {type ModerationCause} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {listUriToHref} from '#/lib/strings/url-helpers' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import * as Dialog from '#/components/Dialog' 10 - import {DialogControlProps} from '#/components/Dialog' 10 + import {type DialogControlProps} from '#/components/Dialog' 11 11 import {InlineLinkText} from '#/components/Link' 12 12 import * as Prompt from '#/components/Prompt' 13 13 import {Text} from '#/components/Typography'
+2 -2
src/components/dms/LeaveConvoPrompt.tsx
··· 2 2 import {useLingui} from '@lingui/react' 3 3 import {StackActions, useNavigation} from '@react-navigation/native' 4 4 5 - import {NavigationProp} from '#/lib/routes/types' 5 + import {type NavigationProp} from '#/lib/routes/types' 6 6 import {isNative} from '#/platform/detection' 7 7 import {useLeaveConvo} from '#/state/queries/messages/leave-conversation' 8 8 import * as Toast from '#/view/com/util/Toast' 9 - import {DialogOuterProps} from '#/components/Dialog' 9 + import {type DialogOuterProps} from '#/components/Dialog' 10 10 import * as Prompt from '#/components/Prompt' 11 11 12 12 export function LeaveConvoPrompt({
+2 -2
src/components/dms/MessagesListBlockedFooter.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ModerationDecision} from '@atproto/api' 3 + import {type ModerationDecision} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 ··· 14 14 import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 15 15 import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' 16 16 import {Text} from '#/components/Typography' 17 - import * as bsky from '#/types/bsky' 17 + import type * as bsky from '#/types/bsky' 18 18 19 19 export function MessagesListBlockedFooter({ 20 20 recipient: initialRecipient,
+1 -1
src/components/dms/ReportConversationPrompt.tsx
··· 1 1 import {msg} from '@lingui/macro' 2 2 import {useLingui} from '@lingui/react' 3 3 4 - import {DialogControlProps} from '#/components/Dialog' 4 + import {type DialogControlProps} from '#/components/Dialog' 5 5 import * as Prompt from '#/components/Prompt' 6 6 7 7 export function ReportConversationPrompt({
+2 -2
src/components/feeds/PostFeedVideoGridRow.tsx
··· 2 2 import {AppBskyEmbedVideo} from '@atproto/api' 3 3 4 4 import {logEvent} from '#/lib/statsig/statsig' 5 - import {FeedPostSliceItem} from '#/state/queries/post-feed' 6 - import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' 5 + import {type FeedPostSliceItem} from '#/state/queries/post-feed' 6 + import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' 7 7 import {atoms as a, useGutters} from '#/alf' 8 8 import * as Grid from '#/components/Grid' 9 9 import {
+2 -2
src/components/forms/DateField/index.web.tsx
··· 1 1 import React from 'react' 2 - import {StyleSheet, TextInput, TextInputProps} from 'react-native' 2 + import {StyleSheet, type TextInput, type TextInputProps} from 'react-native' 3 3 // @ts-expect-error untyped 4 4 import {unstable_createElement} from 'react-native-web' 5 5 6 - import {DateFieldProps} from '#/components/forms/DateField/types' 6 + import {type DateFieldProps} from '#/components/forms/DateField/types' 7 7 import {toSimpleDateString} from '#/components/forms/DateField/utils' 8 8 import * as TextField from '#/components/forms/TextField' 9 9 import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
+5 -2
src/components/forms/InputGroup.tsx
··· 23 23 {React.cloneElement(child, { 24 24 // @ts-ignore 25 25 style: [ 26 + // @ts-ignore 26 27 ...(Array.isArray(child.props?.style) 27 - ? child.props.style 28 - : [child.props.style || {}]), 28 + ? // @ts-ignore 29 + child.props.style 30 + : // @ts-ignore 31 + [child.props.style || {}]), 29 32 { 30 33 borderTopLeftRadius: i > 0 ? 0 : undefined, 31 34 borderTopRightRadius: i > 0 ? 0 : undefined,
+1 -1
src/components/forms/SearchInput.tsx
··· 1 1 import React from 'react' 2 - import {TextInput, View} from 'react-native' 2 + import {type TextInput, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/components/forms/TextField.tsx
··· 28 28 import {Text} from '#/components/Typography' 29 29 30 30 const Context = createContext<{ 31 - inputRef: React.RefObject<TextInput> | null 31 + inputRef: React.RefObject<TextInput | null> | null 32 32 isInvalid: boolean 33 33 hovered: boolean 34 34 onHoverIn: () => void ··· 152 152 value?: string 153 153 onChangeText?: (value: string) => void 154 154 isInvalid?: boolean 155 - inputRef?: React.RefObject<TextInput> | React.ForwardedRef<TextInput> 155 + inputRef?: React.RefObject<TextInput | null> | React.ForwardedRef<TextInput> 156 156 } 157 157 158 158 export function createInput(Component: typeof TextInput) {
+7 -2
src/components/forms/ToggleButton.tsx
··· 1 1 import React from 'react' 2 - import {AccessibilityProps, TextStyle, View, ViewStyle} from 'react-native' 2 + import { 3 + type AccessibilityProps, 4 + type TextStyle, 5 + View, 6 + type ViewStyle, 7 + } from 'react-native' 3 8 4 9 import {atoms as a, native, useTheme} from '#/alf' 5 10 import * as Toggle from '#/components/forms/Toggle' ··· 7 12 8 13 type ItemProps = Omit<Toggle.ItemProps, 'style' | 'role' | 'children'> & 9 14 AccessibilityProps & { 10 - children: React.ReactElement 15 + children: React.ReactElement<any> 11 16 testID?: string 12 17 } 13 18
+3 -3
src/components/hooks/useFollowMethods.ts
··· 2 2 import {msg} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4 5 - import {LogEvents} from '#/lib/statsig/statsig' 5 + import {type LogEvents} from '#/lib/statsig/statsig' 6 6 import {logger} from '#/logger' 7 - import {Shadow} from '#/state/cache/types' 7 + import {type Shadow} from '#/state/cache/types' 8 8 import {useProfileFollowMutationQueue} from '#/state/queries/profile' 9 9 import {useRequireAuth} from '#/state/session' 10 10 import * as Toast from '#/view/com/util/Toast' 11 - import * as bsky from '#/types/bsky' 11 + import type * as bsky from '#/types/bsky' 12 12 13 13 export function useFollowMethods({ 14 14 profile,
+1 -1
src/components/hooks/useFullscreen.ts
··· 14 14 return () => document.removeEventListener('fullscreenchange', onChange) 15 15 } 16 16 17 - export function useFullscreen(ref?: React.RefObject<HTMLElement>) { 17 + export function useFullscreen(ref?: React.RefObject<HTMLElement | null>) { 18 18 if (!isWeb) throw new Error("'useFullscreen' is a web-only hook") 19 19 const isFullscreen = useSyncExternalStore(fullscreenSubscribe, () => 20 20 Boolean(document.fullscreenElement),
+1 -1
src/components/icons/TEMPLATE.tsx
··· 1 1 import React from 'react' 2 2 import Svg, {Path} from 'react-native-svg' 3 3 4 - import {Props, useCommonSVGProps} from '#/components/icons/common' 4 + import {type Props, useCommonSVGProps} from '#/components/icons/common' 5 5 6 6 export const IconTemplate_Stroke2_Corner0_Rounded = React.forwardRef( 7 7 function LogoImpl(props: Props, ref) {
+1 -1
src/components/intents/VerifyEmailIntentDialog.tsx
··· 8 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 10 import * as Dialog from '#/components/Dialog' 11 - import {DialogControlProps} from '#/components/Dialog' 11 + import {type DialogControlProps} from '#/components/Dialog' 12 12 import {Divider} from '#/components/Divider' 13 13 import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Resend} from '#/components/icons/ArrowRotateCounterClockwise' 14 14 import {useIntentDialogs} from '#/components/intents/IntentDialogs'
-1
src/components/moderation/LabelPreference.tsx
··· 5 5 } from '@atproto/api' 6 6 import {msg, Trans} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' 8 - import type React from 'react' 9 8 10 9 import {useGlobalLabelStrings} from '#/lib/moderation/useGlobalLabelStrings' 11 10 import {useLabelBehaviorDescription} from '#/lib/moderation/useLabelBehaviorDescription'
+8 -3
src/components/moderation/LabelsOnMe.tsx
··· 1 - import {StyleProp, View, ViewStyle} from 'react-native' 2 - import {AppBskyFeedDefs, ComAtprotoLabelDefs} from '@atproto/api' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 2 + import {type AppBskyFeedDefs, type ComAtprotoLabelDefs} from '@atproto/api' 3 3 import {msg, Plural, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {useSession} from '#/state/session' 7 7 import {atoms as a} from '#/alf' 8 - import {Button, ButtonIcon, ButtonSize, ButtonText} from '#/components/Button' 8 + import { 9 + Button, 10 + ButtonIcon, 11 + type ButtonSize, 12 + ButtonText, 13 + } from '#/components/Button' 9 14 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 10 15 import { 11 16 LabelsOnMeDialog,
+1 -1
src/components/moderation/LabelsOnMeDialog.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' 3 + import {type ComAtprotoLabelDefs, ComAtprotoModerationDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {useMutation} from '@tanstack/react-query'
+2 -2
src/components/moderation/ModerationDetailsDialog.tsx
··· 1 1 import {View} from 'react-native' 2 - import {ModerationCause} from '@atproto/api' 2 + import {type ModerationCause} from '@atproto/api' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 ··· 12 12 import {atoms as a, useGutters, useTheme} from '#/alf' 13 13 import * as Dialog from '#/components/Dialog' 14 14 import {InlineLinkText} from '#/components/Link' 15 - import {AppModerationCause} from '#/components/Pills' 15 + import {type AppModerationCause} from '#/components/Pills' 16 16 import {Text} from '#/components/Typography' 17 17 18 18 export {useDialogControl as useModerationDetailsDialogControl} from '#/components/Dialog'
+2 -2
src/components/moderation/PostAlerts.tsx
··· 1 - import {StyleProp, ViewStyle} from 'react-native' 2 - import {ModerationCause, ModerationUI} from '@atproto/api' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 2 + import {type ModerationCause, type ModerationUI} from '@atproto/api' 3 3 4 4 import {getModerationCauseKey, unique} from '#/lib/moderation' 5 5 import * as Pills from '#/components/Pills'
+2 -2
src/components/moderation/ProfileHeaderAlerts.tsx
··· 1 - import {StyleProp, ViewStyle} from 'react-native' 2 - import {ModerationDecision} from '@atproto/api' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 2 + import {type ModerationDecision} from '@atproto/api' 3 3 4 4 import {getModerationCauseKey, unique} from '#/lib/moderation' 5 5 import * as Pills from '#/components/Pills'
+5 -5
src/components/moderation/ReportDialog/action.ts
··· 1 1 import { 2 - $Typed, 3 - ChatBskyConvoDefs, 4 - ComAtprotoModerationCreateReport, 2 + type $Typed, 3 + type ChatBskyConvoDefs, 4 + type ComAtprotoModerationCreateReport, 5 5 } from '@atproto/api' 6 6 import {msg} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react' ··· 9 9 10 10 import {logger} from '#/logger' 11 11 import {useAgent} from '#/state/session' 12 - import {ReportState} from './state' 13 - import {ParsedReportSubject} from './types' 12 + import {type ReportState} from './state' 13 + import {type ParsedReportSubject} from './types' 14 14 15 15 export function useSubmitReportMutation() { 16 16 const {_} = useLingui()
+1 -1
src/components/moderation/ReportDialog/copy.ts
··· 2 2 import {msg} from '@lingui/macro' 3 3 import {useLingui} from '@lingui/react' 4 4 5 - import {ParsedReportSubject} from './types' 5 + import {type ParsedReportSubject} from './types' 6 6 7 7 export function useCopyForSubject(subject: ParsedReportSubject) { 8 8 const {_} = useLingui()
+2 -2
src/components/moderation/ReportDialog/state.ts
··· 1 - import {AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api' 1 + import {type AppBskyLabelerDefs, ComAtprotoModerationDefs} from '@atproto/api' 2 2 3 - import {ReportOption} from './utils/useReportOptions' 3 + import {type ReportOption} from './utils/useReportOptions' 4 4 5 5 export type ReportState = { 6 6 selectedOption?: ReportOption
+6 -6
src/components/moderation/ReportDialog/types.ts
··· 1 1 import { 2 - $Typed, 3 - AppBskyActorDefs, 4 - AppBskyFeedDefs, 5 - AppBskyGraphDefs, 6 - ChatBskyConvoDefs, 2 + type $Typed, 3 + type AppBskyActorDefs, 4 + type AppBskyFeedDefs, 5 + type AppBskyGraphDefs, 6 + type ChatBskyConvoDefs, 7 7 } from '@atproto/api' 8 8 9 - import * as Dialog from '#/components/Dialog' 9 + import type * as Dialog from '#/components/Dialog' 10 10 11 11 export type ReportSubject = 12 12 | $Typed<AppBskyActorDefs.ProfileViewBasic>
+2 -2
src/components/moderation/ReportDialog/utils/parseReportSubject.ts
··· 6 6 } from '@atproto/api' 7 7 8 8 import { 9 - ParsedReportSubject, 10 - ReportSubject, 9 + type ParsedReportSubject, 10 + type ReportSubject, 11 11 } from '#/components/moderation/ReportDialog/types' 12 12 import * as bsky from '#/types/bsky' 13 13
+4 -4
src/components/moderation/ScreenHider.tsx
··· 1 1 import React from 'react' 2 2 import { 3 - StyleProp, 3 + type StyleProp, 4 4 TouchableWithoutFeedback, 5 5 View, 6 - ViewStyle, 6 + type ViewStyle, 7 7 } from 'react-native' 8 - import {ModerationUI} from '@atproto/api' 8 + import {type ModerationUI} from '@atproto/api' 9 9 import {msg, Trans} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 11 import {useNavigation} from '@react-navigation/native' 12 12 13 13 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 14 14 import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription' 15 - import {NavigationProp} from '#/lib/routes/types' 15 + import {type NavigationProp} from '#/lib/routes/types' 16 16 import {CenteredView} from '#/view/com/util/Views' 17 17 import {atoms as a, useTheme, web} from '#/alf' 18 18 import {Button, ButtonText} from '#/components/Button'
+4 -4
src/components/verification/VerificationCheckButton.tsx
··· 85 85 if (size === 'lg') { 86 86 dimensions = gtPhone ? 20 : 18 87 87 } else if (size === 'md') { 88 - dimensions = 16 88 + dimensions = 14 89 89 } 90 90 91 91 const verifiedByHidden = !state.profile.showBadge && state.profile.isViewer ··· 99 99 : _(msg`View this user's verifications`) 100 100 } 101 101 hitSlop={20} 102 - onPress={() => { 102 + onPress={evt => { 103 + evt.preventDefault() 103 104 logger.metric('verification:badge:click', {}, {statsig: true}) 104 105 if (state.profile.role === 'verifier') { 105 106 verifierDialogControl.open() 106 107 } else { 107 108 verificationsDialogControl.open() 108 109 } 109 - }} 110 - style={[]}> 110 + }}> 111 111 {({hovered}) => ( 112 112 <View 113 113 style={[
+3 -3
src/lib/api/feed/custom.ts
··· 1 1 import { 2 - AppBskyFeedDefs, 3 - AppBskyFeedGetFeed as GetCustomFeed, 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetFeed as GetCustomFeed, 4 4 BskyAgent, 5 5 jsonStringToLex, 6 6 } from '@atproto/api' ··· 9 9 getAppLanguageAsContentLanguage, 10 10 getContentLanguages, 11 11 } from '#/state/preferences/languages' 12 - import {FeedAPI, FeedAPIResponse} from './types' 12 + import {type FeedAPI, type FeedAPIResponse} from './types' 13 13 import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' 14 14 15 15 export class CustomFeedAPI implements FeedAPI {
+2 -2
src/lib/api/feed/following.ts
··· 1 - import {AppBskyFeedDefs, BskyAgent} from '@atproto/api' 1 + import {type AppBskyFeedDefs, type BskyAgent} from '@atproto/api' 2 2 3 - import {FeedAPI, FeedAPIResponse} from './types' 3 + import {type FeedAPI, type FeedAPIResponse} from './types' 4 4 5 5 export class FollowingFeedAPI implements FeedAPI { 6 6 agent: BskyAgent
+4 -4
src/lib/api/feed/likes.ts
··· 1 1 import { 2 - AppBskyFeedDefs, 3 - AppBskyFeedGetActorLikes as GetActorLikes, 4 - BskyAgent, 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetActorLikes as GetActorLikes, 4 + type BskyAgent, 5 5 } from '@atproto/api' 6 6 7 - import {FeedAPI, FeedAPIResponse} from './types' 7 + import {type FeedAPI, type FeedAPIResponse} from './types' 8 8 9 9 export class LikesFeedAPI implements FeedAPI { 10 10 agent: BskyAgent
+12 -4
src/lib/api/feed/merge.ts
··· 1 - import {AppBskyFeedDefs, AppBskyFeedGetTimeline, BskyAgent} from '@atproto/api' 1 + import { 2 + type AppBskyFeedDefs, 3 + type AppBskyFeedGetTimeline, 4 + type BskyAgent, 5 + } from '@atproto/api' 2 6 import shuffle from 'lodash.shuffle' 3 7 4 8 import {bundleAsync} from '#/lib/async/bundle' 5 9 import {timeout} from '#/lib/async/timeout' 6 10 import {feedUriToHref} from '#/lib/strings/url-helpers' 7 11 import {getContentLanguages} from '#/state/preferences/languages' 8 - import {FeedParams} from '#/state/queries/post-feed' 12 + import {type FeedParams} from '#/state/queries/post-feed' 9 13 import {FeedTuner} from '../feed-manip' 10 - import {FeedTunerFn} from '../feed-manip' 11 - import {FeedAPI, FeedAPIResponse, ReasonFeedSource} from './types' 14 + import {type FeedTunerFn} from '../feed-manip' 15 + import { 16 + type FeedAPI, 17 + type FeedAPIResponse, 18 + type ReasonFeedSource, 19 + } from './types' 12 20 import {createBskyTopicsHeader, isBlueskyOwnedFeed} from './utils' 13 21 14 22 const REQUEST_WAIT_MS = 500 // 500ms
+1 -1
src/lib/api/feed/types.ts
··· 1 - import {AppBskyFeedDefs} from '@atproto/api' 1 + import {type AppBskyFeedDefs} from '@atproto/api' 2 2 3 3 export interface FeedAPIResponse { 4 4 cursor?: string
+1 -1
src/lib/api/feed/utils.ts
··· 2 2 3 3 import {BSKY_FEED_OWNER_DIDS} from '#/lib/constants' 4 4 import {isWeb} from '#/platform/detection' 5 - import {UsePreferencesQueryResponse} from '#/state/queries/preferences' 5 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 6 6 7 7 let debugTopics = '' 8 8 if (isWeb && typeof window !== 'undefined') {
+1 -1
src/lib/api/upload-blob.ts
··· 1 1 import {copyAsync} from 'expo-file-system' 2 - import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api' 2 + import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' 3 3 4 4 import {safeDeleteAsync} from '#/lib/media/manip' 5 5
+1 -1
src/lib/api/upload-blob.web.ts
··· 1 - import {BskyAgent, ComAtprotoRepoUploadBlob} from '@atproto/api' 1 + import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api' 2 2 3 3 /** 4 4 * @note It is recommended, on web, to use the `file` instance of the file
+1 -1
src/lib/assets.native.ts
··· 1 - import {ImageRequireSource} from 'react-native' 1 + import {type ImageRequireSource} from 'react-native' 2 2 3 3 export const DEF_AVATAR: ImageRequireSource = require('../../assets/default-avatar.png') 4 4 export const CLOUD_SPLASH: ImageRequireSource = require('../../assets/splash.png')
+1 -1
src/lib/assets.ts
··· 1 - import {ImageRequireSource} from 'react-native' 1 + import {type ImageRequireSource} from 'react-native' 2 2 3 3 // @ts-ignore we need to pretend -prf 4 4 export const DEF_AVATAR: ImageRequireSource = {uri: '/img/default-avatar.png'}
+1 -1
src/lib/custom-animations/GestureActionView.web.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 3 export function GestureActionView({children}: {children: React.ReactNode}) { 4 4 return children
+6 -1
src/lib/custom-animations/PressableScale.tsx
··· 1 - import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 1 + import { 2 + Pressable, 3 + type PressableProps, 4 + type StyleProp, 5 + type ViewStyle, 6 + } from 'react-native' 2 7 import Animated, { 3 8 cancelAnimation, 4 9 useAnimatedStyle,
+2 -2
src/lib/hooks/useAccountSwitcher.ts
··· 4 4 5 5 import {logger} from '#/logger' 6 6 import {isWeb} from '#/platform/detection' 7 - import {SessionAccount, useSessionApi} from '#/state/session' 7 + import {type SessionAccount, useSessionApi} from '#/state/session' 8 8 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 9 9 import * as Toast from '#/view/com/util/Toast' 10 10 import {logEvent} from '../statsig/statsig' 11 - import {LogEvents} from '../statsig/statsig' 11 + import {type LogEvents} from '../statsig/statsig' 12 12 13 13 export function useAccountSwitcher() { 14 14 const [pendingDid, setPendingDid] = useState<string | null>(null)
+1 -1
src/lib/hooks/useAnimatedValue.ts
··· 2 2 import {Animated} from 'react-native' 3 3 4 4 export function useAnimatedValue(initialValue: number) { 5 - const lazyRef = React.useRef<Animated.Value>() 5 + const lazyRef = React.useRef<Animated.Value>(undefined) 6 6 7 7 if (lazyRef.current === undefined) { 8 8 lazyRef.current = new Animated.Value(initialValue)
+1 -1
src/lib/hooks/useGoBack.ts
··· 1 1 import {StackActions, useNavigation} from '@react-navigation/native' 2 2 3 - import {NavigationProp} from '#/lib/routes/types' 3 + import {type NavigationProp} from '#/lib/routes/types' 4 4 import {router} from '#/routes' 5 5 6 6 export function useGoBack(onGoBack?: () => unknown) {
+1 -1
src/lib/hooks/useOTAUpdates.ts
··· 127 127 const appState = React.useRef<AppStateStatus>('active') 128 128 const lastMinimize = React.useRef(0) 129 129 const ranInitialCheck = React.useRef(false) 130 - const timeout = React.useRef<NodeJS.Timeout>() 130 + const timeout = React.useRef<NodeJS.Timeout>(undefined) 131 131 const {currentlyRunning, isUpdatePending} = useUpdates() 132 132 const currentChannel = currentlyRunning?.channel 133 133
+1 -1
src/lib/hooks/useSetTitle.ts
··· 1 1 import {useEffect} from 'react' 2 2 import {useNavigation} from '@react-navigation/native' 3 3 4 - import {NavigationProp} from '#/lib/routes/types' 4 + import {type NavigationProp} from '#/lib/routes/types' 5 5 import {bskyTitle} from '#/lib/strings/headings' 6 6 import {useUnreadNotifications} from '#/state/queries/notifications/unread' 7 7
+1 -1
src/lib/hooks/useTimeAgo.ts
··· 1 1 import {useCallback} from 'react' 2 - import {I18n} from '@lingui/core' 2 + import {type I18n} from '@lingui/core' 3 3 import {defineMessage, msg, plural} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {differenceInSeconds} from 'date-fns'
+1 -1
src/lib/hooks/useWebScrollRestoration.ts
··· 1 1 import {useEffect, useMemo, useState} from 'react' 2 - import {EventArg, useNavigation} from '@react-navigation/core' 2 + import {type EventArg, useNavigation} from '@react-navigation/core' 3 3 4 4 if ('scrollRestoration' in history) { 5 5 // Tell the brower not to mess with the scroll.
+2 -2
src/lib/media/video/compress.web.ts
··· 1 - import {ImagePickerAsset} from 'expo-image-picker' 1 + import {type ImagePickerAsset} from 'expo-image-picker' 2 2 3 3 import {VIDEO_MAX_SIZE} from '#/lib/constants' 4 4 import {VideoTooLargeError} from '#/lib/media/video/errors' 5 - import {CompressedVideo} from './types' 5 + import {type CompressedVideo} from './types' 6 6 7 7 // doesn't actually compress, converts to ArrayBuffer 8 8 export async function compressVideo(
+2 -2
src/lib/media/video/upload.shared.ts
··· 1 - import {BskyAgent} from '@atproto/api' 2 - import {I18n} from '@lingui/core' 1 + import {type BskyAgent} from '@atproto/api' 2 + import {type I18n} from '@lingui/core' 3 3 import {msg} from '@lingui/macro' 4 4 5 5 import {VIDEO_SERVICE_DID} from '#/lib/constants'
+3 -3
src/lib/media/video/upload.ts
··· 1 1 import {createUploadTask, FileSystemUploadType} from 'expo-file-system' 2 - import {AppBskyVideoDefs, BskyAgent} from '@atproto/api' 3 - import {I18n} from '@lingui/core' 2 + import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api' 3 + import {type I18n} from '@lingui/core' 4 4 import {msg} from '@lingui/macro' 5 5 import {nanoid} from 'nanoid/non-secure' 6 6 7 7 import {AbortError} from '#/lib/async/cancelable' 8 8 import {ServerError} from '#/lib/media/video/errors' 9 - import {CompressedVideo} from '#/lib/media/video/types' 9 + import {type CompressedVideo} from '#/lib/media/video/types' 10 10 import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' 11 11 import {createVideoEndpointUrl, mimeToExt} from './util' 12 12
+4 -4
src/lib/media/video/upload.web.ts
··· 1 - import {AppBskyVideoDefs} from '@atproto/api' 2 - import {BskyAgent} from '@atproto/api' 3 - import {I18n} from '@lingui/core' 1 + import {type AppBskyVideoDefs} from '@atproto/api' 2 + import {type BskyAgent} from '@atproto/api' 3 + import {type I18n} from '@lingui/core' 4 4 import {msg} from '@lingui/macro' 5 5 import {nanoid} from 'nanoid/non-secure' 6 6 7 7 import {AbortError} from '#/lib/async/cancelable' 8 8 import {ServerError} from '#/lib/media/video/errors' 9 - import {CompressedVideo} from '#/lib/media/video/types' 9 + import {type CompressedVideo} from '#/lib/media/video/types' 10 10 import {getServiceAuthToken, getVideoUploadLimits} from './upload.shared' 11 11 import {createVideoEndpointUrl, mimeToExt} from './util' 12 12
+1 -1
src/lib/media/video/util.ts
··· 1 1 import {AtpAgent} from '@atproto/api' 2 2 3 - import {SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants' 3 + import {type SupportedMimeTypes, VIDEO_SERVICE} from '#/lib/constants' 4 4 5 5 export const createVideoEndpointUrl = ( 6 6 route: string,
+1 -1
src/lib/merge-refs.ts
··· 13 13 * returns a ref callback function that can be used to merge multiple refs into a single ref. 14 14 */ 15 15 export function mergeRefs<T = any>( 16 - refs: Array<React.MutableRefObject<T> | React.LegacyRef<T>>, 16 + refs: Array<React.MutableRefObject<T> | React.Ref<T>>, 17 17 ): React.RefCallback<T> { 18 18 return value => { 19 19 refs.forEach(ref => {
+1 -1
src/lib/moderation/blocked-and-muted.ts
··· 1 - import * as bsky from '#/types/bsky' 1 + import type * as bsky from '#/types/bsky' 2 2 3 3 export function isBlockedOrBlocking(profile: bsky.profile.AnyProfileView) { 4 4 return profile.viewer?.blockedBy || profile.viewer?.blocking
+4 -1
src/lib/moderation/useLabelBehaviorDescription.ts
··· 1 - import {InterpretedLabelValueDefinition, LabelPreference} from '@atproto/api' 1 + import { 2 + type InterpretedLabelValueDefinition, 3 + type LabelPreference, 4 + } from '@atproto/api' 2 5 import {msg} from '@lingui/macro' 3 6 import {useLingui} from '@lingui/react' 4 7
+4 -4
src/lib/moderation/useLabelInfo.ts
··· 1 1 import { 2 - AppBskyLabelerDefs, 3 - ComAtprotoLabelDefs, 4 - InterpretedLabelValueDefinition, 2 + type AppBskyLabelerDefs, 3 + type ComAtprotoLabelDefs, 4 + type InterpretedLabelValueDefinition, 5 5 interpretLabelValueDefinition, 6 6 LABELS, 7 7 } from '@atproto/api' ··· 9 9 import * as bcp47Match from 'bcp-47-match' 10 10 11 11 import { 12 - GlobalLabelStrings, 12 + type GlobalLabelStrings, 13 13 useGlobalLabelStrings, 14 14 } from '#/lib/moderation/useGlobalLabelStrings' 15 15 import {useLabelDefinitions} from '#/state/preferences'
+4 -3
src/lib/react-query.tsx
··· 1 - import React, {useRef, useState} from 'react' 2 - import {AppState, AppStateStatus} from 'react-native' 1 + import {useRef, useState} from 'react' 2 + import {AppState, type AppStateStatus} from 'react-native' 3 3 import AsyncStorage from '@react-native-async-storage/async-storage' 4 4 import {createAsyncStoragePersister} from '@tanstack/query-async-storage-persister' 5 5 import {focusManager, onlineManager, QueryClient} from '@tanstack/react-query' 6 6 import { 7 7 PersistQueryClientProvider, 8 - PersistQueryClientProviderProps, 8 + type PersistQueryClientProviderProps, 9 9 } from '@tanstack/react-query-persist-client' 10 + import type React from 'react' 10 11 11 12 import {isNative} from '#/platform/detection' 12 13 import {listenNetworkConfirmed, listenNetworkLost} from '#/state/events'
+2 -2
src/lib/routes/helpers.ts
··· 1 - import {NavigationProp} from '@react-navigation/native' 1 + import {type NavigationProp} from '@react-navigation/native' 2 2 3 - import {RouteParams, State} from './types' 3 + import {type RouteParams, type State} from './types' 4 4 5 5 export function getRootNavigation<T extends {}>( 6 6 nav: NavigationProp<T>,
+1 -1
src/lib/strings/display-names.ts
··· 1 - import {ModerationUI} from '@atproto/api' 1 + import {type ModerationUI} from '@atproto/api' 2 2 3 3 // \u2705 = ✅ 4 4 // \u2713 = ✓
+1 -1
src/lib/strings/rich-text-helpers.ts
··· 1 - import {AppBskyRichtextFacet, RichText} from '@atproto/api' 1 + import {AppBskyRichtextFacet, type RichText} from '@atproto/api' 2 2 3 3 import {linkRequiresWarning} from './url-helpers' 4 4
+1 -1
src/lib/strings/rich-text-manip.ts
··· 1 - import {AppBskyRichtextFacet, RichText, UnicodeString} from '@atproto/api' 1 + import {AppBskyRichtextFacet, type RichText, UnicodeString} from '@atproto/api' 2 2 3 3 import {toShortUrl} from './url-helpers' 4 4
+1 -1
src/lib/strings/time.ts
··· 1 - import {I18n} from '@lingui/core' 1 + import {type I18n} from '@lingui/core' 2 2 3 3 export function niceDate(i18n: I18n, date: number | string | Date) { 4 4 const d = new Date(date)
+1 -1
src/lib/themes.ts
··· 4 4 import {darkPalette, dimPalette, lightPalette} from '#/alf/themes' 5 5 import {fontWeight} from '#/alf/tokens' 6 6 import {colors} from './styles' 7 - import type {Theme} from './ThemeContext' 7 + import {type Theme} from './ThemeContext' 8 8 9 9 export const defaultTheme: Theme = { 10 10 colorScheme: 'light',
+1 -1
src/locale/deviceLocales.ts
··· 1 - import {getLocales as defaultGetLocales, Locale} from 'expo-localization' 1 + import {getLocales as defaultGetLocales, type Locale} from 'expo-localization' 2 2 3 3 import {dedupArray} from '#/lib/functions' 4 4
+1 -1
src/locale/i18nProvider.tsx
··· 1 - import React from 'react' 2 1 import {i18n} from '@lingui/core' 3 2 import {I18nProvider as DefaultI18nProvider} from '@lingui/react' 3 + import type React from 'react' 4 4 5 5 import {useLocaleLanguage} from './i18n' 6 6
+322 -278
src/locale/locales/en/messages.po
··· 30 30 msgid "{0, plural, one {# day} other {# days}}" 31 31 msgstr "" 32 32 33 - #: src/screens/Profile/ProfileFollowers.tsx:40 33 + #: src/screens/Profile/ProfileFollowers.tsx:43 34 34 msgid "{0, plural, one {# follower} other {# followers}}" 35 35 msgstr "" 36 36 37 - #: src/screens/Profile/ProfileFollows.tsx:40 37 + #: src/screens/Profile/ProfileFollows.tsx:43 38 38 msgid "{0, plural, one {# following} other {# following}}" 39 39 msgstr "" 40 40 ··· 42 42 msgid "{0, plural, one {# hour} other {# hours}}" 43 43 msgstr "" 44 44 45 - #: src/components/moderation/LabelsOnMe.tsx:53 45 + #: src/components/moderation/LabelsOnMe.tsx:58 46 46 msgid "{0, plural, one {# label has} other {# labels have}} been placed on this account" 47 47 msgstr "" 48 48 49 - #: src/components/moderation/LabelsOnMe.tsx:62 49 + #: src/components/moderation/LabelsOnMe.tsx:67 50 50 msgid "{0, plural, one {# label has} other {# labels have}} been placed on this content" 51 51 msgstr "" 52 52 53 - #: src/screens/Post/PostLikedBy.tsx:41 53 + #: src/screens/Post/PostLikedBy.tsx:44 54 54 msgid "{0, plural, one {# like} other {# likes}}" 55 55 msgstr "" 56 56 ··· 62 62 msgid "{0, plural, one {# month} other {# months}}" 63 63 msgstr "" 64 64 65 - #: src/screens/Post/PostQuotes.tsx:41 65 + #: src/screens/Post/PostQuotes.tsx:44 66 66 msgid "{0, plural, one {# quote} other {# quotes}}" 67 67 msgstr "" 68 68 69 - #: src/screens/Post/PostRepostedBy.tsx:41 69 + #: src/screens/Post/PostRepostedBy.tsx:44 70 70 msgid "{0, plural, one {# repost} other {# reposts}}" 71 71 msgstr "" 72 72 ··· 158 158 msgid "{0} joined this week" 159 159 msgstr "" 160 160 161 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:204 161 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:203 162 162 msgid "{0} of {1}" 163 163 msgstr "" 164 164 ··· 183 183 msgid "{0}, a list by {1}" 184 184 msgstr "" 185 185 186 - #: src/view/com/util/UserAvatar.tsx:572 187 - #: src/view/com/util/UserAvatar.tsx:590 186 + #: src/view/com/util/UserAvatar.tsx:577 187 + #: src/view/com/util/UserAvatar.tsx:595 188 188 msgid "{0}'s avatar" 189 189 msgstr "" 190 190 ··· 422 422 msgid "{notificationCount, plural, one {# unread item} other {# unread items}}" 423 423 msgstr "" 424 424 425 - #: src/components/NewskieDialog.tsx:116 425 + #: src/components/NewskieDialog.tsx:131 426 426 msgid "{profileName} joined Bluesky {0} ago" 427 427 msgstr "" 428 428 429 - #: src/components/NewskieDialog.tsx:111 429 + #: src/components/NewskieDialog.tsx:126 430 430 msgid "{profileName} joined Bluesky using a starter pack {0} ago" 431 431 msgstr "" 432 432 ··· 677 677 msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event." 678 678 msgstr "" 679 679 680 - #: src/view/screens/ProfileList.tsx:924 681 - #: src/view/screens/ProfileList.tsx:942 680 + #: src/screens/ProfileList/AboutSection.tsx:62 681 + #: src/screens/ProfileList/AboutSection.tsx:80 682 682 msgid "Add a user to this list" 683 683 msgstr "" 684 684 ··· 748 748 msgid "Add muted words and tags" 749 749 msgstr "" 750 750 751 - #: src/view/screens/ProfileList.tsx:932 752 - #: src/view/screens/ProfileList.tsx:950 751 + #: src/screens/ProfileList/AboutSection.tsx:70 752 + #: src/screens/ProfileList/AboutSection.tsx:88 753 753 msgid "Add people" 754 754 msgstr "" 755 755 ··· 769 769 msgid "Add some feeds to your starter pack!" 770 770 msgstr "" 771 771 772 - #: src/screens/Feeds/NoFollowingFeed.tsx:41 772 + #: src/screens/Feeds/NoFollowingFeed.tsx:39 773 773 msgid "Add the default feed of only people you follow" 774 774 msgstr "" 775 775 ··· 832 832 msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>." 833 833 msgstr "" 834 834 835 - #: src/components/moderation/LabelPreference.tsx:247 835 + #: src/components/moderation/LabelPreference.tsx:246 836 836 msgid "Adult content is disabled." 837 837 msgstr "" 838 838 ··· 987 987 msgid "An error occurred while fetching the feed." 988 988 msgstr "" 989 989 990 - #: src/components/StarterPack/ProfileStarterPacks.tsx:343 990 + #: src/components/StarterPack/ProfileStarterPacks.tsx:339 991 991 msgid "An error occurred while generating your starter pack. Want to try again?" 992 992 msgstr "" 993 993 ··· 995 995 msgid "An error occurred while loading the video. Please try again later." 996 996 msgstr "" 997 997 998 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:227 998 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:232 999 999 msgid "An error occurred while loading the video. Please try again." 1000 1000 msgstr "" 1001 1001 1002 - #: src/components/StarterPack/QrCodeDialog.tsx:72 1002 + #: src/components/StarterPack/QrCodeDialog.tsx:75 1003 1003 msgid "An error occurred while saving the QR code!" 1004 1004 msgstr "" 1005 1005 ··· 1173 1173 msgid "Appearance" 1174 1174 msgstr "" 1175 1175 1176 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47 1176 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:51 1177 1177 #: src/screens/Home/NoFeedsPinned.tsx:93 1178 1178 msgid "Apply default recommended feeds" 1179 1179 msgstr "" ··· 1232 1232 msgid "Are you sure?" 1233 1233 msgstr "" 1234 1234 1235 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:87 1235 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:89 1236 1236 msgid "Are you writing in <0>{suggestedLanguageName}</0>?" 1237 1237 msgstr "" 1238 1238 ··· 1311 1311 msgstr "" 1312 1312 1313 1313 #: src/components/dialogs/StarterPackDialog.tsx:71 1314 - #: src/components/StarterPack/ProfileStarterPacks.tsx:235 1315 - #: src/components/StarterPack/ProfileStarterPacks.tsx:245 1314 + #: src/components/StarterPack/ProfileStarterPacks.tsx:231 1315 + #: src/components/StarterPack/ProfileStarterPacks.tsx:241 1316 1316 msgid "Before creating a starter pack, you must first verify your email." 1317 1317 msgstr "" 1318 1318 ··· 1370 1370 msgid "Block Account?" 1371 1371 msgstr "" 1372 1372 1373 - #: src/view/screens/ProfileList.tsx:669 1373 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:97 1374 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:100 1374 1375 msgid "Block accounts" 1375 1376 msgstr "" 1376 1377 ··· 1382 1383 msgid "Block and/or delete this conversation" 1383 1384 msgstr "" 1384 1385 1385 - #: src/view/screens/ProfileList.tsx:789 1386 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:125 1386 1387 msgid "Block list" 1387 1388 msgstr "" 1388 1389 ··· 1390 1391 msgid "Block or report" 1391 1392 msgstr "" 1392 1393 1393 - #: src/view/screens/ProfileList.tsx:784 1394 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:120 1394 1395 msgid "Block these accounts?" 1395 1396 msgstr "" 1396 1397 ··· 1425 1426 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." 1426 1427 msgstr "" 1427 1428 1428 - #: src/screens/Profile/Sections/Labels.tsx:203 1429 + #: src/screens/Profile/Sections/Labels.tsx:204 1429 1430 msgid "Blocking does not prevent this labeler from placing labels on your account." 1430 1431 msgstr "" 1431 1432 1432 - #: src/view/screens/ProfileList.tsx:786 1433 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:122 1433 1434 msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." 1434 1435 msgstr "" 1435 1436 ··· 1441 1442 msgid "Blog" 1442 1443 msgstr "" 1443 1444 1445 + #: src/view/com/auth/server-input/index.tsx:136 1446 + #: src/view/com/auth/server-input/index.tsx:137 1447 + msgid "Bluesky" 1448 + msgstr "" 1449 + 1444 1450 #: src/screens/PostThread/components/ThreadItemAnchor.tsx:684 1445 1451 msgid "Bluesky cannot confirm the authenticity of the claimed date." 1446 1452 msgstr "" ··· 1467 1473 msgid "Bluesky Social Terms of Service" 1468 1474 msgstr "" 1469 1475 1470 - #: src/components/StarterPack/ProfileStarterPacks.tsx:310 1476 + #: src/components/StarterPack/ProfileStarterPacks.tsx:306 1471 1477 msgid "Bluesky will choose a set of recommended accounts from people in your network." 1472 1478 msgstr "" 1473 1479 ··· 1495 1501 msgid "Bluesky+ icons" 1496 1502 msgstr "" 1497 1503 1498 - #: src/lib/moderation/useLabelBehaviorDescription.ts:53 1504 + #: src/lib/moderation/useLabelBehaviorDescription.ts:56 1499 1505 msgid "Blur images" 1500 1506 msgstr "" 1501 1507 1502 - #: src/lib/moderation/useLabelBehaviorDescription.ts:51 1508 + #: src/lib/moderation/useLabelBehaviorDescription.ts:54 1503 1509 msgid "Blur images and filter from feeds" 1504 1510 msgstr "" 1505 1511 ··· 1710 1716 msgid "Change password dialog" 1711 1717 msgstr "" 1712 1718 1713 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:98 1719 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:100 1714 1720 msgid "Change post language to {suggestedLanguageName}" 1715 1721 msgstr "" 1716 1722 ··· 1817 1823 msgid "Choose Feeds" 1818 1824 msgstr "" 1819 1825 1820 - #: src/components/StarterPack/ProfileStarterPacks.tsx:318 1826 + #: src/components/StarterPack/ProfileStarterPacks.tsx:314 1821 1827 msgid "Choose for me" 1822 1828 msgstr "" 1823 1829 ··· 1874 1880 msgid "Click for information" 1875 1881 msgstr "" 1876 1882 1877 - #: src/view/screens/Support.tsx:41 1883 + #: src/view/screens/Support.tsx:44 1878 1884 msgid "click here" 1879 1885 msgstr "" 1880 1886 ··· 1928 1934 #: src/components/dms/ReportDialog.tsx:395 1929 1935 #: src/components/live/EditLiveDialog.tsx:229 1930 1936 #: src/components/live/EditLiveDialog.tsx:235 1931 - #: src/components/NewskieDialog.tsx:146 1932 - #: src/components/NewskieDialog.tsx:153 1937 + #: src/components/NewskieDialog.tsx:159 1938 + #: src/components/NewskieDialog.tsx:165 1933 1939 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:197 1934 1940 #: src/components/ProgressGuide/FollowDialog.tsx:379 1935 1941 #: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:118 ··· 1955 1961 msgid "Close alert" 1956 1962 msgstr "" 1957 1963 1958 - #: src/view/com/util/BottomSheetCustomBackdrop.tsx:36 1964 + #: src/view/com/util/BottomSheetCustomBackdrop.tsx:37 1959 1965 msgid "Close bottom drawer" 1960 1966 msgstr "" 1961 1967 ··· 2051 2057 2052 2058 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:45 2053 2059 #: src/Navigation.tsx:341 2054 - #: src/view/screens/CommunityGuidelines.tsx:34 2060 + #: src/view/screens/CommunityGuidelines.tsx:37 2055 2061 msgid "Community Guidelines" 2056 2062 msgstr "" 2057 2063 ··· 2080 2086 msgid "Compressing video..." 2081 2087 msgstr "" 2082 2088 2083 - #: src/components/moderation/LabelPreference.tsx:88 2089 + #: src/components/moderation/LabelPreference.tsx:87 2084 2090 msgid "Configure content filtering setting for category: {name}" 2085 2091 msgstr "" 2086 2092 2087 - #: src/components/moderation/LabelPreference.tsx:249 2093 + #: src/components/moderation/LabelPreference.tsx:248 2088 2094 msgid "Configured in <0>moderation settings</0>." 2089 2095 msgstr "" 2090 2096 ··· 2280 2286 msgid "Copies build version to clipboard" 2281 2287 msgstr "" 2282 2288 2283 - #: src/components/StarterPack/QrCodeDialog.tsx:182 2289 + #: src/components/StarterPack/QrCodeDialog.tsx:192 2284 2290 msgid "Copy" 2285 2291 msgstr "" 2286 2292 ··· 2293 2299 msgid "Copy at:// URI" 2294 2300 msgstr "" 2295 2301 2296 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:153 2297 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:156 2302 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:152 2303 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:155 2298 2304 msgid "Copy author DID" 2299 2305 msgstr "" 2300 2306 ··· 2313 2319 msgid "Copy host" 2314 2320 msgstr "" 2315 2321 2316 - #: src/components/StarterPack/ShareDialog.tsx:104 2322 + #: src/components/StarterPack/ShareDialog.tsx:113 2317 2323 #: src/screens/StarterPack/StarterPackScreen.tsx:617 2318 2324 msgid "Copy link" 2319 2325 msgstr "" 2320 2326 2321 - #: src/components/StarterPack/ShareDialog.tsx:111 2327 + #: src/components/StarterPack/ShareDialog.tsx:119 2322 2328 msgid "Copy Link" 2323 2329 msgstr "" 2324 2330 2325 - #: src/view/screens/ProfileList.tsx:513 2331 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172 2332 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:176 2326 2333 msgid "Copy link to list" 2327 2334 msgstr "" 2328 2335 2329 2336 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:127 2330 2337 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:130 2331 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:87 2332 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:90 2338 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:86 2339 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:89 2333 2340 msgid "Copy link to post" 2334 2341 msgstr "" 2335 2342 ··· 2347 2354 msgid "Copy message text" 2348 2355 msgstr "" 2349 2356 2350 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:144 2351 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:147 2357 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:143 2358 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:146 2352 2359 msgid "Copy post at:// URI" 2353 2360 msgstr "" 2354 2361 ··· 2357 2364 msgid "Copy post text" 2358 2365 msgstr "" 2359 2366 2360 - #: src/components/StarterPack/QrCodeDialog.tsx:176 2367 + #: src/components/StarterPack/QrCodeDialog.tsx:178 2361 2368 msgid "Copy QR code" 2362 2369 msgstr "" 2363 2370 ··· 2368 2375 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:40 2369 2376 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:107 2370 2377 #: src/Navigation.tsx:346 2371 - #: src/view/screens/CopyrightPolicy.tsx:31 2378 + #: src/view/screens/CopyrightPolicy.tsx:34 2372 2379 msgid "Copyright Policy" 2373 2380 msgstr "" 2374 2381 ··· 2393 2400 msgid "Could not load feed" 2394 2401 msgstr "" 2395 2402 2396 - #: src/view/screens/ProfileList.tsx:1029 2403 + #: src/screens/ProfileList/components/ErrorScreen.tsx:26 2404 + #: src/screens/ProfileList/index.tsx:79 2405 + #: src/screens/ProfileList/index.tsx:101 2397 2406 msgid "Could not load list" 2398 2407 msgstr "" 2399 2408 ··· 2417 2426 #. Text on button to create a new starter pack 2418 2427 #: src/components/dialogs/StarterPackDialog.tsx:112 2419 2428 #: src/components/dialogs/StarterPackDialog.tsx:201 2420 - #: src/components/StarterPack/ProfileStarterPacks.tsx:300 2429 + #: src/components/StarterPack/ProfileStarterPacks.tsx:296 2421 2430 msgid "Create" 2422 2431 msgstr "" 2423 2432 2424 - #: src/components/StarterPack/QrCodeDialog.tsx:160 2433 + #: src/components/StarterPack/QrCodeDialog.tsx:163 2425 2434 msgid "Create a QR code for a starter pack" 2426 2435 msgstr "" 2427 2436 2428 - #: src/components/StarterPack/ProfileStarterPacks.tsx:178 2429 - #: src/components/StarterPack/ProfileStarterPacks.tsx:287 2437 + #: src/components/StarterPack/ProfileStarterPacks.tsx:174 2438 + #: src/components/StarterPack/ProfileStarterPacks.tsx:283 2430 2439 #: src/Navigation.tsx:589 2431 2440 msgid "Create a starter pack" 2432 2441 msgstr "" 2433 2442 2434 - #: src/components/StarterPack/ProfileStarterPacks.tsx:274 2443 + #: src/components/StarterPack/ProfileStarterPacks.tsx:270 2435 2444 msgid "Create a starter pack for me" 2436 2445 msgstr "" 2437 2446 ··· 2469 2478 msgid "Create an avatar instead" 2470 2479 msgstr "" 2471 2480 2472 - #: src/components/StarterPack/ProfileStarterPacks.tsx:185 2481 + #: src/components/StarterPack/ProfileStarterPacks.tsx:181 2473 2482 msgid "Create another" 2474 2483 msgstr "" 2475 2484 ··· 2479 2488 msgstr "" 2480 2489 2481 2490 #: src/components/moderation/ReportDialog/index.tsx:585 2482 - #: src/components/ReportDialog/SelectReportOptionView.tsx:99 2491 + #: src/components/ReportDialog/SelectReportOptionView.tsx:102 2483 2492 msgid "Create report for {0}" 2484 2493 msgstr "" 2485 2494 ··· 2562 2571 #: src/components/dms/MessageContextMenu.tsx:185 2563 2572 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:704 2564 2573 #: src/screens/Messages/components/ChatStatusInfo.tsx:55 2574 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:285 2565 2575 #: src/screens/Settings/AppPasswords.tsx:212 2566 2576 #: src/screens/StarterPack/StarterPackScreen.tsx:599 2567 2577 #: src/screens/StarterPack/StarterPackScreen.tsx:688 2568 2578 #: src/screens/StarterPack/StarterPackScreen.tsx:760 2569 - #: src/view/screens/ProfileList.tsx:768 2570 2579 msgid "Delete" 2571 2580 msgstr "" 2572 2581 ··· 2611 2620 msgid "Delete for me" 2612 2621 msgstr "" 2613 2622 2614 - #: src/view/screens/ProfileList.tsx:556 2623 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:211 2624 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:214 2615 2625 msgid "Delete list" 2616 2626 msgstr "" 2617 2627 ··· 2642 2652 msgid "Delete starter pack?" 2643 2653 msgstr "" 2644 2654 2645 - #: src/view/screens/ProfileList.tsx:763 2655 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:280 2646 2656 msgid "Delete this list?" 2647 2657 msgstr "" 2648 2658 ··· 2731 2741 msgid "Disable subtitles" 2732 2742 msgstr "" 2733 2743 2734 - #: src/lib/moderation/useLabelBehaviorDescription.ts:32 2735 - #: src/lib/moderation/useLabelBehaviorDescription.ts:42 2736 - #: src/lib/moderation/useLabelBehaviorDescription.ts:68 2744 + #: src/lib/moderation/useLabelBehaviorDescription.ts:35 2745 + #: src/lib/moderation/useLabelBehaviorDescription.ts:45 2746 + #: src/lib/moderation/useLabelBehaviorDescription.ts:71 2737 2747 #: src/screens/Messages/Settings.tsx:144 2738 2748 #: src/screens/Messages/Settings.tsx:147 2739 2749 #: src/screens/Moderation/index.tsx:413 ··· 2892 2902 msgid "Download Bluesky" 2893 2903 msgstr "" 2894 2904 2895 - #: src/screens/Settings/components/ExportCarDialog.tsx:79 2896 - #: src/screens/Settings/components/ExportCarDialog.tsx:84 2905 + #: src/screens/Settings/components/ExportCarDialog.tsx:78 2906 + #: src/screens/Settings/components/ExportCarDialog.tsx:83 2897 2907 msgid "Download CAR file" 2898 2908 msgstr "" 2899 2909 ··· 2980 2990 msgid "Edit interests" 2981 2991 msgstr "" 2982 2992 2983 - #: src/view/screens/ProfileList.tsx:544 2993 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:203 2994 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:206 2984 2995 msgid "Edit list details" 2985 2996 msgstr "" 2986 2997 ··· 3088 3099 3089 3100 #: src/components/dialogs/Embed.tsx:104 3090 3101 #: src/components/dialogs/Embed.tsx:108 3091 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:119 3092 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:124 3102 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:118 3103 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:123 3093 3104 msgid "Embed post" 3094 3105 msgstr "" 3095 3106 ··· 3161 3172 msgid "Enabled" 3162 3173 msgstr "" 3163 3174 3164 - #: src/screens/Profile/Sections/Feed.tsx:113 3175 + #: src/screens/Profile/Sections/Feed.tsx:109 3165 3176 msgid "End of feed" 3166 3177 msgstr "" 3167 3178 ··· 3416 3427 msgid "Failed to accept chat" 3417 3428 msgstr "" 3418 3429 3419 - #: src/components/dms/ActionsWrapper.web.tsx:67 3430 + #: src/components/dms/ActionsWrapper.web.tsx:66 3420 3431 #: src/components/dms/MessageContextMenu.tsx:99 3421 3432 msgid "Failed to add emoji reaction" 3422 3433 msgstr "" ··· 3526 3537 msgid "Failed to pin post" 3527 3538 msgstr "" 3528 3539 3529 - #: src/components/dms/ActionsWrapper.web.tsx:61 3540 + #: src/components/dms/ActionsWrapper.web.tsx:60 3530 3541 #: src/components/dms/MessageContextMenu.tsx:93 3531 3542 msgid "Failed to remove emoji reaction" 3532 3543 msgstr "" ··· 3574 3585 msgid "Failed to toggle thread mute, please try again" 3575 3586 msgstr "" 3576 3587 3588 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:111 3589 + msgid "Failed to unpin list" 3590 + msgstr "" 3591 + 3577 3592 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:149 3578 3593 #: src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx:83 3579 3594 msgid "Failed to update email 2FA settings" ··· 3652 3667 msgstr "" 3653 3668 3654 3669 #: src/Navigation.tsx:574 3670 + #: src/screens/SavedFeeds.tsx:108 3655 3671 #: src/screens/Search/SearchResults.tsx:73 3656 3672 #: src/screens/StarterPack/StarterPackScreen.tsx:190 3657 3673 #: src/view/screens/Feeds.tsx:511 3658 3674 #: src/view/screens/Profile.tsx:230 3659 - #: src/view/screens/SavedFeeds.tsx:104 3660 3675 #: src/view/shell/desktop/LeftNav.tsx:727 3661 3676 #: src/view/shell/Drawer.tsx:530 3662 3677 msgid "Feeds" 3663 3678 msgstr "" 3664 3679 3665 - #: src/view/screens/SavedFeeds.tsx:206 3666 - msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information." 3680 + #: src/screens/SavedFeeds.tsx:215 3681 + msgid "Feeds are custom algorithms that users build with a little coding expertise. <0>See this guide</0> for more information." 3667 3682 msgstr "" 3668 3683 3669 3684 #: src/components/FeedCard.tsx:282 3670 - #: src/view/screens/SavedFeeds.tsx:86 3685 + #: src/screens/SavedFeeds.tsx:90 3671 3686 msgctxt "toast" 3672 3687 msgid "Feeds updated!" 3673 3688 msgstr "" ··· 3684 3699 msgid "File saved successfully!" 3685 3700 msgstr "" 3686 3701 3687 - #: src/lib/moderation/useLabelBehaviorDescription.ts:66 3702 + #: src/lib/moderation/useLabelBehaviorDescription.ts:69 3688 3703 msgid "Filter from feeds" 3689 3704 msgstr "" 3690 3705 ··· 3844 3859 msgid "Followers of @{0} that you know" 3845 3860 msgstr "" 3846 3861 3847 - #: src/screens/Profile/KnownFollowers.tsx:104 3848 - #: src/screens/Profile/KnownFollowers.tsx:121 3862 + #: src/screens/Profile/KnownFollowers.tsx:107 3863 + #: src/screens/Profile/KnownFollowers.tsx:124 3849 3864 msgid "Followers you know" 3850 3865 msgstr "" 3851 3866 ··· 3859 3874 msgid "Following" 3860 3875 msgstr "" 3861 3876 3877 + #: src/screens/SavedFeeds.tsx:410 3862 3878 #: src/view/screens/Feeds.tsx:603 3863 - #: src/view/screens/SavedFeeds.tsx:420 3864 3879 msgctxt "feed-name" 3865 3880 msgid "Following" 3866 3881 msgstr "" ··· 3969 3984 msgid "From <0/>" 3970 3985 msgstr "" 3971 3986 3972 - #: src/components/StarterPack/ProfileStarterPacks.tsx:307 3987 + #: src/components/StarterPack/ProfileStarterPacks.tsx:303 3973 3988 msgid "Generate a starter pack" 3974 3989 msgstr "" 3975 3990 ··· 4062 4077 #: src/components/moderation/ScreenHider.tsx:163 4063 4078 #: src/screens/Messages/Inbox.tsx:249 4064 4079 #: src/screens/Profile/ProfileFeed/index.tsx:92 4080 + #: src/screens/ProfileList/components/ErrorScreen.tsx:34 4081 + #: src/screens/ProfileList/components/ErrorScreen.tsx:40 4065 4082 #: src/screens/VideoFeed/components/Header.tsx:163 4066 4083 #: src/screens/VideoFeed/index.tsx:1146 4067 4084 #: src/screens/VideoFeed/index.tsx:1150 4068 4085 #: src/view/com/auth/LoggedOut.tsx:72 4069 4086 #: src/view/screens/NotFound.tsx:57 4070 - #: src/view/screens/ProfileList.tsx:1038 4071 4087 msgid "Go back" 4072 4088 msgstr "" 4073 4089 ··· 4078 4094 #: src/screens/Profile/ProfileFeed/index.tsx:97 4079 4095 #: src/screens/StarterPack/StarterPackScreen.tsx:773 4080 4096 #: src/view/screens/NotFound.tsx:56 4081 - #: src/view/screens/ProfileList.tsx:1043 4082 4097 msgid "Go Back" 4083 4098 msgstr "" 4084 4099 4085 4100 #: src/components/dms/ReportDialog.tsx:197 4086 - #: src/components/ReportDialog/SelectReportOptionView.tsx:78 4101 + #: src/components/ReportDialog/SelectReportOptionView.tsx:81 4087 4102 #: src/components/ReportDialog/SubmitView.tsx:110 4088 4103 #: src/screens/Onboarding/Layout.tsx:121 4089 4104 #: src/screens/Onboarding/Layout.tsx:214 ··· 4243 4258 #: src/components/interstitials/Trending.tsx:131 4244 4259 #: src/components/interstitials/TrendingVideos.tsx:138 4245 4260 #: src/components/moderation/ContentHider.tsx:203 4246 - #: src/components/moderation/LabelPreference.tsx:141 4261 + #: src/components/moderation/LabelPreference.tsx:140 4247 4262 #: src/components/moderation/PostHider.tsx:134 4248 4263 #: src/components/PostControls/PostMenu/PostMenuItems.tsx:715 4249 - #: src/lib/moderation/useLabelBehaviorDescription.ts:15 4250 - #: src/lib/moderation/useLabelBehaviorDescription.ts:20 4251 - #: src/lib/moderation/useLabelBehaviorDescription.ts:25 4252 - #: src/lib/moderation/useLabelBehaviorDescription.ts:30 4264 + #: src/lib/moderation/useLabelBehaviorDescription.ts:18 4265 + #: src/lib/moderation/useLabelBehaviorDescription.ts:23 4266 + #: src/lib/moderation/useLabelBehaviorDescription.ts:28 4267 + #: src/lib/moderation/useLabelBehaviorDescription.ts:33 4253 4268 #: src/view/shell/desktop/SidebarTrendingTopics.tsx:111 4254 4269 msgid "Hide" 4255 4270 msgstr "" ··· 4400 4415 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." 4401 4416 msgstr "" 4402 4417 4403 - #: src/view/screens/ProfileList.tsx:765 4418 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:282 4404 4419 msgid "If you delete this list, you won't be able to recover it." 4405 4420 msgstr "" 4406 4421 ··· 4441 4456 msgid "Illegal and Urgent" 4442 4457 msgstr "" 4443 4458 4444 - #: src/view/com/util/images/Gallery.tsx:71 4459 + #: src/view/com/util/images/Gallery.tsx:70 4445 4460 msgid "Image" 4446 4461 msgstr "" 4447 4462 ··· 4587 4602 msgid "Invite codes: 1 available" 4588 4603 msgstr "" 4589 4604 4590 - #: src/components/StarterPack/ShareDialog.tsx:77 4605 + #: src/components/StarterPack/ShareDialog.tsx:81 4591 4606 msgid "Invite people to this starter pack!" 4592 4607 msgstr "" 4593 4608 ··· 4675 4690 msgid "Labels added" 4676 4691 msgstr "" 4677 4692 4678 - #: src/screens/Profile/Sections/Labels.tsx:194 4693 + #: src/screens/Profile/Sections/Labels.tsx:195 4679 4694 msgid "Labels are annotations on users and content. They can be used to hide, warn, and categorize the network." 4680 4695 msgstr "" 4681 4696 ··· 4809 4824 msgid "left to go." 4810 4825 msgstr "" 4811 4826 4812 - #: src/components/StarterPack/ProfileStarterPacks.tsx:323 4827 + #: src/components/StarterPack/ProfileStarterPacks.tsx:319 4813 4828 msgid "Let me choose" 4814 4829 msgstr "" 4815 4830 ··· 4869 4884 msgid "Liked by" 4870 4885 msgstr "" 4871 4886 4872 - #: src/screens/Post/PostLikedBy.tsx:38 4873 - #: src/screens/Profile/ProfileLabelerLikedBy.tsx:29 4874 - #: src/view/screens/ProfileFeedLikedBy.tsx:30 4887 + #: src/screens/Post/PostLikedBy.tsx:41 4888 + #: src/screens/Profile/ProfileLabelerLikedBy.tsx:32 4889 + #: src/view/screens/ProfileFeedLikedBy.tsx:33 4875 4890 msgid "Liked By" 4876 4891 msgstr "" 4877 4892 ··· 4921 4936 msgid "List Avatar" 4922 4937 msgstr "" 4923 4938 4924 - #: src/view/screens/ProfileList.tsx:438 4939 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:50 4925 4940 msgctxt "toast" 4926 4941 msgid "List blocked" 4927 4942 msgstr "" ··· 4943 4958 msgid "List creator" 4944 4959 msgstr "" 4945 4960 4946 - #: src/view/screens/ProfileList.tsx:485 4961 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:97 4947 4962 msgctxt "toast" 4948 4963 msgid "List deleted" 4949 4964 msgstr "" ··· 4952 4967 msgid "List has been hidden" 4953 4968 msgstr "" 4954 4969 4955 - #: src/view/screens/ProfileList.tsx:176 4970 + #: src/screens/ProfileList/index.tsx:172 4956 4971 msgid "List Hidden" 4957 4972 msgstr "" 4958 4973 4959 - #: src/view/screens/ProfileList.tsx:402 4974 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:31 4960 4975 msgctxt "toast" 4961 4976 msgid "List muted" 4962 4977 msgstr "" ··· 4965 4980 msgid "List Name" 4966 4981 msgstr "" 4967 4982 4968 - #: src/view/screens/ProfileList.tsx:456 4983 + #: src/screens/ProfileList/components/Header.tsx:116 4984 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:138 4969 4985 msgctxt "toast" 4970 4986 msgid "List unblocked" 4971 4987 msgstr "" 4972 4988 4973 - #: src/view/screens/ProfileList.tsx:420 4989 + #: src/screens/ProfileList/components/Header.tsx:98 4990 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:120 4974 4991 msgctxt "toast" 4975 4992 msgid "List unmuted" 4976 4993 msgstr "" ··· 5018 5035 msgstr "" 5019 5036 5020 5037 #: src/screens/Profile/ProfileFeed/index.tsx:224 5021 - #: src/screens/Profile/Sections/Feed.tsx:98 5022 - #: src/view/com/feeds/FeedPage.tsx:162 5023 - #: src/view/screens/ProfileList.tsx:878 5038 + #: src/screens/Profile/Sections/Feed.tsx:94 5039 + #: src/screens/ProfileList/FeedSection.tsx:105 5040 + #: src/view/com/feeds/FeedPage.tsx:169 5024 5041 msgid "Load new posts" 5025 5042 msgstr "" 5026 5043 ··· 5053 5070 msgid "Looks like XXXXX-XXXXX" 5054 5071 msgstr "" 5055 5072 5056 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:39 5073 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:43 5057 5074 msgid "Looks like you haven't saved any feeds! Use our recommendations or browse more below." 5058 5075 msgstr "" 5059 5076 ··· 5061 5078 msgid "Looks like you unpinned all your feeds. But don't worry, you can add some below 😄" 5062 5079 msgstr "" 5063 5080 5064 - #: src/screens/Feeds/NoFollowingFeed.tsx:37 5081 + #: src/screens/Feeds/NoFollowingFeed.tsx:35 5065 5082 msgid "Looks like you're missing a following feed. <0>Click here to add one.</0>" 5066 5083 msgstr "" 5067 5084 ··· 5069 5086 msgid "Make adjustments to email settings for your account" 5070 5087 msgstr "" 5071 5088 5072 - #: src/components/StarterPack/ProfileStarterPacks.tsx:282 5089 + #: src/components/StarterPack/ProfileStarterPacks.tsx:278 5073 5090 msgid "Make one for me" 5074 5091 msgstr "" 5075 5092 ··· 5243 5260 msgid "Moderation Lists" 5244 5261 msgstr "" 5245 5262 5246 - #: src/components/moderation/LabelPreference.tsx:252 5263 + #: src/components/moderation/LabelPreference.tsx:251 5247 5264 msgid "moderation settings" 5248 5265 msgstr "" 5249 5266 ··· 5270 5287 msgid "More languages..." 5271 5288 msgstr "" 5272 5289 5290 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:156 5273 5291 #: src/view/com/profile/ProfileMenu.tsx:223 5274 5292 #: src/view/com/profile/ProfileMenu.tsx:229 5275 - #: src/view/screens/ProfileList.tsx:750 5276 5293 msgid "More options" 5277 5294 msgstr "" 5278 5295 5296 + #: src/screens/SavedFeeds.tsx:329 5297 + msgid "Move feed down" 5298 + msgstr "" 5299 + 5300 + #: src/screens/SavedFeeds.tsx:320 5301 + msgid "Move feed up" 5302 + msgstr "" 5303 + 5279 5304 #: src/screens/Onboarding/state.ts:113 5280 5305 msgid "Movies" 5281 5306 msgstr "" ··· 5285 5310 msgstr "" 5286 5311 5287 5312 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153 5288 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96 5313 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95 5289 5314 msgctxt "video" 5290 5315 msgid "Mute" 5291 5316 msgstr "" ··· 5302 5327 msgid "Mute account" 5303 5328 msgstr "" 5304 5329 5305 - #: src/view/screens/ProfileList.tsx:657 5330 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:89 5331 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:92 5306 5332 msgid "Mute accounts" 5307 5333 msgstr "" 5308 5334 ··· 5315 5341 msgid "Mute in:" 5316 5342 msgstr "" 5317 5343 5318 - #: src/view/screens/ProfileList.tsx:779 5344 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:115 5319 5345 msgid "Mute list" 5320 5346 msgstr "" 5321 5347 5322 - #: src/view/screens/ProfileList.tsx:774 5348 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:110 5323 5349 msgid "Mute these accounts?" 5324 5350 msgstr "" 5325 5351 ··· 5378 5404 msgid "Muted words & tags" 5379 5405 msgstr "" 5380 5406 5381 - #: src/view/screens/ProfileList.tsx:776 5407 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:112 5382 5408 msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them." 5383 5409 msgstr "" 5384 5410 ··· 5438 5464 msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?" 5439 5465 msgstr "" 5440 5466 5441 - #: src/components/ReportDialog/SelectReportOptionView.tsx:128 5467 + #: src/components/ReportDialog/SelectReportOptionView.tsx:131 5442 5468 msgid "Need to report a copyright violation?" 5443 5469 msgstr "" 5444 5470 ··· 5520 5546 msgstr "" 5521 5547 5522 5548 #: src/screens/Profile/ProfileFeed/index.tsx:241 5549 + #: src/screens/ProfileList/index.tsx:246 5550 + #: src/screens/ProfileList/index.tsx:284 5523 5551 #: src/view/screens/Feeds.tsx:552 5524 5552 #: src/view/screens/Notifications.tsx:167 5525 5553 #: src/view/screens/Profile.tsx:510 5526 - #: src/view/screens/ProfileList.tsx:250 5527 - #: src/view/screens/ProfileList.tsx:288 5528 5554 msgid "New post" 5529 5555 msgstr "" 5530 5556 5531 - #: src/view/com/feeds/FeedPage.tsx:173 5557 + #: src/view/com/feeds/FeedPage.tsx:180 5532 5558 msgctxt "action" 5533 5559 msgid "New post" 5534 5560 msgstr "" ··· 5550 5576 msgid "New starter pack" 5551 5577 msgstr "" 5552 5578 5553 - #: src/components/NewskieDialog.tsx:83 5579 + #: src/components/NewskieDialog.tsx:102 5554 5580 msgid "New user info dialog" 5555 5581 msgstr "" 5556 5582 ··· 5665 5691 msgid "No posts here" 5666 5692 msgstr "" 5667 5693 5668 - #: src/screens/Profile/Sections/Feed.tsx:66 5694 + #: src/screens/Profile/Sections/Feed.tsx:62 5669 5695 msgid "No posts yet." 5670 5696 msgstr "" 5671 5697 ··· 5691 5717 msgid "No results for \"{0}\"." 5692 5718 msgstr "" 5693 5719 5694 - #: src/components/Lists.tsx:190 5720 + #: src/components/Lists.tsx:189 5695 5721 msgid "No results found" 5696 5722 msgstr "" 5697 5723 ··· 5768 5794 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." 5769 5795 msgstr "" 5770 5796 5771 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:134 5797 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:133 5772 5798 msgid "Note: This post is only visible to logged-in users." 5773 5799 msgstr "" 5774 5800 ··· 5839 5865 msgid "Nudity or adult content not labeled as such" 5840 5866 msgstr "" 5841 5867 5842 - #: src/lib/moderation/useLabelBehaviorDescription.ts:11 5868 + #: src/lib/moderation/useLabelBehaviorDescription.ts:14 5843 5869 #: src/screens/Settings/NotificationSettings/index.tsx:291 5844 5870 msgid "Off" 5845 5871 msgstr "" ··· 5920 5946 msgid "Only image files are supported" 5921 5947 msgstr "" 5922 5948 5923 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:40 5949 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:41 5924 5950 msgid "Only WebVTT (.vtt) files are supported" 5925 5951 msgstr "" 5926 5952 5927 - #: src/components/Lists.tsx:95 5953 + #: src/components/Lists.tsx:94 5928 5954 msgid "Oops, something went wrong!" 5929 5955 msgstr "" 5930 5956 5931 - #: src/components/Lists.tsx:174 5932 - #: src/components/StarterPack/ProfileStarterPacks.tsx:332 5933 - #: src/components/StarterPack/ProfileStarterPacks.tsx:341 5957 + #: src/components/Lists.tsx:173 5958 + #: src/components/StarterPack/ProfileStarterPacks.tsx:328 5959 + #: src/components/StarterPack/ProfileStarterPacks.tsx:337 5934 5960 #: src/screens/Settings/AppPasswords.tsx:59 5935 5961 #: src/screens/Settings/components/ChangeHandleDialog.tsx:106 5936 5962 #: src/view/screens/Profile.tsx:125 ··· 5992 6018 msgid "Open pack" 5993 6019 msgstr "" 5994 6020 5995 - #: src/components/PostControls/PostMenu/index.tsx:65 6021 + #: src/components/PostControls/PostMenu/index.tsx:64 5996 6022 msgid "Open post options menu" 5997 6023 msgstr "" 5998 6024 ··· 6001 6027 msgid "Open profile" 6002 6028 msgstr "" 6003 6029 6004 - #: src/components/PostControls/ShareMenu/index.tsx:90 6030 + #: src/components/PostControls/ShareMenu/index.tsx:89 6005 6031 msgid "Open share menu" 6006 6032 msgstr "" 6007 6033 ··· 6085 6111 msgid "Opens list of invite codes" 6086 6112 msgstr "" 6087 6113 6088 - #: src/view/com/util/UserAvatar.tsx:576 6114 + #: src/view/com/util/UserAvatar.tsx:581 6089 6115 msgid "Opens live status dialog" 6090 6116 msgstr "" 6091 6117 ··· 6098 6124 msgstr "" 6099 6125 6100 6126 #: src/view/com/notifications/NotificationFeedItem.tsx:906 6101 - #: src/view/com/util/UserAvatar.tsx:594 6127 + #: src/view/com/util/UserAvatar.tsx:599 6102 6128 msgid "Opens this profile" 6103 6129 msgstr "" 6104 6130 ··· 6161 6187 msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky." 6162 6188 msgstr "" 6163 6189 6164 - #: src/components/Lists.tsx:191 6190 + #: src/components/Lists.tsx:190 6165 6191 #: src/view/screens/NotFound.tsx:47 6166 6192 msgid "Page not found" 6167 6193 msgstr "" ··· 6205 6231 msgid "Pause video" 6206 6232 msgstr "" 6207 6233 6234 + #: src/screens/ProfileList/index.tsx:166 6208 6235 #: src/screens/Search/SearchResults.tsx:67 6209 6236 #: src/screens/StarterPack/StarterPackScreen.tsx:189 6210 - #: src/view/screens/ProfileList.tsx:170 6211 6237 msgid "People" 6212 6238 msgstr "" 6213 6239 ··· 6247 6273 6248 6274 #: src/screens/Profile/components/ProfileFeedHeader.tsx:523 6249 6275 #: src/screens/Profile/components/ProfileFeedHeader.tsx:530 6276 + #: src/screens/SavedFeeds.tsx:351 6250 6277 msgid "Pin feed" 6251 6278 msgstr "" 6252 6279 ··· 6254 6281 msgid "Pin Feed" 6255 6282 msgstr "" 6256 6283 6257 - #: src/view/screens/ProfileList.tsx:714 6284 + #: src/screens/ProfileList/components/Header.tsx:156 6285 + #: src/screens/ProfileList/components/Header.tsx:163 6258 6286 msgid "Pin to home" 6259 6287 msgstr "" 6260 6288 ··· 6276 6304 msgid "Pinned {0} to Home" 6277 6305 msgstr "" 6278 6306 6279 - #: src/view/screens/SavedFeeds.tsx:131 6307 + #: src/screens/SavedFeeds.tsx:142 6280 6308 msgid "Pinned Feeds" 6281 6309 msgstr "" 6282 6310 6283 - #: src/view/screens/ProfileList.tsx:361 6311 + #: src/screens/ProfileList/components/Header.tsx:74 6284 6312 msgid "Pinned to your feeds" 6285 6313 msgstr "" 6286 6314 ··· 6560 6588 6561 6589 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:250 6562 6590 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:262 6591 + #: src/screens/ProfileList/index.tsx:166 6563 6592 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:213 6564 6593 #: src/screens/StarterPack/StarterPackScreen.tsx:191 6565 6594 #: src/view/screens/Profile.tsx:225 6566 - #: src/view/screens/ProfileList.tsx:170 6567 6595 msgid "Posts" 6568 6596 msgstr "" 6569 6597 ··· 6592 6620 msgstr "" 6593 6621 6594 6622 #: src/components/Error.tsx:60 6595 - #: src/components/Lists.tsx:100 6623 + #: src/components/Lists.tsx:99 6596 6624 #: src/screens/Messages/components/MessageListError.tsx:24 6597 6625 #: src/screens/Signup/BackNextButtons.tsx:47 6598 6626 msgid "Press to retry" ··· 6641 6669 #: src/Navigation.tsx:331 6642 6670 #: src/screens/Settings/AboutSettings.tsx:92 6643 6671 #: src/screens/Settings/AboutSettings.tsx:95 6644 - #: src/view/screens/PrivacyPolicy.tsx:31 6672 + #: src/view/screens/PrivacyPolicy.tsx:34 6645 6673 #: src/view/shell/Drawer.tsx:704 6646 6674 #: src/view/shell/Drawer.tsx:705 6647 6675 msgid "Privacy Policy" ··· 6721 6749 msgid "Push, People you follow" 6722 6750 msgstr "" 6723 6751 6724 - #: src/components/StarterPack/QrCodeDialog.tsx:134 6752 + #: src/components/StarterPack/QrCodeDialog.tsx:137 6725 6753 msgid "QR code copied to your clipboard!" 6726 6754 msgstr "" 6727 6755 6728 - #: src/components/StarterPack/QrCodeDialog.tsx:112 6756 + #: src/components/StarterPack/QrCodeDialog.tsx:115 6729 6757 msgid "QR code has been downloaded!" 6730 6758 msgstr "" 6731 6759 6732 - #: src/components/StarterPack/QrCodeDialog.tsx:113 6760 + #: src/components/StarterPack/QrCodeDialog.tsx:116 6733 6761 msgid "QR code saved to your camera roll!" 6734 6762 msgstr "" 6735 6763 ··· 6764 6792 msgstr "" 6765 6793 6766 6794 #: src/lib/hooks/useNotificationHandler.ts:154 6767 - #: src/screens/Post/PostQuotes.tsx:38 6795 + #: src/screens/Post/PostQuotes.tsx:41 6768 6796 #: src/screens/Settings/NotificationSettings/index.tsx:170 6769 6797 #: src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx:41 6770 6798 msgid "Quotes" ··· 6927 6955 msgid "Remove Banner" 6928 6956 msgstr "" 6929 6957 6930 - #: src/screens/Messages/components/MessageInputEmbed.tsx:209 6958 + #: src/screens/Messages/components/MessageInputEmbed.tsx:212 6931 6959 msgid "Remove embed" 6932 6960 msgstr "" 6933 6961 ··· 6943 6971 6944 6972 #: src/screens/Profile/components/ProfileFeedHeader.tsx:319 6945 6973 #: src/screens/Profile/components/ProfileFeedHeader.tsx:325 6946 - #: src/view/screens/ProfileList.tsx:528 6947 - #: src/view/screens/SavedFeeds.tsx:350 6974 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:188 6975 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:191 6976 + #: src/screens/SavedFeeds.tsx:340 6948 6977 msgid "Remove from my feeds" 6949 6978 msgstr "" 6950 6979 ··· 7038 7067 msgstr "" 7039 7068 7040 7069 #: src/screens/Profile/components/ProfileFeedHeader.tsx:122 7070 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:74 7041 7071 #: src/view/com/posts/FeedShutdownMsg.tsx:44 7042 - #: src/view/screens/ProfileList.tsx:392 7043 7072 msgid "Removed from your feeds" 7044 7073 msgstr "" 7045 7074 ··· 7165 7194 msgid "Report feed" 7166 7195 msgstr "" 7167 7196 7168 - #: src/view/screens/ProfileList.tsx:570 7197 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:222 7198 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:225 7169 7199 msgid "Report list" 7170 7200 msgstr "" 7171 7201 ··· 7187 7217 msgid "Report submitted" 7188 7218 msgstr "" 7189 7219 7190 - #: src/components/ReportDialog/SelectReportOptionView.tsx:41 7220 + #: src/components/ReportDialog/SelectReportOptionView.tsx:44 7191 7221 msgid "Report this content" 7192 7222 msgstr "" 7193 7223 7194 7224 #: src/components/moderation/ReportDialog/copy.ts:31 7195 - #: src/components/ReportDialog/SelectReportOptionView.tsx:54 7225 + #: src/components/ReportDialog/SelectReportOptionView.tsx:57 7196 7226 msgid "Report this feed" 7197 7227 msgstr "" 7198 7228 7199 7229 #: src/components/moderation/ReportDialog/copy.ts:25 7200 - #: src/components/ReportDialog/SelectReportOptionView.tsx:51 7230 + #: src/components/ReportDialog/SelectReportOptionView.tsx:54 7201 7231 msgid "Report this list" 7202 7232 msgstr "" 7203 7233 7204 7234 #: src/components/dms/ReportDialog.tsx:61 7205 7235 #: src/components/dms/ReportDialog.tsx:185 7206 7236 #: src/components/moderation/ReportDialog/copy.ts:43 7207 - #: src/components/ReportDialog/SelectReportOptionView.tsx:60 7237 + #: src/components/ReportDialog/SelectReportOptionView.tsx:63 7208 7238 msgid "Report this message" 7209 7239 msgstr "" 7210 7240 7211 7241 #: src/components/moderation/ReportDialog/copy.ts:19 7212 - #: src/components/ReportDialog/SelectReportOptionView.tsx:48 7242 + #: src/components/ReportDialog/SelectReportOptionView.tsx:51 7213 7243 msgid "Report this post" 7214 7244 msgstr "" 7215 7245 7216 7246 #: src/components/moderation/ReportDialog/copy.ts:37 7217 - #: src/components/ReportDialog/SelectReportOptionView.tsx:57 7247 + #: src/components/ReportDialog/SelectReportOptionView.tsx:60 7218 7248 msgid "Report this starter pack" 7219 7249 msgstr "" 7220 7250 7221 7251 #: src/components/moderation/ReportDialog/copy.ts:13 7222 - #: src/components/ReportDialog/SelectReportOptionView.tsx:45 7252 + #: src/components/ReportDialog/SelectReportOptionView.tsx:48 7223 7253 msgid "Report this user" 7224 7254 msgstr "" 7225 7255 ··· 7247 7277 msgid "Repost or quote post" 7248 7278 msgstr "" 7249 7279 7250 - #: src/screens/Post/PostRepostedBy.tsx:38 7280 + #: src/screens/Post/PostRepostedBy.tsx:41 7251 7281 msgid "Reposted By" 7252 7282 msgstr "" 7253 7283 ··· 7352 7382 7353 7383 #: src/components/dms/MessageItem.tsx:322 7354 7384 #: src/components/Error.tsx:65 7355 - #: src/components/Lists.tsx:111 7385 + #: src/components/Lists.tsx:110 7356 7386 #: src/components/moderation/ReportDialog/index.tsx:229 7357 7387 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55 7358 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:57 7359 - #: src/components/StarterPack/ProfileStarterPacks.tsx:346 7360 - #: src/screens/Login/LoginForm.tsx:348 7361 - #: src/screens/Login/LoginForm.tsx:355 7388 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:58 7389 + #: src/components/StarterPack/ProfileStarterPacks.tsx:342 7390 + #: src/screens/Login/LoginForm.tsx:323 7391 + #: src/screens/Login/LoginForm.tsx:330 7362 7392 #: src/screens/Messages/ChatList.tsx:291 7363 7393 #: src/screens/Messages/components/MessageListError.tsx:25 7364 7394 #: src/screens/Messages/Inbox.tsx:218 ··· 7389 7419 msgstr "" 7390 7420 7391 7421 #: src/screens/Profile/ProfileFeed/index.tsx:93 7422 + #: src/screens/ProfileList/components/ErrorScreen.tsx:35 7392 7423 #: src/screens/Settings/components/ChangeHandleDialog.tsx:569 7393 7424 #: src/screens/VideoFeed/index.tsx:1147 7394 7425 #: src/view/screens/NotFound.tsx:60 7395 - #: src/view/screens/ProfileList.tsx:1039 7396 7426 msgid "Returns to previous page" 7397 7427 msgstr "" 7398 7428 ··· 7405 7435 #: src/components/dialogs/PostInteractionSettingsDialog.tsx:489 7406 7436 #: src/components/live/EditLiveDialog.tsx:216 7407 7437 #: src/components/live/EditLiveDialog.tsx:223 7408 - #: src/components/StarterPack/QrCodeDialog.tsx:192 7438 + #: src/components/StarterPack/QrCodeDialog.tsx:204 7409 7439 #: src/screens/Profile/Header/EditProfileDialog.tsx:238 7410 7440 #: src/screens/Profile/Header/EditProfileDialog.tsx:252 7441 + #: src/screens/SavedFeeds.tsx:120 7411 7442 #: src/screens/Settings/components/ChangeHandleDialog.tsx:267 7412 7443 #: src/view/com/composer/GifAltText.tsx:193 7413 7444 #: src/view/com/composer/GifAltText.tsx:202 ··· 7416 7447 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:152 7417 7448 #: src/view/com/composer/photos/ImageAltTextDialog.tsx:162 7418 7449 #: src/view/com/modals/CreateOrEditList.tsx:315 7419 - #: src/view/screens/SavedFeeds.tsx:117 7420 7450 msgid "Save" 7421 7451 msgstr "" 7422 7452 ··· 7432 7462 7433 7463 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:191 7434 7464 #: src/components/activity-notifications/SubscribeProfileDialog.tsx:200 7435 - #: src/view/screens/SavedFeeds.tsx:113 7436 - #: src/view/screens/SavedFeeds.tsx:117 7465 + #: src/screens/SavedFeeds.tsx:116 7466 + #: src/screens/SavedFeeds.tsx:120 7437 7467 msgid "Save changes" 7438 7468 msgstr "" 7439 7469 7440 - #: src/components/StarterPack/ShareDialog.tsx:131 7441 7470 #: src/components/StarterPack/ShareDialog.tsx:138 7471 + #: src/components/StarterPack/ShareDialog.tsx:144 7442 7472 msgid "Save image" 7443 7473 msgstr "" 7444 7474 ··· 7450 7480 msgid "Save new handle" 7451 7481 msgstr "" 7452 7482 7453 - #: src/components/StarterPack/QrCodeDialog.tsx:186 7483 + #: src/components/StarterPack/QrCodeDialog.tsx:196 7454 7484 msgid "Save QR code" 7455 7485 msgstr "" 7456 7486 ··· 7465 7495 msgid "Saved" 7466 7496 msgstr "" 7467 7497 7468 - #: src/view/screens/SavedFeeds.tsx:172 7498 + #: src/screens/SavedFeeds.tsx:184 7469 7499 msgid "Saved Feeds" 7470 7500 msgstr "" 7471 7501 ··· 7476 7506 msgstr "" 7477 7507 7478 7508 #: src/screens/Profile/components/ProfileFeedHeader.tsx:132 7479 - #: src/view/screens/ProfileList.tsx:372 7509 + #: src/screens/ProfileList/components/Header.tsx:85 7480 7510 msgid "Saved to your feeds" 7481 7511 msgstr "" 7482 7512 ··· 7485 7515 msgstr "" 7486 7516 7487 7517 #: src/components/dms/ChatEmptyPill.tsx:33 7488 - #: src/components/NewskieDialog.tsx:105 7518 + #: src/components/NewskieDialog.tsx:121 7489 7519 #: src/view/com/notifications/NotificationFeedItem.tsx:751 7490 7520 #: src/view/com/notifications/NotificationFeedItem.tsx:776 7491 7521 msgid "Say hello!" ··· 7504 7534 msgid "Scroll right" 7505 7535 msgstr "" 7506 7536 7507 - #: src/view/screens/ProfileList.tsx:996 7537 + #: src/screens/ProfileList/AboutSection.tsx:130 7508 7538 msgid "Scroll to top" 7509 7539 msgstr "" 7510 7540 ··· 7637 7667 msgid "See more suggested profiles on the Explore page" 7638 7668 msgstr "" 7639 7669 7640 - #: src/view/screens/SavedFeeds.tsx:213 7670 + #: src/screens/SavedFeeds.tsx:220 7641 7671 msgid "See this guide" 7642 7672 msgstr "" 7643 7673 7644 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:197 7674 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:196 7645 7675 msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause" 7646 7676 msgstr "" 7647 7677 ··· 7741 7771 msgid "Select primary language" 7742 7772 msgstr "" 7743 7773 7744 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:59 7745 - #: src/view/com/composer/videos/SubtitleFilePicker.tsx:66 7774 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:60 7775 + #: src/view/com/composer/videos/SubtitleFilePicker.tsx:67 7746 7776 msgid "Select subtitle file (.vtt)" 7747 7777 msgstr "" 7748 7778 ··· 7847 7877 7848 7878 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:101 7849 7879 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:107 7850 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:104 7851 - #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:110 7880 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:103 7881 + #: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:109 7852 7882 msgid "Send via direct message" 7853 7883 msgstr "" 7854 7884 ··· 7944 7974 msgid "Sexually Suggestive" 7945 7975 msgstr "" 7946 7976 7947 - #: src/components/StarterPack/QrCodeDialog.tsx:182 7977 + #: src/components/StarterPack/QrCodeDialog.tsx:192 7948 7978 #: src/screens/Hashtag.tsx:126 7949 7979 #: src/screens/StarterPack/StarterPackScreen.tsx:433 7950 7980 #: src/screens/Topic.tsx:102 7951 - #: src/view/screens/ProfileList.tsx:513 7952 7981 msgid "Share" 7953 7982 msgstr "" 7954 7983 ··· 7976 8005 7977 8006 #: src/components/dialogs/LinkWarning.tsx:96 7978 8007 #: src/components/dialogs/LinkWarning.tsx:104 7979 - #: src/components/StarterPack/ShareDialog.tsx:104 7980 - #: src/components/StarterPack/ShareDialog.tsx:111 8008 + #: src/components/StarterPack/ShareDialog.tsx:113 8009 + #: src/components/StarterPack/ShareDialog.tsx:119 7981 8010 msgid "Share link" 7982 8011 msgstr "" 7983 8012 7984 - #: src/components/StarterPack/ShareDialog.tsx:68 8013 + #: src/components/StarterPack/ShareDialog.tsx:72 7985 8014 msgid "Share link dialog" 7986 8015 msgstr "" 7987 8016 ··· 7990 8019 msgid "Share post at:// URI" 7991 8020 msgstr "" 7992 8021 7993 - #: src/components/StarterPack/ShareDialog.tsx:115 7994 - #: src/components/StarterPack/ShareDialog.tsx:126 8022 + #: src/components/StarterPack/ShareDialog.tsx:123 8023 + #: src/components/StarterPack/ShareDialog.tsx:133 7995 8024 msgid "Share QR code" 7996 8025 msgstr "" 7997 8026 ··· 8003 8032 msgid "Share this starter pack" 8004 8033 msgstr "" 8005 8034 8006 - #: src/components/StarterPack/ShareDialog.tsx:80 8035 + #: src/components/StarterPack/ShareDialog.tsx:84 8007 8036 msgid "Share this starter pack and help people join your community on Bluesky." 8008 8037 msgstr "" 8009 8038 8010 8039 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:117 8011 8040 #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:120 8041 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172 8042 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:178 8012 8043 #: src/screens/StarterPack/StarterPackScreen.tsx:611 8013 8044 #: src/screens/StarterPack/StarterPackScreen.tsx:619 8014 8045 #: src/view/com/profile/ProfileMenu.tsx:246 ··· 8025 8056 msgstr "" 8026 8057 8027 8058 #: src/components/moderation/ContentHider.tsx:203 8028 - #: src/components/moderation/LabelPreference.tsx:143 8059 + #: src/components/moderation/LabelPreference.tsx:142 8029 8060 #: src/components/moderation/PostHider.tsx:134 8030 8061 msgid "Show" 8031 8062 msgstr "" ··· 8042 8073 msgid "Show anyway" 8043 8074 msgstr "" 8044 8075 8045 - #: src/lib/moderation/useLabelBehaviorDescription.ts:27 8046 - #: src/lib/moderation/useLabelBehaviorDescription.ts:63 8076 + #: src/lib/moderation/useLabelBehaviorDescription.ts:30 8077 + #: src/lib/moderation/useLabelBehaviorDescription.ts:66 8047 8078 msgid "Show badge" 8048 8079 msgstr "" 8049 8080 8050 - #: src/lib/moderation/useLabelBehaviorDescription.ts:61 8081 + #: src/lib/moderation/useLabelBehaviorDescription.ts:64 8051 8082 msgid "Show badge and filter from feeds" 8052 8083 msgstr "" 8053 8084 ··· 8114 8145 msgid "Show samples of your saved feeds in your Following feed" 8115 8146 msgstr "" 8116 8147 8117 - #: src/lib/moderation/useLabelBehaviorDescription.ts:58 8148 + #: src/lib/moderation/useLabelBehaviorDescription.ts:61 8118 8149 msgid "Show warning" 8119 8150 msgstr "" 8120 8151 8121 - #: src/lib/moderation/useLabelBehaviorDescription.ts:56 8152 + #: src/lib/moderation/useLabelBehaviorDescription.ts:59 8122 8153 msgid "Show warning and filter from feeds" 8123 8154 msgstr "" 8124 8155 ··· 8293 8324 8294 8325 #: src/components/ReportDialog/index.tsx:54 8295 8326 #: src/screens/Moderation/index.tsx:112 8296 - #: src/screens/Profile/Sections/Labels.tsx:184 8327 + #: src/screens/Profile/Sections/Labels.tsx:185 8297 8328 msgid "Something went wrong, please try again." 8298 8329 msgstr "" 8299 8330 8300 - #: src/components/Lists.tsx:175 8331 + #: src/components/Lists.tsx:174 8301 8332 msgid "Something went wrong!" 8302 8333 msgstr "" 8303 8334 ··· 8361 8392 msgid "Start a new chat" 8362 8393 msgstr "" 8363 8394 8364 - #: src/view/screens/ProfileList.tsx:846 8365 - #: src/view/screens/ProfileList.tsx:967 8395 + #: src/screens/ProfileList/AboutSection.tsx:102 8396 + #: src/screens/ProfileList/FeedSection.tsx:74 8366 8397 msgid "Start adding people" 8367 8398 msgstr "" 8368 8399 8369 - #: src/view/screens/ProfileList.tsx:853 8370 - #: src/view/screens/ProfileList.tsx:974 8400 + #: src/screens/ProfileList/AboutSection.tsx:108 8401 + #: src/screens/ProfileList/FeedSection.tsx:80 8371 8402 msgid "Start adding people!" 8372 8403 msgstr "" 8373 8404 ··· 8403 8434 msgid "Starter Packs" 8404 8435 msgstr "" 8405 8436 8406 - #: src/components/StarterPack/ProfileStarterPacks.tsx:266 8437 + #: src/components/StarterPack/ProfileStarterPacks.tsx:262 8407 8438 msgid "Starter packs let you easily share your favorite feeds and people with your friends." 8408 8439 msgstr "" 8409 8440 ··· 8448 8479 msgid "Submit report" 8449 8480 msgstr "" 8450 8481 8451 - #: src/view/screens/ProfileList.tsx:741 8482 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:81 8452 8483 msgid "Subscribe" 8453 8484 msgstr "" 8454 8485 8455 - #: src/screens/Profile/Sections/Labels.tsx:231 8486 + #: src/screens/Profile/Sections/Labels.tsx:232 8456 8487 msgid "Subscribe to @{0} to use these labels:" 8457 8488 msgstr "" 8458 8489 ··· 8468 8499 msgid "Subscribe to this labeler" 8469 8500 msgstr "" 8470 8501 8471 - #: src/view/screens/ProfileList.tsx:737 8502 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:69 8472 8503 msgid "Subscribe to this list" 8473 8504 msgstr "" 8474 8505 ··· 8511 8542 msgstr "" 8512 8543 8513 8544 #: src/Navigation.tsx:326 8514 - #: src/view/screens/Support.tsx:31 8515 8545 #: src/view/screens/Support.tsx:34 8546 + #: src/view/screens/Support.tsx:37 8516 8547 msgid "Support" 8517 8548 msgstr "" 8518 8549 ··· 8620 8651 #: src/Navigation.tsx:336 8621 8652 #: src/screens/Settings/AboutSettings.tsx:84 8622 8653 #: src/screens/Settings/AboutSettings.tsx:87 8623 - #: src/view/screens/TermsOfService.tsx:31 8654 + #: src/view/screens/TermsOfService.tsx:34 8624 8655 #: src/view/shell/Drawer.tsx:697 8625 8656 #: src/view/shell/Drawer.tsx:699 8626 8657 msgid "Terms of Service" ··· 8715 8746 msgid "The Bluesky web application" 8716 8747 msgstr "" 8717 8748 8718 - #: src/view/screens/CommunityGuidelines.tsx:38 8749 + #: src/view/screens/CommunityGuidelines.tsx:41 8719 8750 msgid "The Community Guidelines have been moved to <0/>" 8720 8751 msgstr "" 8721 8752 8722 - #: src/view/screens/CopyrightPolicy.tsx:35 8753 + #: src/view/screens/CopyrightPolicy.tsx:38 8723 8754 msgid "The Copyright Policy has been moved to <0/>" 8724 8755 msgstr "" 8725 8756 ··· 8763 8794 msgid "The open social network." 8764 8795 msgstr "" 8765 8796 8766 - #: src/view/screens/PrivacyPolicy.tsx:35 8797 + #: src/view/screens/PrivacyPolicy.tsx:38 8767 8798 msgid "The Privacy Policy has been moved to <0/>" 8768 8799 msgstr "" 8769 8800 ··· 8781 8812 msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead." 8782 8813 msgstr "" 8783 8814 8784 - #: src/components/ContextMenu/index.tsx:433 8815 + #: src/components/ContextMenu/index.tsx:434 8785 8816 msgid "The subject of the context menu" 8786 8817 msgstr "" 8787 8818 8788 - #: src/view/screens/Support.tsx:37 8819 + #: src/view/screens/Support.tsx:40 8789 8820 msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us." 8790 8821 msgstr "" 8791 8822 8792 - #: src/view/screens/TermsOfService.tsx:35 8823 + #: src/view/screens/TermsOfService.tsx:38 8793 8824 msgid "The Terms of Service have been moved to" 8794 8825 msgstr "" 8795 8826 ··· 8810 8841 msgstr "" 8811 8842 8812 8843 #: src/screens/Profile/components/ProfileFeedHeader.tsx:178 8813 - #: src/view/screens/ProfileList.tsx:375 8814 - #: src/view/screens/ProfileList.tsx:394 8815 - #: src/view/screens/SavedFeeds.tsx:93 8844 + #: src/screens/ProfileList/components/Header.tsx:88 8845 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:76 8846 + #: src/screens/SavedFeeds.tsx:97 8816 8847 msgid "There was an issue contacting the server" 8817 8848 msgstr "" 8818 8849 ··· 8826 8857 msgstr "" 8827 8858 8828 8859 #: src/screens/Search/Explore.tsx:986 8829 - #: src/view/com/posts/PostFeed.tsx:701 8860 + #: src/view/com/posts/PostFeed.tsx:709 8830 8861 msgid "There was an issue fetching posts. Tap here to try again." 8831 8862 msgstr "" 8832 8863 ··· 8838 8869 msgid "There was an issue fetching your app passwords" 8839 8870 msgstr "" 8840 8871 8841 - #: src/view/com/feeds/ProfileFeedgens.tsx:151 8842 - #: src/view/com/lists/ProfileLists.tsx:150 8872 + #: src/view/com/feeds/ProfileFeedgens.tsx:163 8873 + #: src/view/com/lists/ProfileLists.tsx:161 8843 8874 msgid "There was an issue fetching your lists. Tap here to try again." 8844 8875 msgstr "" 8845 8876 ··· 8883 8914 #: src/screens/List/ListHiddenScreen.tsx:63 8884 8915 #: src/screens/List/ListHiddenScreen.tsx:77 8885 8916 #: src/screens/List/ListHiddenScreen.tsx:99 8886 - #: src/view/screens/ProfileList.tsx:411 8887 - #: src/view/screens/ProfileList.tsx:429 8888 - #: src/view/screens/ProfileList.tsx:447 8889 - #: src/view/screens/ProfileList.tsx:465 8917 + #: src/screens/ProfileList/components/Header.tsx:107 8918 + #: src/screens/ProfileList/components/Header.tsx:125 8919 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:129 8920 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:147 8921 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:40 8922 + #: src/screens/ProfileList/components/SubscribeMenu.tsx:59 8890 8923 msgid "There was an issue. Please check your internet connection and try again." 8891 8924 msgstr "" 8892 8925 ··· 8977 9010 msgid "This feature allows users to receive notifications for your new posts and replies. Who do you want to enable this for?" 8978 9011 msgstr "" 8979 9012 8980 - #: src/screens/Settings/components/ExportCarDialog.tsx:96 9013 + #: src/screens/Settings/components/ExportCarDialog.tsx:95 8981 9014 msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost</0>." 8982 9015 msgstr "" 8983 9016 ··· 8999 9032 9000 9033 #: src/components/StarterPack/Main/PostsList.tsx:36 9001 9034 #: src/screens/Profile/ProfileFeed/index.tsx:192 9002 - #: src/view/screens/ProfileList.tsx:843 9035 + #: src/screens/ProfileList/FeedSection.tsx:71 9003 9036 msgid "This feed is empty." 9004 9037 msgstr "" 9005 9038 ··· 9028 9061 msgid "This label was applied by you." 9029 9062 msgstr "" 9030 9063 9031 - #: src/screens/Profile/Sections/Labels.tsx:218 9064 + #: src/screens/Profile/Sections/Labels.tsx:219 9032 9065 msgid "This labeler hasn't declared what labels it publishes, and may not be active." 9033 9066 msgstr "" 9034 9067 ··· 9044 9077 msgid "This list – created by you – contains possible violations of Bluesky's community guidelines in its name or description." 9045 9078 msgstr "" 9046 9079 9047 - #: src/view/screens/ProfileList.tsx:962 9080 + #: src/screens/ProfileList/AboutSection.tsx:98 9048 9081 msgid "This list is empty." 9049 9082 msgstr "" 9050 9083 ··· 9125 9158 msgid "This user is included in the <0>{0}</0> list which you have muted." 9126 9159 msgstr "" 9127 9160 9128 - #: src/components/NewskieDialog.tsx:65 9161 + #: src/components/NewskieDialog.tsx:47 9129 9162 msgid "This user is new here. Press for more info about when they joined." 9130 9163 msgstr "" 9131 9164 ··· 9313 9346 #: src/components/dms/MessagesListBlockedFooter.tsx:119 9314 9347 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:208 9315 9348 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:328 9349 + #: src/screens/ProfileList/components/Header.tsx:171 9350 + #: src/screens/ProfileList/components/Header.tsx:178 9316 9351 #: src/view/com/profile/ProfileMenu.tsx:490 9317 - #: src/view/screens/ProfileList.tsx:723 9318 9352 msgid "Unblock" 9319 9353 msgstr "" 9320 9354 ··· 9335 9369 msgid "Unblock Account?" 9336 9370 msgstr "" 9337 9371 9338 - #: src/view/screens/ProfileList.tsx:620 9372 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:254 9373 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:257 9339 9374 msgid "Unblock list" 9340 9375 msgstr "" 9341 9376 ··· 9394 9429 msgstr "" 9395 9430 9396 9431 #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152 9397 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95 9432 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:94 9398 9433 msgctxt "video" 9399 9434 msgid "Unmute" 9400 9435 msgstr "" 9401 9436 9402 - #: src/view/screens/ProfileList.tsx:730 9437 + #: src/screens/ProfileList/components/Header.tsx:185 9438 + #: src/screens/ProfileList/components/Header.tsx:192 9403 9439 msgid "Unmute" 9404 9440 msgstr "" 9405 9441 ··· 9419 9455 msgid "Unmute conversation" 9420 9456 msgstr "" 9421 9457 9422 - #: src/view/screens/ProfileList.tsx:605 9458 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:264 9459 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:267 9423 9460 msgid "Unmute list" 9424 9461 msgstr "" 9425 9462 ··· 9432 9469 msgid "Unmute video" 9433 9470 msgstr "" 9434 9471 9435 - #: src/view/screens/ProfileList.tsx:714 9472 + #: src/screens/ProfileList/components/Header.tsx:156 9473 + #: src/screens/ProfileList/components/Header.tsx:163 9436 9474 msgid "Unpin" 9437 9475 msgstr "" 9438 9476 9439 9477 #: src/screens/Profile/components/ProfileFeedHeader.tsx:523 9440 9478 #: src/screens/Profile/components/ProfileFeedHeader.tsx:530 9479 + #: src/screens/SavedFeeds.tsx:351 9441 9480 msgid "Unpin feed" 9442 9481 msgstr "" 9443 9482 ··· 9455 9494 msgid "Unpin from profile" 9456 9495 msgstr "" 9457 9496 9458 - #: src/view/screens/ProfileList.tsx:585 9497 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:237 9498 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:240 9459 9499 msgid "Unpin moderation list" 9460 9500 msgstr "" 9461 9501 ··· 9463 9503 msgid "Unpinned {0} from Home" 9464 9504 msgstr "" 9465 9505 9466 - #: src/view/screens/ProfileList.tsx:362 9506 + #: src/screens/ProfileList/components/Header.tsx:75 9467 9507 msgid "Unpinned from your feeds" 9508 + msgstr "" 9509 + 9510 + #: src/screens/ProfileList/components/MoreOptionsMenu.tsx:109 9511 + msgid "Unpinned list" 9468 9512 msgstr "" 9469 9513 9470 9514 #: src/screens/Settings/Settings.tsx:474 ··· 9602 9646 msgid "Use my default browser" 9603 9647 msgstr "" 9604 9648 9605 - #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:53 9649 + #: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:56 9606 9650 msgid "Use recommended" 9607 9651 msgstr "" 9608 9652 ··· 9829 9873 msgid "Video is playing" 9830 9874 msgstr "" 9831 9875 9832 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:220 9876 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:225 9833 9877 msgid "Video not found." 9834 9878 msgstr "" 9835 9879 ··· 9878 9922 msgid "View blocked user's profile" 9879 9923 msgstr "" 9880 9924 9881 - #: src/screens/Settings/components/ExportCarDialog.tsx:100 9925 + #: src/screens/Settings/components/ExportCarDialog.tsx:99 9882 9926 msgid "View blogpost for more details" 9883 9927 msgstr "" 9884 9928 ··· 9886 9930 msgid "View debug entry" 9887 9931 msgstr "" 9888 9932 9889 - #: src/components/ReportDialog/SelectReportOptionView.tsx:137 9933 + #: src/components/ReportDialog/SelectReportOptionView.tsx:140 9890 9934 #: src/screens/VideoFeed/index.tsx:659 9891 9935 #: src/screens/VideoFeed/index.tsx:677 9892 9936 msgid "View details" 9893 9937 msgstr "" 9894 9938 9895 - #: src/components/ReportDialog/SelectReportOptionView.tsx:132 9939 + #: src/components/ReportDialog/SelectReportOptionView.tsx:135 9896 9940 msgid "View details for reporting a copyright violation" 9897 9941 msgstr "" 9898 9942 ··· 9900 9944 msgid "View full thread" 9901 9945 msgstr "" 9902 9946 9903 - #: src/components/moderation/LabelsOnMe.tsx:46 9947 + #: src/components/moderation/LabelsOnMe.tsx:51 9904 9948 msgid "View information about these labels" 9905 9949 msgstr "" 9906 9950 ··· 9923 9967 #: src/components/ProfileHoverCard/index.web.tsx:486 9924 9968 #: src/components/ProfileHoverCard/index.web.tsx:513 9925 9969 #: src/view/com/posts/PostFeedErrorMessage.tsx:179 9926 - #: src/view/com/util/PostMeta.tsx:91 9927 - #: src/view/com/util/PostMeta.tsx:128 9970 + #: src/view/com/util/PostMeta.tsx:90 9971 + #: src/view/com/util/PostMeta.tsx:127 9928 9972 msgid "View profile" 9929 9973 msgstr "" 9930 9974 ··· 9956 10000 msgid "View your default post interaction settings" 9957 10001 msgstr "" 9958 10002 9959 - #: src/view/com/home/HomeHeaderLayout.web.tsx:56 9960 - #: src/view/com/home/HomeHeaderLayoutMobile.tsx:71 10003 + #: src/view/com/home/HomeHeaderLayout.web.tsx:57 10004 + #: src/view/com/home/HomeHeaderLayoutMobile.tsx:72 9961 10005 msgid "View your feeds and explore more" 9962 10006 msgstr "" 9963 10007 ··· 9991 10035 msgid "Visit your notification settings" 9992 10036 msgstr "" 9993 10037 9994 - #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:81 10038 + #: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:80 9995 10039 msgid "Volume" 9996 10040 msgstr "" 9997 10041 9998 - #: src/components/moderation/LabelPreference.tsx:142 9999 - #: src/lib/moderation/useLabelBehaviorDescription.ts:17 10000 - #: src/lib/moderation/useLabelBehaviorDescription.ts:22 10042 + #: src/components/moderation/LabelPreference.tsx:141 10043 + #: src/lib/moderation/useLabelBehaviorDescription.ts:20 10044 + #: src/lib/moderation/useLabelBehaviorDescription.ts:25 10001 10045 msgid "Warn" 10002 10046 msgstr "" 10003 10047 10004 - #: src/lib/moderation/useLabelBehaviorDescription.ts:48 10048 + #: src/lib/moderation/useLabelBehaviorDescription.ts:51 10005 10049 msgid "Warn content" 10006 10050 msgstr "" 10007 10051 10008 - #: src/lib/moderation/useLabelBehaviorDescription.ts:46 10052 + #: src/lib/moderation/useLabelBehaviorDescription.ts:49 10009 10053 msgid "Warn content and filter from feeds" 10010 10054 msgstr "" 10011 10055 ··· 10131 10175 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." 10132 10176 msgstr "" 10133 10177 10134 - #: src/view/screens/ProfileList.tsx:117 10178 + #: src/screens/ProfileList/index.tsx:87 10135 10179 msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}." 10136 10180 msgstr "" 10137 10181 ··· 10147 10191 msgid "We're sorry! The post you are replying to has been deleted." 10148 10192 msgstr "" 10149 10193 10150 - #: src/components/Lists.tsx:195 10194 + #: src/components/Lists.tsx:194 10151 10195 #: src/view/screens/NotFound.tsx:50 10152 10196 msgid "We're sorry! We can't find the page you were looking for." 10153 10197 msgstr "" ··· 10180 10224 msgid "Welcome back!" 10181 10225 msgstr "" 10182 10226 10183 - #: src/components/NewskieDialog.tsx:103 10227 + #: src/components/NewskieDialog.tsx:121 10184 10228 msgid "Welcome, friend!" 10185 10229 msgstr "" 10186 10230 ··· 10233 10277 msgid "Why are you appealing?" 10234 10278 msgstr "" 10235 10279 10236 - #: src/components/ReportDialog/SelectReportOptionView.tsx:42 10280 + #: src/components/ReportDialog/SelectReportOptionView.tsx:45 10237 10281 msgid "Why should this content be reviewed?" 10238 10282 msgstr "" 10239 10283 10240 10284 #: src/components/moderation/ReportDialog/copy.ts:32 10241 - #: src/components/ReportDialog/SelectReportOptionView.tsx:55 10285 + #: src/components/ReportDialog/SelectReportOptionView.tsx:58 10242 10286 msgid "Why should this feed be reviewed?" 10243 10287 msgstr "" 10244 10288 10245 10289 #: src/components/moderation/ReportDialog/copy.ts:26 10246 - #: src/components/ReportDialog/SelectReportOptionView.tsx:52 10290 + #: src/components/ReportDialog/SelectReportOptionView.tsx:55 10247 10291 msgid "Why should this list be reviewed?" 10248 10292 msgstr "" 10249 10293 10250 10294 #: src/components/moderation/ReportDialog/copy.ts:44 10251 - #: src/components/ReportDialog/SelectReportOptionView.tsx:61 10295 + #: src/components/ReportDialog/SelectReportOptionView.tsx:64 10252 10296 msgid "Why should this message be reviewed?" 10253 10297 msgstr "" 10254 10298 10255 10299 #: src/components/moderation/ReportDialog/copy.ts:20 10256 - #: src/components/ReportDialog/SelectReportOptionView.tsx:49 10300 + #: src/components/ReportDialog/SelectReportOptionView.tsx:52 10257 10301 msgid "Why should this post be reviewed?" 10258 10302 msgstr "" 10259 10303 10260 10304 #: src/components/moderation/ReportDialog/copy.ts:38 10261 - #: src/components/ReportDialog/SelectReportOptionView.tsx:58 10305 + #: src/components/ReportDialog/SelectReportOptionView.tsx:61 10262 10306 msgid "Why should this starter pack be reviewed?" 10263 10307 msgstr "" 10264 10308 10265 10309 #: src/components/moderation/ReportDialog/copy.ts:14 10266 - #: src/components/ReportDialog/SelectReportOptionView.tsx:46 10310 + #: src/components/ReportDialog/SelectReportOptionView.tsx:49 10267 10311 msgid "Why should this user be reviewed?" 10268 10312 msgstr "" 10269 10313 ··· 10295 10339 msgid "www.mylivestream.tv" 10296 10340 msgstr "" 10297 10341 10298 - #: src/view/com/composer/select-language/SuggestedLanguage.tsx:100 10342 + #: src/view/com/composer/select-language/SuggestedLanguage.tsx:102 10299 10343 msgid "Yes" 10300 10344 msgstr "" 10301 10345 ··· 10324 10368 msgid "Yesterday" 10325 10369 msgstr "" 10326 10370 10327 - #: src/components/NewskieDialog.tsx:43 10371 + #: src/components/NewskieDialog.tsx:91 10328 10372 msgid "You" 10329 10373 msgstr "" 10330 10374 ··· 10446 10490 msgid "You do not have any followers." 10447 10491 msgstr "" 10448 10492 10449 - #: src/screens/Profile/KnownFollowers.tsx:109 10493 + #: src/screens/Profile/KnownFollowers.tsx:112 10450 10494 msgid "You don't follow any users who follow @{name}." 10451 10495 msgstr "" 10452 10496 ··· 10458 10502 msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer." 10459 10503 msgstr "" 10460 10504 10461 - #: src/view/screens/SavedFeeds.tsx:145 10505 + #: src/screens/SavedFeeds.tsx:149 10462 10506 msgid "You don't have any pinned feeds." 10463 10507 msgstr "" 10464 10508 10465 - #: src/view/screens/SavedFeeds.tsx:185 10509 + #: src/screens/SavedFeeds.tsx:191 10466 10510 msgid "You don't have any saved feeds." 10467 10511 msgstr "" 10468 10512 ··· 10507 10551 msgid "You have no conversations yet. Start one!" 10508 10552 msgstr "" 10509 10553 10510 - #: src/view/com/feeds/ProfileFeedgens.tsx:139 10554 + #: src/view/com/feeds/ProfileFeedgens.tsx:151 10511 10555 msgid "You have no feeds." 10512 10556 msgstr "" 10513 10557 10514 10558 #: src/view/com/lists/MyLists.tsx:81 10515 - #: src/view/com/lists/ProfileLists.tsx:135 10559 + #: src/view/com/lists/ProfileLists.tsx:149 10516 10560 msgid "You have no lists." 10517 10561 msgstr "" 10518 10562 ··· 10528 10572 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." 10529 10573 msgstr "" 10530 10574 10531 - #: src/components/Lists.tsx:58 10575 + #: src/components/Lists.tsx:57 10532 10576 msgid "You have reached the end" 10533 10577 msgstr "" 10534 10578 ··· 10540 10584 msgid "You have temporarily reached the limit for video uploads. Please try again later." 10541 10585 msgstr "" 10542 10586 10543 - #: src/components/StarterPack/ProfileStarterPacks.tsx:263 10587 + #: src/components/StarterPack/ProfileStarterPacks.tsx:259 10544 10588 msgid "You haven't created a starter pack yet!" 10545 10589 msgstr "" 10546 10590 ··· 10581 10625 msgid "You must be at least 13 years old to use Bluesky. Read our <0>Terms of Service</0> for more information." 10582 10626 msgstr "" 10583 10627 10584 - #: src/components/StarterPack/ProfileStarterPacks.tsx:334 10628 + #: src/components/StarterPack/ProfileStarterPacks.tsx:330 10585 10629 msgid "You must be following at least seven other people to generate a starter pack." 10586 10630 msgstr "" 10587 10631 ··· 10593 10637 msgid "You must complete age assurance in order to access this screen." 10594 10638 msgstr "" 10595 10639 10596 - #: src/components/StarterPack/QrCodeDialog.tsx:61 10640 + #: src/components/StarterPack/QrCodeDialog.tsx:65 10597 10641 msgid "You must grant access to your photo library to save a QR code" 10598 10642 msgstr "" 10599 10643 ··· 10769 10813 msgid "Your birth date" 10770 10814 msgstr "" 10771 10815 10772 - #: src/components/Post/Embed/VideoEmbed/index.web.tsx:224 10816 + #: src/components/Post/Embed/VideoEmbed/index.web.tsx:229 10773 10817 msgid "Your browser does not support the video format. Please try a different browser." 10774 10818 msgstr "" 10775 10819
+1 -1
src/logger/__tests__/logDump.test.ts
··· 1 1 import {expect, test} from '@jest/globals' 2 2 3 - import {add, ConsoleTransportEntry, getEntries} from '#/logger/logDump' 3 + import {add, type ConsoleTransportEntry, getEntries} from '#/logger/logDump' 4 4 import {LogContext, LogLevel} from '#/logger/types' 5 5 6 6 test('works', () => {
+1 -1
src/logger/logDump.ts
··· 1 - import type {LogContext, LogLevel, Metadata} from '#/logger/types' 1 + import {type LogContext, type LogLevel, type Metadata} from '#/logger/types' 2 2 3 3 export type ConsoleTransportEntry = { 4 4 id: string
+1 -1
src/logger/transports/bitdrift.ts
··· 1 1 import {debug, error, info, warn} from '#/logger/bitdrift/lib' 2 - import {LogLevel, Transport} from '#/logger/types' 2 + import {LogLevel, type Transport} from '#/logger/types' 3 3 import {prepareMetadata} from '#/logger/util' 4 4 5 5 const logFunctions = {
+1 -1
src/logger/transports/console.ts
··· 1 1 import format from 'date-fns/format' 2 2 3 - import {LogLevel, Transport} from '#/logger/types' 3 + import {LogLevel, type Transport} from '#/logger/types' 4 4 import {prepareMetadata} from '#/logger/util' 5 5 import {isWeb} from '#/platform/detection' 6 6
+1 -1
src/logger/util.ts
··· 1 - import {LogLevel, Metadata, Serializable} from '#/logger/types' 1 + import {LogLevel, type Metadata, type Serializable} from '#/logger/types' 2 2 3 3 export const enabledLogLevels: { 4 4 [key in LogLevel]: LogLevel[]
-49
src/platform/polyfills.ts
··· 1 1 import 'react-native-url-polyfill/auto' 2 2 import 'fast-text-encoding' 3 3 export {} 4 - 5 - /** 6 - https://github.com/MaxArt2501/base64-js 7 - The MIT License (MIT) 8 - Copyright (c) 2014 MaxArt2501 9 - */ 10 - 11 - const b64 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=' 12 - // Regular expression to check formal correctness of base64 encoded strings 13 - const b64re = 14 - /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/ 15 - 16 - globalThis.atob = (str: string): string => { 17 - // atob can work with strings with whitespaces, even inside the encoded part, 18 - // but only \t, \n, \f, \r and ' ', which can be stripped. 19 - str = String(str).replace(/[\t\n\f\r ]+/g, '') 20 - if (!b64re.test(str)) { 21 - throw new TypeError( 22 - "Failed to execute 'atob' on 'Window': The string to be decoded is not correctly encoded.", 23 - ) 24 - } 25 - 26 - // Adding the padding if missing, for simplicity 27 - str += '=='.slice(2 - (str.length & 3)) 28 - var bitmap, 29 - result = '', 30 - r1, 31 - r2, 32 - i = 0 33 - for (; i < str.length; ) { 34 - bitmap = 35 - (b64.indexOf(str.charAt(i++)) << 18) | 36 - (b64.indexOf(str.charAt(i++)) << 12) | 37 - ((r1 = b64.indexOf(str.charAt(i++))) << 6) | 38 - (r2 = b64.indexOf(str.charAt(i++))) 39 - 40 - result += 41 - r1 === 64 42 - ? String.fromCharCode((bitmap >> 16) & 255) 43 - : r2 === 64 44 - ? String.fromCharCode((bitmap >> 16) & 255, (bitmap >> 8) & 255) 45 - : String.fromCharCode( 46 - (bitmap >> 16) & 255, 47 - (bitmap >> 8) & 255, 48 - bitmap & 255, 49 - ) 50 - } 51 - return result 52 - }
+16 -18
src/screens/Feeds/NoFollowingFeed.tsx
··· 1 - import React from 'react' 2 - import {View} from 'react-native' 1 + import {type GestureResponderEvent, View} from 'react-native' 3 2 import {msg, Trans} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' 5 4 ··· 9 8 import {InlineLinkText} from '#/components/Link' 10 9 import {Text} from '#/components/Typography' 11 10 12 - export function NoFollowingFeed() { 11 + export function NoFollowingFeed({onAddFeed}: {onAddFeed?: () => void}) { 13 12 const t = useTheme() 14 13 const {_} = useLingui() 15 14 const {mutateAsync: addSavedFeeds} = useAddSavedFeedsMutation() 16 15 17 - const addRecommendedFeeds = React.useCallback( 18 - (e: any) => { 19 - e.preventDefault() 16 + const addRecommendedFeeds = (e: GestureResponderEvent) => { 17 + e.preventDefault() 20 18 21 - addSavedFeeds([ 22 - { 23 - ...TIMELINE_SAVED_FEED, 24 - pinned: true, 25 - }, 26 - ]) 19 + addSavedFeeds([ 20 + { 21 + ...TIMELINE_SAVED_FEED, 22 + pinned: true, 23 + }, 24 + ]) 27 25 28 - // prevent navigation 29 - return false 30 - }, 31 - [addSavedFeeds], 32 - ) 26 + onAddFeed?.() 27 + 28 + // prevent navigation 29 + return false as const 30 + } 33 31 34 32 return ( 35 33 <View style={[a.flex_row, a.flex_wrap, a.align_center, a.py_md, a.px_lg]}> ··· 37 35 <Trans> 38 36 Looks like you're missing a following feed.{' '} 39 37 <InlineLinkText 40 - to="/" 38 + to="#" 41 39 label={_(msg`Add the default feed of only people you follow`)} 42 40 onPress={addRecommendedFeeds} 43 41 style={[a.leading_snug]}>
+10 -7
src/screens/Feeds/NoSavedFeedsOfAnyType.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 2 import {TID} from '@atproto/common-web' 4 3 import {msg, Trans} from '@lingui/macro' ··· 16 15 * feeds if pressed. It should only be presented to the user if they actually 17 16 * have no other feeds saved. 18 17 */ 19 - export function NoSavedFeedsOfAnyType() { 18 + export function NoSavedFeedsOfAnyType({ 19 + onAddRecommendedFeeds, 20 + }: { 21 + onAddRecommendedFeeds?: () => void 22 + }) { 20 23 const t = useTheme() 21 24 const {_} = useLingui() 22 25 const {isPending, mutateAsync: overwriteSavedFeeds} = 23 26 useOverwriteSavedFeedsMutation() 24 27 25 - const addRecommendedFeeds = React.useCallback(async () => { 28 + const addRecommendedFeeds = async () => { 29 + onAddRecommendedFeeds?.() 26 30 await overwriteSavedFeeds( 27 31 RECOMMENDED_SAVED_FEEDS.map(f => ({ 28 32 ...f, 29 33 id: TID.nextStr(), 30 34 })), 31 35 ) 32 - }, [overwriteSavedFeeds]) 36 + } 33 37 34 38 return ( 35 39 <View ··· 46 50 disabled={isPending} 47 51 label={_(msg`Apply default recommended feeds`)} 48 52 size="small" 49 - variant="solid" 50 - color="primary" 53 + color="primary_subtle" 51 54 onPress={addRecommendedFeeds}> 52 - <ButtonIcon icon={Plus} position="left" /> 55 + <ButtonIcon icon={Plus} /> 53 56 <ButtonText>{_(msg`Use recommended`)}</ButtonText> 54 57 </Button> 55 58 </View>
+1 -1
src/screens/Home/NoFeedsPinned.tsx
··· 6 6 7 7 import {DISCOVER_SAVED_FEED, TIMELINE_SAVED_FEED} from '#/lib/constants' 8 8 import {useOverwriteSavedFeedsMutation} from '#/state/queries/preferences' 9 - import {UsePreferencesQueryResponse} from '#/state/queries/preferences' 9 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences' 10 10 import {CenteredView} from '#/view/com/util/Views' 11 11 import {atoms as a} from '#/alf' 12 12 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
+1 -1
src/screens/List/ListHiddenScreen.tsx
··· 11 11 import {RQKEY_ROOT as listQueryRoot} from '#/state/queries/list' 12 12 import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 13 13 import { 14 - UsePreferencesQueryResponse, 14 + type UsePreferencesQueryResponse, 15 15 useRemoveFeedMutation, 16 16 } from '#/state/queries/preferences' 17 17 import {useSession} from '#/state/session'
+1 -1
src/screens/Login/ChooseAccountForm.tsx
··· 5 5 6 6 import {logEvent} from '#/lib/statsig/statsig' 7 7 import {logger} from '#/logger' 8 - import {SessionAccount, useSession, useSessionApi} from '#/state/session' 8 + import {type SessionAccount, useSession, useSessionApi} from '#/state/session' 9 9 import {useLoggedOutViewControls} from '#/state/shell/logged-out' 10 10 import * as Toast from '#/view/com/util/Toast' 11 11 import {atoms as a} from '#/alf'
+1 -1
src/screens/Login/FormContainer.tsx
··· 1 - import React from 'react' 2 1 import {type StyleProp, View, type ViewStyle} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 5 import {Text} from '#/components/Typography'
+2 -2
src/screens/Login/ScreenTransition.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {type StyleProp, type ViewStyle} from 'react-native' 3 2 import Animated, {FadeInRight, FadeOutLeft} from 'react-native-reanimated' 3 + import type React from 'react' 4 4 5 5 export function ScreenTransition({ 6 6 style,
+1 -1
src/screens/Messages/components/ChatStatusInfo.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {ActiveConvoStates} from '#/state/messages/convo' 6 + import {type ActiveConvoStates} from '#/state/messages/convo' 7 7 import {useModerationOpts} from '#/state/preferences/moderation-opts' 8 8 import {useSession} from '#/state/session' 9 9 import {atoms as a, useTheme} from '#/alf'
+5 -2
src/screens/Messages/components/MessageInputEmbed.tsx
··· 9 9 } from '@atproto/api' 10 10 import {msg} from '@lingui/macro' 11 11 import {useLingui} from '@lingui/react' 12 - import {RouteProp, useNavigation, useRoute} from '@react-navigation/native' 12 + import {type RouteProp, useNavigation, useRoute} from '@react-navigation/native' 13 13 14 14 import {makeProfileLink} from '#/lib/routes/links' 15 - import {CommonNavigatorParams, NavigationProp} from '#/lib/routes/types' 15 + import { 16 + type CommonNavigatorParams, 17 + type NavigationProp, 18 + } from '#/lib/routes/types' 16 19 import { 17 20 convertBskyAppUrlIfNeeded, 18 21 isBskyPostUrl,
+1 -1
src/screens/Messages/components/MessageListError.tsx
··· 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {ConvoItem, ConvoItemError} from '#/state/messages/convo/types' 6 + import {type ConvoItem, ConvoItemError} from '#/state/messages/convo/types' 7 7 import {atoms as a, useTheme} from '#/alf' 8 8 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 9 9 import {InlineLinkText} from '#/components/Link'
+1 -1
src/screens/ModerationInteractionSettings/index.tsx
··· 9 9 import {createPostgateRecord} from '#/state/queries/postgate/util' 10 10 import { 11 11 usePreferencesQuery, 12 - UsePreferencesQueryResponse, 12 + type UsePreferencesQueryResponse, 13 13 } from '#/state/queries/preferences' 14 14 import { 15 15 threadgateAllowUISettingToAllowRecordValue,
+1 -1
src/screens/Onboarding/StepInterests/InterestButton.tsx
··· 1 1 import React from 'react' 2 - import {TextStyle, View, ViewStyle} from 'react-native' 2 + import {type TextStyle, View, type ViewStyle} from 'react-native' 3 3 4 4 import {capitalize} from '#/lib/strings/capitalize' 5 5 import {useInterestsDisplayNames} from '#/screens/Onboarding/state'
+1 -1
src/screens/Onboarding/StepProfile/AvatarCreatorCircle.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 3 4 - import {Avatar} from '#/screens/Onboarding/StepProfile/index' 4 + import {type Avatar} from '#/screens/Onboarding/StepProfile/index' 5 5 import {atoms as a, useTheme} from '#/alf' 6 6 7 7 export function AvatarCreatorCircle({
+3 -3
src/screens/Onboarding/StepProfile/AvatarCreatorItems.tsx
··· 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {Avatar} from '#/screens/Onboarding/StepProfile/index' 6 + import {type Avatar} from '#/screens/Onboarding/StepProfile/index' 7 7 import { 8 - AvatarColor, 8 + type AvatarColor, 9 9 avatarColors, 10 10 emojiItems, 11 - EmojiName, 11 + type EmojiName, 12 12 emojiNames, 13 13 } from '#/screens/Onboarding/StepProfile/types' 14 14 import {atoms as a, useTheme} from '#/alf'
+4 -1
src/screens/Post/PostLikedBy.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+4 -1
src/screens/Post/PostQuotes.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+4 -1
src/screens/Post/PostRepostedBy.tsx
··· 2 2 import {Plural, Trans} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {makeRecordUri} from '#/lib/strings/url-helpers' 7 10 import {usePostThreadQuery} from '#/state/queries/post-thread' 8 11 import {useSetMinimalShellMode} from '#/state/shell'
+1 -1
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 354 354 )} 355 355 </Text> 356 356 357 - <View style={[{paddingLeft: 3, top: -1}]}> 357 + <View style={[a.pl_xs]}> 358 358 <VerificationCheckButton profile={authorShadow} size="md" /> 359 359 </View> 360 360 </View>
+1 -1
src/screens/Profile/ErrorState.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 - import {NavigationProp} from '#/lib/routes/types' 7 + import {type NavigationProp} from '#/lib/routes/types' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import {Button, ButtonText} from '#/components/Button' 10 10 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+2 -2
src/screens/Profile/Header/DisplayName.tsx
··· 1 1 import {View} from 'react-native' 2 - import {AppBskyActorDefs, ModerationDecision} from '@atproto/api' 2 + import {type AppBskyActorDefs, type ModerationDecision} from '@atproto/api' 3 3 4 4 import {sanitizeDisplayName} from '#/lib/strings/display-names' 5 5 import {sanitizeHandle} from '#/lib/strings/handles' 6 - import {Shadow} from '#/state/cache/types' 6 + import {type Shadow} from '#/state/cache/types' 7 7 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 8 8 import {Text} from '#/components/Typography' 9 9
+3 -3
src/screens/Profile/Header/GrowableAvatar.tsx
··· 1 - import React from 'react' 2 - import {StyleProp, View, ViewStyle} from 'react-native' 1 + import {type StyleProp, View, type ViewStyle} from 'react-native' 3 2 import Animated, { 4 3 Extrapolation, 5 4 interpolate, 6 - SharedValue, 5 + type SharedValue, 7 6 useAnimatedStyle, 8 7 } from 'react-native-reanimated' 8 + import type React from 'react' 9 9 10 10 import {isIOS} from '#/platform/detection' 11 11 import {usePagerHeaderContext} from '#/view/com/pager/PagerHeaderContext'
+3 -2
src/screens/Profile/Header/GrowableBanner.tsx
··· 1 - import React, {useEffect, useState} from 'react' 1 + import {useEffect, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {ActivityIndicator} from 'react-native' 4 4 import Animated, { 5 5 Extrapolation, 6 6 interpolate, 7 7 runOnJS, 8 - SharedValue, 8 + type SharedValue, 9 9 useAnimatedProps, 10 10 useAnimatedReaction, 11 11 useAnimatedStyle, ··· 13 13 import {useSafeAreaInsets} from 'react-native-safe-area-context' 14 14 import {BlurView} from 'expo-blur' 15 15 import {useIsFetching} from '@tanstack/react-query' 16 + import type React from 'react' 16 17 17 18 import {isIOS} from '#/platform/detection' 18 19 import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs'
+2 -2
src/screens/Profile/Header/Metrics.tsx
··· 1 1 import {View} from 'react-native' 2 - import {AppBskyActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs} from '@atproto/api' 3 3 import {msg, plural} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {makeProfileLink} from '#/lib/routes/links' 7 - import {Shadow} from '#/state/cache/types' 7 + import {type Shadow} from '#/state/cache/types' 8 8 import {formatCount} from '#/view/com/util/numeric/format' 9 9 import {atoms as a, useTheme} from '#/alf' 10 10 import {InlineLinkText} from '#/components/Link'
+4 -1
src/screens/Profile/Header/StatusBarShadow.tsx
··· 1 - import Animated, {SharedValue, useAnimatedStyle} from 'react-native-reanimated' 1 + import Animated, { 2 + type SharedValue, 3 + useAnimatedStyle, 4 + } from 'react-native-reanimated' 2 5 import {useSafeAreaInsets} from 'react-native-safe-area-context' 3 6 import {LinearGradient} from 'expo-linear-gradient' 4 7
+5 -5
src/screens/Profile/Header/index.tsx
··· 1 1 import React, {memo, useState} from 'react' 2 - import {LayoutChangeEvent, StyleSheet, View} from 'react-native' 2 + import {type LayoutChangeEvent, StyleSheet, View} from 'react-native' 3 3 import Animated, { 4 4 runOnJS, 5 5 useAnimatedReaction, ··· 8 8 } from 'react-native-reanimated' 9 9 import {useSafeAreaInsets} from 'react-native-safe-area-context' 10 10 import { 11 - AppBskyActorDefs, 12 - AppBskyLabelerDefs, 13 - ModerationOpts, 14 - RichText as RichTextAPI, 11 + type AppBskyActorDefs, 12 + type AppBskyLabelerDefs, 13 + type ModerationOpts, 14 + type RichText as RichTextAPI, 15 15 } from '@atproto/api' 16 16 import {useIsFocused} from '@react-navigation/native' 17 17
+5 -2
src/screens/Profile/KnownFollowers.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {cleanError} from '#/lib/strings/errors' 10 13 import {logger} from '#/logger' 11 14 import {useProfileKnownFollowersQuery} from '#/state/queries/known-followers'
+4 -1
src/screens/Profile/ProfileFollowers.tsx
··· 2 2 import {Plural} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {sanitizeDisplayName} from '#/lib/strings/display-names' 7 10 import {useProfileQuery} from '#/state/queries/profile' 8 11 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
+4 -1
src/screens/Profile/ProfileFollows.tsx
··· 2 2 import {Plural} from '@lingui/macro' 3 3 import {useFocusEffect} from '@react-navigation/native' 4 4 5 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 5 + import { 6 + type CommonNavigatorParams, 7 + type NativeStackScreenProps, 8 + } from '#/lib/routes/types' 6 9 import {sanitizeDisplayName} from '#/lib/strings/display-names' 7 10 import {useProfileQuery} from '#/state/queries/profile' 8 11 import {useResolveDidQuery} from '#/state/queries/resolve-uri'
+4 -1
src/screens/Profile/ProfileLabelerLikedBy.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 6 + import { 7 + type CommonNavigatorParams, 8 + type NativeStackScreenProps, 9 + } from '#/lib/routes/types' 7 10 import {makeRecordUri} from '#/lib/strings/url-helpers' 8 11 import {useSetMinimalShellMode} from '#/state/shell' 9 12 import {ViewHeader} from '#/view/com/util/ViewHeader'
+17 -21
src/screens/Profile/Sections/Feed.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useEffect, useImperativeHandle, useState} from 'react' 2 2 import {findNodeHandle, View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 18 18 import {type SectionRef} from './types' 19 19 20 20 interface FeedSectionProps { 21 + ref?: React.Ref<SectionRef> 21 22 feed: FeedDescriptor 22 23 headerHeight: number 23 24 isFocused: boolean ··· 25 26 ignoreFilterFor?: string 26 27 setScrollViewTag: (tag: number | null) => void 27 28 } 28 - export const ProfileFeedSection = React.forwardRef< 29 - SectionRef, 30 - FeedSectionProps 31 - >(function FeedSectionImpl( 32 - { 33 - feed, 34 - headerHeight, 35 - isFocused, 36 - scrollElRef, 37 - ignoreFilterFor, 38 - setScrollViewTag, 39 - }, 29 + export function ProfileFeedSection({ 40 30 ref, 41 - ) { 31 + feed, 32 + headerHeight, 33 + isFocused, 34 + scrollElRef, 35 + ignoreFilterFor, 36 + setScrollViewTag, 37 + }: FeedSectionProps) { 42 38 const {_} = useLingui() 43 39 const queryClient = useQueryClient() 44 - const [hasNew, setHasNew] = React.useState(false) 45 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 40 + const [hasNew, setHasNew] = useState(false) 41 + const [isScrolledDown, setIsScrolledDown] = useState(false) 46 42 const shouldUseAdjustedNumToRender = feed.endsWith('posts_and_author_threads') 47 43 const isVideoFeed = isNative && feed.endsWith('posts_with_video') 48 44 const adjustedInitialNumToRender = useInitialNumToRender({ 49 45 screenHeightOffset: headerHeight, 50 46 }) 51 47 52 - const onScrollToTop = React.useCallback(() => { 48 + const onScrollToTop = useCallback(() => { 53 49 scrollElRef.current?.scrollToOffset({ 54 50 animated: isNative, 55 51 offset: -headerHeight, ··· 58 54 setHasNew(false) 59 55 }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) 60 56 61 - React.useImperativeHandle(ref, () => ({ 57 + useImperativeHandle(ref, () => ({ 62 58 scrollToTop: onScrollToTop, 63 59 })) 64 60 65 - const renderPostsEmpty = React.useCallback(() => { 61 + const renderPostsEmpty = useCallback(() => { 66 62 return <EmptyState icon="growth" message={_(msg`No posts yet.`)} /> 67 63 }, [_]) 68 64 69 - React.useEffect(() => { 65 + useEffect(() => { 70 66 if (isIOS && isFocused && scrollElRef.current) { 71 67 const nativeTag = findNodeHandle(scrollElRef.current) 72 68 setScrollViewTag(nativeTag) ··· 101 97 )} 102 98 </View> 103 99 ) 104 - }) 100 + } 105 101 106 102 function ProfileEndOfFeed() { 107 103 const t = useTheme()
+1
src/screens/Profile/Sections/Labels.tsx
··· 33 33 isFocused: boolean 34 34 setScrollViewTag: (tag: number | null) => void 35 35 } 36 + 36 37 export function ProfileLabelsSection({ 37 38 ref, 38 39 isLabelerLoading,
+136
src/screens/ProfileList/AboutSection.tsx
··· 1 + import {useCallback, useImperativeHandle, useState} from 'react' 2 + import {View} from 'react-native' 3 + import {type AppBskyGraphDefs} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import {isNative} from '#/platform/detection' 8 + import {useSession} from '#/state/session' 9 + import {ListMembers} from '#/view/com/lists/ListMembers' 10 + import {EmptyState} from '#/view/com/util/EmptyState' 11 + import {type ListRef} from '#/view/com/util/List' 12 + import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 13 + import {atoms as a, useBreakpoints} from '#/alf' 14 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 15 + import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 16 + 17 + interface SectionRef { 18 + scrollToTop: () => void 19 + } 20 + 21 + interface AboutSectionProps { 22 + ref?: React.Ref<SectionRef> 23 + list: AppBskyGraphDefs.ListView 24 + onPressAddUser: () => void 25 + headerHeight: number 26 + scrollElRef: ListRef 27 + } 28 + 29 + export function AboutSection({ 30 + ref, 31 + list, 32 + onPressAddUser, 33 + headerHeight, 34 + scrollElRef, 35 + }: AboutSectionProps) { 36 + const {_} = useLingui() 37 + const {currentAccount} = useSession() 38 + const {gtMobile} = useBreakpoints() 39 + const [isScrolledDown, setIsScrolledDown] = useState(false) 40 + const isOwner = list.creator.did === currentAccount?.did 41 + 42 + const onScrollToTop = useCallback(() => { 43 + scrollElRef.current?.scrollToOffset({ 44 + animated: isNative, 45 + offset: -headerHeight, 46 + }) 47 + }, [scrollElRef, headerHeight]) 48 + 49 + useImperativeHandle(ref, () => ({ 50 + scrollToTop: onScrollToTop, 51 + })) 52 + 53 + const renderHeader = useCallback(() => { 54 + if (!isOwner) { 55 + return <View /> 56 + } 57 + if (!gtMobile) { 58 + return ( 59 + <View style={[a.px_sm, a.py_sm]}> 60 + <Button 61 + testID="addUserBtn" 62 + label={_(msg`Add a user to this list`)} 63 + onPress={onPressAddUser} 64 + color="primary" 65 + size="small" 66 + variant="outline" 67 + style={[a.py_md]}> 68 + <ButtonIcon icon={PersonPlusIcon} /> 69 + <ButtonText> 70 + <Trans>Add people</Trans> 71 + </ButtonText> 72 + </Button> 73 + </View> 74 + ) 75 + } 76 + return ( 77 + <View style={[a.px_lg, a.py_md, a.flex_row_reverse]}> 78 + <Button 79 + testID="addUserBtn" 80 + label={_(msg`Add a user to this list`)} 81 + onPress={onPressAddUser} 82 + color="primary" 83 + size="small" 84 + variant="ghost" 85 + style={[a.py_sm]}> 86 + <ButtonIcon icon={PersonPlusIcon} /> 87 + <ButtonText> 88 + <Trans>Add people</Trans> 89 + </ButtonText> 90 + </Button> 91 + </View> 92 + ) 93 + }, [isOwner, _, onPressAddUser, gtMobile]) 94 + 95 + const renderEmptyState = useCallback(() => { 96 + return ( 97 + <View style={[a.gap_xl, a.align_center]}> 98 + <EmptyState icon="users-slash" message={_(msg`This list is empty.`)} /> 99 + {isOwner && ( 100 + <Button 101 + testID="emptyStateAddUserBtn" 102 + label={_(msg`Start adding people`)} 103 + onPress={onPressAddUser} 104 + color="primary" 105 + size="small"> 106 + <ButtonIcon icon={PersonPlusIcon} /> 107 + <ButtonText> 108 + <Trans>Start adding people!</Trans> 109 + </ButtonText> 110 + </Button> 111 + )} 112 + </View> 113 + ) 114 + }, [_, onPressAddUser, isOwner]) 115 + 116 + return ( 117 + <View> 118 + <ListMembers 119 + testID="listItems" 120 + list={list.uri} 121 + scrollElRef={scrollElRef} 122 + renderHeader={renderHeader} 123 + renderEmptyState={renderEmptyState} 124 + headerOffset={headerHeight} 125 + onScrolledDownChange={setIsScrolledDown} 126 + /> 127 + {isScrolledDown && ( 128 + <LoadLatestBtn 129 + onPress={onScrollToTop} 130 + label={_(msg`Scroll to top`)} 131 + showIndicator={false} 132 + /> 133 + )} 134 + </View> 135 + ) 136 + }
+111
src/screens/ProfileList/FeedSection.tsx
··· 1 + import {useCallback, useEffect, useImperativeHandle, useState} from 'react' 2 + import {View} from 'react-native' 3 + import {msg, Trans} from '@lingui/macro' 4 + import {useLingui} from '@lingui/react' 5 + import {useIsFocused} from '@react-navigation/native' 6 + import {useQueryClient} from '@tanstack/react-query' 7 + 8 + import {isNative} from '#/platform/detection' 9 + import {listenSoftReset} from '#/state/events' 10 + import {type FeedDescriptor} from '#/state/queries/post-feed' 11 + import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 12 + import {PostFeed} from '#/view/com/posts/PostFeed' 13 + import {EmptyState} from '#/view/com/util/EmptyState' 14 + import {type ListRef} from '#/view/com/util/List' 15 + import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn' 16 + import {atoms as a} from '#/alf' 17 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 18 + import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 19 + 20 + interface SectionRef { 21 + scrollToTop: () => void 22 + } 23 + 24 + interface FeedSectionProps { 25 + ref?: React.Ref<SectionRef> 26 + feed: FeedDescriptor 27 + headerHeight: number 28 + scrollElRef: ListRef 29 + isFocused: boolean 30 + isOwner: boolean 31 + onPressAddUser: () => void 32 + } 33 + 34 + export function FeedSection({ 35 + ref, 36 + feed, 37 + scrollElRef, 38 + headerHeight, 39 + isFocused, 40 + isOwner, 41 + onPressAddUser, 42 + }: FeedSectionProps) { 43 + const queryClient = useQueryClient() 44 + const [hasNew, setHasNew] = useState(false) 45 + const [isScrolledDown, setIsScrolledDown] = useState(false) 46 + const isScreenFocused = useIsFocused() 47 + const {_} = useLingui() 48 + 49 + const onScrollToTop = useCallback(() => { 50 + scrollElRef.current?.scrollToOffset({ 51 + animated: isNative, 52 + offset: -headerHeight, 53 + }) 54 + queryClient.resetQueries({queryKey: FEED_RQKEY(feed)}) 55 + setHasNew(false) 56 + }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) 57 + useImperativeHandle(ref, () => ({ 58 + scrollToTop: onScrollToTop, 59 + })) 60 + 61 + useEffect(() => { 62 + if (!isScreenFocused) { 63 + return 64 + } 65 + return listenSoftReset(onScrollToTop) 66 + }, [onScrollToTop, isScreenFocused]) 67 + 68 + const renderPostsEmpty = useCallback(() => { 69 + return ( 70 + <View style={[a.gap_xl, a.align_center]}> 71 + <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 72 + {isOwner && ( 73 + <Button 74 + label={_(msg`Start adding people`)} 75 + onPress={onPressAddUser} 76 + color="primary" 77 + size="small"> 78 + <ButtonIcon icon={PersonPlusIcon} /> 79 + <ButtonText> 80 + <Trans>Start adding people!</Trans> 81 + </ButtonText> 82 + </Button> 83 + )} 84 + </View> 85 + ) 86 + }, [_, onPressAddUser, isOwner]) 87 + 88 + return ( 89 + <View> 90 + <PostFeed 91 + testID="listFeed" 92 + enabled={isFocused} 93 + feed={feed} 94 + pollInterval={60e3} 95 + disablePoll={hasNew} 96 + scrollElRef={scrollElRef} 97 + onHasNew={setHasNew} 98 + onScrolledDownChange={setIsScrolledDown} 99 + renderEmptyState={renderPostsEmpty} 100 + headerOffset={headerHeight} 101 + /> 102 + {(isScrolledDown || hasNew) && ( 103 + <LoadLatestBtn 104 + onPress={onScrollToTop} 105 + label={_(msg`Load new posts`)} 106 + showIndicator={hasNew} 107 + /> 108 + )} 109 + </View> 110 + ) 111 + }
+46
src/screens/ProfileList/components/ErrorScreen.tsx
··· 1 + import {View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {useNavigation} from '@react-navigation/native' 5 + 6 + import {type NavigationProp} from '#/lib/routes/types' 7 + import {atoms as a, useTheme} from '#/alf' 8 + import {Button, ButtonText} from '#/components/Button' 9 + import {Text} from '#/components/Typography' 10 + 11 + export function ErrorScreen({error}: {error: React.ReactNode}) { 12 + const t = useTheme() 13 + const navigation = useNavigation<NavigationProp>() 14 + const {_} = useLingui() 15 + const onPressBack = () => { 16 + if (navigation.canGoBack()) { 17 + navigation.goBack() 18 + } else { 19 + navigation.navigate('Home') 20 + } 21 + } 22 + 23 + return ( 24 + <View style={[a.px_xl, a.py_md, a.gap_md]}> 25 + <Text style={[a.text_4xl, a.font_heavy]}> 26 + <Trans>Could not load list</Trans> 27 + </Text> 28 + <Text style={[a.text_md, t.atoms.text_contrast_high, a.leading_snug]}> 29 + {error} 30 + </Text> 31 + 32 + <View style={[a.flex_row, a.mt_lg]}> 33 + <Button 34 + label={_(msg`Go back`)} 35 + accessibilityHint={_(msg`Returns to previous page`)} 36 + onPress={onPressBack} 37 + size="small" 38 + color="secondary"> 39 + <ButtonText> 40 + <Trans>Go back</Trans> 41 + </ButtonText> 42 + </Button> 43 + </View> 44 + </View> 45 + ) 46 + }
+208
src/screens/ProfileList/components/Header.tsx
··· 1 + import {useMemo} from 'react' 2 + import {View} from 'react-native' 3 + import {AppBskyGraphDefs, RichText as RichTextAPI} from '@atproto/api' 4 + import {msg, Trans} from '@lingui/macro' 5 + import {useLingui} from '@lingui/react' 6 + 7 + import {useHaptics} from '#/lib/haptics' 8 + import {makeListLink} from '#/lib/routes/links' 9 + import {logger} from '#/logger' 10 + import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 11 + import { 12 + useAddSavedFeedsMutation, 13 + type UsePreferencesQueryResponse, 14 + useUpdateSavedFeedsMutation, 15 + } from '#/state/queries/preferences' 16 + import {useSession} from '#/state/session' 17 + import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader' 18 + import {atoms as a} from '#/alf' 19 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 20 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 21 + import {Loader} from '#/components/Loader' 22 + import {RichText} from '#/components/RichText' 23 + import * as Toast from '#/components/Toast' 24 + import {MoreOptionsMenu} from './MoreOptionsMenu' 25 + import {SubscribeMenu} from './SubscribeMenu' 26 + 27 + export function Header({ 28 + rkey, 29 + list, 30 + preferences, 31 + }: { 32 + rkey: string 33 + list: AppBskyGraphDefs.ListView 34 + preferences: UsePreferencesQueryResponse 35 + }) { 36 + const {_} = useLingui() 37 + const {currentAccount} = useSession() 38 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 39 + const isModList = list.purpose === AppBskyGraphDefs.MODLIST 40 + const isBlocking = !!list.viewer?.blocked 41 + const isMuting = !!list.viewer?.muted 42 + const playHaptic = useHaptics() 43 + 44 + const {mutateAsync: muteList, isPending: isMutePending} = 45 + useListMuteMutation() 46 + const {mutateAsync: blockList, isPending: isBlockPending} = 47 + useListBlockMutation() 48 + const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} = 49 + useAddSavedFeedsMutation() 50 + const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} = 51 + useUpdateSavedFeedsMutation() 52 + 53 + const isPending = isAddSavedFeedPending || isUpdatingSavedFeeds 54 + 55 + const savedFeedConfig = preferences?.savedFeeds?.find( 56 + f => f.value === list.uri, 57 + ) 58 + const isPinned = Boolean(savedFeedConfig?.pinned) 59 + 60 + const onTogglePinned = async () => { 61 + playHaptic() 62 + 63 + try { 64 + if (savedFeedConfig) { 65 + const pinned = !savedFeedConfig.pinned 66 + await updateSavedFeeds([ 67 + { 68 + ...savedFeedConfig, 69 + pinned, 70 + }, 71 + ]) 72 + Toast.show( 73 + pinned 74 + ? _(msg`Pinned to your feeds`) 75 + : _(msg`Unpinned from your feeds`), 76 + ) 77 + } else { 78 + await addSavedFeeds([ 79 + { 80 + type: 'list', 81 + value: list.uri, 82 + pinned: true, 83 + }, 84 + ]) 85 + Toast.show(_(msg`Saved to your feeds`)) 86 + } 87 + } catch (e) { 88 + Toast.show(_(msg`There was an issue contacting the server`), { 89 + type: 'error', 90 + }) 91 + logger.error('Failed to toggle pinned feed', {message: e}) 92 + } 93 + } 94 + 95 + const onUnsubscribeMute = async () => { 96 + try { 97 + await muteList({uri: list.uri, mute: false}) 98 + Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 99 + logger.metric( 100 + 'moderation:unsubscribedFromList', 101 + {listType: 'mute'}, 102 + {statsig: true}, 103 + ) 104 + } catch { 105 + Toast.show( 106 + _( 107 + msg`There was an issue. Please check your internet connection and try again.`, 108 + ), 109 + ) 110 + } 111 + } 112 + 113 + const onUnsubscribeBlock = async () => { 114 + try { 115 + await blockList({uri: list.uri, block: false}) 116 + Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 117 + logger.metric( 118 + 'moderation:unsubscribedFromList', 119 + {listType: 'block'}, 120 + {statsig: true}, 121 + ) 122 + } catch { 123 + Toast.show( 124 + _( 125 + msg`There was an issue. Please check your internet connection and try again.`, 126 + ), 127 + ) 128 + } 129 + } 130 + 131 + const descriptionRT = useMemo( 132 + () => 133 + list.description 134 + ? new RichTextAPI({ 135 + text: list.description, 136 + facets: list.descriptionFacets, 137 + }) 138 + : undefined, 139 + [list], 140 + ) 141 + 142 + return ( 143 + <> 144 + <ProfileSubpageHeader 145 + href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} 146 + title={list.name} 147 + avatar={list.avatar} 148 + isOwner={list.creator.did === currentAccount?.did} 149 + creator={list.creator} 150 + purpose={list.purpose} 151 + avatarType="list"> 152 + {isCurateList ? ( 153 + <Button 154 + testID={isPinned ? 'unpinBtn' : 'pinBtn'} 155 + color={isPinned ? 'secondary' : 'primary_subtle'} 156 + label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} 157 + onPress={onTogglePinned} 158 + disabled={isPending} 159 + size="small" 160 + style={[a.rounded_full]}> 161 + {!isPinned && <ButtonIcon icon={isPending ? Loader : PinIcon} />} 162 + <ButtonText> 163 + {isPinned ? <Trans>Unpin</Trans> : <Trans>Pin to home</Trans>} 164 + </ButtonText> 165 + </Button> 166 + ) : isModList ? ( 167 + isBlocking ? ( 168 + <Button 169 + testID="unblockBtn" 170 + color="secondary" 171 + label={_(msg`Unblock`)} 172 + onPress={onUnsubscribeBlock} 173 + size="small" 174 + style={[a.rounded_full]} 175 + disabled={isBlockPending}> 176 + {isBlockPending && <ButtonIcon icon={Loader} />} 177 + <ButtonText> 178 + <Trans>Unblock</Trans> 179 + </ButtonText> 180 + </Button> 181 + ) : isMuting ? ( 182 + <Button 183 + testID="unmuteBtn" 184 + color="secondary" 185 + label={_(msg`Unmute`)} 186 + onPress={onUnsubscribeMute} 187 + size="small" 188 + style={[a.rounded_full]} 189 + disabled={isMutePending}> 190 + {isMutePending && <ButtonIcon icon={Loader} />} 191 + <ButtonText> 192 + <Trans>Unmute</Trans> 193 + </ButtonText> 194 + </Button> 195 + ) : ( 196 + <SubscribeMenu list={list} /> 197 + ) 198 + ) : null} 199 + <MoreOptionsMenu list={list} /> 200 + </ProfileSubpageHeader> 201 + {descriptionRT ? ( 202 + <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> 203 + <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> 204 + </View> 205 + ) : null} 206 + </> 207 + ) 208 + }
+298
src/screens/ProfileList/components/MoreOptionsMenu.tsx
··· 1 + import {type AppBskyActorDefs, AppBskyGraphDefs, AtUri} from '@atproto/api' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + import {useNavigation} from '@react-navigation/native' 5 + 6 + import {type NavigationProp} from '#/lib/routes/types' 7 + import {shareUrl} from '#/lib/sharing' 8 + import {toShareUrl} from '#/lib/strings/url-helpers' 9 + import {logger} from '#/logger' 10 + import {isWeb} from '#/platform/detection' 11 + import {useModalControls} from '#/state/modals' 12 + import { 13 + useListBlockMutation, 14 + useListDeleteMutation, 15 + useListMuteMutation, 16 + } from '#/state/queries/list' 17 + import {useRemoveFeedMutation} from '#/state/queries/preferences' 18 + import {useSession} from '#/state/session' 19 + import {Button, ButtonIcon} from '#/components/Button' 20 + import {useDialogControl} from '#/components/Dialog' 21 + import {ArrowOutOfBoxModified_Stroke2_Corner2_Rounded as ShareIcon} from '#/components/icons/ArrowOutOfBox' 22 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLink} from '#/components/icons/ChainLink' 23 + import {DotGrid_Stroke2_Corner0_Rounded as DotGridIcon} from '#/components/icons/DotGrid' 24 + import {PencilLine_Stroke2_Corner0_Rounded as PencilLineIcon} from '#/components/icons/Pencil' 25 + import {PersonCheck_Stroke2_Corner0_Rounded as PersonCheckIcon} from '#/components/icons/Person' 26 + import {Pin_Stroke2_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 27 + import {SpeakerVolumeFull_Stroke2_Corner0_Rounded as UnmuteIcon} from '#/components/icons/Speaker' 28 + import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 29 + import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 30 + import * as Menu from '#/components/Menu' 31 + import { 32 + ReportDialog, 33 + useReportDialogControl, 34 + } from '#/components/moderation/ReportDialog' 35 + import * as Prompt from '#/components/Prompt' 36 + import * as Toast from '#/components/Toast' 37 + 38 + export function MoreOptionsMenu({ 39 + list, 40 + savedFeedConfig, 41 + }: { 42 + list: AppBskyGraphDefs.ListView 43 + savedFeedConfig?: AppBskyActorDefs.SavedFeed 44 + }) { 45 + const {_} = useLingui() 46 + const {currentAccount} = useSession() 47 + const {openModal} = useModalControls() 48 + const deleteListPromptControl = useDialogControl() 49 + const reportDialogControl = useReportDialogControl() 50 + const navigation = useNavigation<NavigationProp>() 51 + 52 + const {mutateAsync: removeSavedFeed} = useRemoveFeedMutation() 53 + const {mutateAsync: deleteList} = useListDeleteMutation() 54 + const {mutateAsync: muteList} = useListMuteMutation() 55 + const {mutateAsync: blockList} = useListBlockMutation() 56 + 57 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 58 + const isModList = list.purpose === AppBskyGraphDefs.MODLIST 59 + const isBlocking = !!list.viewer?.blocked 60 + const isMuting = !!list.viewer?.muted 61 + const isPinned = Boolean(savedFeedConfig?.pinned) 62 + const isOwner = currentAccount?.did === list.creator.did 63 + 64 + const onPressShare = () => { 65 + const {rkey} = new AtUri(list.uri) 66 + const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`) 67 + shareUrl(url) 68 + } 69 + 70 + const onRemoveFromSavedFeeds = async () => { 71 + if (!savedFeedConfig) return 72 + try { 73 + await removeSavedFeed(savedFeedConfig) 74 + Toast.show(_(msg`Removed from your feeds`)) 75 + } catch (e) { 76 + Toast.show(_(msg`There was an issue contacting the server`), { 77 + type: 'error', 78 + }) 79 + logger.error('Failed to remove pinned list', {message: e}) 80 + } 81 + } 82 + 83 + const onPressEdit = () => { 84 + openModal({ 85 + name: 'create-or-edit-list', 86 + list, 87 + }) 88 + } 89 + 90 + const onPressDelete = async () => { 91 + await deleteList({uri: list.uri}) 92 + 93 + if (savedFeedConfig) { 94 + await removeSavedFeed(savedFeedConfig) 95 + } 96 + 97 + Toast.show(_(msg({message: 'List deleted', context: 'toast'}))) 98 + if (navigation.canGoBack()) { 99 + navigation.goBack() 100 + } else { 101 + navigation.navigate('Home') 102 + } 103 + } 104 + 105 + const onUnpinModList = async () => { 106 + try { 107 + if (!savedFeedConfig) return 108 + await removeSavedFeed(savedFeedConfig) 109 + Toast.show(_(msg`Unpinned list`)) 110 + } catch { 111 + Toast.show(_(msg`Failed to unpin list`), { 112 + type: 'error', 113 + }) 114 + } 115 + } 116 + 117 + const onUnsubscribeMute = async () => { 118 + try { 119 + await muteList({uri: list.uri, mute: false}) 120 + Toast.show(_(msg({message: 'List unmuted', context: 'toast'}))) 121 + logger.metric( 122 + 'moderation:unsubscribedFromList', 123 + {listType: 'mute'}, 124 + {statsig: true}, 125 + ) 126 + } catch { 127 + Toast.show( 128 + _( 129 + msg`There was an issue. Please check your internet connection and try again.`, 130 + ), 131 + ) 132 + } 133 + } 134 + 135 + const onUnsubscribeBlock = async () => { 136 + try { 137 + await blockList({uri: list.uri, block: false}) 138 + Toast.show(_(msg({message: 'List unblocked', context: 'toast'}))) 139 + logger.metric( 140 + 'moderation:unsubscribedFromList', 141 + {listType: 'block'}, 142 + {statsig: true}, 143 + ) 144 + } catch { 145 + Toast.show( 146 + _( 147 + msg`There was an issue. Please check your internet connection and try again.`, 148 + ), 149 + ) 150 + } 151 + } 152 + 153 + return ( 154 + <> 155 + <Menu.Root> 156 + <Menu.Trigger label={_(msg`More options`)}> 157 + {({props}) => ( 158 + <Button 159 + label={props.accessibilityLabel} 160 + testID="moreOptionsBtn" 161 + size="small" 162 + color="secondary" 163 + shape="round" 164 + {...props}> 165 + <ButtonIcon icon={DotGridIcon} /> 166 + </Button> 167 + )} 168 + </Menu.Trigger> 169 + <Menu.Outer> 170 + <Menu.Group> 171 + <Menu.Item 172 + label={isWeb ? _(msg`Copy link to list`) : _(msg`Share via...`)} 173 + onPress={onPressShare}> 174 + <Menu.ItemText> 175 + {isWeb ? ( 176 + <Trans>Copy link to list</Trans> 177 + ) : ( 178 + <Trans>Share via...</Trans> 179 + )} 180 + </Menu.ItemText> 181 + <Menu.ItemIcon 182 + position="right" 183 + icon={isWeb ? ChainLink : ShareIcon} 184 + /> 185 + </Menu.Item> 186 + {savedFeedConfig && ( 187 + <Menu.Item 188 + label={_(msg`Remove from my feeds`)} 189 + onPress={onRemoveFromSavedFeeds}> 190 + <Menu.ItemText> 191 + <Trans>Remove from my feeds</Trans> 192 + </Menu.ItemText> 193 + <Menu.ItemIcon position="right" icon={TrashIcon} /> 194 + </Menu.Item> 195 + )} 196 + </Menu.Group> 197 + 198 + <Menu.Divider /> 199 + 200 + {isOwner ? ( 201 + <Menu.Group> 202 + <Menu.Item 203 + label={_(msg`Edit list details`)} 204 + onPress={onPressEdit}> 205 + <Menu.ItemText> 206 + <Trans>Edit list details</Trans> 207 + </Menu.ItemText> 208 + <Menu.ItemIcon position="right" icon={PencilLineIcon} /> 209 + </Menu.Item> 210 + <Menu.Item 211 + label={_(msg`Delete list`)} 212 + onPress={deleteListPromptControl.open}> 213 + <Menu.ItemText> 214 + <Trans>Delete list</Trans> 215 + </Menu.ItemText> 216 + <Menu.ItemIcon position="right" icon={TrashIcon} /> 217 + </Menu.Item> 218 + </Menu.Group> 219 + ) : ( 220 + <Menu.Group> 221 + <Menu.Item 222 + label={_(msg`Report list`)} 223 + onPress={reportDialogControl.open}> 224 + <Menu.ItemText> 225 + <Trans>Report list</Trans> 226 + </Menu.ItemText> 227 + <Menu.ItemIcon position="right" icon={WarningIcon} /> 228 + </Menu.Item> 229 + </Menu.Group> 230 + )} 231 + 232 + {isModList && isPinned && ( 233 + <> 234 + <Menu.Divider /> 235 + <Menu.Group> 236 + <Menu.Item 237 + label={_(msg`Unpin moderation list`)} 238 + onPress={onUnpinModList}> 239 + <Menu.ItemText> 240 + <Trans>Unpin moderation list</Trans> 241 + </Menu.ItemText> 242 + <Menu.ItemIcon icon={PinIcon} /> 243 + </Menu.Item> 244 + </Menu.Group> 245 + </> 246 + )} 247 + 248 + {isCurateList && (isBlocking || isMuting) && ( 249 + <> 250 + <Menu.Divider /> 251 + <Menu.Group> 252 + {isBlocking && ( 253 + <Menu.Item 254 + label={_(msg`Unblock list`)} 255 + onPress={onUnsubscribeBlock}> 256 + <Menu.ItemText> 257 + <Trans>Unblock list</Trans> 258 + </Menu.ItemText> 259 + <Menu.ItemIcon icon={PersonCheckIcon} /> 260 + </Menu.Item> 261 + )} 262 + {isMuting && ( 263 + <Menu.Item 264 + label={_(msg`Unmute list`)} 265 + onPress={onUnsubscribeMute}> 266 + <Menu.ItemText> 267 + <Trans>Unmute list</Trans> 268 + </Menu.ItemText> 269 + <Menu.ItemIcon icon={UnmuteIcon} /> 270 + </Menu.Item> 271 + )} 272 + </Menu.Group> 273 + </> 274 + )} 275 + </Menu.Outer> 276 + </Menu.Root> 277 + 278 + <Prompt.Basic 279 + control={deleteListPromptControl} 280 + title={_(msg`Delete this list?`)} 281 + description={_( 282 + msg`If you delete this list, you won't be able to recover it.`, 283 + )} 284 + onConfirm={onPressDelete} 285 + confirmButtonCta={_(msg`Delete`)} 286 + confirmButtonColor="negative" 287 + /> 288 + 289 + <ReportDialog 290 + control={reportDialogControl} 291 + subject={{ 292 + ...list, 293 + $type: 'app.bsky.graph.defs#listView', 294 + }} 295 + /> 296 + </> 297 + ) 298 + }
+130
src/screens/ProfileList/components/SubscribeMenu.tsx
··· 1 + import {type AppBskyGraphDefs} from '@atproto/api' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 4 + 5 + import {logger} from '#/logger' 6 + import {useListBlockMutation, useListMuteMutation} from '#/state/queries/list' 7 + import {atoms as a} from '#/alf' 8 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 9 + import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute' 10 + import {PersonX_Stroke2_Corner0_Rounded as PersonXIcon} from '#/components/icons/Person' 11 + import {Loader} from '#/components/Loader' 12 + import * as Menu from '#/components/Menu' 13 + import * as Prompt from '#/components/Prompt' 14 + import * as Toast from '#/components/Toast' 15 + 16 + export function SubscribeMenu({list}: {list: AppBskyGraphDefs.ListView}) { 17 + const {_} = useLingui() 18 + const subscribeMutePromptControl = Prompt.usePromptControl() 19 + const subscribeBlockPromptControl = Prompt.usePromptControl() 20 + 21 + const {mutateAsync: muteList, isPending: isMutePending} = 22 + useListMuteMutation() 23 + const {mutateAsync: blockList, isPending: isBlockPending} = 24 + useListBlockMutation() 25 + 26 + const isPending = isMutePending || isBlockPending 27 + 28 + const onSubscribeMute = async () => { 29 + try { 30 + await muteList({uri: list.uri, mute: true}) 31 + Toast.show(_(msg({message: 'List muted', context: 'toast'}))) 32 + logger.metric( 33 + 'moderation:subscribedToList', 34 + {listType: 'mute'}, 35 + {statsig: true}, 36 + ) 37 + } catch { 38 + Toast.show( 39 + _( 40 + msg`There was an issue. Please check your internet connection and try again.`, 41 + ), 42 + {type: 'error'}, 43 + ) 44 + } 45 + } 46 + 47 + const onSubscribeBlock = async () => { 48 + try { 49 + await blockList({uri: list.uri, block: true}) 50 + Toast.show(_(msg({message: 'List blocked', context: 'toast'}))) 51 + logger.metric( 52 + 'moderation:subscribedToList', 53 + {listType: 'block'}, 54 + {statsig: true}, 55 + ) 56 + } catch { 57 + Toast.show( 58 + _( 59 + msg`There was an issue. Please check your internet connection and try again.`, 60 + ), 61 + {type: 'error'}, 62 + ) 63 + } 64 + } 65 + 66 + return ( 67 + <> 68 + <Menu.Root> 69 + <Menu.Trigger label={_(msg`Subscribe to this list`)}> 70 + {({props}) => ( 71 + <Button 72 + label={props.accessibilityLabel} 73 + testID="subscribeBtn" 74 + size="small" 75 + color="primary_subtle" 76 + style={[a.rounded_full]} 77 + disabled={isPending} 78 + {...props}> 79 + {isPending && <ButtonIcon icon={Loader} />} 80 + <ButtonText> 81 + <Trans>Subscribe</Trans> 82 + </ButtonText> 83 + </Button> 84 + )} 85 + </Menu.Trigger> 86 + <Menu.Outer showCancel> 87 + <Menu.Group> 88 + <Menu.Item 89 + label={_(msg`Mute accounts`)} 90 + onPress={subscribeMutePromptControl.open}> 91 + <Menu.ItemText> 92 + <Trans>Mute accounts</Trans> 93 + </Menu.ItemText> 94 + <Menu.ItemIcon position="right" icon={MuteIcon} /> 95 + </Menu.Item> 96 + <Menu.Item 97 + label={_(msg`Block accounts`)} 98 + onPress={subscribeBlockPromptControl.open}> 99 + <Menu.ItemText> 100 + <Trans>Block accounts</Trans> 101 + </Menu.ItemText> 102 + <Menu.ItemIcon position="right" icon={PersonXIcon} /> 103 + </Menu.Item> 104 + </Menu.Group> 105 + </Menu.Outer> 106 + </Menu.Root> 107 + 108 + <Prompt.Basic 109 + control={subscribeMutePromptControl} 110 + title={_(msg`Mute these accounts?`)} 111 + description={_( 112 + msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, 113 + )} 114 + onConfirm={onSubscribeMute} 115 + confirmButtonCta={_(msg`Mute list`)} 116 + /> 117 + 118 + <Prompt.Basic 119 + control={subscribeBlockPromptControl} 120 + title={_(msg`Block these accounts?`)} 121 + description={_( 122 + msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 123 + )} 124 + onConfirm={onSubscribeBlock} 125 + confirmButtonCta={_(msg`Block list`)} 126 + confirmButtonColor="negative" 127 + /> 128 + </> 129 + ) 130 + }
+296
src/screens/ProfileList/index.tsx
··· 1 + import {useCallback, useMemo, useRef} from 'react' 2 + import {View} from 'react-native' 3 + import {useAnimatedRef} from 'react-native-reanimated' 4 + import { 5 + AppBskyGraphDefs, 6 + AtUri, 7 + moderateUserList, 8 + type ModerationOpts, 9 + } from '@atproto/api' 10 + import {msg, Trans} from '@lingui/macro' 11 + import {useLingui} from '@lingui/react' 12 + import {useFocusEffect, useIsFocused} from '@react-navigation/native' 13 + import {useQueryClient} from '@tanstack/react-query' 14 + 15 + import {useOpenComposer} from '#/lib/hooks/useOpenComposer' 16 + import {useSetTitle} from '#/lib/hooks/useSetTitle' 17 + import {ComposeIcon2} from '#/lib/icons' 18 + import { 19 + type CommonNavigatorParams, 20 + type NativeStackScreenProps, 21 + } from '#/lib/routes/types' 22 + import {cleanError} from '#/lib/strings/errors' 23 + import {useModerationOpts} from '#/state/preferences/moderation-opts' 24 + import {useListQuery} from '#/state/queries/list' 25 + import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed' 26 + import { 27 + usePreferencesQuery, 28 + type UsePreferencesQueryResponse, 29 + } from '#/state/queries/preferences' 30 + import {useResolveUriQuery} from '#/state/queries/resolve-uri' 31 + import {truncateAndInvalidate} from '#/state/queries/util' 32 + import {useSession} from '#/state/session' 33 + import {useSetMinimalShellMode} from '#/state/shell' 34 + import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader' 35 + import {FAB} from '#/view/com/util/fab/FAB' 36 + import {type ListRef} from '#/view/com/util/List' 37 + import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' 38 + import {atoms as a, platform} from '#/alf' 39 + import {useDialogControl} from '#/components/Dialog' 40 + import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog' 41 + import * as Layout from '#/components/Layout' 42 + import {Loader} from '#/components/Loader' 43 + import * as Hider from '#/components/moderation/Hider' 44 + import {AboutSection} from './AboutSection' 45 + import {ErrorScreen} from './components/ErrorScreen' 46 + import {Header} from './components/Header' 47 + import {FeedSection} from './FeedSection' 48 + 49 + interface SectionRef { 50 + scrollToTop: () => void 51 + } 52 + 53 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'> 54 + export function ProfileListScreen(props: Props) { 55 + return ( 56 + <Layout.Screen testID="profileListScreen"> 57 + <ProfileListScreenInner {...props} /> 58 + </Layout.Screen> 59 + ) 60 + } 61 + 62 + function ProfileListScreenInner(props: Props) { 63 + const {_} = useLingui() 64 + const {name: handleOrDid, rkey} = props.route.params 65 + const {data: resolvedUri, error: resolveError} = useResolveUriQuery( 66 + AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(), 67 + ) 68 + const {data: preferences} = usePreferencesQuery() 69 + const {data: list, error: listError} = useListQuery(resolvedUri?.uri) 70 + const moderationOpts = useModerationOpts() 71 + 72 + if (resolveError) { 73 + return ( 74 + <> 75 + <Layout.Header.Outer> 76 + <Layout.Header.BackButton /> 77 + <Layout.Header.Content> 78 + <Layout.Header.TitleText> 79 + <Trans>Could not load list</Trans> 80 + </Layout.Header.TitleText> 81 + </Layout.Header.Content> 82 + <Layout.Header.Slot /> 83 + </Layout.Header.Outer> 84 + <Layout.Content centerContent> 85 + <ErrorScreen 86 + error={_( 87 + msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`, 88 + )} 89 + /> 90 + </Layout.Content> 91 + </> 92 + ) 93 + } 94 + if (listError) { 95 + return ( 96 + <> 97 + <Layout.Header.Outer> 98 + <Layout.Header.BackButton /> 99 + <Layout.Header.Content> 100 + <Layout.Header.TitleText> 101 + <Trans>Could not load list</Trans> 102 + </Layout.Header.TitleText> 103 + </Layout.Header.Content> 104 + <Layout.Header.Slot /> 105 + </Layout.Header.Outer> 106 + <Layout.Content centerContent> 107 + <ErrorScreen error={cleanError(listError)} /> 108 + </Layout.Content> 109 + </> 110 + ) 111 + } 112 + 113 + return resolvedUri && list && moderationOpts && preferences ? ( 114 + <ProfileListScreenLoaded 115 + {...props} 116 + uri={resolvedUri.uri} 117 + list={list} 118 + moderationOpts={moderationOpts} 119 + preferences={preferences} 120 + /> 121 + ) : ( 122 + <> 123 + <Layout.Header.Outer> 124 + <Layout.Header.BackButton /> 125 + <Layout.Header.Content /> 126 + <Layout.Header.Slot /> 127 + </Layout.Header.Outer> 128 + <Layout.Content 129 + centerContent 130 + contentContainerStyle={platform({ 131 + web: [a.mx_auto], 132 + native: [a.align_center], 133 + })}> 134 + <Loader size="2xl" /> 135 + </Layout.Content> 136 + </> 137 + ) 138 + } 139 + 140 + function ProfileListScreenLoaded({ 141 + route, 142 + uri, 143 + list, 144 + moderationOpts, 145 + preferences, 146 + }: Props & { 147 + uri: string 148 + list: AppBskyGraphDefs.ListView 149 + moderationOpts: ModerationOpts 150 + preferences: UsePreferencesQueryResponse 151 + }) { 152 + const {_} = useLingui() 153 + const queryClient = useQueryClient() 154 + const {openComposer} = useOpenComposer() 155 + const setMinimalShellMode = useSetMinimalShellMode() 156 + const {currentAccount} = useSession() 157 + const {rkey} = route.params 158 + const feedSectionRef = useRef<SectionRef>(null) 159 + const aboutSectionRef = useRef<SectionRef>(null) 160 + const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST 161 + const isScreenFocused = useIsFocused() 162 + const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1 163 + const isOwner = currentAccount?.did === list.creator.did 164 + const scrollElRef = useAnimatedRef() 165 + const addUserDialogControl = useDialogControl() 166 + const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)] 167 + 168 + const moderation = useMemo(() => { 169 + return moderateUserList(list, moderationOpts) 170 + }, [list, moderationOpts]) 171 + 172 + useSetTitle(isHidden ? _(msg`List Hidden`) : list.name) 173 + 174 + useFocusEffect( 175 + useCallback(() => { 176 + setMinimalShellMode(false) 177 + }, [setMinimalShellMode]), 178 + ) 179 + 180 + const onChangeMembers = () => { 181 + if (isCurateList) { 182 + truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`)) 183 + } 184 + } 185 + 186 + const onCurrentPageSelected = useCallback( 187 + (index: number) => { 188 + if (index === 0) { 189 + feedSectionRef.current?.scrollToTop() 190 + } else if (index === 1) { 191 + aboutSectionRef.current?.scrollToTop() 192 + } 193 + }, 194 + [feedSectionRef], 195 + ) 196 + 197 + const renderHeader = useCallback(() => { 198 + return <Header rkey={rkey} list={list} preferences={preferences} /> 199 + }, [rkey, list, preferences]) 200 + 201 + if (isCurateList) { 202 + return ( 203 + <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 204 + <Hider.Mask> 205 + <ListHiddenScreen list={list} preferences={preferences} /> 206 + </Hider.Mask> 207 + <Hider.Content> 208 + <View style={[a.util_screen_outer]}> 209 + <PagerWithHeader 210 + items={sectionTitlesCurate} 211 + isHeaderReady={true} 212 + renderHeader={renderHeader} 213 + onCurrentPageSelected={onCurrentPageSelected}> 214 + {({headerHeight, scrollElRef, isFocused}) => ( 215 + <FeedSection 216 + ref={feedSectionRef} 217 + feed={`list|${uri}`} 218 + scrollElRef={scrollElRef as ListRef} 219 + headerHeight={headerHeight} 220 + isFocused={isScreenFocused && isFocused} 221 + isOwner={isOwner} 222 + onPressAddUser={addUserDialogControl.open} 223 + /> 224 + )} 225 + {({headerHeight, scrollElRef}) => ( 226 + <AboutSection 227 + ref={aboutSectionRef} 228 + scrollElRef={scrollElRef as ListRef} 229 + list={list} 230 + onPressAddUser={addUserDialogControl.open} 231 + headerHeight={headerHeight} 232 + /> 233 + )} 234 + </PagerWithHeader> 235 + <FAB 236 + testID="composeFAB" 237 + onPress={() => openComposer({})} 238 + icon={ 239 + <ComposeIcon2 240 + strokeWidth={1.5} 241 + size={29} 242 + style={{color: 'white'}} 243 + /> 244 + } 245 + accessibilityRole="button" 246 + accessibilityLabel={_(msg`New post`)} 247 + accessibilityHint="" 248 + /> 249 + </View> 250 + <ListAddRemoveUsersDialog 251 + control={addUserDialogControl} 252 + list={list} 253 + onChange={onChangeMembers} 254 + /> 255 + </Hider.Content> 256 + </Hider.Outer> 257 + ) 258 + } 259 + return ( 260 + <Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}> 261 + <Hider.Mask> 262 + <ListHiddenScreen list={list} preferences={preferences} /> 263 + </Hider.Mask> 264 + <Hider.Content> 265 + <View style={[a.util_screen_outer]}> 266 + <Layout.Center>{renderHeader()}</Layout.Center> 267 + <AboutSection 268 + list={list} 269 + scrollElRef={scrollElRef as ListRef} 270 + onPressAddUser={addUserDialogControl.open} 271 + headerHeight={0} 272 + /> 273 + <FAB 274 + testID="composeFAB" 275 + onPress={() => openComposer({})} 276 + icon={ 277 + <ComposeIcon2 278 + strokeWidth={1.5} 279 + size={29} 280 + style={{color: 'white'}} 281 + /> 282 + } 283 + accessibilityRole="button" 284 + accessibilityLabel={_(msg`New post`)} 285 + accessibilityHint="" 286 + /> 287 + </View> 288 + <ListAddRemoveUsersDialog 289 + control={addUserDialogControl} 290 + list={list} 291 + onChange={onChangeMembers} 292 + /> 293 + </Hider.Content> 294 + </Hider.Outer> 295 + ) 296 + }
+415
src/screens/SavedFeeds.tsx
··· 1 + import {useCallback, useState} from 'react' 2 + import {View} from 'react-native' 3 + import Animated, {LinearTransition} from 'react-native-reanimated' 4 + import {type AppBskyActorDefs} from '@atproto/api' 5 + import {TID} from '@atproto/common-web' 6 + import {msg, Trans} from '@lingui/macro' 7 + import {useLingui} from '@lingui/react' 8 + import {useFocusEffect} from '@react-navigation/native' 9 + import {useNavigation} from '@react-navigation/native' 10 + import {type NativeStackScreenProps} from '@react-navigation/native-stack' 11 + 12 + import {RECOMMENDED_SAVED_FEEDS, TIMELINE_SAVED_FEED} from '#/lib/constants' 13 + import {useHaptics} from '#/lib/haptics' 14 + import { 15 + type CommonNavigatorParams, 16 + type NavigationProp, 17 + } from '#/lib/routes/types' 18 + import {logger} from '#/logger' 19 + import { 20 + useOverwriteSavedFeedsMutation, 21 + usePreferencesQuery, 22 + } from '#/state/queries/preferences' 23 + import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 24 + import {useSetMinimalShellMode} from '#/state/shell' 25 + import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' 26 + import * as Toast from '#/view/com/util/Toast' 27 + import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' 28 + import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' 29 + import {atoms as a, useBreakpoints, useTheme} from '#/alf' 30 + import {Admonition} from '#/components/Admonition' 31 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 32 + import { 33 + ArrowBottom_Stroke2_Corner0_Rounded as ArrowDownIcon, 34 + ArrowTop_Stroke2_Corner0_Rounded as ArrowUpIcon, 35 + } from '#/components/icons/Arrow' 36 + import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 37 + import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 38 + import {Pin_Filled_Corner0_Rounded as PinIcon} from '#/components/icons/Pin' 39 + import {Trash_Stroke2_Corner0_Rounded as TrashIcon} from '#/components/icons/Trash' 40 + import * as Layout from '#/components/Layout' 41 + import {InlineLinkText} from '#/components/Link' 42 + import {Loader} from '#/components/Loader' 43 + import {Text} from '#/components/Typography' 44 + 45 + type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 46 + export function SavedFeeds({}: Props) { 47 + const {data: preferences} = usePreferencesQuery() 48 + if (!preferences) { 49 + return <View /> 50 + } 51 + return <SavedFeedsInner preferences={preferences} /> 52 + } 53 + 54 + function SavedFeedsInner({ 55 + preferences, 56 + }: { 57 + preferences: UsePreferencesQueryResponse 58 + }) { 59 + const t = useTheme() 60 + const {_} = useLingui() 61 + const {gtMobile} = useBreakpoints() 62 + const setMinimalShellMode = useSetMinimalShellMode() 63 + const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} = 64 + useOverwriteSavedFeedsMutation() 65 + const navigation = useNavigation<NavigationProp>() 66 + 67 + /* 68 + * Use optimistic data if exists and no error, otherwise fallback to remote 69 + * data 70 + */ 71 + const [currentFeeds, setCurrentFeeds] = useState( 72 + () => preferences.savedFeeds || [], 73 + ) 74 + const hasUnsavedChanges = currentFeeds !== preferences.savedFeeds 75 + const pinnedFeeds = currentFeeds.filter(f => f.pinned) 76 + const unpinnedFeeds = currentFeeds.filter(f => !f.pinned) 77 + const noSavedFeedsOfAnyType = pinnedFeeds.length + unpinnedFeeds.length === 0 78 + const noFollowingFeed = 79 + currentFeeds.every(f => f.type !== 'timeline') && !noSavedFeedsOfAnyType 80 + 81 + useFocusEffect( 82 + useCallback(() => { 83 + setMinimalShellMode(false) 84 + }, [setMinimalShellMode]), 85 + ) 86 + 87 + const onSaveChanges = async () => { 88 + try { 89 + await overwriteSavedFeeds(currentFeeds) 90 + Toast.show(_(msg({message: 'Feeds updated!', context: 'toast'}))) 91 + if (navigation.canGoBack()) { 92 + navigation.goBack() 93 + } else { 94 + navigation.navigate('Feeds') 95 + } 96 + } catch (e) { 97 + Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 98 + logger.error('Failed to toggle pinned feed', {message: e}) 99 + } 100 + } 101 + 102 + return ( 103 + <Layout.Screen> 104 + <Layout.Header.Outer> 105 + <Layout.Header.BackButton /> 106 + <Layout.Header.Content align="left"> 107 + <Layout.Header.TitleText> 108 + <Trans>Feeds</Trans> 109 + </Layout.Header.TitleText> 110 + </Layout.Header.Content> 111 + <Button 112 + testID="saveChangesBtn" 113 + size="small" 114 + color={hasUnsavedChanges ? 'primary' : 'secondary'} 115 + onPress={onSaveChanges} 116 + label={_(msg`Save changes`)} 117 + disabled={isOverwritePending || !hasUnsavedChanges}> 118 + <ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} /> 119 + <ButtonText> 120 + {gtMobile ? <Trans>Save changes</Trans> : <Trans>Save</Trans>} 121 + </ButtonText> 122 + </Button> 123 + </Layout.Header.Outer> 124 + 125 + <Layout.Content> 126 + {noSavedFeedsOfAnyType && ( 127 + <View style={[t.atoms.border_contrast_low, a.border_b]}> 128 + <NoSavedFeedsOfAnyType 129 + onAddRecommendedFeeds={() => 130 + setCurrentFeeds( 131 + RECOMMENDED_SAVED_FEEDS.map(f => ({ 132 + ...f, 133 + id: TID.nextStr(), 134 + })), 135 + ) 136 + } 137 + /> 138 + </View> 139 + )} 140 + 141 + <SectionHeaderText> 142 + <Trans>Pinned Feeds</Trans> 143 + </SectionHeaderText> 144 + 145 + {preferences ? ( 146 + !pinnedFeeds.length ? ( 147 + <View style={[a.flex_1, a.p_lg]}> 148 + <Admonition type="info"> 149 + <Trans>You don't have any pinned feeds.</Trans> 150 + </Admonition> 151 + </View> 152 + ) : ( 153 + pinnedFeeds.map(f => ( 154 + <ListItem 155 + key={f.id} 156 + feed={f} 157 + isPinned 158 + currentFeeds={currentFeeds} 159 + setCurrentFeeds={setCurrentFeeds} 160 + preferences={preferences} 161 + /> 162 + )) 163 + ) 164 + ) : ( 165 + <View style={[a.w_full, a.py_2xl, a.align_center]}> 166 + <Loader size="xl" /> 167 + </View> 168 + )} 169 + 170 + {noFollowingFeed && ( 171 + <View style={[t.atoms.border_contrast_low, a.border_b]}> 172 + <NoFollowingFeed 173 + onAddFeed={() => 174 + setCurrentFeeds(feeds => [ 175 + ...feeds, 176 + {...TIMELINE_SAVED_FEED, id: TID.next().toString()}, 177 + ]) 178 + } 179 + /> 180 + </View> 181 + )} 182 + 183 + <SectionHeaderText> 184 + <Trans>Saved Feeds</Trans> 185 + </SectionHeaderText> 186 + 187 + {preferences ? ( 188 + !unpinnedFeeds.length ? ( 189 + <View style={[a.flex_1, a.p_lg]}> 190 + <Admonition type="info"> 191 + <Trans>You don't have any saved feeds.</Trans> 192 + </Admonition> 193 + </View> 194 + ) : ( 195 + unpinnedFeeds.map(f => ( 196 + <ListItem 197 + key={f.id} 198 + feed={f} 199 + isPinned={false} 200 + currentFeeds={currentFeeds} 201 + setCurrentFeeds={setCurrentFeeds} 202 + preferences={preferences} 203 + /> 204 + )) 205 + ) 206 + ) : ( 207 + <View style={[a.w_full, a.py_2xl, a.align_center]}> 208 + <Loader size="xl" /> 209 + </View> 210 + )} 211 + 212 + <View style={[a.px_lg, a.py_xl]}> 213 + <Text 214 + style={[a.text_sm, t.atoms.text_contrast_medium, a.leading_snug]}> 215 + <Trans> 216 + Feeds are custom algorithms that users build with a little coding 217 + expertise.{' '} 218 + <InlineLinkText 219 + to="https://github.com/bluesky-social/feed-generator" 220 + label={_(msg`See this guide`)} 221 + disableMismatchWarning 222 + style={[a.leading_snug]}> 223 + See this guide 224 + </InlineLinkText>{' '} 225 + for more information. 226 + </Trans> 227 + </Text> 228 + </View> 229 + </Layout.Content> 230 + </Layout.Screen> 231 + ) 232 + } 233 + 234 + function ListItem({ 235 + feed, 236 + isPinned, 237 + currentFeeds, 238 + setCurrentFeeds, 239 + }: { 240 + feed: AppBskyActorDefs.SavedFeed 241 + isPinned: boolean 242 + currentFeeds: AppBskyActorDefs.SavedFeed[] 243 + setCurrentFeeds: React.Dispatch<AppBskyActorDefs.SavedFeed[]> 244 + preferences: UsePreferencesQueryResponse 245 + }) { 246 + const {_} = useLingui() 247 + const t = useTheme() 248 + const playHaptic = useHaptics() 249 + const feedUri = feed.value 250 + 251 + const onTogglePinned = async () => { 252 + playHaptic() 253 + setCurrentFeeds( 254 + currentFeeds.map(f => 255 + f.id === feed.id ? {...feed, pinned: !feed.pinned} : f, 256 + ), 257 + ) 258 + } 259 + 260 + const onPressUp = async () => { 261 + if (!isPinned) return 262 + 263 + const nextFeeds = currentFeeds.slice() 264 + const ids = currentFeeds.map(f => f.id) 265 + const index = ids.indexOf(feed.id) 266 + const nextIndex = index - 1 267 + 268 + if (index === -1 || index === 0) return 269 + ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 270 + nextFeeds[nextIndex], 271 + nextFeeds[index], 272 + ] 273 + 274 + setCurrentFeeds(nextFeeds) 275 + } 276 + 277 + const onPressDown = async () => { 278 + if (!isPinned) return 279 + 280 + const nextFeeds = currentFeeds.slice() 281 + const ids = currentFeeds.map(f => f.id) 282 + const index = ids.indexOf(feed.id) 283 + const nextIndex = index + 1 284 + 285 + if (index === -1 || index >= nextFeeds.filter(f => f.pinned).length - 1) 286 + return 287 + ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 288 + nextFeeds[nextIndex], 289 + nextFeeds[index], 290 + ] 291 + 292 + setCurrentFeeds(nextFeeds) 293 + } 294 + 295 + const onPressRemove = async () => { 296 + playHaptic() 297 + setCurrentFeeds(currentFeeds.filter(f => f.id !== feed.id)) 298 + } 299 + 300 + return ( 301 + <Animated.View 302 + style={[a.flex_row, a.border_b, t.atoms.border_contrast_low]} 303 + layout={LinearTransition.duration(100)}> 304 + {feed.type === 'timeline' ? ( 305 + <FollowingFeedCard /> 306 + ) : ( 307 + <FeedSourceCard 308 + key={feedUri} 309 + feedUri={feedUri} 310 + style={[isPinned && a.pr_sm]} 311 + showMinimalPlaceholder 312 + hideTopBorder={true} 313 + /> 314 + )} 315 + <View style={[a.pr_lg, a.flex_row, a.align_center, a.gap_sm]}> 316 + {isPinned ? ( 317 + <> 318 + <Button 319 + testID={`feed-${feed.type}-moveUp`} 320 + label={_(msg`Move feed up`)} 321 + onPress={onPressUp} 322 + size="small" 323 + color="secondary" 324 + shape="square"> 325 + <ButtonIcon icon={ArrowUpIcon} /> 326 + </Button> 327 + <Button 328 + testID={`feed-${feed.type}-moveDown`} 329 + label={_(msg`Move feed down`)} 330 + onPress={onPressDown} 331 + size="small" 332 + color="secondary" 333 + shape="square"> 334 + <ButtonIcon icon={ArrowDownIcon} /> 335 + </Button> 336 + </> 337 + ) : ( 338 + <Button 339 + testID={`feed-${feedUri}-toggleSave`} 340 + label={_(msg`Remove from my feeds`)} 341 + onPress={onPressRemove} 342 + size="small" 343 + color="secondary" 344 + variant="ghost" 345 + shape="square"> 346 + <ButtonIcon icon={TrashIcon} /> 347 + </Button> 348 + )} 349 + <Button 350 + testID={`feed-${feed.type}-togglePin`} 351 + label={isPinned ? _(msg`Unpin feed`) : _(msg`Pin feed`)} 352 + onPress={onTogglePinned} 353 + size="small" 354 + color={isPinned ? 'primary_subtle' : 'secondary'} 355 + shape="square"> 356 + <ButtonIcon icon={PinIcon} /> 357 + </Button> 358 + </View> 359 + </Animated.View> 360 + ) 361 + } 362 + 363 + function SectionHeaderText({children}: {children: React.ReactNode}) { 364 + const t = useTheme() 365 + // eslint-disable-next-line bsky-internal/avoid-unwrapped-text 366 + return ( 367 + <View 368 + style={[ 369 + a.flex_row, 370 + a.flex_1, 371 + a.px_lg, 372 + a.pt_2xl, 373 + a.pb_md, 374 + a.border_b, 375 + t.atoms.border_contrast_low, 376 + ]}> 377 + <Text style={[a.text_xl, a.font_heavy, a.leading_snug]}>{children}</Text> 378 + </View> 379 + ) 380 + } 381 + 382 + function FollowingFeedCard() { 383 + const t = useTheme() 384 + return ( 385 + <View style={[a.flex_row, a.align_center, a.flex_1, a.p_lg]}> 386 + <View 387 + style={[ 388 + a.align_center, 389 + a.justify_center, 390 + a.rounded_sm, 391 + a.mr_md, 392 + { 393 + width: 36, 394 + height: 36, 395 + backgroundColor: t.palette.primary_500, 396 + }, 397 + ]}> 398 + <FilterTimeline 399 + style={[ 400 + { 401 + width: 22, 402 + height: 22, 403 + }, 404 + ]} 405 + fill={t.palette.white} 406 + /> 407 + </View> 408 + <View style={[a.flex_1, a.flex_row, a.gap_sm, a.align_center]}> 409 + <Text style={[a.text_sm, a.font_bold, a.leading_snug]}> 410 + <Trans context="feed-name">Following</Trans> 411 + </Text> 412 + </View> 413 + </View> 414 + ) 415 + }
+1 -1
src/screens/Settings/AppIconSettings/AppIconImage.tsx
··· 1 1 import {Image} from 'expo-image' 2 2 3 - import {AppIconSet} from '#/screens/Settings/AppIconSettings/types' 3 + import {type AppIconSet} from '#/screens/Settings/AppIconSettings/types' 4 4 import {atoms as a, platform, useTheme} from '#/alf' 5 5 6 6 export function AppIconImage({
+1 -1
src/screens/Settings/components/AddAppPasswordDialog.tsx
··· 8 8 SlideInRight, 9 9 SlideOutLeft, 10 10 } from 'react-native-reanimated' 11 - import {ComAtprotoServerCreateAppPassword} from '@atproto/api' 11 + import {type ComAtprotoServerCreateAppPassword} from '@atproto/api' 12 12 import {msg, Trans} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react' 14 14 import {useMutation} from '@tanstack/react-query'
+2 -2
src/screens/Settings/components/CopyButton.tsx
··· 1 1 import {useCallback, useEffect, useState} from 'react' 2 - import {GestureResponderEvent, View} from 'react-native' 2 + import {type GestureResponderEvent, View} from 'react-native' 3 3 import Animated, { 4 4 FadeOutUp, 5 5 useReducedMotion, ··· 9 9 import {Trans} from '@lingui/macro' 10 10 11 11 import {atoms as a, useTheme} from '#/alf' 12 - import {Button, ButtonProps} from '#/components/Button' 12 + import {Button, type ButtonProps} from '#/components/Button' 13 13 import {Text} from '#/components/Typography' 14 14 15 15 export function CopyButton({
+1 -1
src/screens/Settings/components/DeactivateAccountDialog.tsx
··· 7 7 import {useAgent, useSessionApi} from '#/state/session' 8 8 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 9 9 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 10 - import {DialogOuterProps} from '#/components/Dialog' 10 + import {type DialogOuterProps} from '#/components/Dialog' 11 11 import {Divider} from '#/components/Divider' 12 12 import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 13 13 import {Loader} from '#/components/Loader'
+6 -7
src/screens/Settings/components/ExportCarDialog.tsx
··· 1 - import React from 'react' 1 + import {useCallback, useState} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' ··· 18 18 export function ExportCarDialog({ 19 19 control, 20 20 }: { 21 - control: Dialog.DialogOuterProps['control'] 21 + control: Dialog.DialogControlProps 22 22 }) { 23 23 const {_} = useLingui() 24 24 const t = useTheme() 25 25 const agent = useAgent() 26 - const [loading, setLoading] = React.useState(false) 26 + const [loading, setLoading] = useState(false) 27 27 28 - const download = React.useCallback(async () => { 28 + const download = useCallback(async () => { 29 29 if (!agent.session) { 30 30 return // shouldnt ever happen 31 31 } ··· 52 52 }, [_, control, agent]) 53 53 54 54 return ( 55 - <Dialog.Outer control={control}> 55 + <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 56 56 <Dialog.Handle /> 57 57 <Dialog.ScrollableInner 58 58 accessibilityDescribedBy="dialog-description" ··· 63 63 </Text> 64 64 <Text 65 65 nativeID="dialog-description" 66 - style={[a.text_sm, a.leading_normal, t.atoms.text_contrast_high]}> 66 + style={[a.text_sm, a.leading_snug, t.atoms.text_contrast_high]}> 67 67 <Trans> 68 68 Your account repository, containing all public data records, can 69 69 be downloaded as a "CAR" file. This file does not include media ··· 73 73 </Text> 74 74 75 75 <Button 76 - variant="solid" 77 76 color="primary" 78 77 size="large" 79 78 label={_(msg`Download CAR file`)}
+1 -1
src/screens/Settings/components/PwiOptOut.tsx
··· 1 1 import React from 'react' 2 2 import {View} from 'react-native' 3 - import {$Typed, ComAtprotoLabelDefs} from '@atproto/api' 3 + import {type $Typed, ComAtprotoLabelDefs} from '@atproto/api' 4 4 import {msg, Trans} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6
+1 -1
src/screens/Signup/StepCaptcha/CaptchaWebView.tsx
··· 31 31 onError: (error: unknown) => void 32 32 }) { 33 33 const startedAt = useRef(Date.now()) 34 - const successTo = useRef<NodeJS.Timeout>() 34 + const successTo = useRef<NodeJS.Timeout>(undefined) 35 35 36 36 useEffect(() => { 37 37 return () => {
+1 -1
src/screens/Signup/StepInfo/Policies.tsx
··· 72 72 ) 73 73 } 74 74 75 - let els: ReactElement 75 + let els: ReactElement<any> 76 76 if (tos && pp) { 77 77 els = ( 78 78 <Trans>
+1 -1
src/screens/Signup/StepInfo/index.tsx
··· 60 60 61 61 const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false) 62 62 63 - const tldtsRef = React.useRef<typeof tldts>() 63 + const tldtsRef = React.useRef<typeof tldts>(undefined) 64 64 React.useEffect(() => { 65 65 // @ts-expect-error - valid path 66 66 import('tldts/dist/index.cjs.min.js').then(tldts => {
+2 -2
src/screens/StarterPack/Wizard/StepFeeds.tsx
··· 1 1 import {useState} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 4 - import {AppBskyFeedDefs, ModerationOpts} from '@atproto/api' 4 + import {type AppBskyFeedDefs, type ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 7 import {DISCOVER_FEED_URI} from '#/lib/constants'
+3 -3
src/screens/StarterPack/Wizard/StepProfiles.tsx
··· 1 1 import {useState} from 'react' 2 - import {ListRenderItemInfo, View} from 'react-native' 2 + import {type ListRenderItemInfo, View} from 'react-native' 3 3 import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' 4 - import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' 4 + import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api' 5 5 import {Trans} from '@lingui/macro' 6 6 7 7 import {isNative} from '#/platform/detection' ··· 16 16 import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' 17 17 import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' 18 18 import {Text} from '#/components/Typography' 19 - import * as bsky from '#/types/bsky' 19 + import type * as bsky from '#/types/bsky' 20 20 21 21 function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { 22 22 return item?.did ?? ''
+18 -9
src/screens/Takendown.tsx
··· 9 9 import {useMutation} from '@tanstack/react-query' 10 10 import Graphemer from 'graphemer' 11 11 12 - import {MAX_REPORT_REASON_GRAPHEME_LENGTH} from '#/lib/constants' 12 + import { 13 + BLUESKY_MOD_SERVICE_HEADERS, 14 + MAX_REPORT_REASON_GRAPHEME_LENGTH, 15 + } from '#/lib/constants' 13 16 import {useEnableKeyboardController} from '#/lib/hooks/useEnableKeyboardController' 14 17 import {cleanError} from '#/lib/strings/errors' 15 18 import {isIOS, isWeb} from '#/platform/detection' ··· 49 52 } = useMutation({ 50 53 mutationFn: async (appealText: string) => { 51 54 if (!currentAccount) throw new Error('No session') 52 - await agent.com.atproto.moderation.createReport({ 53 - reasonType: ComAtprotoModerationDefs.REASONAPPEAL, 54 - subject: { 55 - $type: 'com.atproto.admin.defs#repoRef', 56 - did: currentAccount.did, 57 - } satisfies ComAtprotoAdminDefs.RepoRef, 58 - reason: appealText, 59 - }) 55 + await agent.com.atproto.moderation.createReport( 56 + { 57 + reasonType: ComAtprotoModerationDefs.REASONAPPEAL, 58 + subject: { 59 + $type: 'com.atproto.admin.defs#repoRef', 60 + did: currentAccount.did, 61 + } satisfies ComAtprotoAdminDefs.RepoRef, 62 + reason: appealText, 63 + }, 64 + { 65 + encoding: 'application/json', 66 + headers: BLUESKY_MOD_SERVICE_HEADERS, 67 + }, 68 + ) 60 69 }, 61 70 onSuccess: () => setReason(''), 62 71 })
+4 -4
src/screens/VideoFeed/components/Header.tsx
··· 1 1 import {useCallback} from 'react' 2 - import {GestureResponderEvent, View} from 'react-native' 2 + import {type GestureResponderEvent, View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 import {useNavigation} from '@react-navigation/native' 6 6 7 7 import {HITSLOP_30} from '#/lib/constants' 8 - import {NavigationProp} from '#/lib/routes/types' 8 + import {type NavigationProp} from '#/lib/routes/types' 9 9 import {sanitizeHandle} from '#/lib/strings/handles' 10 10 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 11 11 import {UserAvatar} from '#/view/com/util/UserAvatar' 12 - import {VideoFeedSourceContext} from '#/screens/VideoFeed/types' 12 + import {type VideoFeedSourceContext} from '#/screens/VideoFeed/types' 13 13 import {atoms as a, useBreakpoints} from '#/alf' 14 - import {Button, ButtonProps} from '#/components/Button' 14 + import {Button, type ButtonProps} from '#/components/Button' 15 15 import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow' 16 16 import * as Layout from '#/components/Layout' 17 17 import {BUTTON_VISUAL_ALIGNMENT_OFFSET} from '#/components/Layout/const'
+1 -1
src/screens/VideoFeed/types.ts
··· 1 - import {AuthorFilter} from '#/state/queries/post-feed' 1 + import {type AuthorFilter} from '#/state/queries/post-feed' 2 2 3 3 /** 4 4 * Kind of like `FeedDescriptor` but not
+5 -5
src/state/messages/convo/util.ts
··· 1 1 import { 2 - ConvoState, 3 - ConvoStateBackgrounded, 4 - ConvoStateDisabled, 5 - ConvoStateReady, 6 - ConvoStateSuspended, 2 + type ConvoState, 3 + type ConvoStateBackgrounded, 4 + type ConvoStateDisabled, 5 + type ConvoStateReady, 6 + type ConvoStateSuspended, 7 7 ConvoStatus, 8 8 } from './types' 9 9
+1 -1
src/state/messages/events/types.ts
··· 1 - import {BskyAgent, ChatBskyConvoGetLog} from '@atproto/api' 1 + import {type BskyAgent, type ChatBskyConvoGetLog} from '@atproto/api' 2 2 3 3 export type MessagesEventBusParams = { 4 4 agent: BskyAgent
+1 -1
src/state/messages/index.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 3 import {CurrentConvoIdProvider} from '#/state/messages/current-convo-id' 4 4 import {MessagesEventBusProvider} from '#/state/messages/events'
+2 -2
src/state/persisted/index.web.ts
··· 4 4 import {logger} from '#/logger' 5 5 import { 6 6 defaults, 7 - Schema, 7 + type Schema, 8 8 tryParse, 9 9 tryStringify, 10 10 } from '#/state/persisted/schema' 11 - import {PersistedApi} from './types' 11 + import {type PersistedApi} from './types' 12 12 import {normalizeData} from './util' 13 13 14 14 export type {PersistedAccount, Schema} from '#/state/persisted/schema'
+1 -1
src/state/persisted/types.ts
··· 1 - import type {Schema} from './schema' 1 + import {type Schema} from './schema' 2 2 3 3 export type PersistedApi = { 4 4 init(): Promise<void>
+1 -1
src/state/persisted/util.ts
··· 2 2 3 3 import {dedupArray} from '#/lib/functions' 4 4 import {logger} from '#/logger' 5 - import {Schema} from '#/state/persisted/schema' 5 + import {type Schema} from '#/state/persisted/schema' 6 6 7 7 export function normalizeData(data: Schema) { 8 8 const next = {...data}
+1 -1
src/state/preferences/index.tsx
··· 1 - import React from 'react' 1 + import type React from 'react' 2 2 3 3 import {Provider as AltTextRequiredProvider} from './alt-text-required' 4 4 import {Provider as AutoplayProvider} from './autoplay'
+5 -1
src/state/queries/actor-autocomplete.ts
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs, moderateProfile, ModerationOpts} from '@atproto/api' 2 + import { 3 + type AppBskyActorDefs, 4 + moderateProfile, 5 + type ModerationOpts, 6 + } from '@atproto/api' 3 7 import {keepPreviousData, useQuery, useQueryClient} from '@tanstack/react-query' 4 8 5 9 import {isJustAMute, moduiContainsHideableOffense} from '#/lib/moderation'
+1 -1
src/state/queries/app-passwords.ts
··· 1 - import {ComAtprotoServerCreateAppPassword} from '@atproto/api' 1 + import {type ComAtprotoServerCreateAppPassword} from '@atproto/api' 2 2 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 3 3 4 4 import {STALE} from '#/state/queries'
+1 -1
src/state/queries/invites.ts
··· 1 - import {ComAtprotoServerDefs} from '@atproto/api' 1 + import {type ComAtprotoServerDefs} from '@atproto/api' 2 2 import {useQuery} from '@tanstack/react-query' 3 3 4 4 import {cleanError} from '#/lib/strings/errors'
+7 -4
src/state/queries/known-followers.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetKnownFollowers} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyGraphGetKnownFollowers, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+4 -4
src/state/queries/my-blocked-accounts.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetBlocks} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetBlocks} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+2 -2
src/state/queries/my-lists.ts
··· 1 - import {AppBskyGraphDefs} from '@atproto/api' 2 - import {QueryClient, useQuery} from '@tanstack/react-query' 1 + import {type AppBskyGraphDefs} from '@atproto/api' 2 + import {type QueryClient, useQuery} from '@tanstack/react-query' 3 3 4 4 import {accumulate} from '#/lib/async/accumulate' 5 5 import {STALE} from '#/state/queries'
+4 -4
src/state/queries/my-muted-accounts.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetMutes} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetMutes} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+1 -1
src/state/queries/nuxs/types.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import {type AppBskyActorDefs} from '@atproto/api' 2 2 3 3 export type Data = Record<string, unknown> | undefined 4 4
+3 -3
src/state/queries/nuxs/util.ts
··· 1 - import {AppBskyActorDefs, nuxSchema} from '@atproto/api' 1 + import {type AppBskyActorDefs, nuxSchema} from '@atproto/api' 2 2 3 3 import { 4 - AppNux, 5 - Nux, 4 + type AppNux, 5 + type Nux, 6 6 nuxNames, 7 7 NuxSchemas, 8 8 } from '#/state/queries/nuxs/definitions'
+1 -1
src/state/queries/post-interaction-settings.ts
··· 1 - import {AppBskyActorDefs} from '@atproto/api' 1 + import {type AppBskyActorDefs} from '@atproto/api' 2 2 import {useMutation, useQueryClient} from '@tanstack/react-query' 3 3 4 4 import {preferencesQueryKey} from '#/state/queries/preferences'
+4 -4
src/state/queries/post-liked-by.ts
··· 1 - import {AppBskyActorDefs, AppBskyFeedGetLikes} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyFeedGetLikes} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+6 -6
src/state/queries/post-quotes.ts
··· 1 1 import { 2 - AppBskyActorDefs, 2 + type AppBskyActorDefs, 3 3 AppBskyEmbedRecord, 4 - AppBskyFeedDefs, 5 - AppBskyFeedGetQuotes, 4 + type AppBskyFeedDefs, 5 + type AppBskyFeedGetQuotes, 6 6 AtUri, 7 7 } from '@atproto/api' 8 8 import { 9 - InfiniteData, 10 - QueryClient, 11 - QueryKey, 9 + type InfiniteData, 10 + type QueryClient, 11 + type QueryKey, 12 12 useInfiniteQuery, 13 13 } from '@tanstack/react-query' 14 14
+7 -4
src/state/queries/post-reposted-by.ts
··· 1 - import {AppBskyActorDefs, AppBskyFeedGetRepostedBy} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyFeedGetRepostedBy, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+1 -1
src/state/queries/postgate/index.ts
··· 173 173 const agent = useAgent() 174 174 const queryClient = useQueryClient() 175 175 const getPosts = useGetPosts() 176 - const prevEmbed = React.useRef<AppBskyFeedDefs.PostView['embed']>() 176 + const prevEmbed = React.useRef<AppBskyFeedDefs.PostView['embed']>(undefined) 177 177 178 178 return useMutation({ 179 179 mutationFn: async ({
+3 -3
src/state/queries/preferences/types.ts
··· 1 1 import { 2 - BskyFeedViewPreference, 3 - BskyPreferences, 4 - BskyThreadViewPreference, 2 + type BskyFeedViewPreference, 3 + type BskyPreferences, 4 + type BskyThreadViewPreference, 5 5 } from '@atproto/api' 6 6 7 7 export type UsePreferencesQueryResponse = Omit<
+9 -2
src/state/queries/profile-feedgens.ts
··· 1 - import {AppBskyFeedGetActorFeeds, moderateFeedGenerator} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import { 2 + type AppBskyFeedGetActorFeeds, 3 + moderateFeedGenerator, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryKey, 8 + useInfiniteQuery, 9 + } from '@tanstack/react-query' 3 10 4 11 import {useAgent} from '#/state/session' 5 12 import {useModerationOpts} from '../preferences/moderation-opts'
+7 -4
src/state/queries/profile-followers.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetFollowers} from '@atproto/api' 2 1 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 2 + type AppBskyActorDefs, 3 + type AppBskyGraphGetFollowers, 4 + } from '@atproto/api' 5 + import { 6 + type InfiniteData, 7 + type QueryClient, 8 + type QueryKey, 6 9 useInfiniteQuery, 7 10 } from '@tanstack/react-query' 8 11
+4 -4
src/state/queries/profile-follows.ts
··· 1 - import {AppBskyActorDefs, AppBskyGraphGetFollows} from '@atproto/api' 1 + import {type AppBskyActorDefs, type AppBskyGraphGetFollows} from '@atproto/api' 2 2 import { 3 - InfiniteData, 4 - QueryClient, 5 - QueryKey, 3 + type InfiniteData, 4 + type QueryClient, 5 + type QueryKey, 6 6 useInfiniteQuery, 7 7 } from '@tanstack/react-query' 8 8
+6 -2
src/state/queries/profile-lists.ts
··· 1 - import {AppBskyGraphGetLists, moderateUserList} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import {type AppBskyGraphGetLists, moderateUserList} from '@atproto/api' 2 + import { 3 + type InfiniteData, 4 + type QueryKey, 5 + useInfiniteQuery, 6 + } from '@tanstack/react-query' 3 7 4 8 import {useAgent} from '#/state/session' 5 9 import {useModerationOpts} from '../preferences/moderation-opts'
+4 -4
src/state/queries/resolve-link.ts
··· 1 - import {QueryClient, useQuery} from '@tanstack/react-query' 1 + import {type QueryClient, useQuery} from '@tanstack/react-query' 2 2 3 3 import {STALE} from '#/state/queries/index' 4 4 import {useAgent} from '../session' ··· 9 9 const RQKEY_GIF_ROOT = 'resolve-gif' 10 10 export const RQKEY_GIF = (url: string) => [RQKEY_GIF_ROOT, url] 11 11 12 - import {BskyAgent} from '@atproto/api' 12 + import {type BskyAgent} from '@atproto/api' 13 13 14 - import {ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' 15 - import {Gif} from './tenor' 14 + import {type ResolvedLink, resolveGif, resolveLink} from '#/lib/api/resolve' 15 + import {type Gif} from './tenor' 16 16 17 17 export function useResolveLinkQuery(url: string) { 18 18 const agent = useAgent()
+5 -1
src/state/queries/resolve-uri.ts
··· 1 1 import {AtUri} from '@atproto/api' 2 - import {QueryClient, useQuery, UseQueryResult} from '@tanstack/react-query' 2 + import { 3 + type QueryClient, 4 + useQuery, 5 + type UseQueryResult, 6 + } from '@tanstack/react-query' 3 7 4 8 import {STALE} from '#/state/queries' 5 9 import {useAgent} from '#/state/session'
+6 -6
src/state/queries/search-posts.ts
··· 1 1 import React from 'react' 2 2 import { 3 - AppBskyActorDefs, 4 - AppBskyFeedDefs, 5 - AppBskyFeedSearchPosts, 3 + type AppBskyActorDefs, 4 + type AppBskyFeedDefs, 5 + type AppBskyFeedSearchPosts, 6 6 AtUri, 7 7 moderatePost, 8 8 } from '@atproto/api' 9 9 import { 10 - InfiniteData, 11 - QueryClient, 12 - QueryKey, 10 + type InfiniteData, 11 + type QueryClient, 12 + type QueryKey, 13 13 useInfiniteQuery, 14 14 } from '@tanstack/react-query' 15 15
+6 -2
src/state/queries/suggested-feeds.ts
··· 1 - import {AppBskyFeedGetSuggestedFeeds} from '@atproto/api' 2 - import {InfiniteData, QueryKey, useInfiniteQuery} from '@tanstack/react-query' 1 + import {type AppBskyFeedGetSuggestedFeeds} from '@atproto/api' 2 + import { 3 + type InfiniteData, 4 + type QueryKey, 5 + useInfiniteQuery, 6 + } from '@tanstack/react-query' 3 7 4 8 import {STALE} from '#/state/queries' 5 9 import {useAgent} from '#/state/session'
+3 -3
src/state/queries/threadgate/index.ts
··· 1 1 import { 2 2 AppBskyFeedDefs, 3 - AppBskyFeedGetPostThread, 3 + type AppBskyFeedGetPostThread, 4 4 AppBskyFeedThreadgate, 5 5 AtUri, 6 - BskyAgent, 6 + type BskyAgent, 7 7 } from '@atproto/api' 8 8 import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query' 9 9 ··· 11 11 import {until} from '#/lib/async/until' 12 12 import {STALE} from '#/state/queries' 13 13 import {RQKEY_ROOT as postThreadQueryKeyRoot} from '#/state/queries/post-thread' 14 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 14 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 15 15 import { 16 16 createThreadgateRecord, 17 17 mergeThreadgateRecords,
+2 -2
src/state/queries/threadgate/util.ts
··· 1 - import {AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' 1 + import {type AppBskyFeedDefs, AppBskyFeedThreadgate} from '@atproto/api' 2 2 3 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 3 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate/types' 4 4 import * as bsky from '#/types/bsky' 5 5 6 6 export function threadgateViewToAllowUISetting(
+2 -2
src/state/queries/unstable-profile-cache.ts
··· 1 1 import {useCallback} from 'react' 2 - import {QueryClient, useQueryClient} from '@tanstack/react-query' 2 + import {type QueryClient, useQueryClient} from '@tanstack/react-query' 3 3 4 - import * as bsky from '#/types/bsky' 4 + import type * as bsky from '#/types/bsky' 5 5 6 6 const unstableProfileViewCacheQueryKeyRoot = 'unstableProfileViewCache' 7 7 export const unstableProfileViewCacheQueryKey = (didOrHandle: string) => [
+8 -4
src/state/queries/util.ts
··· 1 1 import { 2 - AppBskyActorDefs, 2 + type AppBskyActorDefs, 3 3 AppBskyEmbedRecord, 4 4 AppBskyEmbedRecordWithMedia, 5 - AppBskyFeedDefs, 5 + type AppBskyFeedDefs, 6 6 AppBskyFeedPost, 7 - AtUri, 7 + type AtUri, 8 8 } from '@atproto/api' 9 - import {InfiniteData, QueryClient, QueryKey} from '@tanstack/react-query' 9 + import { 10 + type InfiniteData, 11 + type QueryClient, 12 + type QueryKey, 13 + } from '@tanstack/react-query' 10 14 11 15 import * as bsky from '#/types/bsky' 12 16
+1 -1
src/state/session/__tests__/session-test.ts
··· 2 2 import {describe, expect, it, jest} from '@jest/globals' 3 3 4 4 import {agentToSessionAccountOrThrow} from '../agent' 5 - import {Action, getInitialState, reducer, State} from '../reducer' 5 + import {type Action, getInitialState, reducer, type State} from '../reducer' 6 6 7 7 jest.mock('jwt-decode', () => ({ 8 8 jwtDecode(_token: string) {
+1 -1
src/state/session/moderation.ts
··· 3 3 import {IS_TEST_USER} from '#/lib/constants' 4 4 import {configureAdditionalModerationAuthorities} from './additional-moderation-authorities' 5 5 import {readLabelers} from './agent-config' 6 - import {SessionAccount} from './types' 6 + import {type SessionAccount} from './types' 7 7 8 8 export function configureModerationForGuest() { 9 9 // This global mutation is *only* OK because this code is only relevant for testing.
+1 -1
src/state/session/util.ts
··· 3 3 import {hasProp} from '#/lib/type-guards' 4 4 import {logger} from '#/logger' 5 5 import * as persisted from '#/state/persisted' 6 - import {SessionAccount} from './types' 6 + import {type SessionAccount} from './types' 7 7 8 8 export function readLastActiveAccount() { 9 9 const {currentAccount, accounts} = persisted.get('session')
-2
src/state/shell/index.tsx
··· 1 - import type React from 'react' 2 - 3 1 import {Provider as ColorModeProvider} from './color-mode' 4 2 import {Provider as DrawerOpenProvider} from './drawer-open' 5 3 import {Provider as DrawerSwipableProvider} from './drawer-swipe-disabled'
+1 -1
src/state/shell/reminders.ts
··· 1 1 import {simpleAreDatesEqual} from '#/lib/strings/time' 2 2 import {logger} from '#/logger' 3 3 import * as persisted from '#/state/persisted' 4 - import {SessionAccount} from '../session' 4 + import {type SessionAccount} from '../session' 5 5 import {isOnboardingActive} from './onboarding' 6 6 7 7 export function shouldRequestEmailConfirmation(account: SessionAccount) {
+1 -1
src/types/bsky/index.ts
··· 1 - import {ValidationResult} from '@atproto/lexicon' 1 + import {type ValidationResult} from '@atproto/lexicon' 2 2 3 3 export * as post from '#/types/bsky/post' 4 4 export * as profile from '#/types/bsky/profile'
+1 -1
src/types/bsky/post.ts
··· 1 1 import { 2 - $Typed, 2 + type $Typed, 3 3 AppBskyEmbedExternal, 4 4 AppBskyEmbedImages, 5 5 AppBskyEmbedRecord,
+1 -1
src/view/com/composer/AltTextCounterWrapper.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {MAX_ALT_TEXT} from '#/lib/constants' 5 5 import {CharProgress} from '#/view/com/composer/char-progress/CharProgress'
+1 -1
src/view/com/composer/Composer.tsx
··· 174 174 videoUri: initVideoUri, 175 175 cancelRef, 176 176 }: Props & { 177 - cancelRef?: React.RefObject<CancelRef> 177 + cancelRef?: React.RefObject<CancelRef | null> 178 178 }) => { 179 179 const {currentAccount} = useSession() 180 180 const agent = useAgent()
+1 -1
src/view/com/composer/KeyboardAccessory.tsx
··· 1 - import React from 'react' 2 1 import {View} from 'react-native' 3 2 import {KeyboardStickyView} from 'react-native-keyboard-controller' 4 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 + import type React from 'react' 5 5 6 6 import {isWeb} from '#/platform/detection' 7 7 import {atoms as a, useTheme} from '#/alf'
+6 -1
src/view/com/composer/char-progress/CharProgress.tsx
··· 1 - import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' 1 + import { 2 + type StyleProp, 3 + type TextStyle, 4 + View, 5 + type ViewStyle, 6 + } from 'react-native' 2 7 // @ts-ignore no type definition -prf 3 8 import ProgressCircle from 'react-native-progress/Circle' 4 9 // @ts-ignore no type definition -prf
-2
src/view/com/composer/photos/EditImageDialog.tsx
··· 1 - import type React from 'react' 2 - 3 1 import {type ComposerImage} from '#/state/gallery' 4 2 import type * as Dialog from '#/components/Dialog' 5 3
+1 -1
src/view/com/composer/photos/EditImageDialog.web.tsx
··· 112 112 aspectRatio, 113 113 }: Required<Pick<EditImageDialogProps, 'image'>> & 114 114 Omit<EditImageDialogProps, 'control' | 'image'> & { 115 - saveRef: React.RefObject<{save: () => Promise<void>}> 115 + saveRef: React.RefObject<{save: () => Promise<void>} | null> 116 116 }) { 117 117 const t = useTheme() 118 118 const [isDragging, setIsDragging] = useState(false)
+1 -1
src/view/com/composer/photos/SelectGifBtn.tsx
··· 4 4 import {useLingui} from '@lingui/react' 5 5 6 6 import {logEvent} from '#/lib/statsig/statsig' 7 - import {Gif} from '#/state/queries/tenor' 7 + import {type Gif} from '#/state/queries/tenor' 8 8 import {atoms as a, useTheme} from '#/alf' 9 9 import {Button} from '#/components/Button' 10 10 import {GifSelectDialog} from '#/components/dialogs/GifSelect'
+11 -1
src/view/com/composer/select-language/SuggestedLanguage.tsx
··· 1 1 import {useEffect, useState} from 'react' 2 2 import {View} from 'react-native' 3 + import {parseLanguage} from '@atproto/api' 3 4 import {msg, Trans} from '@lingui/macro' 4 5 import {useLingui} from '@lingui/react' 5 6 import lande from 'lande' ··· 21 22 22 23 export function SuggestedLanguage({ 23 24 text, 24 - replyToLanguage, 25 + replyToLanguage: replyToLanguageProp, 25 26 }: { 26 27 text: string 27 28 replyToLanguage?: string 28 29 }) { 30 + const replyToLanguage = cleanUpLanguage(replyToLanguageProp) 29 31 const [suggestedLanguage, setSuggestedLanguage] = useState< 30 32 string | undefined 31 33 >(text.length === 0 ? replyToLanguage : undefined) ··· 125 127 } 126 128 return code3ToCode2Strict(lang) 127 129 } 130 + 131 + function cleanUpLanguage(text: string | undefined): string | undefined { 132 + if (!text) { 133 + return undefined 134 + } 135 + 136 + return parseLanguage(text)?.language 137 + }
+1 -1
src/view/com/composer/text-input/text-input-util.ts
··· 1 - import {AppBskyRichtextFacet, RichText} from '@atproto/api' 1 + import {type AppBskyRichtextFacet, type RichText} from '@atproto/api' 2 2 3 3 export type LinkFacetMatch = { 4 4 rt: RichText
+1 -1
src/view/com/composer/text-input/web/LinkDecorator.ts
··· 16 16 17 17 import {URL_REGEX} from '@atproto/api' 18 18 import {Mark} from '@tiptap/core' 19 - import {Node as ProsemirrorNode} from '@tiptap/pm/model' 19 + import {type Node as ProsemirrorNode} from '@tiptap/pm/model' 20 20 import {Plugin, PluginKey} from '@tiptap/pm/state' 21 21 import {Decoration, DecorationSet} from '@tiptap/pm/view' 22 22
+1 -1
src/view/com/composer/text-input/web/TagDecorator.ts
··· 16 16 17 17 import {TAG_REGEX, TRAILING_PUNCTUATION_REGEX} from '@atproto/api' 18 18 import {Mark} from '@tiptap/core' 19 - import {Node as ProsemirrorNode} from '@tiptap/pm/model' 19 + import {type Node as ProsemirrorNode} from '@tiptap/pm/model' 20 20 import {Plugin, PluginKey} from '@tiptap/pm/state' 21 21 import {Decoration, DecorationSet} from '@tiptap/pm/view' 22 22
+4 -4
src/view/com/composer/threadgate/ThreadgateBtn.tsx
··· 1 - import {Keyboard, StyleProp, ViewStyle} from 'react-native' 2 - import {AnimatedStyle} from 'react-native-reanimated' 3 - import {AppBskyFeedPostgate} from '@atproto/api' 1 + import {Keyboard, type StyleProp, type ViewStyle} from 'react-native' 2 + import {type AnimatedStyle} from 'react-native-reanimated' 3 + import {type AppBskyFeedPostgate} from '@atproto/api' 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 7 7 import {isNative} from '#/platform/detection' 8 - import {ThreadgateAllowUISetting} from '#/state/queries/threadgate' 8 + import {type ThreadgateAllowUISetting} from '#/state/queries/threadgate' 9 9 import {native} from '#/alf' 10 10 import {Button, ButtonIcon, ButtonText} from '#/components/Button' 11 11 import * as Dialog from '#/components/Dialog'
+2 -1
src/view/com/composer/videos/SubtitleFilePicker.tsx
··· 1 - import React, {useRef} from 'react' 1 + import {useRef} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg, Trans} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 5 6 6 7 import {logger} from '#/logger' 7 8 import * as Toast from '#/view/com/util/Toast'
+2 -2
src/view/com/composer/videos/VideoPreview.web.tsx
··· 1 1 import {View} from 'react-native' 2 - import {ImagePickerAsset} from 'expo-image-picker' 2 + import {type ImagePickerAsset} from 'expo-image-picker' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5 6 - import {CompressedVideo} from '#/lib/media/video/types' 6 + import {type CompressedVideo} from '#/lib/media/video/types' 7 7 import {clamp} from '#/lib/numbers' 8 8 import {useAutoplayDisabled} from '#/state/preferences' 9 9 import {ExternalEmbedRemoveBtn} from '#/view/com/composer/ExternalEmbedRemoveBtn'
+1 -1
src/view/com/composer/videos/VideoTranscodeProgress.tsx
··· 1 1 import {View} from 'react-native' 2 2 // @ts-expect-error no type definition 3 3 import ProgressPie from 'react-native-progress/Pie' 4 - import {ImagePickerAsset} from 'expo-image-picker' 4 + import {type ImagePickerAsset} from 'expo-image-picker' 5 5 6 6 import {clamp} from '#/lib/numbers' 7 7 import {isWeb} from '#/platform/detection'
+1 -1
src/view/com/composer/videos/pickVideo.web.ts
··· 1 - import {ImagePickerAsset, ImagePickerResult} from 'expo-image-picker' 1 + import {type ImagePickerAsset, type ImagePickerResult} from 'expo-image-picker' 2 2 3 3 import {SUPPORTED_MIME_TYPES} from '#/lib/constants' 4 4
+8 -1
src/view/com/feeds/FeedPage.tsx
··· 1 - import {useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import { 2 + type JSX, 3 + useCallback, 4 + useEffect, 5 + useMemo, 6 + useRef, 7 + useState, 8 + } from 'react' 2 9 import {View} from 'react-native' 3 10 import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api' 4 11 import {msg} from '@lingui/macro'
+32 -19
src/view/com/feeds/ProfileFeedgens.tsx
··· 1 - import React from 'react' 1 + import { 2 + useCallback, 3 + useEffect, 4 + useImperativeHandle, 5 + useMemo, 6 + useState, 7 + } from 'react' 2 8 import { 3 9 findNodeHandle, 4 10 type ListRenderItemInfo, 5 11 type StyleProp, 12 + useWindowDimensions, 6 13 View, 7 14 type ViewStyle, 8 15 } from 'react-native' ··· 34 41 } 35 42 36 43 interface ProfileFeedgensProps { 44 + ref?: React.Ref<SectionRef> 37 45 did: string 38 46 scrollElRef: ListRef 39 47 headerOffset: number ··· 43 51 setScrollViewTag: (tag: number | null) => void 44 52 } 45 53 46 - export const ProfileFeedgens = React.forwardRef< 47 - SectionRef, 48 - ProfileFeedgensProps 49 - >(function ProfileFeedgensImpl( 50 - {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, 54 + export function ProfileFeedgens({ 51 55 ref, 52 - ) { 56 + did, 57 + scrollElRef, 58 + headerOffset, 59 + enabled, 60 + style, 61 + testID, 62 + setScrollViewTag, 63 + }: ProfileFeedgensProps) { 53 64 const {_} = useLingui() 54 65 const t = useTheme() 55 - const [isPTRing, setIsPTRing] = React.useState(false) 56 - const opts = React.useMemo(() => ({enabled}), [enabled]) 66 + const [isPTRing, setIsPTRing] = useState(false) 67 + const {height} = useWindowDimensions() 68 + const opts = useMemo(() => ({enabled}), [enabled]) 57 69 const { 58 70 data, 59 71 isPending, ··· 67 79 const isEmpty = !isPending && !data?.pages[0]?.feeds.length 68 80 const {data: preferences} = usePreferencesQuery() 69 81 70 - const items = React.useMemo(() => { 82 + const items = useMemo(() => { 71 83 let items: any[] = [] 72 84 if (isError && isEmpty) { 73 85 items = items.concat([ERROR_ITEM]) ··· 91 103 92 104 const queryClient = useQueryClient() 93 105 94 - const onScrollToTop = React.useCallback(() => { 106 + const onScrollToTop = useCallback(() => { 95 107 scrollElRef.current?.scrollToOffset({ 96 108 animated: isNative, 97 109 offset: -headerOffset, ··· 99 111 queryClient.invalidateQueries({queryKey: RQKEY(did)}) 100 112 }, [scrollElRef, queryClient, headerOffset, did]) 101 113 102 - React.useImperativeHandle(ref, () => ({ 114 + useImperativeHandle(ref, () => ({ 103 115 scrollToTop: onScrollToTop, 104 116 })) 105 117 106 - const onRefresh = React.useCallback(async () => { 118 + const onRefresh = useCallback(async () => { 107 119 setIsPTRing(true) 108 120 try { 109 121 await refetch() ··· 113 125 setIsPTRing(false) 114 126 }, [refetch, setIsPTRing]) 115 127 116 - const onEndReached = React.useCallback(async () => { 128 + const onEndReached = useCallback(async () => { 117 129 if (isFetchingNextPage || !hasNextPage || isError) return 118 130 119 131 try { ··· 123 135 } 124 136 }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 125 137 126 - const onPressRetryLoadMore = React.useCallback(() => { 138 + const onPressRetryLoadMore = useCallback(() => { 127 139 fetchNextPage() 128 140 }, [fetchNextPage]) 129 141 130 142 // rendering 131 143 // = 132 144 133 - const renderItem = React.useCallback( 145 + const renderItem = useCallback( 134 146 ({item, index}: ListRenderItemInfo<any>) => { 135 147 if (item === EMPTY) { 136 148 return ( ··· 174 186 [_, t, error, refetch, onPressRetryLoadMore, preferences], 175 187 ) 176 188 177 - React.useEffect(() => { 189 + useEffect(() => { 178 190 if (isIOS && enabled && scrollElRef.current) { 179 191 const nativeTag = findNodeHandle(scrollElRef.current) 180 192 setScrollViewTag(nativeTag) 181 193 } 182 194 }, [enabled, scrollElRef, setScrollViewTag]) 183 195 184 - const ProfileFeedgensFooter = React.useCallback(() => { 196 + const ProfileFeedgensFooter = useCallback(() => { 185 197 if (isEmpty) return null 186 198 return ( 187 199 <ListFooter ··· 217 229 removeClippedSubviews={true} 218 230 desktopFixedHeight 219 231 onEndReached={onEndReached} 232 + contentContainerStyle={{minHeight: height + headerOffset}} 220 233 /> 221 234 </View> 222 235 ) 223 - }) 236 + } 224 237 225 238 function keyExtractor(item: any) { 226 239 return item._reactKey || item.uri
+2 -1
src/view/com/home/HomeHeaderLayout.web.tsx
··· 1 - import React from 'react' 1 + import {type JSX} from 'react' 2 2 import {View} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 + import type React from 'react' 5 6 6 7 import {useKawaiiMode} from '#/state/preferences/kawaii' 7 8 import {useSession} from '#/state/session'
+1
src/view/com/home/HomeHeaderLayoutMobile.tsx
··· 1 + import {type JSX} from 'react' 1 2 import {View} from 'react-native' 2 3 import Animated from 'react-native-reanimated' 3 4 import {msg} from '@lingui/macro'
+2 -2
src/view/com/lightbox/ImageViewing/@types/index.ts
··· 6 6 * 7 7 */ 8 8 9 - import {TransformsStyle} from 'react-native' 10 - import {MeasuredDimensions} from 'react-native-reanimated' 9 + import {type TransformsStyle} from 'react-native' 10 + import {type MeasuredDimensions} from 'react-native-reanimated' 11 11 12 12 export type Dimensions = { 13 13 width: number
+1 -1
src/view/com/lightbox/ImageViewing/components/ImageDefaultHeader.tsx
··· 5 5 * LICENSE file in the root directory of this source tree. 6 6 * 7 7 */ 8 - import {StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' 8 + import {StyleSheet, TouchableOpacity, type ViewStyle} from 'react-native' 9 9 import {SafeAreaView} from 'react-native-safe-area-context' 10 10 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 11 11 import {msg} from '@lingui/macro'
+7 -7
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx
··· 3 3 import { 4 4 Gesture, 5 5 GestureDetector, 6 - PanGesture, 6 + type PanGesture, 7 7 } from 'react-native-gesture-handler' 8 8 import Animated, { 9 9 runOnJS, 10 - SharedValue, 10 + type SharedValue, 11 11 useAnimatedReaction, 12 12 useAnimatedRef, 13 13 useAnimatedStyle, ··· 16 16 } from 'react-native-reanimated' 17 17 import {Image} from 'expo-image' 18 18 19 - import type { 20 - Dimensions as ImageDimensions, 21 - ImageSource, 22 - Transform, 19 + import { 20 + type Dimensions as ImageDimensions, 21 + type ImageSource, 22 + type Transform, 23 23 } from '../../@types' 24 24 import { 25 25 applyRounding, ··· 28 28 prependPinch, 29 29 prependTransform, 30 30 readTransform, 31 - TransformMatrix, 31 + type TransformMatrix, 32 32 } from '../../transforms' 33 33 34 34 const MIN_SCREEN_ZOOM = 2
+5 -5
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx
··· 11 11 import { 12 12 Gesture, 13 13 GestureDetector, 14 - PanGesture, 14 + type PanGesture, 15 15 } from 'react-native-gesture-handler' 16 16 import Animated, { 17 17 runOnJS, 18 - SharedValue, 18 + type SharedValue, 19 19 useAnimatedProps, 20 20 useAnimatedReaction, 21 21 useAnimatedRef, ··· 27 27 28 28 import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' 29 29 import { 30 - Dimensions as ImageDimensions, 31 - ImageSource, 32 - Transform, 30 + type Dimensions as ImageDimensions, 31 + type ImageSource, 32 + type Transform, 33 33 } from '../../@types' 34 34 35 35 const MAX_ORIGINAL_IMAGE_ZOOM = 2
+6 -6
src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.tsx
··· 2 2 3 3 import React from 'react' 4 4 import {View} from 'react-native' 5 - import {PanGesture} from 'react-native-gesture-handler' 6 - import {SharedValue} from 'react-native-reanimated' 5 + import {type PanGesture} from 'react-native-gesture-handler' 6 + import {type SharedValue} from 'react-native-reanimated' 7 7 8 - import {Dimensions} from '#/lib/media/types' 8 + import {type Dimensions} from '#/lib/media/types' 9 9 import { 10 - Dimensions as ImageDimensions, 11 - ImageSource, 12 - Transform, 10 + type Dimensions as ImageDimensions, 11 + type ImageSource, 12 + type Transform, 13 13 } from '../../@types' 14 14 15 15 type Props = {
+1 -1
src/view/com/lightbox/ImageViewing/transforms.ts
··· 1 - import type {Position} from './@types' 1 + import {type Position} from './@types' 2 2 3 3 export type TransformMatrix = [ 4 4 number,
+1 -1
src/view/com/lists/ListMembers.tsx
··· 1 - import React, {useCallback} from 'react' 1 + import React, {type JSX, useCallback} from 'react' 2 2 import { 3 3 Dimensions, 4 4 type GestureResponderEvent,
+5 -5
src/view/com/lists/MyLists.tsx
··· 1 - import React from 'react' 1 + import React, {type JSX} from 'react' 2 2 import { 3 3 ActivityIndicator, 4 4 FlatList as RNFlatList, 5 5 RefreshControl, 6 - StyleProp, 6 + type StyleProp, 7 7 View, 8 - ViewStyle, 8 + type ViewStyle, 9 9 } from 'react-native' 10 - import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' 10 + import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' 11 11 import {msg} from '@lingui/macro' 12 12 import {useLingui} from '@lingui/react' 13 13 ··· 16 16 import {s} from '#/lib/styles' 17 17 import {logger} from '#/logger' 18 18 import {useModerationOpts} from '#/state/preferences/moderation-opts' 19 - import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 19 + import {type MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists' 20 20 import {atoms as a, useTheme} from '#/alf' 21 21 import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList' 22 22 import * as ListCard from '#/components/ListCard'
+169 -158
src/view/com/lists/ProfileLists.tsx
··· 1 - import React from 'react' 1 + import { 2 + useCallback, 3 + useEffect, 4 + useImperativeHandle, 5 + useMemo, 6 + useState, 7 + } from 'react' 2 8 import { 3 9 findNodeHandle, 4 10 type ListRenderItemInfo, 5 11 type StyleProp, 12 + useWindowDimensions, 6 13 View, 7 14 type ViewStyle, 8 15 } from 'react-native' ··· 33 40 } 34 41 35 42 interface ProfileListsProps { 43 + ref?: React.Ref<SectionRef> 36 44 did: string 37 45 scrollElRef: ListRef 38 46 headerOffset: number ··· 42 50 setScrollViewTag: (tag: number | null) => void 43 51 } 44 52 45 - export const ProfileLists = React.forwardRef<SectionRef, ProfileListsProps>( 46 - function ProfileListsImpl( 47 - {did, scrollElRef, headerOffset, enabled, style, testID, setScrollViewTag}, 48 - ref, 49 - ) { 50 - const t = useTheme() 51 - const {_} = useLingui() 52 - const [isPTRing, setIsPTRing] = React.useState(false) 53 - const opts = React.useMemo(() => ({enabled}), [enabled]) 54 - const { 55 - data, 56 - isPending, 57 - hasNextPage, 58 - fetchNextPage, 59 - isFetchingNextPage, 60 - isError, 61 - error, 62 - refetch, 63 - } = useProfileListsQuery(did, opts) 64 - const isEmpty = !isPending && !data?.pages[0]?.lists.length 53 + export function ProfileLists({ 54 + ref, 55 + did, 56 + scrollElRef, 57 + headerOffset, 58 + enabled, 59 + style, 60 + testID, 61 + setScrollViewTag, 62 + }: ProfileListsProps) { 63 + const t = useTheme() 64 + const {_} = useLingui() 65 + const {height} = useWindowDimensions() 66 + const [isPTRing, setIsPTRing] = useState(false) 67 + const opts = useMemo(() => ({enabled}), [enabled]) 68 + const { 69 + data, 70 + isPending, 71 + hasNextPage, 72 + fetchNextPage, 73 + isFetchingNextPage, 74 + isError, 75 + error, 76 + refetch, 77 + } = useProfileListsQuery(did, opts) 78 + const isEmpty = !isPending && !data?.pages[0]?.lists.length 65 79 66 - const items = React.useMemo(() => { 67 - let items: any[] = [] 68 - if (isError && isEmpty) { 69 - items = items.concat([ERROR_ITEM]) 70 - } 71 - if (isPending) { 72 - items = items.concat([LOADING]) 73 - } else if (isEmpty) { 74 - items = items.concat([EMPTY]) 75 - } else if (data?.pages) { 76 - for (const page of data?.pages) { 77 - items = items.concat(page.lists) 78 - } 79 - } 80 - if (isError && !isEmpty) { 81 - items = items.concat([LOAD_MORE_ERROR_ITEM]) 80 + const items = useMemo(() => { 81 + let items: any[] = [] 82 + if (isError && isEmpty) { 83 + items = items.concat([ERROR_ITEM]) 84 + } 85 + if (isPending) { 86 + items = items.concat([LOADING]) 87 + } else if (isEmpty) { 88 + items = items.concat([EMPTY]) 89 + } else if (data?.pages) { 90 + for (const page of data?.pages) { 91 + items = items.concat(page.lists) 82 92 } 83 - return items 84 - }, [isError, isEmpty, isPending, data]) 93 + } 94 + if (isError && !isEmpty) { 95 + items = items.concat([LOAD_MORE_ERROR_ITEM]) 96 + } 97 + return items 98 + }, [isError, isEmpty, isPending, data]) 85 99 86 - // events 87 - // = 100 + // events 101 + // = 88 102 89 - const queryClient = useQueryClient() 103 + const queryClient = useQueryClient() 90 104 91 - const onScrollToTop = React.useCallback(() => { 92 - scrollElRef.current?.scrollToOffset({ 93 - animated: isNative, 94 - offset: -headerOffset, 95 - }) 96 - queryClient.invalidateQueries({queryKey: RQKEY(did)}) 97 - }, [scrollElRef, queryClient, headerOffset, did]) 105 + const onScrollToTop = useCallback(() => { 106 + scrollElRef.current?.scrollToOffset({ 107 + animated: isNative, 108 + offset: -headerOffset, 109 + }) 110 + queryClient.invalidateQueries({queryKey: RQKEY(did)}) 111 + }, [scrollElRef, queryClient, headerOffset, did]) 98 112 99 - React.useImperativeHandle(ref, () => ({ 100 - scrollToTop: onScrollToTop, 101 - })) 113 + useImperativeHandle(ref, () => ({ 114 + scrollToTop: onScrollToTop, 115 + })) 102 116 103 - const onRefresh = React.useCallback(async () => { 104 - setIsPTRing(true) 105 - try { 106 - await refetch() 107 - } catch (err) { 108 - logger.error('Failed to refresh lists', {message: err}) 109 - } 110 - setIsPTRing(false) 111 - }, [refetch, setIsPTRing]) 117 + const onRefresh = useCallback(async () => { 118 + setIsPTRing(true) 119 + try { 120 + await refetch() 121 + } catch (err) { 122 + logger.error('Failed to refresh lists', {message: err}) 123 + } 124 + setIsPTRing(false) 125 + }, [refetch, setIsPTRing]) 112 126 113 - const onEndReached = React.useCallback(async () => { 114 - if (isFetchingNextPage || !hasNextPage || isError) return 115 - try { 116 - await fetchNextPage() 117 - } catch (err) { 118 - logger.error('Failed to load more lists', {message: err}) 119 - } 120 - }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 127 + const onEndReached = useCallback(async () => { 128 + if (isFetchingNextPage || !hasNextPage || isError) return 129 + try { 130 + await fetchNextPage() 131 + } catch (err) { 132 + logger.error('Failed to load more lists', {message: err}) 133 + } 134 + }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) 121 135 122 - const onPressRetryLoadMore = React.useCallback(() => { 123 - fetchNextPage() 124 - }, [fetchNextPage]) 136 + const onPressRetryLoadMore = useCallback(() => { 137 + fetchNextPage() 138 + }, [fetchNextPage]) 125 139 126 - // rendering 127 - // = 140 + // rendering 141 + // = 128 142 129 - const renderItemInner = React.useCallback( 130 - ({item, index}: ListRenderItemInfo<any>) => { 131 - if (item === EMPTY) { 132 - return ( 133 - <EmptyState 134 - icon="list-ul" 135 - message={_(msg`You have no lists.`)} 136 - testID="listsEmpty" 137 - /> 138 - ) 139 - } else if (item === ERROR_ITEM) { 140 - return ( 141 - <ErrorMessage 142 - message={cleanError(error)} 143 - onPressTryAgain={refetch} 144 - /> 145 - ) 146 - } else if (item === LOAD_MORE_ERROR_ITEM) { 147 - return ( 148 - <LoadMoreRetryBtn 149 - label={_( 150 - msg`There was an issue fetching your lists. Tap here to try again.`, 151 - )} 152 - onPress={onPressRetryLoadMore} 153 - /> 154 - ) 155 - } else if (item === LOADING) { 156 - return <FeedLoadingPlaceholder /> 157 - } 143 + const renderItemInner = useCallback( 144 + ({item, index}: ListRenderItemInfo<any>) => { 145 + if (item === EMPTY) { 158 146 return ( 159 - <View 160 - style={[ 161 - (index !== 0 || isWeb) && a.border_t, 162 - t.atoms.border_contrast_low, 163 - a.px_lg, 164 - a.py_lg, 165 - ]}> 166 - <ListCard.Default view={item} /> 167 - </View> 147 + <EmptyState 148 + icon="list-ul" 149 + message={_(msg`You have no lists.`)} 150 + testID="listsEmpty" 151 + /> 168 152 ) 169 - }, 170 - [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low], 171 - ) 172 - 173 - React.useEffect(() => { 174 - if (isIOS && enabled && scrollElRef.current) { 175 - const nativeTag = findNodeHandle(scrollElRef.current) 176 - setScrollViewTag(nativeTag) 153 + } else if (item === ERROR_ITEM) { 154 + return ( 155 + <ErrorMessage message={cleanError(error)} onPressTryAgain={refetch} /> 156 + ) 157 + } else if (item === LOAD_MORE_ERROR_ITEM) { 158 + return ( 159 + <LoadMoreRetryBtn 160 + label={_( 161 + msg`There was an issue fetching your lists. Tap here to try again.`, 162 + )} 163 + onPress={onPressRetryLoadMore} 164 + /> 165 + ) 166 + } else if (item === LOADING) { 167 + return <FeedLoadingPlaceholder /> 177 168 } 178 - }, [enabled, scrollElRef, setScrollViewTag]) 179 - 180 - const ProfileListsFooter = React.useCallback(() => { 181 - if (isEmpty) return null 182 169 return ( 183 - <ListFooter 184 - hasNextPage={hasNextPage} 185 - isFetchingNextPage={isFetchingNextPage} 186 - onRetry={fetchNextPage} 187 - error={cleanError(error)} 188 - height={180 + headerOffset} 189 - /> 170 + <View 171 + style={[ 172 + (index !== 0 || isWeb) && a.border_t, 173 + t.atoms.border_contrast_low, 174 + a.px_lg, 175 + a.py_lg, 176 + ]}> 177 + <ListCard.Default view={item} /> 178 + </View> 190 179 ) 191 - }, [ 192 - hasNextPage, 193 - error, 194 - isFetchingNextPage, 195 - headerOffset, 196 - fetchNextPage, 197 - isEmpty, 198 - ]) 180 + }, 181 + [error, refetch, onPressRetryLoadMore, _, t.atoms.border_contrast_low], 182 + ) 183 + 184 + useEffect(() => { 185 + if (isIOS && enabled && scrollElRef.current) { 186 + const nativeTag = findNodeHandle(scrollElRef.current) 187 + setScrollViewTag(nativeTag) 188 + } 189 + }, [enabled, scrollElRef, setScrollViewTag]) 199 190 191 + const ProfileListsFooter = useCallback(() => { 192 + if (isEmpty) return null 200 193 return ( 201 - <View testID={testID} style={style}> 202 - <List 203 - testID={testID ? `${testID}-flatlist` : undefined} 204 - ref={scrollElRef} 205 - data={items} 206 - keyExtractor={keyExtractor} 207 - renderItem={renderItemInner} 208 - ListFooterComponent={ProfileListsFooter} 209 - refreshing={isPTRing} 210 - onRefresh={onRefresh} 211 - headerOffset={headerOffset} 212 - progressViewOffset={ios(0)} 213 - removeClippedSubviews={true} 214 - desktopFixedHeight 215 - onEndReached={onEndReached} 216 - /> 217 - </View> 194 + <ListFooter 195 + hasNextPage={hasNextPage} 196 + isFetchingNextPage={isFetchingNextPage} 197 + onRetry={fetchNextPage} 198 + error={cleanError(error)} 199 + height={180 + headerOffset} 200 + /> 218 201 ) 219 - }, 220 - ) 202 + }, [ 203 + hasNextPage, 204 + error, 205 + isFetchingNextPage, 206 + headerOffset, 207 + fetchNextPage, 208 + isEmpty, 209 + ]) 210 + 211 + return ( 212 + <View testID={testID} style={style}> 213 + <List 214 + testID={testID ? `${testID}-flatlist` : undefined} 215 + ref={scrollElRef} 216 + data={items} 217 + keyExtractor={keyExtractor} 218 + renderItem={renderItemInner} 219 + ListFooterComponent={ProfileListsFooter} 220 + refreshing={isPTRing} 221 + onRefresh={onRefresh} 222 + headerOffset={headerOffset} 223 + progressViewOffset={ios(0)} 224 + removeClippedSubviews={true} 225 + desktopFixedHeight 226 + onEndReached={onEndReached} 227 + contentContainerStyle={{minHeight: height + headerOffset}} 228 + /> 229 + </View> 230 + ) 231 + } 221 232 222 233 function keyExtractor(item: any) { 223 234 return item._reactKey || item.uri
+3 -3
src/view/com/modals/InviteCodes.tsx
··· 6 6 View, 7 7 } from 'react-native' 8 8 import {setStringAsync} from 'expo-clipboard' 9 - import {ComAtprotoServerDefs} from '@atproto/api' 9 + import {type ComAtprotoServerDefs} from '@atproto/api' 10 10 import { 11 11 FontAwesomeIcon, 12 - FontAwesomeIconStyle, 12 + type FontAwesomeIconStyle, 13 13 } from '@fortawesome/react-native-fontawesome' 14 14 import {msg, Trans} from '@lingui/macro' 15 15 import {useLingui} from '@lingui/react' ··· 22 22 import {useInvitesAPI, useInvitesState} from '#/state/invites' 23 23 import {useModalControls} from '#/state/modals' 24 24 import { 25 - InviteCodesQueryResponse, 25 + type InviteCodesQueryResponse, 26 26 useInviteCodesQuery, 27 27 } from '#/state/queries/invites' 28 28 import {ErrorMessage} from '../util/error/ErrorMessage'
+2 -2
src/view/com/modals/UserAddRemoveLists.tsx
··· 5 5 useWindowDimensions, 6 6 View, 7 7 } from 'react-native' 8 - import {AppBskyGraphDefs as GraphDefs} from '@atproto/api' 8 + import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api' 9 9 import {msg, Trans} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 11 ··· 18 18 import {useModalControls} from '#/state/modals' 19 19 import { 20 20 getMembership, 21 - ListMembersip, 21 + type ListMembersip, 22 22 useDangerousListMembershipsQuery, 23 23 useListMembershipAddMutation, 24 24 useListMembershipRemoveMutation,
+1 -1
src/view/com/notifications/NotificationFeedItem.tsx
··· 248 248 : '' 249 249 250 250 let a11yLabel = '' 251 - let notificationContent: ReactElement 251 + let notificationContent: ReactElement<any> 252 252 let icon = ( 253 253 <HeartIconFilled 254 254 size="xl"
+1
src/view/com/pager/Pager.tsx
··· 1 1 import { 2 + type JSX, 2 3 memo, 3 4 useCallback, 4 5 useContext,
+1
src/view/com/pager/Pager.web.tsx
··· 1 1 import { 2 2 Children, 3 + type JSX, 3 4 useCallback, 4 5 useImperativeHandle, 5 6 useRef,
+1 -1
src/view/com/pager/PagerWithHeader.tsx
··· 1 - import {memo, useCallback, useEffect, useRef, useState} from 'react' 1 + import {type JSX, memo, useCallback, useEffect, useRef, useState} from 'react' 2 2 import { 3 3 type LayoutChangeEvent, 4 4 type NativeScrollEvent,
+8 -3
src/view/com/pager/PagerWithHeader.web.tsx
··· 1 1 import * as React from 'react' 2 - import {ScrollView, View} from 'react-native' 2 + import {type JSX} from 'react' 3 + import {type ScrollView, View} from 'react-native' 3 4 import {useAnimatedRef} from 'react-native-reanimated' 4 5 5 - import {Pager, PagerRef, RenderTabBarFnProps} from '#/view/com/pager/Pager' 6 + import { 7 + Pager, 8 + type PagerRef, 9 + type RenderTabBarFnProps, 10 + } from '#/view/com/pager/Pager' 6 11 import {atoms as a, web} from '#/alf' 7 12 import * as Layout from '#/components/Layout' 8 - import {ListMethods} from '../util/List' 13 + import {type ListMethods} from '../util/List' 9 14 import {TabBar} from './TabBar' 10 15 11 16 export interface PagerWithHeaderChildParams {
+3 -1
src/view/com/pager/TabBar.web.tsx
··· 106 106 <PressableWithHover 107 107 testID={`${testID}-selector-${i}`} 108 108 key={`${item}-${i}`} 109 - ref={node => (itemRefs.current[i] = node as any)} 109 + ref={node => { 110 + itemRefs.current[i] = node as any 111 + }} 110 112 style={styles.item} 111 113 hoverStyle={t.atoms.bg_contrast_25} 112 114 onPress={() => onPressItem(i)}
+1 -1
src/view/com/post-thread/PostLikedBy.tsx
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 - import {AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 2 + import {type AppBskyFeedGetLikes as GetLikes} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/view/com/post-thread/PostQuotes.tsx
··· 1 1 import {useCallback, useState} from 'react' 2 2 import { 3 - AppBskyFeedDefs, 3 + type AppBskyFeedDefs, 4 4 AppBskyFeedPost, 5 5 moderatePost, 6 - ModerationDecision, 6 + type ModerationDecision, 7 7 } from '@atproto/api' 8 8 import {msg} from '@lingui/macro' 9 9 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/post-thread/PostRepostedBy.tsx
··· 1 1 import {useCallback, useMemo, useState} from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+2 -2
src/view/com/posts/CustomFeedEmptyState.tsx
··· 2 2 import {StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {isWeb} from '#/platform/detection' 15 15 import {Button} from '../util/forms/Button'
+2 -2
src/view/com/posts/FollowingEmptyState.tsx
··· 2 2 import {StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 11 import {MagnifyingGlassIcon} from '#/lib/icons' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {isWeb} from '#/platform/detection' 15 15 import {Button} from '../util/forms/Button'
+2 -2
src/view/com/posts/FollowingEndOfFeed.tsx
··· 2 2 import {Dimensions, StyleSheet, View} from 'react-native' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 import {Trans} from '@lingui/macro' 8 8 import {useNavigation} from '@react-navigation/native' 9 9 10 10 import {usePalette} from '#/lib/hooks/usePalette' 11 - import {NavigationProp} from '#/lib/routes/types' 11 + import {type NavigationProp} from '#/lib/routes/types' 12 12 import {s} from '#/lib/styles' 13 13 import {isWeb} from '#/platform/detection' 14 14 import {Button} from '../util/forms/Button'
+9 -1
src/view/com/posts/PostFeed.tsx
··· 1 - import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' 1 + import { 2 + type JSX, 3 + memo, 4 + useCallback, 5 + useEffect, 6 + useMemo, 7 + useRef, 8 + useState, 9 + } from 'react' 2 10 import { 3 11 ActivityIndicator, 4 12 AppState,
+1 -1
src/view/com/profile/ProfileFollowers.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+1 -1
src/view/com/profile/ProfileFollows.tsx
··· 1 1 import React from 'react' 2 - import {AppBskyActorDefs as ActorDefs} from '@atproto/api' 2 + import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
+1 -1
src/view/com/util/Alert.web.tsx
··· 1 - import {AlertButton, AlertStatic} from 'react-native' 1 + import {type AlertButton, type AlertStatic} from 'react-native' 2 2 3 3 class WebAlert implements Pick<AlertStatic, 'alert'> { 4 4 public alert(title: string, message?: string, buttons?: AlertButton[]): void {
+3 -2
src/view/com/util/BottomSheetCustomBackdrop.tsx
··· 1 - import React, {useMemo} from 'react' 1 + import {useMemo} from 'react' 2 2 import {TouchableWithoutFeedback} from 'react-native' 3 3 import Animated, { 4 4 Extrapolation, 5 5 interpolate, 6 6 useAnimatedStyle, 7 7 } from 'react-native-reanimated' 8 - import {BottomSheetBackdropProps} from '@discord/bottom-sheet/src' 8 + import {type BottomSheetBackdropProps} from '@discord/bottom-sheet/src' 9 9 import {msg} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 + import type React from 'react' 11 12 12 13 export function createCustomBackdrop( 13 14 onClose?: (() => void) | undefined,
+3 -3
src/view/com/util/EmptyState.tsx
··· 1 - import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native' 2 - import {IconProp} from '@fortawesome/fontawesome-svg-core' 1 + import {type StyleProp, StyleSheet, View, type ViewStyle} from 'react-native' 2 + import {type IconProp} from '@fortawesome/fontawesome-svg-core' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 8 8 import {usePalette} from '#/lib/hooks/usePalette'
+2 -2
src/view/com/util/EmptyStateWithButton.tsx
··· 1 1 import {StyleSheet, View} from 'react-native' 2 - import {IconProp} from '@fortawesome/fontawesome-svg-core' 2 + import {type IconProp} from '@fortawesome/fontawesome-svg-core' 3 3 import { 4 4 FontAwesomeIcon, 5 - FontAwesomeIconStyle, 5 + type FontAwesomeIconStyle, 6 6 } from '@fortawesome/react-native-fontawesome' 7 7 8 8 import {usePalette} from '#/lib/hooks/usePalette'
+2 -2
src/view/com/util/ErrorBoundary.tsx
··· 1 - import {Component, ErrorInfo, ReactNode} from 'react' 2 - import {StyleProp, ViewStyle} from 'react-native' 1 + import {Component, type ErrorInfo, type ReactNode} from 'react' 2 + import {type StyleProp, type ViewStyle} from 'react-native' 3 3 import {msg} from '@lingui/macro' 4 4 import {useLingui} from '@lingui/react' 5 5
-1
src/view/com/util/EventStopper.tsx
··· 1 1 import {View, type ViewStyle} from 'react-native' 2 - import type React from 'react' 3 2 4 3 /** 5 4 * This utility function captures events and stops
+2 -2
src/view/com/util/FeedInfoText.tsx
··· 1 - import {StyleProp, StyleSheet, TextStyle} from 'react-native' 1 + import {type StyleProp, StyleSheet, type TextStyle} from 'react-native' 2 2 3 3 import {sanitizeDisplayName} from '#/lib/strings/display-names' 4 - import {TypographyVariant} from '#/lib/ThemeContext' 4 + import {type TypographyVariant} from '#/lib/ThemeContext' 5 5 import {useFeedSourceInfoQuery} from '#/state/queries/feed' 6 6 import {TextLinkOnWebOnly} from './Link' 7 7 import {LoadingPlaceholder} from './LoadingPlaceholder'
+1 -1
src/view/com/util/Link.tsx
··· 1 - import {memo, useCallback, useMemo} from 'react' 1 + import {type JSX, memo, useCallback, useMemo} from 'react' 2 2 import { 3 3 type GestureResponderEvent, 4 4 Platform,
+1 -1
src/view/com/util/List.tsx
··· 57 57 ...props 58 58 }, 59 59 ref, 60 - ): React.ReactElement => { 60 + ): React.ReactElement<any> => { 61 61 const isScrolledDown = useSharedValue(false) 62 62 const t = useTheme() 63 63 const dedupe = useDedupe(400)
+13 -6
src/view/com/util/List.web.tsx
··· 1 - import React, {isValidElement, memo, startTransition, useRef} from 'react' 1 + import React, { 2 + isValidElement, 3 + type JSX, 4 + memo, 5 + startTransition, 6 + useRef, 7 + } from 'react' 2 8 import { 3 9 type FlatListProps, 4 10 StyleSheet, ··· 202 208 behavior: animated ? 'smooth' : 'instant', 203 209 }) 204 210 }, 211 + 205 212 scrollToEnd({animated = true}: {animated?: boolean}) { 206 213 const element = getScrollableNode() 207 214 element?.scrollTo({ ··· 382 389 containerRef, 383 390 onVisibleChange, 384 391 }: { 385 - root?: React.RefObject<HTMLDivElement> | null 392 + root?: React.RefObject<HTMLDivElement | null> | null 386 393 topMargin?: string 387 394 bottomMargin?: string 388 - containerRef: React.RefObject<Element> 395 + containerRef: React.RefObject<Element | null> 389 396 onVisibleChange: (isVisible: boolean) => void 390 397 }) { 391 398 const [containerHeight, setContainerHeight] = React.useState(0) ··· 404 411 } 405 412 406 413 function useResizeObserver( 407 - ref: React.RefObject<Element>, 414 + ref: React.RefObject<Element | null>, 408 415 onResize: undefined | ((w: number, h: number) => void), 409 416 ) { 410 417 const handleResize = useNonReactiveCallback(onResize ?? (() => {})) ··· 509 516 onVisibleChange, 510 517 style, 511 518 }: { 512 - root?: React.RefObject<HTMLDivElement> | null 519 + root?: React.RefObject<HTMLDivElement | null> | null 513 520 topMargin?: string 514 521 bottomMargin?: string 515 522 onVisibleChange: (isVisible: boolean) => void ··· 551 558 552 559 export const List = memo(React.forwardRef(ListImpl)) as <ItemT>( 553 560 props: ListProps<ItemT> & {ref?: React.Ref<ListMethods>}, 554 - ) => React.ReactElement 561 + ) => React.ReactElement<any> 555 562 556 563 // https://stackoverflow.com/questions/7944460/detect-safari-browser 557 564
+1 -1
src/view/com/util/LoadMoreRetryBtn.tsx
··· 1 1 import {StyleSheet} from 'react-native' 2 2 import { 3 3 FontAwesomeIcon, 4 - FontAwesomeIconStyle, 4 + type FontAwesomeIconStyle, 5 5 } from '@fortawesome/react-native-fontawesome' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette'
-17
src/view/com/util/LoadingScreen.tsx
··· 1 - import {ActivityIndicator, View} from 'react-native' 2 - 3 - import {s} from '#/lib/styles' 4 - import * as Layout from '#/components/Layout' 5 - 6 - /** 7 - * @deprecated use Layout compoenents directly 8 - */ 9 - export function LoadingScreen() { 10 - return ( 11 - <Layout.Content> 12 - <View style={s.p20}> 13 - <ActivityIndicator size="large" /> 14 - </View> 15 - </Layout.Content> 16 - ) 17 - }
+1 -1
src/view/com/util/MainScrollProvider.tsx
··· 1 1 import React, {useCallback, useEffect} from 'react' 2 - import {NativeScrollEvent} from 'react-native' 2 + import {type NativeScrollEvent} from 'react-native' 3 3 import {interpolate, useSharedValue, withSpring} from 'react-native-reanimated' 4 4 import EventEmitter from 'eventemitter3' 5 5
-1
src/view/com/util/PostMeta.tsx
··· 4 4 import {msg} from '@lingui/macro' 5 5 import {useLingui} from '@lingui/react' 6 6 import {useQueryClient} from '@tanstack/react-query' 7 - import type React from 'react' 8 7 9 8 import {useActorStatus} from '#/lib/actor-status' 10 9 import {makeProfileLink} from '#/lib/routes/links'
+8 -3
src/view/com/util/PressableWithHover.tsx
··· 1 - import {forwardRef, PropsWithChildren} from 'react' 2 - import {Pressable, PressableProps, StyleProp, ViewStyle} from 'react-native' 3 - import {View} from 'react-native' 1 + import {forwardRef, type PropsWithChildren} from 'react' 2 + import { 3 + Pressable, 4 + type PressableProps, 5 + type StyleProp, 6 + type ViewStyle, 7 + } from 'react-native' 8 + import {type View} from 'react-native' 4 9 5 10 import {addStyle} from '#/lib/styles' 6 11 import {useInteractionState} from '#/components/hooks/useInteractionState'
+2 -2
src/view/com/util/TimeElapsed.tsx
··· 1 - import React from 'react' 2 - import {I18n} from '@lingui/core' 1 + import React, {type JSX} from 'react' 2 + import {type I18n} from '@lingui/core' 3 3 import {useLingui} from '@lingui/react' 4 4 5 5 import {useGetTimeAgo} from '#/lib/hooks/useTimeAgo'
+13 -7
src/view/com/util/UserAvatar.tsx
··· 1 - import React, {memo, useCallback, useMemo, useState} from 'react' 1 + import {memo, useCallback, useMemo, useState} from 'react' 2 2 import { 3 3 Image, 4 4 Pressable, ··· 363 363 } 364 364 }, [circular, size]) 365 365 366 - const onOpenCamera = React.useCallback(async () => { 366 + const onOpenCamera = useCallback(async () => { 367 367 if (!(await requestCameraAccessIfNeeded())) { 368 368 return 369 369 } ··· 377 377 ) 378 378 }, [onSelectNewAvatar, requestCameraAccessIfNeeded]) 379 379 380 - const onOpenLibrary = React.useCallback(async () => { 380 + const onOpenLibrary = useCallback(async () => { 381 381 if (!(await requestPhotoAccessIfNeeded())) { 382 382 return 383 383 } ··· 421 421 circular, 422 422 ]) 423 423 424 - const onRemoveAvatar = React.useCallback(() => { 424 + const onRemoveAvatar = useCallback(() => { 425 425 onSelectNewAvatar(null) 426 426 }, [onSelectNewAvatar]) 427 427 ··· 528 528 disableNavigation, 529 529 onBeforePress, 530 530 live, 531 - ...rest 531 + ...props 532 532 }: PreviewableUserAvatarProps): React.ReactNode => { 533 533 const {_} = useLingui() 534 534 const queryClient = useQueryClient() ··· 557 557 moderation={moderation} 558 558 type={profile.associated?.labeler ? 'labeler' : 'user'} 559 559 live={status.isActive || live} 560 - {...rest} 560 + {...props} 561 561 /> 562 562 ) 563 + 564 + const linkStyle = 565 + props.type !== 'algo' && props.type !== 'list' 566 + ? a.rounded_full 567 + : {borderRadius: props.size > 32 ? 8 : 3} 563 568 564 569 return ( 565 570 <ProfileHoverCard did={profile.did} disable={disableHoverCard}> ··· 596 601 did: profile.did, 597 602 handle: profile.handle, 598 603 })} 599 - onPress={onPress}> 604 + onPress={onPress} 605 + style={linkStyle}> 600 606 {avatarEl} 601 607 </Link> 602 608 )}
+2
src/view/com/util/ViewHeader.tsx
··· 1 + import {type JSX} from 'react' 2 + 1 3 import {Header} from '#/components/Layout' 2 4 3 5 /**
+4 -4
src/view/com/util/ViewSelector.tsx
··· 1 - import React, {useEffect, useState} from 'react' 1 + import React, {type JSX, useEffect, useState} from 'react' 2 2 import { 3 - NativeScrollEvent, 4 - NativeSyntheticEvent, 3 + type NativeScrollEvent, 4 + type NativeSyntheticEvent, 5 5 Pressable, 6 6 RefreshControl, 7 7 ScrollView, ··· 36 36 renderItem: (item: any) => JSX.Element 37 37 ListFooterComponent?: 38 38 | React.ComponentType<any> 39 - | React.ReactElement 39 + | React.ReactElement<any> 40 40 | null 41 41 | undefined 42 42 onSelectView?: (viewIndex: number) => void
+3 -3
src/view/com/util/Views.tsx
··· 1 1 import {forwardRef} from 'react' 2 - import {FlatListComponent} from 'react-native' 3 - import {View, ViewProps} from 'react-native' 2 + import {type FlatListComponent} from 'react-native' 3 + import {View, type ViewProps} from 'react-native' 4 4 import Animated from 'react-native-reanimated' 5 - import {FlatListPropsWithLayout} from 'react-native-reanimated' 5 + import {type FlatListPropsWithLayout} from 'react-native-reanimated' 6 6 7 7 // If you explode these into functions, don't forget to forwardRef! 8 8
+1 -1
src/view/com/util/WebAuxClickWrapper.tsx
··· 1 - import React from 'react' 2 1 import {Platform} from 'react-native' 2 + import type React from 'react' 3 3 4 4 const onMouseUp = (e: React.MouseEvent & {target: HTMLElement}) => { 5 5 // Only handle whenever it is the middle button
+3 -3
src/view/com/util/error/ErrorMessage.tsx
··· 1 1 import { 2 - StyleProp, 2 + type StyleProp, 3 3 StyleSheet, 4 4 TouchableOpacity, 5 5 View, 6 - ViewStyle, 6 + type ViewStyle, 7 7 } from 'react-native' 8 8 import { 9 9 FontAwesomeIcon, 10 - FontAwesomeIconStyle, 10 + type FontAwesomeIconStyle, 11 11 } from '@fortawesome/react-native-fontawesome' 12 12 import {msg} from '@lingui/macro' 13 13 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/util/error/ErrorScreen.tsx
··· 1 1 import {View} from 'react-native' 2 2 import { 3 3 FontAwesomeIcon, 4 - FontAwesomeIconStyle, 4 + type FontAwesomeIconStyle, 5 5 } from '@fortawesome/react-native-fontawesome' 6 6 import {msg, Trans} from '@lingui/macro' 7 7 import {useLingui} from '@lingui/react'
+1 -1
src/view/com/util/fab/FAB.web.tsx
··· 1 1 import {View} from 'react-native' 2 2 3 3 import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 4 - import {FABInner, FABProps} from './FABInner' 4 + import {FABInner, type FABProps} from './FABInner' 5 5 6 6 export const FAB = (_opts: FABProps) => { 7 7 const {isDesktop} = useWebMediaQueries()
+12 -5
src/view/com/util/fab/FABInner.tsx
··· 1 - import {ComponentProps} from 'react' 2 - import {StyleSheet, TouchableWithoutFeedback} from 'react-native' 1 + import {type ComponentProps, type JSX} from 'react' 2 + import { 3 + type Pressable, 4 + type StyleProp, 5 + StyleSheet, 6 + type ViewStyle, 7 + } from 'react-native' 3 8 import Animated from 'react-native-reanimated' 4 9 import {useSafeAreaInsets} from 'react-native-safe-area-context' 5 10 import {LinearGradient} from 'expo-linear-gradient' ··· 12 17 import {gradients} from '#/lib/styles' 13 18 import {isWeb} from '#/platform/detection' 14 19 import {ios} from '#/alf' 20 + import {atoms as a} from '#/alf' 15 21 16 - export interface FABProps 17 - extends ComponentProps<typeof TouchableWithoutFeedback> { 22 + export interface FABProps extends ComponentProps<typeof Pressable> { 18 23 testID?: string 19 24 icon: JSX.Element 25 + style?: StyleProp<ViewStyle> 20 26 } 21 27 22 - export function FABInner({testID, icon, onPress, ...props}: FABProps) { 28 + export function FABInner({testID, icon, onPress, style, ...props}: FABProps) { 23 29 const insets = useSafeAreaInsets() 24 30 const {isMobile, isTablet} = useWebMediaQueries() 25 31 const playHaptic = useHaptics() ··· 51 57 playHaptic('Heavy') 52 58 })} 53 59 targetScale={0.9} 60 + style={[a.rounded_full, style]} 54 61 {...props}> 55 62 <LinearGradient 56 63 colors={[gradients.blueLight.start, gradients.blueLight.end]}
+1 -1
src/view/com/util/forms/NativeDropdown.web.tsx
··· 161 161 menuRef, 162 162 }: { 163 163 items: DropdownItem[] 164 - menuRef: React.RefObject<HTMLDivElement> 164 + menuRef: React.RefObject<HTMLDivElement | null> 165 165 }) { 166 166 const pal = usePalette('default') 167 167 const theme = useTheme()
-1
src/view/com/util/images/Gallery.tsx
··· 4 4 import {type AppBskyEmbedImages} from '@atproto/api' 5 5 import {msg} from '@lingui/macro' 6 6 import {useLingui} from '@lingui/react' 7 - import type React from 'react' 8 7 9 8 import {type Dimensions} from '#/lib/media/types' 10 9 import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
+1 -1
src/view/com/util/images/Image.tsx
··· 1 - import {Image, ImageProps, ImageSource} from 'expo-image' 1 + import {Image, type ImageProps, type ImageSource} from 'expo-image' 2 2 3 3 interface HighPriorityImageProps extends ImageProps { 4 4 source: ImageSource
+1 -1
src/view/com/util/layouts/LoggedOutLayout.tsx
··· 1 - import React from 'react' 2 1 import {ScrollView, StyleSheet, View} from 'react-native' 2 + import type React from 'react' 3 3 4 4 import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' 5 5 import {useIsKeyboardVisible} from '#/lib/hooks/useIsKeyboardVisible'
+3 -3
src/view/icons/Logo.tsx
··· 1 1 import React from 'react' 2 - import {StyleSheet, TextProps} from 'react-native' 2 + import {StyleSheet, type TextProps} from 'react-native' 3 3 import Svg, { 4 4 Defs, 5 5 LinearGradient, 6 6 Path, 7 - PathProps, 7 + type PathProps, 8 8 Stop, 9 - SvgProps, 9 + type SvgProps, 10 10 } from 'react-native-svg' 11 11 import {Image} from 'expo-image' 12 12
+1 -1
src/view/icons/Logomark.tsx
··· 1 - import Svg, {Path, PathProps, SvgProps} from 'react-native-svg' 1 + import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg' 2 2 3 3 import {usePalette} from '#/lib/hooks/usePalette' 4 4
+1 -1
src/view/icons/Logotype.tsx
··· 1 - import Svg, {Path, PathProps, SvgProps} from 'react-native-svg' 1 + import Svg, {Path, type PathProps, type SvgProps} from 'react-native-svg' 2 2 3 3 import {usePalette} from '#/lib/hooks/usePalette' 4 4
+4 -1
src/view/screens/CommunityGuidelines.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+4 -1
src/view/screens/CopyrightPolicy.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+1 -1
src/view/screens/NotFound.tsx
··· 9 9 } from '@react-navigation/native' 10 10 11 11 import {usePalette} from '#/lib/hooks/usePalette' 12 - import {NavigationProp} from '#/lib/routes/types' 12 + import {type NavigationProp} from '#/lib/routes/types' 13 13 import {s} from '#/lib/styles' 14 14 import {useSetMinimalShellMode} from '#/state/shell' 15 15 import {Button} from '#/view/com/util/forms/Button'
+4 -1
src/view/screens/PrivacyPolicy.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+4 -1
src/view/screens/ProfileFeedLikedBy.tsx
··· 3 3 import {useLingui} from '@lingui/react' 4 4 import {useFocusEffect} from '@react-navigation/native' 5 5 6 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 6 + import { 7 + type CommonNavigatorParams, 8 + type NativeStackScreenProps, 9 + } from '#/lib/routes/types' 7 10 import {makeRecordUri} from '#/lib/strings/url-helpers' 8 11 import {useSetMinimalShellMode} from '#/state/shell' 9 12 import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
-1061
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} 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 dropdownItems: DropdownItem[] = useMemo(() => { 510 - let items: DropdownItem[] = [ 511 - { 512 - testID: 'listHeaderDropdownShareBtn', 513 - label: isWeb ? _(msg`Copy link to list`) : _(msg`Share`), 514 - onPress: onPressShare, 515 - icon: { 516 - ios: { 517 - name: 'square.and.arrow.up', 518 - }, 519 - android: '', 520 - web: 'share', 521 - }, 522 - }, 523 - ] 524 - 525 - if (savedFeedConfig) { 526 - items.push({ 527 - testID: 'listHeaderDropdownRemoveFromFeedsBtn', 528 - label: _(msg`Remove from my feeds`), 529 - onPress: onRemoveFromSavedFeeds, 530 - icon: { 531 - ios: { 532 - name: 'trash', 533 - }, 534 - android: '', 535 - web: ['far', 'trash-can'], 536 - }, 537 - }) 538 - } 539 - 540 - if (isOwner) { 541 - items.push({label: 'separator'}) 542 - items.push({ 543 - testID: 'listHeaderDropdownEditBtn', 544 - label: _(msg`Edit list details`), 545 - onPress: onPressEdit, 546 - icon: { 547 - ios: { 548 - name: 'pencil', 549 - }, 550 - android: '', 551 - web: 'pen', 552 - }, 553 - }) 554 - items.push({ 555 - testID: 'listHeaderDropdownDeleteBtn', 556 - label: _(msg`Delete list`), 557 - onPress: deleteListPromptControl.open, 558 - icon: { 559 - ios: { 560 - name: 'trash', 561 - }, 562 - android: '', 563 - web: ['far', 'trash-can'], 564 - }, 565 - }) 566 - } else { 567 - items.push({label: 'separator'}) 568 - items.push({ 569 - testID: 'listHeaderDropdownReportBtn', 570 - label: _(msg`Report list`), 571 - onPress: onPressReport, 572 - icon: { 573 - ios: { 574 - name: 'exclamationmark.triangle', 575 - }, 576 - android: '', 577 - web: 'circle-exclamation', 578 - }, 579 - }) 580 - } 581 - if (isModList && isPinned) { 582 - items.push({label: 'separator'}) 583 - items.push({ 584 - testID: 'listHeaderDropdownUnpinBtn', 585 - label: _(msg`Unpin moderation list`), 586 - onPress: 587 - isPending || !savedFeedConfig 588 - ? undefined 589 - : () => removeSavedFeed(savedFeedConfig), 590 - icon: { 591 - ios: { 592 - name: 'pin', 593 - }, 594 - android: '', 595 - web: 'thumbtack', 596 - }, 597 - }) 598 - } 599 - if (isCurateList && (isBlocking || isMuting)) { 600 - items.push({label: 'separator'}) 601 - 602 - if (isMuting) { 603 - items.push({ 604 - testID: 'listHeaderDropdownMuteBtn', 605 - label: _(msg`Unmute list`), 606 - onPress: onUnsubscribeMute, 607 - icon: { 608 - ios: { 609 - name: 'eye', 610 - }, 611 - android: '', 612 - web: 'eye', 613 - }, 614 - }) 615 - } 616 - 617 - if (isBlocking) { 618 - items.push({ 619 - testID: 'listHeaderDropdownBlockBtn', 620 - label: _(msg`Unblock list`), 621 - onPress: onUnsubscribeBlock, 622 - icon: { 623 - ios: { 624 - name: 'person.fill.xmark', 625 - }, 626 - android: '', 627 - web: 'user-slash', 628 - }, 629 - }) 630 - } 631 - } 632 - return items 633 - }, [ 634 - _, 635 - onPressShare, 636 - isOwner, 637 - isModList, 638 - isPinned, 639 - isCurateList, 640 - onPressEdit, 641 - deleteListPromptControl.open, 642 - onPressReport, 643 - isPending, 644 - isBlocking, 645 - isMuting, 646 - onUnsubscribeMute, 647 - onUnsubscribeBlock, 648 - removeSavedFeed, 649 - savedFeedConfig, 650 - onRemoveFromSavedFeeds, 651 - ]) 652 - 653 - const subscribeDropdownItems: DropdownItem[] = useMemo(() => { 654 - return [ 655 - { 656 - testID: 'subscribeDropdownMuteBtn', 657 - label: _(msg`Mute accounts`), 658 - onPress: subscribeMutePromptControl.open, 659 - icon: { 660 - ios: { 661 - name: 'speaker.slash', 662 - }, 663 - android: '', 664 - web: 'user-slash', 665 - }, 666 - }, 667 - { 668 - testID: 'subscribeDropdownBlockBtn', 669 - label: _(msg`Block accounts`), 670 - onPress: subscribeBlockPromptControl.open, 671 - icon: { 672 - ios: { 673 - name: 'person.fill.xmark', 674 - }, 675 - android: '', 676 - web: 'ban', 677 - }, 678 - }, 679 - ] 680 - }, [_, subscribeMutePromptControl.open, subscribeBlockPromptControl.open]) 681 - 682 - const descriptionRT = useMemo( 683 - () => 684 - list.description 685 - ? new RichTextAPI({ 686 - text: list.description, 687 - facets: list.descriptionFacets, 688 - }) 689 - : undefined, 690 - [list], 691 - ) 692 - 693 - return ( 694 - <> 695 - <ProfileSubpageHeader 696 - href={makeListLink(list.creator.handle || list.creator.did || '', rkey)} 697 - title={list.name} 698 - avatar={list.avatar} 699 - isOwner={list.creator.did === currentAccount?.did} 700 - creator={list.creator} 701 - purpose={list.purpose} 702 - avatarType="list"> 703 - <ReportDialog 704 - control={reportDialogControl} 705 - subject={{ 706 - ...list, 707 - $type: 'app.bsky.graph.defs#listView', 708 - }} 709 - /> 710 - {isCurateList ? ( 711 - <Button 712 - testID={isPinned ? 'unpinBtn' : 'pinBtn'} 713 - type={isPinned ? 'default' : 'inverted'} 714 - label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)} 715 - onPress={onTogglePinned} 716 - disabled={isPending} 717 - /> 718 - ) : isModList ? ( 719 - isBlocking ? ( 720 - <Button 721 - testID="unblockBtn" 722 - type="default" 723 - label={_(msg`Unblock`)} 724 - onPress={onUnsubscribeBlock} 725 - /> 726 - ) : isMuting ? ( 727 - <Button 728 - testID="unmuteBtn" 729 - type="default" 730 - label={_(msg`Unmute`)} 731 - onPress={onUnsubscribeMute} 732 - /> 733 - ) : ( 734 - <NativeDropdown 735 - testID="subscribeBtn" 736 - items={subscribeDropdownItems} 737 - accessibilityLabel={_(msg`Subscribe to this list`)} 738 - accessibilityHint=""> 739 - <View style={[palInverted.view, styles.btn]}> 740 - <Text style={palInverted.text}> 741 - <Trans>Subscribe</Trans> 742 - </Text> 743 - </View> 744 - </NativeDropdown> 745 - ) 746 - ) : null} 747 - <NativeDropdown 748 - testID="headerDropdownBtn" 749 - items={dropdownItems} 750 - accessibilityLabel={_(msg`More options`)} 751 - accessibilityHint=""> 752 - <View style={[pal.viewLight, styles.btn]}> 753 - <FontAwesomeIcon 754 - icon="ellipsis" 755 - size={20} 756 - color={pal.colors.text} 757 - /> 758 - </View> 759 - </NativeDropdown> 760 - 761 - <Prompt.Basic 762 - control={deleteListPromptControl} 763 - title={_(msg`Delete this list?`)} 764 - description={_( 765 - msg`If you delete this list, you won't be able to recover it.`, 766 - )} 767 - onConfirm={onPressDelete} 768 - confirmButtonCta={_(msg`Delete`)} 769 - confirmButtonColor="negative" 770 - /> 771 - 772 - <Prompt.Basic 773 - control={subscribeMutePromptControl} 774 - title={_(msg`Mute these accounts?`)} 775 - description={_( 776 - msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`, 777 - )} 778 - onConfirm={onSubscribeMute} 779 - confirmButtonCta={_(msg`Mute list`)} 780 - /> 781 - 782 - <Prompt.Basic 783 - control={subscribeBlockPromptControl} 784 - title={_(msg`Block these accounts?`)} 785 - description={_( 786 - msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`, 787 - )} 788 - onConfirm={onSubscribeBlock} 789 - confirmButtonCta={_(msg`Block list`)} 790 - confirmButtonColor="negative" 791 - /> 792 - </ProfileSubpageHeader> 793 - {descriptionRT ? ( 794 - <View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}> 795 - <RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} /> 796 - </View> 797 - ) : null} 798 - </> 799 - ) 800 - } 801 - 802 - interface FeedSectionProps { 803 - feed: FeedDescriptor 804 - headerHeight: number 805 - scrollElRef: ListRef 806 - isFocused: boolean 807 - isOwner: boolean 808 - onPressAddUser: () => void 809 - } 810 - const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( 811 - function FeedSectionImpl( 812 - {feed, scrollElRef, headerHeight, isFocused, isOwner, onPressAddUser}, 813 - ref, 814 - ) { 815 - const queryClient = useQueryClient() 816 - const [hasNew, setHasNew] = React.useState(false) 817 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 818 - const isScreenFocused = useIsFocused() 819 - const {_} = useLingui() 820 - 821 - const onScrollToTop = useCallback(() => { 822 - scrollElRef.current?.scrollToOffset({ 823 - animated: isNative, 824 - offset: -headerHeight, 825 - }) 826 - queryClient.resetQueries({queryKey: FEED_RQKEY(feed)}) 827 - setHasNew(false) 828 - }, [scrollElRef, headerHeight, queryClient, feed, setHasNew]) 829 - React.useImperativeHandle(ref, () => ({ 830 - scrollToTop: onScrollToTop, 831 - })) 832 - 833 - React.useEffect(() => { 834 - if (!isScreenFocused) { 835 - return 836 - } 837 - return listenSoftReset(onScrollToTop) 838 - }, [onScrollToTop, isScreenFocused]) 839 - 840 - const renderPostsEmpty = useCallback(() => { 841 - return ( 842 - <View style={[a.gap_xl, a.align_center]}> 843 - <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> 844 - {isOwner && ( 845 - <NewButton 846 - label={_(msg`Start adding people`)} 847 - onPress={onPressAddUser} 848 - color="primary" 849 - size="small" 850 - variant="solid"> 851 - <ButtonIcon icon={PersonPlusIcon} /> 852 - <ButtonText> 853 - <Trans>Start adding people!</Trans> 854 - </ButtonText> 855 - </NewButton> 856 - )} 857 - </View> 858 - ) 859 - }, [_, onPressAddUser, isOwner]) 860 - 861 - return ( 862 - <View> 863 - <PostFeed 864 - testID="listFeed" 865 - enabled={isFocused} 866 - feed={feed} 867 - pollInterval={60e3} 868 - disablePoll={hasNew} 869 - scrollElRef={scrollElRef} 870 - onHasNew={setHasNew} 871 - onScrolledDownChange={setIsScrolledDown} 872 - renderEmptyState={renderPostsEmpty} 873 - headerOffset={headerHeight} 874 - /> 875 - {(isScrolledDown || hasNew) && ( 876 - <LoadLatestBtn 877 - onPress={onScrollToTop} 878 - label={_(msg`Load new posts`)} 879 - showIndicator={hasNew} 880 - /> 881 - )} 882 - </View> 883 - ) 884 - }, 885 - ) 886 - 887 - interface AboutSectionProps { 888 - list: AppBskyGraphDefs.ListView 889 - onPressAddUser: () => void 890 - headerHeight: number 891 - scrollElRef: ListRef 892 - } 893 - const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( 894 - function AboutSectionImpl( 895 - {list, onPressAddUser, headerHeight, scrollElRef}, 896 - ref, 897 - ) { 898 - const {_} = useLingui() 899 - const {currentAccount} = useSession() 900 - const {isMobile} = useWebMediaQueries() 901 - const [isScrolledDown, setIsScrolledDown] = React.useState(false) 902 - const isOwner = list.creator.did === currentAccount?.did 903 - 904 - const onScrollToTop = useCallback(() => { 905 - scrollElRef.current?.scrollToOffset({ 906 - animated: isNative, 907 - offset: -headerHeight, 908 - }) 909 - }, [scrollElRef, headerHeight]) 910 - 911 - React.useImperativeHandle(ref, () => ({ 912 - scrollToTop: onScrollToTop, 913 - })) 914 - 915 - const renderHeader = React.useCallback(() => { 916 - if (!isOwner) { 917 - return <View /> 918 - } 919 - if (isMobile) { 920 - return ( 921 - <View style={[a.px_sm, a.py_sm]}> 922 - <NewButton 923 - testID="addUserBtn" 924 - label={_(msg`Add a user to this list`)} 925 - onPress={onPressAddUser} 926 - color="primary" 927 - size="small" 928 - variant="outline" 929 - style={[a.py_md]}> 930 - <ButtonIcon icon={PersonPlusIcon} /> 931 - <ButtonText> 932 - <Trans>Add people</Trans> 933 - </ButtonText> 934 - </NewButton> 935 - </View> 936 - ) 937 - } 938 - return ( 939 - <View style={[a.px_lg, a.py_md, a.flex_row_reverse]}> 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="ghost" 947 - style={[a.py_sm]}> 948 - <ButtonIcon icon={PersonPlusIcon} /> 949 - <ButtonText> 950 - <Trans>Add people</Trans> 951 - </ButtonText> 952 - </NewButton> 953 - </View> 954 - ) 955 - }, [isOwner, _, onPressAddUser, isMobile]) 956 - 957 - const renderEmptyState = useCallback(() => { 958 - return ( 959 - <View style={[a.gap_xl, a.align_center]}> 960 - <EmptyState 961 - icon="users-slash" 962 - message={_(msg`This list is empty.`)} 963 - /> 964 - {isOwner && ( 965 - <NewButton 966 - testID="emptyStateAddUserBtn" 967 - label={_(msg`Start adding people`)} 968 - onPress={onPressAddUser} 969 - color="primary" 970 - size="small" 971 - variant="solid"> 972 - <ButtonIcon icon={PersonPlusIcon} /> 973 - <ButtonText> 974 - <Trans>Start adding people!</Trans> 975 - </ButtonText> 976 - </NewButton> 977 - )} 978 - </View> 979 - ) 980 - }, [_, onPressAddUser, isOwner]) 981 - 982 - return ( 983 - <View> 984 - <ListMembers 985 - testID="listItems" 986 - list={list.uri} 987 - scrollElRef={scrollElRef} 988 - renderHeader={renderHeader} 989 - renderEmptyState={renderEmptyState} 990 - headerOffset={headerHeight} 991 - onScrolledDownChange={setIsScrolledDown} 992 - /> 993 - {isScrolledDown && ( 994 - <LoadLatestBtn 995 - onPress={onScrollToTop} 996 - label={_(msg`Scroll to top`)} 997 - showIndicator={false} 998 - /> 999 - )} 1000 - </View> 1001 - ) 1002 - }, 1003 - ) 1004 - 1005 - function ErrorScreen({error}: {error: string}) { 1006 - const pal = usePalette('default') 1007 - const navigation = useNavigation<NavigationProp>() 1008 - const {_} = useLingui() 1009 - const onPressBack = useCallback(() => { 1010 - if (navigation.canGoBack()) { 1011 - navigation.goBack() 1012 - } else { 1013 - navigation.navigate('Home') 1014 - } 1015 - }, [navigation]) 1016 - 1017 - return ( 1018 - <View 1019 - style={[ 1020 - pal.view, 1021 - pal.border, 1022 - { 1023 - paddingHorizontal: 18, 1024 - paddingVertical: 14, 1025 - borderTopWidth: StyleSheet.hairlineWidth, 1026 - }, 1027 - ]}> 1028 - <Text type="title-lg" style={[pal.text, s.mb10]}> 1029 - <Trans>Could not load list</Trans> 1030 - </Text> 1031 - <Text type="md" style={[pal.text, s.mb20]}> 1032 - {error} 1033 - </Text> 1034 - 1035 - <View style={{flexDirection: 'row'}}> 1036 - <Button 1037 - type="default" 1038 - accessibilityLabel={_(msg`Go back`)} 1039 - accessibilityHint={_(msg`Returns to previous page`)} 1040 - onPress={onPressBack} 1041 - style={{flexShrink: 1}}> 1042 - <Text type="button" style={pal.text}> 1043 - <Trans>Go Back</Trans> 1044 - </Text> 1045 - </Button> 1046 - </View> 1047 - </View> 1048 - ) 1049 - } 1050 - 1051 - const styles = StyleSheet.create({ 1052 - btn: { 1053 - flexDirection: 'row', 1054 - alignItems: 'center', 1055 - gap: 6, 1056 - paddingVertical: 7, 1057 - paddingHorizontal: 14, 1058 - borderRadius: 50, 1059 - marginLeft: 6, 1060 - }, 1061 - })
-450
src/view/screens/SavedFeeds.tsx
··· 1 - import React from 'react' 2 - import {ActivityIndicator, Pressable, StyleSheet, View} from 'react-native' 3 - import Animated, {LinearTransition} from 'react-native-reanimated' 4 - import {type AppBskyActorDefs} from '@atproto/api' 5 - import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' 6 - import {msg, Trans} from '@lingui/macro' 7 - import {useLingui} from '@lingui/react' 8 - import {useFocusEffect} from '@react-navigation/native' 9 - import {useNavigation} from '@react-navigation/native' 10 - import {type NativeStackScreenProps} from '@react-navigation/native-stack' 11 - 12 - import {useHaptics} from '#/lib/haptics' 13 - import {usePalette} from '#/lib/hooks/usePalette' 14 - import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' 15 - import { 16 - type CommonNavigatorParams, 17 - type NavigationProp, 18 - } from '#/lib/routes/types' 19 - import {colors, s} from '#/lib/styles' 20 - import {logger} from '#/logger' 21 - import { 22 - useOverwriteSavedFeedsMutation, 23 - usePreferencesQuery, 24 - } from '#/state/queries/preferences' 25 - import {type UsePreferencesQueryResponse} from '#/state/queries/preferences/types' 26 - import {useSetMinimalShellMode} from '#/state/shell' 27 - import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard' 28 - import {TextLink} from '#/view/com/util/Link' 29 - import {Text} from '#/view/com/util/text/Text' 30 - import * as Toast from '#/view/com/util/Toast' 31 - import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed' 32 - import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType' 33 - import {atoms as a, useTheme} from '#/alf' 34 - import {Button, ButtonIcon, ButtonText} from '#/components/Button' 35 - import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline' 36 - import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk' 37 - import * as Layout from '#/components/Layout' 38 - import {Loader} from '#/components/Loader' 39 - import {Text as NewText} from '#/components/Typography' 40 - 41 - type Props = NativeStackScreenProps<CommonNavigatorParams, 'SavedFeeds'> 42 - export function SavedFeeds({}: Props) { 43 - const {data: preferences} = usePreferencesQuery() 44 - if (!preferences) { 45 - return <View /> 46 - } 47 - return <SavedFeedsInner preferences={preferences} /> 48 - } 49 - 50 - function SavedFeedsInner({ 51 - preferences, 52 - }: { 53 - preferences: UsePreferencesQueryResponse 54 - }) { 55 - const pal = usePalette('default') 56 - const {_} = useLingui() 57 - const {isMobile, isDesktop} = useWebMediaQueries() 58 - const setMinimalShellMode = useSetMinimalShellMode() 59 - const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} = 60 - useOverwriteSavedFeedsMutation() 61 - const navigation = useNavigation<NavigationProp>() 62 - 63 - /* 64 - * Use optimistic data if exists and no error, otherwise fallback to remote 65 - * data 66 - */ 67 - const [currentFeeds, setCurrentFeeds] = React.useState( 68 - () => preferences.savedFeeds || [], 69 - ) 70 - const hasUnsavedChanges = currentFeeds !== preferences.savedFeeds 71 - const pinnedFeeds = currentFeeds.filter(f => f.pinned) 72 - const unpinnedFeeds = currentFeeds.filter(f => !f.pinned) 73 - const noSavedFeedsOfAnyType = pinnedFeeds.length + unpinnedFeeds.length === 0 74 - const noFollowingFeed = 75 - currentFeeds.every(f => f.type !== 'timeline') && !noSavedFeedsOfAnyType 76 - 77 - useFocusEffect( 78 - React.useCallback(() => { 79 - setMinimalShellMode(false) 80 - }, [setMinimalShellMode]), 81 - ) 82 - 83 - const onSaveChanges = React.useCallback(async () => { 84 - try { 85 - await overwriteSavedFeeds(currentFeeds) 86 - Toast.show(_(msg({message: 'Feeds updated!', context: 'toast'}))) 87 - if (navigation.canGoBack()) { 88 - navigation.goBack() 89 - } else { 90 - navigation.navigate('Feeds') 91 - } 92 - } catch (e) { 93 - Toast.show(_(msg`There was an issue contacting the server`), 'xmark') 94 - logger.error('Failed to toggle pinned feed', {message: e}) 95 - } 96 - }, [_, overwriteSavedFeeds, currentFeeds, navigation]) 97 - 98 - return ( 99 - <Layout.Screen> 100 - <Layout.Header.Outer> 101 - <Layout.Header.BackButton /> 102 - <Layout.Header.Content align="left"> 103 - <Layout.Header.TitleText> 104 - <Trans>Feeds</Trans> 105 - </Layout.Header.TitleText> 106 - </Layout.Header.Content> 107 - <Button 108 - testID="saveChangesBtn" 109 - size="small" 110 - variant={hasUnsavedChanges ? 'solid' : 'solid'} 111 - color={hasUnsavedChanges ? 'primary' : 'secondary'} 112 - onPress={onSaveChanges} 113 - label={_(msg`Save changes`)} 114 - disabled={isOverwritePending || !hasUnsavedChanges}> 115 - <ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} /> 116 - <ButtonText> 117 - {isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>} 118 - </ButtonText> 119 - </Button> 120 - </Layout.Header.Outer> 121 - 122 - <Layout.Content> 123 - {noSavedFeedsOfAnyType && ( 124 - <View style={[pal.border, a.border_b]}> 125 - <NoSavedFeedsOfAnyType /> 126 - </View> 127 - )} 128 - 129 - <View style={[pal.text, pal.border, styles.title]}> 130 - <Text type="title" style={pal.text}> 131 - <Trans>Pinned Feeds</Trans> 132 - </Text> 133 - </View> 134 - 135 - {preferences ? ( 136 - !pinnedFeeds.length ? ( 137 - <View 138 - style={[ 139 - pal.border, 140 - isMobile && s.flex1, 141 - pal.viewLight, 142 - styles.empty, 143 - ]}> 144 - <Text type="lg" style={[pal.text]}> 145 - <Trans>You don't have any pinned feeds.</Trans> 146 - </Text> 147 - </View> 148 - ) : ( 149 - pinnedFeeds.map(f => ( 150 - <ListItem 151 - key={f.id} 152 - feed={f} 153 - isPinned 154 - currentFeeds={currentFeeds} 155 - setCurrentFeeds={setCurrentFeeds} 156 - preferences={preferences} 157 - /> 158 - )) 159 - ) 160 - ) : ( 161 - <ActivityIndicator style={{marginTop: 20}} /> 162 - )} 163 - 164 - {noFollowingFeed && ( 165 - <View style={[pal.border, a.border_b]}> 166 - <NoFollowingFeed /> 167 - </View> 168 - )} 169 - 170 - <View style={[pal.text, pal.border, styles.title]}> 171 - <Text type="title" style={pal.text}> 172 - <Trans>Saved Feeds</Trans> 173 - </Text> 174 - </View> 175 - {preferences ? ( 176 - !unpinnedFeeds.length ? ( 177 - <View 178 - style={[ 179 - pal.border, 180 - isMobile && s.flex1, 181 - pal.viewLight, 182 - styles.empty, 183 - ]}> 184 - <Text type="lg" style={[pal.text]}> 185 - <Trans>You don't have any saved feeds.</Trans> 186 - </Text> 187 - </View> 188 - ) : ( 189 - unpinnedFeeds.map(f => ( 190 - <ListItem 191 - key={f.id} 192 - feed={f} 193 - isPinned={false} 194 - currentFeeds={currentFeeds} 195 - setCurrentFeeds={setCurrentFeeds} 196 - preferences={preferences} 197 - /> 198 - )) 199 - ) 200 - ) : ( 201 - <ActivityIndicator style={{marginTop: 20}} /> 202 - )} 203 - 204 - <View style={styles.footerText}> 205 - <Text type="sm" style={pal.textLight}> 206 - <Trans> 207 - Feeds are custom algorithms that users build with a little coding 208 - expertise.{' '} 209 - <TextLink 210 - type="sm" 211 - style={pal.link} 212 - href="https://github.com/bluesky-social/feed-generator" 213 - text={_(msg`See this guide`)} 214 - />{' '} 215 - for more information. 216 - </Trans> 217 - </Text> 218 - </View> 219 - </Layout.Content> 220 - </Layout.Screen> 221 - ) 222 - } 223 - 224 - function ListItem({ 225 - feed, 226 - isPinned, 227 - currentFeeds, 228 - setCurrentFeeds, 229 - }: { 230 - feed: AppBskyActorDefs.SavedFeed 231 - isPinned: boolean 232 - currentFeeds: AppBskyActorDefs.SavedFeed[] 233 - setCurrentFeeds: React.Dispatch<AppBskyActorDefs.SavedFeed[]> 234 - preferences: UsePreferencesQueryResponse 235 - }) { 236 - const {_} = useLingui() 237 - const pal = usePalette('default') 238 - const playHaptic = useHaptics() 239 - const feedUri = feed.value 240 - 241 - const onTogglePinned = React.useCallback(async () => { 242 - playHaptic() 243 - setCurrentFeeds( 244 - currentFeeds.map(f => 245 - f.id === feed.id ? {...feed, pinned: !feed.pinned} : f, 246 - ), 247 - ) 248 - }, [playHaptic, feed, currentFeeds, setCurrentFeeds]) 249 - 250 - const onPressUp = React.useCallback(async () => { 251 - if (!isPinned) return 252 - 253 - const nextFeeds = currentFeeds.slice() 254 - const ids = currentFeeds.map(f => f.id) 255 - const index = ids.indexOf(feed.id) 256 - const nextIndex = index - 1 257 - 258 - if (index === -1 || index === 0) return 259 - ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 260 - nextFeeds[nextIndex], 261 - nextFeeds[index], 262 - ] 263 - 264 - setCurrentFeeds(nextFeeds) 265 - }, [feed, isPinned, setCurrentFeeds, currentFeeds]) 266 - 267 - const onPressDown = React.useCallback(async () => { 268 - if (!isPinned) return 269 - 270 - const nextFeeds = currentFeeds.slice() 271 - const ids = currentFeeds.map(f => f.id) 272 - const index = ids.indexOf(feed.id) 273 - const nextIndex = index + 1 274 - 275 - if (index === -1 || index >= nextFeeds.filter(f => f.pinned).length - 1) 276 - return 277 - ;[nextFeeds[index], nextFeeds[nextIndex]] = [ 278 - nextFeeds[nextIndex], 279 - nextFeeds[index], 280 - ] 281 - 282 - setCurrentFeeds(nextFeeds) 283 - }, [feed, isPinned, setCurrentFeeds, currentFeeds]) 284 - 285 - const onPressRemove = React.useCallback(async () => { 286 - playHaptic() 287 - setCurrentFeeds(currentFeeds.filter(f => f.id !== feed.id)) 288 - }, [playHaptic, feed, currentFeeds, setCurrentFeeds]) 289 - 290 - return ( 291 - <Animated.View 292 - style={[styles.itemContainer, pal.border]} 293 - layout={LinearTransition.duration(100)}> 294 - {feed.type === 'timeline' ? ( 295 - <FollowingFeedCard /> 296 - ) : ( 297 - <FeedSourceCard 298 - key={feedUri} 299 - feedUri={feedUri} 300 - style={[isPinned && a.pr_sm]} 301 - showMinimalPlaceholder 302 - hideTopBorder={true} 303 - /> 304 - )} 305 - {isPinned ? ( 306 - <> 307 - <Pressable 308 - accessibilityRole="button" 309 - onPress={onPressUp} 310 - hitSlop={5} 311 - style={state => ({ 312 - backgroundColor: pal.viewLight.backgroundColor, 313 - paddingHorizontal: 12, 314 - paddingVertical: 10, 315 - borderRadius: 4, 316 - marginRight: 8, 317 - opacity: state.hovered || state.pressed ? 0.5 : 1, 318 - })} 319 - testID={`feed-${feed.type}-moveUp`}> 320 - <FontAwesomeIcon 321 - icon="arrow-up" 322 - size={14} 323 - style={[pal.textLight]} 324 - /> 325 - </Pressable> 326 - <Pressable 327 - accessibilityRole="button" 328 - onPress={onPressDown} 329 - hitSlop={5} 330 - style={state => ({ 331 - backgroundColor: pal.viewLight.backgroundColor, 332 - paddingHorizontal: 12, 333 - paddingVertical: 10, 334 - borderRadius: 4, 335 - marginRight: 8, 336 - opacity: state.hovered || state.pressed ? 0.5 : 1, 337 - })} 338 - testID={`feed-${feed.type}-moveDown`}> 339 - <FontAwesomeIcon 340 - icon="arrow-down" 341 - size={14} 342 - style={[pal.textLight]} 343 - /> 344 - </Pressable> 345 - </> 346 - ) : ( 347 - <Pressable 348 - testID={`feed-${feedUri}-toggleSave`} 349 - accessibilityRole="button" 350 - accessibilityLabel={_(msg`Remove from my feeds`)} 351 - accessibilityHint="" 352 - onPress={onPressRemove} 353 - hitSlop={5} 354 - style={state => ({ 355 - marginRight: 8, 356 - paddingHorizontal: 12, 357 - paddingVertical: 10, 358 - borderRadius: 4, 359 - opacity: state.hovered || state.focused ? 0.5 : 1, 360 - })}> 361 - <FontAwesomeIcon 362 - icon={['far', 'trash-can']} 363 - size={19} 364 - color={pal.colors.icon} 365 - /> 366 - </Pressable> 367 - )} 368 - <View style={{paddingRight: 16}}> 369 - <Pressable 370 - accessibilityRole="button" 371 - hitSlop={5} 372 - onPress={onTogglePinned} 373 - style={state => ({ 374 - backgroundColor: pal.viewLight.backgroundColor, 375 - paddingHorizontal: 12, 376 - paddingVertical: 10, 377 - borderRadius: 4, 378 - opacity: state.hovered || state.focused ? 0.5 : 1, 379 - })} 380 - testID={`feed-${feed.type}-togglePin`}> 381 - <FontAwesomeIcon 382 - icon="thumb-tack" 383 - size={14} 384 - color={isPinned ? colors.blue3 : pal.colors.icon} 385 - /> 386 - </Pressable> 387 - </View> 388 - </Animated.View> 389 - ) 390 - } 391 - 392 - function FollowingFeedCard() { 393 - const t = useTheme() 394 - return ( 395 - <View style={[a.flex_row, a.align_center, a.flex_1, a.p_lg]}> 396 - <View 397 - style={[ 398 - a.align_center, 399 - a.justify_center, 400 - a.rounded_sm, 401 - a.mr_md, 402 - { 403 - width: 36, 404 - height: 36, 405 - backgroundColor: t.palette.primary_500, 406 - }, 407 - ]}> 408 - <FilterTimeline 409 - style={[ 410 - { 411 - width: 22, 412 - height: 22, 413 - }, 414 - ]} 415 - fill={t.palette.white} 416 - /> 417 - </View> 418 - <View style={[a.flex_1, a.flex_row, a.gap_sm, a.align_center]}> 419 - <NewText style={[a.text_sm, a.font_bold, a.leading_snug]}> 420 - <Trans context="feed-name">Following</Trans> 421 - </NewText> 422 - </View> 423 - </View> 424 - ) 425 - } 426 - 427 - const styles = StyleSheet.create({ 428 - empty: { 429 - paddingHorizontal: 20, 430 - paddingVertical: 20, 431 - borderRadius: 8, 432 - marginHorizontal: 10, 433 - marginTop: 10, 434 - }, 435 - title: { 436 - paddingHorizontal: 14, 437 - paddingTop: 20, 438 - paddingBottom: 10, 439 - borderBottomWidth: StyleSheet.hairlineWidth, 440 - }, 441 - itemContainer: { 442 - flexDirection: 'row', 443 - alignItems: 'center', 444 - borderBottomWidth: StyleSheet.hairlineWidth, 445 - }, 446 - footerText: { 447 - paddingHorizontal: 26, 448 - paddingVertical: 22, 449 - }, 450 - })
+1 -1
src/view/screens/Storybook/Dialogs.tsx
··· 22 22 React.useState<boolean>() 23 23 const [shouldRenderUnmountTest, setShouldRenderUnmountTest] = 24 24 React.useState(false) 25 - const unmountTestInterval = React.useRef<number>() 25 + const unmountTestInterval = React.useRef<number>(undefined) 26 26 27 27 const onUnmountTestStartPressWithClose = () => { 28 28 setShouldRenderUnmountTest(true)
+1 -1
src/view/screens/Storybook/ListContained.tsx
··· 2 2 import {View} from 'react-native' 3 3 4 4 import {ScrollProvider} from '#/lib/ScrollContext' 5 - import {List, ListMethods} from '#/view/com/util/List' 5 + import {List, type ListMethods} from '#/view/com/util/List' 6 6 import {Button, ButtonText} from '#/components/Button' 7 7 import * as Toggle from '#/components/forms/Toggle' 8 8 import {Text} from '#/components/Typography'
+4 -1
src/view/screens/Support.tsx
··· 5 5 6 6 import {HELP_DESK_URL} from '#/lib/constants' 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+4 -1
src/view/screens/TermsOfService.tsx
··· 5 5 import {useFocusEffect} from '@react-navigation/native' 6 6 7 7 import {usePalette} from '#/lib/hooks/usePalette' 8 - import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' 8 + import { 9 + type CommonNavigatorParams, 10 + type NativeStackScreenProps, 11 + } from '#/lib/routes/types' 9 12 import {s} from '#/lib/styles' 10 13 import {useSetMinimalShellMode} from '#/state/shell' 11 14 import {TextLink} from '#/view/com/util/Link'
+1 -1
src/view/shell/Drawer.tsx
··· 1 - import React, {type ComponentProps} from 'react' 1 + import React, {type ComponentProps, type JSX} from 'react' 2 2 import {Linking, ScrollView, TouchableOpacity, View} from 'react-native' 3 3 import {useSafeAreaInsets} from 'react-native-safe-area-context' 4 4 import {msg, Plural, plural, Trans} from '@lingui/macro'
+1 -1
src/view/shell/bottom-bar/BottomBar.tsx
··· 1 - import {useCallback} from 'react' 1 + import {type JSX, useCallback} from 'react' 2 2 import {type GestureResponderEvent, View} from 'react-native' 3 3 import Animated from 'react-native-reanimated' 4 4 import {useSafeAreaInsets} from 'react-native-safe-area-context'
+1 -1
src/view/shell/bottom-bar/BottomBarWeb.tsx
··· 230 230 } 231 231 232 232 const NavItem: React.FC<{ 233 - children: (props: {isActive: boolean}) => React.ReactChild 233 + children: (props: {isActive: boolean}) => React.ReactNode 234 234 href: string 235 235 routeName: string 236 236 hasNew?: boolean
+1 -1
src/view/shell/desktop/LeftNav.tsx
··· 1 - import {useCallback, useMemo, useState} from 'react' 1 + import {type JSX, useCallback, useMemo, useState} from 'react' 2 2 import {StyleSheet, View} from 'react-native' 3 3 import {type AppBskyActorDefs} from '@atproto/api' 4 4 import {msg, plural, Trans} from '@lingui/macro'
+22 -41
yarn.lock
··· 7589 7589 resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0" 7590 7590 integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA== 7591 7591 7592 - "@types/prop-types@*": 7593 - version "15.7.5" 7594 - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" 7595 - integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== 7596 - 7597 7592 "@types/psl@^1.1.1": 7598 7593 version "1.1.1" 7599 7594 resolved "https://registry.yarnpkg.com/@types/psl/-/psl-1.1.1.tgz#3ba9e6d4bd2a32652a639fd5df7e539151d0a3b2" ··· 7609 7604 resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" 7610 7605 integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== 7611 7606 7612 - "@types/react-dom@^19.1.2": 7613 - version "19.1.3" 7614 - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.3.tgz#3f0c60804441bf34d19f8dd0d44405c0c0e21bfa" 7615 - integrity sha512-rJXC08OG0h3W6wDMFxQrZF00Kq6qQvw0djHRdzl3U5DnIERz0MRce3WVc7IS6JYBwtaP/DwYtRRjVlvivNveKg== 7607 + "@types/react-dom@^19.1.8": 7608 + version "19.1.9" 7609 + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-19.1.9.tgz#5ab695fce1e804184767932365ae6569c11b4b4b" 7610 + integrity sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ== 7616 7611 7617 - "@types/react-responsive@^8.0.5": 7618 - version "8.0.5" 7619 - resolved "https://registry.yarnpkg.com/@types/react-responsive/-/react-responsive-8.0.5.tgz#77769862d2a0711434feb972be08e3e6c334440a" 7620 - integrity sha512-k3gQJgI87oP5IrVZe//3LKJFnAeFaqqWmmtl5eoYL2H3HqFcIhUaE30kRK1CsW3DHdojZxcVj4ZNc2ClsEu2PA== 7612 + "@types/react@^19.1.12": 7613 + version "19.1.12" 7614 + resolved "https://registry.yarnpkg.com/@types/react/-/react-19.1.12.tgz#7bfaa76aabbb0b4fe0493c21a3a7a93d33e8937b" 7615 + integrity sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w== 7621 7616 dependencies: 7622 - "@types/react" "*" 7623 - 7624 - "@types/react@*", "@types/react@^18": 7625 - version "18.2.20" 7626 - resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.20.tgz#1605557a83df5c8a2cc4eeb743b3dfc0eb6aaeb2" 7627 - integrity sha512-WKNtmsLWJM/3D5mG4U84cysVY31ivmyw85dE84fOCk5Hx78wezB/XEjVPWl2JTZ5FkEeaTJf+VgUAUn3PE7Isw== 7628 - dependencies: 7629 - "@types/prop-types" "*" 7630 - "@types/scheduler" "*" 7631 7617 csstype "^3.0.2" 7632 7618 7633 7619 "@types/retry@0.12.0": 7634 7620 version "0.12.0" 7635 7621 resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" 7636 7622 integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== 7637 - 7638 - "@types/scheduler@*": 7639 - version "0.16.3" 7640 - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" 7641 - integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== 7642 7623 7643 7624 "@types/semver@^7.3.12": 7644 7625 version "7.5.0" ··· 14515 14496 resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" 14516 14497 integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== 14517 14498 14518 - matchmediaquery@^0.3.0: 14519 - version "0.3.1" 14520 - resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.3.1.tgz#8247edc47e499ebb7c58f62a9ff9ccf5b815c6d7" 14521 - integrity sha512-Hlk20WQHRIm9EE9luN1kjRjYXAQToHOIAHPJn9buxBwuhfTHoKUcX+lXBbxc85DVQfXYbEQ4HcwQdd128E3qHQ== 14499 + matchmediaquery@^0.4.2: 14500 + version "0.4.2" 14501 + resolved "https://registry.yarnpkg.com/matchmediaquery/-/matchmediaquery-0.4.2.tgz#22582bd4ae63ad9f54c53001bba80cbed0f7eafa" 14502 + integrity sha512-wrZpoT50ehYOudhDjt/YvUJc6eUzcdFPdmbizfgvswCKNHD1/OBOHYJpHie+HXpu6bSkEGieFMYk6VuutaiRfA== 14522 14503 dependencies: 14523 14504 css-mediaquery "^0.1.2" 14524 14505 ··· 17124 17105 use-callback-ref "^1.3.3" 17125 17106 use-sidecar "^1.1.3" 17126 17107 17127 - react-responsive@^9.0.2: 17128 - version "9.0.2" 17129 - resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-9.0.2.tgz#34531ca77a61e7a8775714016d21241df7e4205c" 17130 - integrity sha512-+4CCab7z8G8glgJoRjAwocsgsv6VA2w7JPxFWHRc7kvz8mec1/K5LutNC2MG28Mn8mu6+bu04XZxHv5gyfT7xQ== 17108 + react-responsive@^10.0.1: 17109 + version "10.0.1" 17110 + resolved "https://registry.yarnpkg.com/react-responsive/-/react-responsive-10.0.1.tgz#293d4d2562da93409861216f0110d146c5676eb3" 17111 + integrity sha512-OM5/cRvbtUWEX8le8RCT8scA8y2OPtb0Q/IViEyCEM5FBN8lRrkUOZnu87I88A6njxDldvxG+rLBxWiA7/UM9g== 17131 17112 dependencies: 17132 17113 hyphenate-style-name "^1.0.0" 17133 - matchmediaquery "^0.3.0" 17114 + matchmediaquery "^0.4.2" 17134 17115 prop-types "^15.6.1" 17135 - shallow-equal "^1.2.1" 17116 + shallow-equal "^3.1.0" 17136 17117 17137 17118 react-server-dom-webpack@~19.0.0: 17138 17119 version "19.0.0" ··· 17985 17966 resolved "https://registry.yarnpkg.com/sf-symbols-typescript/-/sf-symbols-typescript-1.0.0.tgz#94e9210bf27e7583f9749a0d07bd4f4937ea488f" 17986 17967 integrity sha512-DkS7q3nN68dEMb4E18HFPDAvyrjDZK9YAQQF2QxeFu9gp2xRDXFMF8qLJ1EmQ/qeEGQmop4lmMM1WtYJTIcCMw== 17987 17968 17988 - shallow-equal@^1.2.1: 17989 - version "1.2.1" 17990 - resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-1.2.1.tgz#4c16abfa56043aa20d050324efa68940b0da79da" 17991 - integrity sha512-S4vJDjHHMBaiZuT9NPb616CSmLf618jawtv3sufLl6ivK8WocjAo58cXwbRV1cgqxH0Qbv+iUt6m05eqEa2IRA== 17969 + shallow-equal@^3.1.0: 17970 + version "3.1.0" 17971 + resolved "https://registry.yarnpkg.com/shallow-equal/-/shallow-equal-3.1.0.tgz#e7a54bac629c7f248eff6c2f5b63122ba4320bec" 17972 + integrity sha512-pfVOw8QZIXpMbhBWvzBISicvToTiM5WBF1EeAUZDDSb5Dt29yl4AYbyywbJFSEsRUMr7gJaxqCdr4L3tQf9wVg== 17992 17973 17993 17974 sharp@^0.33.5: 17994 17975 version "0.33.5"