+3
-5
package.json
+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
+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
+
[]
+1
-1
src/Splash.tsx
+1
-1
src/Splash.tsx
···
15
15
withTiming,
16
16
} from 'react-native-reanimated'
17
17
import {useSafeAreaInsets} from 'react-native-safe-area-context'
18
-
import Svg, {Path, SvgProps} from 'react-native-svg'
18
+
import Svg, {Path, type SvgProps} from 'react-native-svg'
19
19
import {Image} from 'expo-image'
20
20
import * as SplashScreen from 'expo-splash-screen'
21
21
+1
-1
src/alf/types.ts
+1
-1
src/alf/types.ts
-1
src/alf/typography.tsx
-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
-1
src/alf/util/themeSelector.ts
+2
-2
src/alf/util/useColorModeTheme.ts
+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
src/alf/util/useGutters.ts
+2
-2
src/components/Button.tsx
+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
+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
-1
src/components/ContextMenu/types.ts
+1
-2
src/components/Dialog/types.ts
+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
src/components/Dialog/utils.ts
+2
-2
src/components/Fill.tsx
+2
-2
src/components/Fill.tsx
+1
-1
src/components/GradientFill.tsx
+1
-1
src/components/GradientFill.tsx
+4
-4
src/components/IconCircle.tsx
+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
+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
src/components/LikedByList.tsx
+2
-2
src/components/LinearGradientBackground.tsx
+2
-2
src/components/LinearGradientBackground.tsx
-1
src/components/Lists.tsx
-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
+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
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
+2
-2
src/components/MediaInsetBorder.tsx
+8
-8
src/components/Menu/types.ts
+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
+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
+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
+1
-1
src/components/Portal.tsx
+1
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
+1
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerWeb.tsx
···
139
139
playlist: string
140
140
setHasSubtitleTrack: (v: boolean) => void
141
141
setError: (v: Error | null) => void
142
-
videoRef: React.RefObject<HTMLVideoElement>
142
+
videoRef: React.RefObject<HTMLVideoElement | null>
143
143
setHlsLoading: (v: boolean) => void
144
144
}) {
145
145
const [Hls, setHls] = useState<typeof HlsTypes.default | undefined>(
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/ControlButton.tsx
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx
+5
-5
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VideoControls.tsx
+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
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx
+1
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
+1
-1
src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/utils.tsx
···
4
4
import {logger} from '#/logger'
5
5
import {useVideoVolumeState} from '#/components/Post/Embed/VideoEmbed/VideoVolumeContext'
6
6
7
-
export function useVideoElement(ref: RefObject<HTMLVideoElement>) {
7
+
export function useVideoElement(ref: RefObject<HTMLVideoElement | null>) {
8
8
const [playing, setPlaying] = useState(false)
9
9
const [muted, setMuted] = useState(true)
10
10
const [currentTime, setCurrentTime] = useState(0)
+1
-2
src/components/Post/Embed/VideoEmbed/index.web.tsx
+1
-2
src/components/Post/Embed/VideoEmbed/index.web.tsx
···
10
10
import {type AppBskyEmbedVideo} from '@atproto/api'
11
11
import {msg} from '@lingui/macro'
12
12
import {useLingui} from '@lingui/react'
13
-
import type React from 'react'
14
13
15
14
import {isFirefox} from '#/lib/browser'
16
15
import {ErrorBoundary} from '#/view/com/util/ErrorBoundary'
···
38
37
useActiveVideoWeb()
39
38
const [onScreen, setOnScreen] = useState(false)
40
39
const [isFullscreen] = useFullscreen()
41
-
const lastKnownTime = useRef<number | undefined>()
40
+
const lastKnownTime = useRef<number | undefined>(undefined)
42
41
43
42
useEffect(() => {
44
43
if (!ref.current) return
-1
src/components/PostControls/PostMenu/index.tsx
-1
src/components/PostControls/PostMenu/index.tsx
-2
src/components/ProfileHoverCard/types.ts
-2
src/components/ProfileHoverCard/types.ts
+3
-3
src/components/ProgressGuide/FollowDialog.tsx
+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
-1
src/components/ProgressGuide/List.tsx
+2
-2
src/components/ProgressGuide/Toast.tsx
+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
+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
+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
+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
+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
-1
src/components/ReportDialog/types.ts
+2
-2
src/components/RichTextTag.tsx
+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
+1
-1
src/components/Select/types.ts
+3
-3
src/components/StarterPack/Main/PostsList.tsx
+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
+10
-7
src/components/StarterPack/Main/ProfilesList.tsx
···
1
1
import React, {useCallback} from 'react'
2
-
import {ListRenderItemInfo, View} from 'react-native'
2
+
import {type ListRenderItemInfo, View} from 'react-native'
3
3
import {
4
-
AppBskyActorDefs,
5
-
AppBskyGraphGetList,
4
+
type AppBskyActorDefs,
5
+
type AppBskyGraphGetList,
6
6
AtUri,
7
-
ModerationOpts,
7
+
type ModerationOpts,
8
8
} from '@atproto/api'
9
-
import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
9
+
import {
10
+
type InfiniteData,
11
+
type UseInfiniteQueryResult,
12
+
} from '@tanstack/react-query'
10
13
11
14
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
12
15
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
···
14
17
import {isNative, isWeb} from '#/platform/detection'
15
18
import {useAllListMembersQuery} from '#/state/queries/list-members'
16
19
import {useSession} from '#/state/session'
17
-
import {List, ListRef} from '#/view/com/util/List'
18
-
import {SectionRef} from '#/screens/Profile/Sections/types'
20
+
import {List, type ListRef} from '#/view/com/util/List'
21
+
import {type SectionRef} from '#/screens/Profile/Sections/types'
19
22
import {atoms as a, useTheme} from '#/alf'
20
23
import {ListFooter, ListMaybePlaceholder} from '#/components/Lists'
21
24
import {Default as ProfileCard} from '#/components/ProfileCard'
+11
-11
src/components/StarterPack/QrCode.tsx
+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
+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
)
+2
-2
src/components/StarterPack/Wizard/ScreenTransition.tsx
+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
-1
src/components/SubtleWebHover.tsx
+4
-4
src/components/TrendingTopics.tsx
+4
-4
src/components/TrendingTopics.tsx
···
1
1
import React from 'react'
2
2
import {View} from 'react-native'
3
-
import {AtUri} from '@atproto/api'
3
+
import {type AtUri} from '@atproto/api'
4
4
import {msg} from '@lingui/macro'
5
5
import {useLingui} from '@lingui/react'
6
6
···
10
10
// import {Hashtag_Stroke2_Corner0_Rounded as Hashtag} from '#/components/icons/Hashtag'
11
11
// import {CloseQuote_Filled_Stroke2_Corner0_Rounded as Quote} from '#/components/icons/Quote'
12
12
// import {UserAvatar} from '#/view/com/util/UserAvatar'
13
-
import type {TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
14
-
import {atoms as a, native, useTheme, ViewStyleProp} from '#/alf'
13
+
import {type TrendingTopic} from '#/state/queries/trending/useTrendingTopics'
14
+
import {atoms as a, native, useTheme, type ViewStyleProp} from '#/alf'
15
15
import {StarterPack as StarterPackIcon} from '#/components/icons/StarterPack'
16
-
import {Link as InternalLink, LinkProps} from '#/components/Link'
16
+
import {Link as InternalLink, type LinkProps} from '#/components/Link'
17
17
import {Text} from '#/components/Typography'
18
18
19
19
export function TrendingTopic({
+1
-1
src/components/anim/AnimatedCheck.tsx
+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
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
+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
+1
-1
src/components/dialogs/GifSelect.tsx
+2
-2
src/components/dialogs/MutedWords.tsx
+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
+1
-1
src/components/dialogs/SearchablePeopleList.tsx
-1
src/components/dms/ActionsWrapper.web.tsx
-1
src/components/dms/ActionsWrapper.web.tsx
+2
-2
src/components/dms/BlockedByListDialog.tsx
+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
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({
+1
-1
src/components/dms/ReportConversationPrompt.tsx
+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
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
+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
+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
src/components/forms/SearchInput.tsx
+2
-2
src/components/forms/TextField.tsx
+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
+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
+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
+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
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
+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
-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
+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
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
+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
+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
+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
+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
+1
-1
src/components/moderation/ReportDialog/copy.ts
+2
-2
src/components/moderation/ReportDialog/state.ts
+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
+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
+2
-2
src/components/moderation/ReportDialog/utils/parseReportSubject.ts
+4
-4
src/components/moderation/ScreenHider.tsx
+4
-4
src/components/moderation/ScreenHider.tsx
···
1
1
import React from 'react'
2
2
import {
3
-
StyleProp,
3
+
type StyleProp,
4
4
TouchableWithoutFeedback,
5
5
View,
6
-
ViewStyle,
6
+
type ViewStyle,
7
7
} from 'react-native'
8
-
import {ModerationUI} from '@atproto/api'
8
+
import {type ModerationUI} from '@atproto/api'
9
9
import {msg, Trans} from '@lingui/macro'
10
10
import {useLingui} from '@lingui/react'
11
11
import {useNavigation} from '@react-navigation/native'
12
12
13
13
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
14
14
import {useModerationCauseDescription} from '#/lib/moderation/useModerationCauseDescription'
15
-
import {NavigationProp} from '#/lib/routes/types'
15
+
import {type NavigationProp} from '#/lib/routes/types'
16
16
import {CenteredView} from '#/view/com/util/Views'
17
17
import {atoms as a, useTheme, web} from '#/alf'
18
18
import {Button, ButtonText} from '#/components/Button'
+1
-1
src/components/verification/VerificationCheckButton.tsx
+1
-1
src/components/verification/VerificationCheckButton.tsx
+3
-3
src/lib/api/feed/custom.ts
+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
+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
+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
+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
-1
src/lib/api/feed/types.ts
+1
-1
src/lib/api/feed/utils.ts
+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
src/lib/api/upload-blob.ts
+1
-1
src/lib/api/upload-blob.web.ts
+1
-1
src/lib/api/upload-blob.web.ts
+1
-1
src/lib/assets.native.ts
+1
-1
src/lib/assets.native.ts
+1
-1
src/lib/assets.ts
+1
-1
src/lib/assets.ts
+1
-1
src/lib/custom-animations/GestureActionView.web.tsx
+1
-1
src/lib/custom-animations/GestureActionView.web.tsx
+6
-1
src/lib/custom-animations/PressableScale.tsx
+6
-1
src/lib/custom-animations/PressableScale.tsx
+2
-2
src/lib/hooks/useAccountSwitcher.ts
+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
+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
src/lib/hooks/useGoBack.ts
+1
-1
src/lib/hooks/useOTAUpdates.ts
+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
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
src/lib/hooks/useTimeAgo.ts
+1
-1
src/lib/hooks/useWebScrollRestoration.ts
+1
-1
src/lib/hooks/useWebScrollRestoration.ts
+2
-2
src/lib/media/video/compress.web.ts
+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(
+3
-3
src/lib/media/video/upload.ts
+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
+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
src/lib/media/video/util.ts
+1
-1
src/lib/merge-refs.ts
+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
-1
src/lib/moderation/blocked-and-muted.ts
+4
-1
src/lib/moderation/useLabelBehaviorDescription.ts
+4
-1
src/lib/moderation/useLabelBehaviorDescription.ts
+4
-4
src/lib/moderation/useLabelInfo.ts
+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
+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
+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
-1
src/lib/strings/display-names.ts
+1
-1
src/lib/strings/rich-text-helpers.ts
+1
-1
src/lib/strings/rich-text-helpers.ts
+1
-1
src/lib/strings/rich-text-manip.ts
+1
-1
src/lib/strings/rich-text-manip.ts
+1
-1
src/lib/strings/time.ts
+1
-1
src/lib/strings/time.ts
+1
-1
src/lib/themes.ts
+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
-1
src/locale/deviceLocales.ts
+1
-1
src/locale/i18nProvider.tsx
+1
-1
src/locale/i18nProvider.tsx
+285
-246
src/locale/locales/en/messages.po
+285
-246
src/locale/locales/en/messages.po
···
30
30
msgid "{0, plural, one {# day} other {# days}}"
31
31
msgstr ""
32
32
33
-
#: src/screens/Profile/ProfileFollowers.tsx:40
33
+
#: src/screens/Profile/ProfileFollowers.tsx:43
34
34
msgid "{0, plural, one {# follower} other {# followers}}"
35
35
msgstr ""
36
36
37
-
#: src/screens/Profile/ProfileFollows.tsx:40
37
+
#: src/screens/Profile/ProfileFollows.tsx:43
38
38
msgid "{0, plural, one {# following} other {# following}}"
39
39
msgstr ""
40
40
···
42
42
msgid "{0, plural, one {# hour} other {# hours}}"
43
43
msgstr ""
44
44
45
-
#: src/components/moderation/LabelsOnMe.tsx:53
45
+
#: src/components/moderation/LabelsOnMe.tsx:58
46
46
msgid "{0, plural, one {# label has} other {# labels have}} been placed on this account"
47
47
msgstr ""
48
48
49
-
#: src/components/moderation/LabelsOnMe.tsx:62
49
+
#: src/components/moderation/LabelsOnMe.tsx:67
50
50
msgid "{0, plural, one {# label has} other {# labels have}} been placed on this content"
51
51
msgstr ""
52
52
53
-
#: src/screens/Post/PostLikedBy.tsx:41
53
+
#: src/screens/Post/PostLikedBy.tsx:44
54
54
msgid "{0, plural, one {# like} other {# likes}}"
55
55
msgstr ""
56
56
···
62
62
msgid "{0, plural, one {# month} other {# months}}"
63
63
msgstr ""
64
64
65
-
#: src/screens/Post/PostQuotes.tsx:41
65
+
#: src/screens/Post/PostQuotes.tsx:44
66
66
msgid "{0, plural, one {# quote} other {# quotes}}"
67
67
msgstr ""
68
68
69
-
#: src/screens/Post/PostRepostedBy.tsx:41
69
+
#: src/screens/Post/PostRepostedBy.tsx:44
70
70
msgid "{0, plural, one {# repost} other {# reposts}}"
71
71
msgstr ""
72
72
···
158
158
msgid "{0} joined this week"
159
159
msgstr ""
160
160
161
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:204
161
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:203
162
162
msgid "{0} of {1}"
163
163
msgstr ""
164
164
···
183
183
msgid "{0}, a list by {1}"
184
184
msgstr ""
185
185
186
-
#: src/view/com/util/UserAvatar.tsx:572
187
-
#: src/view/com/util/UserAvatar.tsx:590
186
+
#: src/view/com/util/UserAvatar.tsx:577
187
+
#: src/view/com/util/UserAvatar.tsx:595
188
188
msgid "{0}'s avatar"
189
189
msgstr ""
190
190
···
422
422
msgid "{notificationCount, plural, one {# unread item} other {# unread items}}"
423
423
msgstr ""
424
424
425
-
#: src/components/NewskieDialog.tsx:116
425
+
#: src/components/NewskieDialog.tsx:131
426
426
msgid "{profileName} joined Bluesky {0} ago"
427
427
msgstr ""
428
428
429
-
#: src/components/NewskieDialog.tsx:111
429
+
#: src/components/NewskieDialog.tsx:126
430
430
msgid "{profileName} joined Bluesky using a starter pack {0} ago"
431
431
msgstr ""
432
432
···
677
677
msgid "Add a temporary live status to your profile. When someone clicks on your avatar, they’ll see information about your live event."
678
678
msgstr ""
679
679
680
-
#: src/view/screens/ProfileList.tsx:924
681
-
#: src/view/screens/ProfileList.tsx:942
680
+
#: src/screens/ProfileList/AboutSection.tsx:62
681
+
#: src/screens/ProfileList/AboutSection.tsx:80
682
682
msgid "Add a user to this list"
683
683
msgstr ""
684
684
···
748
748
msgid "Add muted words and tags"
749
749
msgstr ""
750
750
751
-
#: src/view/screens/ProfileList.tsx:932
752
-
#: src/view/screens/ProfileList.tsx:950
751
+
#: src/screens/ProfileList/AboutSection.tsx:70
752
+
#: src/screens/ProfileList/AboutSection.tsx:88
753
753
msgid "Add people"
754
754
msgstr ""
755
755
···
769
769
msgid "Add some feeds to your starter pack!"
770
770
msgstr ""
771
771
772
-
#: src/screens/Feeds/NoFollowingFeed.tsx:41
772
+
#: src/screens/Feeds/NoFollowingFeed.tsx:39
773
773
msgid "Add the default feed of only people you follow"
774
774
msgstr ""
775
775
···
832
832
msgid "Adult content can only be enabled via the Web at <0>bsky.app</0>."
833
833
msgstr ""
834
834
835
-
#: src/components/moderation/LabelPreference.tsx:247
835
+
#: src/components/moderation/LabelPreference.tsx:246
836
836
msgid "Adult content is disabled."
837
837
msgstr ""
838
838
···
995
995
msgid "An error occurred while loading the video. Please try again later."
996
996
msgstr ""
997
997
998
-
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:227
998
+
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:226
999
999
msgid "An error occurred while loading the video. Please try again."
1000
1000
msgstr ""
1001
1001
1002
-
#: src/components/StarterPack/QrCodeDialog.tsx:72
1002
+
#: src/components/StarterPack/QrCodeDialog.tsx:75
1003
1003
msgid "An error occurred while saving the QR code!"
1004
1004
msgstr ""
1005
1005
···
1173
1173
msgid "Appearance"
1174
1174
msgstr ""
1175
1175
1176
-
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:47
1176
+
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:51
1177
1177
#: src/screens/Home/NoFeedsPinned.tsx:93
1178
1178
msgid "Apply default recommended feeds"
1179
1179
msgstr ""
···
1232
1232
msgid "Are you sure?"
1233
1233
msgstr ""
1234
1234
1235
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:87
1235
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:89
1236
1236
msgid "Are you writing in <0>{suggestedLanguageName}</0>?"
1237
1237
msgstr ""
1238
1238
···
1366
1366
msgid "Block Account?"
1367
1367
msgstr ""
1368
1368
1369
-
#: src/view/screens/ProfileList.tsx:669
1369
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:97
1370
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:100
1370
1371
msgid "Block accounts"
1371
1372
msgstr ""
1372
1373
···
1378
1379
msgid "Block and/or delete this conversation"
1379
1380
msgstr ""
1380
1381
1381
-
#: src/view/screens/ProfileList.tsx:789
1382
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:125
1382
1383
msgid "Block list"
1383
1384
msgstr ""
1384
1385
···
1386
1387
msgid "Block or report"
1387
1388
msgstr ""
1388
1389
1389
-
#: src/view/screens/ProfileList.tsx:784
1390
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:120
1390
1391
msgid "Block these accounts?"
1391
1392
msgstr ""
1392
1393
···
1425
1426
msgid "Blocking does not prevent this labeler from placing labels on your account."
1426
1427
msgstr ""
1427
1428
1428
-
#: src/view/screens/ProfileList.tsx:786
1429
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:122
1429
1430
msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you."
1430
1431
msgstr ""
1431
1432
···
1496
1497
msgid "Bluesky+ icons"
1497
1498
msgstr ""
1498
1499
1499
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:53
1500
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:56
1500
1501
msgid "Blur images"
1501
1502
msgstr ""
1502
1503
1503
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:51
1504
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:54
1504
1505
msgid "Blur images and filter from feeds"
1505
1506
msgstr ""
1506
1507
···
1711
1712
msgid "Change password dialog"
1712
1713
msgstr ""
1713
1714
1714
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:98
1715
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:100
1715
1716
msgid "Change post language to {suggestedLanguageName}"
1716
1717
msgstr ""
1717
1718
···
1875
1876
msgid "Click for information"
1876
1877
msgstr ""
1877
1878
1878
-
#: src/view/screens/Support.tsx:41
1879
+
#: src/view/screens/Support.tsx:44
1879
1880
msgid "click here"
1880
1881
msgstr ""
1881
1882
···
1929
1930
#: src/components/dms/ReportDialog.tsx:395
1930
1931
#: src/components/live/EditLiveDialog.tsx:229
1931
1932
#: src/components/live/EditLiveDialog.tsx:235
1932
-
#: src/components/NewskieDialog.tsx:146
1933
-
#: src/components/NewskieDialog.tsx:153
1933
+
#: src/components/NewskieDialog.tsx:159
1934
+
#: src/components/NewskieDialog.tsx:165
1934
1935
#: src/components/Post/Embed/ExternalEmbed/Gif.tsx:197
1935
1936
#: src/components/ProgressGuide/FollowDialog.tsx:379
1936
1937
#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:118
···
1956
1957
msgid "Close alert"
1957
1958
msgstr ""
1958
1959
1959
-
#: src/view/com/util/BottomSheetCustomBackdrop.tsx:36
1960
+
#: src/view/com/util/BottomSheetCustomBackdrop.tsx:37
1960
1961
msgid "Close bottom drawer"
1961
1962
msgstr ""
1962
1963
···
2052
2053
2053
2054
#: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:45
2054
2055
#: src/Navigation.tsx:341
2055
-
#: src/view/screens/CommunityGuidelines.tsx:34
2056
+
#: src/view/screens/CommunityGuidelines.tsx:37
2056
2057
msgid "Community Guidelines"
2057
2058
msgstr ""
2058
2059
···
2081
2082
msgid "Compressing video..."
2082
2083
msgstr ""
2083
2084
2084
-
#: src/components/moderation/LabelPreference.tsx:88
2085
+
#: src/components/moderation/LabelPreference.tsx:87
2085
2086
msgid "Configure content filtering setting for category: {name}"
2086
2087
msgstr ""
2087
2088
2088
-
#: src/components/moderation/LabelPreference.tsx:249
2089
+
#: src/components/moderation/LabelPreference.tsx:248
2089
2090
msgid "Configured in <0>moderation settings</0>."
2090
2091
msgstr ""
2091
2092
···
2281
2282
msgid "Copies build version to clipboard"
2282
2283
msgstr ""
2283
2284
2284
-
#: src/components/StarterPack/QrCodeDialog.tsx:182
2285
+
#: src/components/StarterPack/QrCodeDialog.tsx:192
2285
2286
msgid "Copy"
2286
2287
msgstr ""
2287
2288
···
2294
2295
msgid "Copy at:// URI"
2295
2296
msgstr ""
2296
2297
2297
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:153
2298
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:156
2298
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:152
2299
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:155
2299
2300
msgid "Copy author DID"
2300
2301
msgstr ""
2301
2302
···
2314
2315
msgid "Copy host"
2315
2316
msgstr ""
2316
2317
2317
-
#: src/components/StarterPack/ShareDialog.tsx:104
2318
+
#: src/components/StarterPack/ShareDialog.tsx:113
2318
2319
#: src/screens/StarterPack/StarterPackScreen.tsx:617
2319
2320
msgid "Copy link"
2320
2321
msgstr ""
2321
2322
2322
-
#: src/components/StarterPack/ShareDialog.tsx:111
2323
+
#: src/components/StarterPack/ShareDialog.tsx:119
2323
2324
msgid "Copy Link"
2324
2325
msgstr ""
2325
2326
2326
-
#: src/view/screens/ProfileList.tsx:513
2327
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172
2328
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:176
2327
2329
msgid "Copy link to list"
2328
2330
msgstr ""
2329
2331
2330
2332
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:127
2331
2333
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:130
2332
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:87
2333
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:90
2334
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:86
2335
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:89
2334
2336
msgid "Copy link to post"
2335
2337
msgstr ""
2336
2338
···
2348
2350
msgid "Copy message text"
2349
2351
msgstr ""
2350
2352
2351
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:144
2352
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:147
2353
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:143
2354
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:146
2353
2355
msgid "Copy post at:// URI"
2354
2356
msgstr ""
2355
2357
···
2358
2360
msgid "Copy post text"
2359
2361
msgstr ""
2360
2362
2361
-
#: src/components/StarterPack/QrCodeDialog.tsx:176
2363
+
#: src/components/StarterPack/QrCodeDialog.tsx:178
2362
2364
msgid "Copy QR code"
2363
2365
msgstr ""
2364
2366
···
2369
2371
#: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:40
2370
2372
#: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:107
2371
2373
#: src/Navigation.tsx:346
2372
-
#: src/view/screens/CopyrightPolicy.tsx:31
2374
+
#: src/view/screens/CopyrightPolicy.tsx:34
2373
2375
msgid "Copyright Policy"
2374
2376
msgstr ""
2375
2377
···
2394
2396
msgid "Could not load feed"
2395
2397
msgstr ""
2396
2398
2397
-
#: src/view/screens/ProfileList.tsx:1029
2399
+
#: src/screens/ProfileList/components/ErrorScreen.tsx:26
2400
+
#: src/screens/ProfileList/index.tsx:79
2401
+
#: src/screens/ProfileList/index.tsx:101
2398
2402
msgid "Could not load list"
2399
2403
msgstr ""
2400
2404
···
2422
2426
msgid "Create"
2423
2427
msgstr ""
2424
2428
2425
-
#: src/components/StarterPack/QrCodeDialog.tsx:160
2429
+
#: src/components/StarterPack/QrCodeDialog.tsx:163
2426
2430
msgid "Create a QR code for a starter pack"
2427
2431
msgstr ""
2428
2432
···
2480
2484
msgstr ""
2481
2485
2482
2486
#: src/components/moderation/ReportDialog/index.tsx:585
2483
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:99
2487
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:102
2484
2488
msgid "Create report for {0}"
2485
2489
msgstr ""
2486
2490
···
2568
2572
#: src/components/dms/MessageContextMenu.tsx:185
2569
2573
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:704
2570
2574
#: src/screens/Messages/components/ChatStatusInfo.tsx:55
2575
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:285
2571
2576
#: src/screens/Settings/AppPasswords.tsx:212
2572
2577
#: src/screens/StarterPack/StarterPackScreen.tsx:599
2573
2578
#: src/screens/StarterPack/StarterPackScreen.tsx:688
2574
2579
#: src/screens/StarterPack/StarterPackScreen.tsx:760
2575
-
#: src/view/screens/ProfileList.tsx:768
2576
2580
msgid "Delete"
2577
2581
msgstr ""
2578
2582
···
2617
2621
msgid "Delete for me"
2618
2622
msgstr ""
2619
2623
2620
-
#: src/view/screens/ProfileList.tsx:556
2624
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:211
2625
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:214
2621
2626
msgid "Delete list"
2622
2627
msgstr ""
2623
2628
···
2648
2653
msgid "Delete starter pack?"
2649
2654
msgstr ""
2650
2655
2651
-
#: src/view/screens/ProfileList.tsx:763
2656
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:280
2652
2657
msgid "Delete this list?"
2653
2658
msgstr ""
2654
2659
···
2737
2742
msgid "Disable subtitles"
2738
2743
msgstr ""
2739
2744
2740
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:32
2741
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:42
2742
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:68
2745
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:35
2746
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:45
2747
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:71
2743
2748
#: src/screens/Messages/Settings.tsx:144
2744
2749
#: src/screens/Messages/Settings.tsx:147
2745
2750
#: src/screens/Moderation/index.tsx:413
···
2898
2903
msgid "Download Bluesky"
2899
2904
msgstr ""
2900
2905
2901
-
#: src/screens/Settings/components/ExportCarDialog.tsx:79
2902
-
#: src/screens/Settings/components/ExportCarDialog.tsx:84
2906
+
#: src/screens/Settings/components/ExportCarDialog.tsx:78
2907
+
#: src/screens/Settings/components/ExportCarDialog.tsx:83
2903
2908
msgid "Download CAR file"
2904
2909
msgstr ""
2905
2910
···
2986
2991
msgid "Edit interests"
2987
2992
msgstr ""
2988
2993
2989
-
#: src/view/screens/ProfileList.tsx:544
2994
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:203
2995
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:206
2990
2996
msgid "Edit list details"
2991
2997
msgstr ""
2992
2998
···
3094
3100
3095
3101
#: src/components/dialogs/Embed.tsx:104
3096
3102
#: src/components/dialogs/Embed.tsx:108
3097
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:119
3098
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:124
3103
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:118
3104
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:123
3099
3105
msgid "Embed post"
3100
3106
msgstr ""
3101
3107
···
3422
3428
msgid "Failed to accept chat"
3423
3429
msgstr ""
3424
3430
3425
-
#: src/components/dms/ActionsWrapper.web.tsx:67
3431
+
#: src/components/dms/ActionsWrapper.web.tsx:66
3426
3432
#: src/components/dms/MessageContextMenu.tsx:99
3427
3433
msgid "Failed to add emoji reaction"
3428
3434
msgstr ""
···
3532
3538
msgid "Failed to pin post"
3533
3539
msgstr ""
3534
3540
3535
-
#: src/components/dms/ActionsWrapper.web.tsx:61
3541
+
#: src/components/dms/ActionsWrapper.web.tsx:60
3536
3542
#: src/components/dms/MessageContextMenu.tsx:93
3537
3543
msgid "Failed to remove emoji reaction"
3538
3544
msgstr ""
···
3578
3584
3579
3585
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:219
3580
3586
msgid "Failed to toggle thread mute, please try again"
3587
+
msgstr ""
3588
+
3589
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:111
3590
+
msgid "Failed to unpin list"
3581
3591
msgstr ""
3582
3592
3583
3593
#: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:149
···
3658
3668
msgstr ""
3659
3669
3660
3670
#: src/Navigation.tsx:574
3671
+
#: src/screens/SavedFeeds.tsx:108
3661
3672
#: src/screens/Search/SearchResults.tsx:73
3662
3673
#: src/screens/StarterPack/StarterPackScreen.tsx:190
3663
3674
#: src/view/screens/Feeds.tsx:511
3664
3675
#: src/view/screens/Profile.tsx:230
3665
-
#: src/view/screens/SavedFeeds.tsx:104
3666
3676
#: src/view/shell/desktop/LeftNav.tsx:727
3667
3677
#: src/view/shell/Drawer.tsx:530
3668
3678
msgid "Feeds"
3669
3679
msgstr ""
3670
3680
3671
-
#: src/view/screens/SavedFeeds.tsx:206
3672
-
msgid "Feeds are custom algorithms that users build with a little coding expertise. <0/> for more information."
3681
+
#: src/screens/SavedFeeds.tsx:215
3682
+
msgid "Feeds are custom algorithms that users build with a little coding expertise. <0>See this guide</0> for more information."
3673
3683
msgstr ""
3674
3684
3675
3685
#: src/components/FeedCard.tsx:282
3676
-
#: src/view/screens/SavedFeeds.tsx:86
3686
+
#: src/screens/SavedFeeds.tsx:90
3677
3687
msgctxt "toast"
3678
3688
msgid "Feeds updated!"
3679
3689
msgstr ""
···
3690
3700
msgid "File saved successfully!"
3691
3701
msgstr ""
3692
3702
3693
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:66
3703
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:69
3694
3704
msgid "Filter from feeds"
3695
3705
msgstr ""
3696
3706
···
3850
3860
msgid "Followers of @{0} that you know"
3851
3861
msgstr ""
3852
3862
3853
-
#: src/screens/Profile/KnownFollowers.tsx:104
3854
-
#: src/screens/Profile/KnownFollowers.tsx:121
3863
+
#: src/screens/Profile/KnownFollowers.tsx:107
3864
+
#: src/screens/Profile/KnownFollowers.tsx:124
3855
3865
msgid "Followers you know"
3856
3866
msgstr ""
3857
3867
···
3865
3875
msgid "Following"
3866
3876
msgstr ""
3867
3877
3878
+
#: src/screens/SavedFeeds.tsx:410
3868
3879
#: src/view/screens/Feeds.tsx:603
3869
-
#: src/view/screens/SavedFeeds.tsx:420
3870
3880
msgctxt "feed-name"
3871
3881
msgid "Following"
3872
3882
msgstr ""
···
4068
4078
#: src/components/moderation/ScreenHider.tsx:163
4069
4079
#: src/screens/Messages/Inbox.tsx:249
4070
4080
#: src/screens/Profile/ProfileFeed/index.tsx:92
4081
+
#: src/screens/ProfileList/components/ErrorScreen.tsx:34
4082
+
#: src/screens/ProfileList/components/ErrorScreen.tsx:40
4071
4083
#: src/screens/VideoFeed/components/Header.tsx:163
4072
4084
#: src/screens/VideoFeed/index.tsx:1146
4073
4085
#: src/screens/VideoFeed/index.tsx:1150
4074
4086
#: src/view/com/auth/LoggedOut.tsx:72
4075
4087
#: src/view/screens/NotFound.tsx:57
4076
-
#: src/view/screens/ProfileList.tsx:1038
4077
4088
msgid "Go back"
4078
4089
msgstr ""
4079
4090
···
4084
4095
#: src/screens/Profile/ProfileFeed/index.tsx:97
4085
4096
#: src/screens/StarterPack/StarterPackScreen.tsx:773
4086
4097
#: src/view/screens/NotFound.tsx:56
4087
-
#: src/view/screens/ProfileList.tsx:1043
4088
4098
msgid "Go Back"
4089
4099
msgstr ""
4090
4100
4091
4101
#: src/components/dms/ReportDialog.tsx:197
4092
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:78
4102
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:81
4093
4103
#: src/components/ReportDialog/SubmitView.tsx:110
4094
4104
#: src/screens/Onboarding/Layout.tsx:121
4095
4105
#: src/screens/Onboarding/Layout.tsx:214
···
4249
4259
#: src/components/interstitials/Trending.tsx:131
4250
4260
#: src/components/interstitials/TrendingVideos.tsx:138
4251
4261
#: src/components/moderation/ContentHider.tsx:203
4252
-
#: src/components/moderation/LabelPreference.tsx:141
4262
+
#: src/components/moderation/LabelPreference.tsx:140
4253
4263
#: src/components/moderation/PostHider.tsx:134
4254
4264
#: src/components/PostControls/PostMenu/PostMenuItems.tsx:715
4255
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:15
4256
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:20
4257
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:25
4258
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:30
4265
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:18
4266
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:23
4267
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:28
4268
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:33
4259
4269
#: src/view/shell/desktop/SidebarTrendingTopics.tsx:111
4260
4270
msgid "Hide"
4261
4271
msgstr ""
···
4406
4416
msgid "If you are not yet an adult according to the laws of your country, your parent or legal guardian must read these Terms on your behalf."
4407
4417
msgstr ""
4408
4418
4409
-
#: src/view/screens/ProfileList.tsx:765
4419
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:282
4410
4420
msgid "If you delete this list, you won't be able to recover it."
4411
4421
msgstr ""
4412
4422
···
4447
4457
msgid "Illegal and Urgent"
4448
4458
msgstr ""
4449
4459
4450
-
#: src/view/com/util/images/Gallery.tsx:71
4460
+
#: src/view/com/util/images/Gallery.tsx:70
4451
4461
msgid "Image"
4452
4462
msgstr ""
4453
4463
···
4593
4603
msgid "Invite codes: 1 available"
4594
4604
msgstr ""
4595
4605
4596
-
#: src/components/StarterPack/ShareDialog.tsx:77
4606
+
#: src/components/StarterPack/ShareDialog.tsx:81
4597
4607
msgid "Invite people to this starter pack!"
4598
4608
msgstr ""
4599
4609
···
4875
4885
msgid "Liked by"
4876
4886
msgstr ""
4877
4887
4878
-
#: src/screens/Post/PostLikedBy.tsx:38
4879
-
#: src/screens/Profile/ProfileLabelerLikedBy.tsx:29
4880
-
#: src/view/screens/ProfileFeedLikedBy.tsx:30
4888
+
#: src/screens/Post/PostLikedBy.tsx:41
4889
+
#: src/screens/Profile/ProfileLabelerLikedBy.tsx:32
4890
+
#: src/view/screens/ProfileFeedLikedBy.tsx:33
4881
4891
msgid "Liked By"
4882
4892
msgstr ""
4883
4893
···
4927
4937
msgid "List Avatar"
4928
4938
msgstr ""
4929
4939
4930
-
#: src/view/screens/ProfileList.tsx:438
4940
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:50
4931
4941
msgctxt "toast"
4932
4942
msgid "List blocked"
4933
4943
msgstr ""
···
4949
4959
msgid "List creator"
4950
4960
msgstr ""
4951
4961
4952
-
#: src/view/screens/ProfileList.tsx:485
4962
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:97
4953
4963
msgctxt "toast"
4954
4964
msgid "List deleted"
4955
4965
msgstr ""
···
4958
4968
msgid "List has been hidden"
4959
4969
msgstr ""
4960
4970
4961
-
#: src/view/screens/ProfileList.tsx:176
4971
+
#: src/screens/ProfileList/index.tsx:172
4962
4972
msgid "List Hidden"
4963
4973
msgstr ""
4964
4974
4965
-
#: src/view/screens/ProfileList.tsx:402
4975
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:31
4966
4976
msgctxt "toast"
4967
4977
msgid "List muted"
4968
4978
msgstr ""
···
4971
4981
msgid "List Name"
4972
4982
msgstr ""
4973
4983
4974
-
#: src/view/screens/ProfileList.tsx:456
4984
+
#: src/screens/ProfileList/components/Header.tsx:116
4985
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:138
4975
4986
msgctxt "toast"
4976
4987
msgid "List unblocked"
4977
4988
msgstr ""
4978
4989
4979
-
#: src/view/screens/ProfileList.tsx:420
4990
+
#: src/screens/ProfileList/components/Header.tsx:98
4991
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:120
4980
4992
msgctxt "toast"
4981
4993
msgid "List unmuted"
4982
4994
msgstr ""
···
5025
5037
5026
5038
#: src/screens/Profile/ProfileFeed/index.tsx:224
5027
5039
#: src/screens/Profile/Sections/Feed.tsx:98
5028
-
#: src/view/com/feeds/FeedPage.tsx:162
5029
-
#: src/view/screens/ProfileList.tsx:878
5040
+
#: src/screens/ProfileList/FeedSection.tsx:105
5041
+
#: src/view/com/feeds/FeedPage.tsx:169
5030
5042
msgid "Load new posts"
5031
5043
msgstr ""
5032
5044
···
5059
5071
msgid "Looks like XXXXX-XXXXX"
5060
5072
msgstr ""
5061
5073
5062
-
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:39
5074
+
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:43
5063
5075
msgid "Looks like you haven't saved any feeds! Use our recommendations or browse more below."
5064
5076
msgstr ""
5065
5077
···
5067
5079
msgid "Looks like you unpinned all your feeds. But don't worry, you can add some below 😄"
5068
5080
msgstr ""
5069
5081
5070
-
#: src/screens/Feeds/NoFollowingFeed.tsx:37
5082
+
#: src/screens/Feeds/NoFollowingFeed.tsx:35
5071
5083
msgid "Looks like you're missing a following feed. <0>Click here to add one.</0>"
5072
5084
msgstr ""
5073
5085
···
5249
5261
msgid "Moderation Lists"
5250
5262
msgstr ""
5251
5263
5252
-
#: src/components/moderation/LabelPreference.tsx:252
5264
+
#: src/components/moderation/LabelPreference.tsx:251
5253
5265
msgid "moderation settings"
5254
5266
msgstr ""
5255
5267
···
5276
5288
msgid "More languages..."
5277
5289
msgstr ""
5278
5290
5291
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:156
5279
5292
#: src/view/com/profile/ProfileMenu.tsx:223
5280
5293
#: src/view/com/profile/ProfileMenu.tsx:229
5281
-
#: src/view/screens/ProfileList.tsx:750
5282
5294
msgid "More options"
5283
5295
msgstr ""
5284
5296
5297
+
#: src/screens/SavedFeeds.tsx:329
5298
+
msgid "Move feed down"
5299
+
msgstr ""
5300
+
5301
+
#: src/screens/SavedFeeds.tsx:320
5302
+
msgid "Move feed up"
5303
+
msgstr ""
5304
+
5285
5305
#: src/screens/Onboarding/state.ts:113
5286
5306
msgid "Movies"
5287
5307
msgstr ""
···
5291
5311
msgstr ""
5292
5312
5293
5313
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:153
5294
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:96
5314
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95
5295
5315
msgctxt "video"
5296
5316
msgid "Mute"
5297
5317
msgstr ""
···
5308
5328
msgid "Mute account"
5309
5329
msgstr ""
5310
5330
5311
-
#: src/view/screens/ProfileList.tsx:657
5331
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:89
5332
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:92
5312
5333
msgid "Mute accounts"
5313
5334
msgstr ""
5314
5335
···
5321
5342
msgid "Mute in:"
5322
5343
msgstr ""
5323
5344
5324
-
#: src/view/screens/ProfileList.tsx:779
5345
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:115
5325
5346
msgid "Mute list"
5326
5347
msgstr ""
5327
5348
5328
-
#: src/view/screens/ProfileList.tsx:774
5349
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:110
5329
5350
msgid "Mute these accounts?"
5330
5351
msgstr ""
5331
5352
···
5384
5405
msgid "Muted words & tags"
5385
5406
msgstr ""
5386
5407
5387
-
#: src/view/screens/ProfileList.tsx:776
5408
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:112
5388
5409
msgid "Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them."
5389
5410
msgstr ""
5390
5411
···
5444
5465
msgid "Need to report a copyright violation, legal request, or regulatory compliance issue?"
5445
5466
msgstr ""
5446
5467
5447
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:128
5468
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:131
5448
5469
msgid "Need to report a copyright violation?"
5449
5470
msgstr ""
5450
5471
···
5526
5547
msgstr ""
5527
5548
5528
5549
#: src/screens/Profile/ProfileFeed/index.tsx:241
5550
+
#: src/screens/ProfileList/index.tsx:246
5551
+
#: src/screens/ProfileList/index.tsx:284
5529
5552
#: src/view/screens/Feeds.tsx:552
5530
5553
#: src/view/screens/Notifications.tsx:167
5531
5554
#: src/view/screens/Profile.tsx:510
5532
-
#: src/view/screens/ProfileList.tsx:250
5533
-
#: src/view/screens/ProfileList.tsx:288
5534
5555
msgid "New post"
5535
5556
msgstr ""
5536
5557
5537
-
#: src/view/com/feeds/FeedPage.tsx:173
5558
+
#: src/view/com/feeds/FeedPage.tsx:180
5538
5559
msgctxt "action"
5539
5560
msgid "New post"
5540
5561
msgstr ""
···
5556
5577
msgid "New starter pack"
5557
5578
msgstr ""
5558
5579
5559
-
#: src/components/NewskieDialog.tsx:83
5580
+
#: src/components/NewskieDialog.tsx:102
5560
5581
msgid "New user info dialog"
5561
5582
msgstr ""
5562
5583
···
5697
5718
msgid "No results for \"{0}\"."
5698
5719
msgstr ""
5699
5720
5700
-
#: src/components/Lists.tsx:190
5721
+
#: src/components/Lists.tsx:189
5701
5722
msgid "No results found"
5702
5723
msgstr ""
5703
5724
···
5774
5795
msgid "Note: Bluesky is an open and public network. This setting only limits the visibility of your content on the Bluesky app and website, and other apps may not respect this setting. Your content may still be shown to logged-out users by other apps and websites."
5775
5796
msgstr ""
5776
5797
5777
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:134
5798
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:133
5778
5799
msgid "Note: This post is only visible to logged-in users."
5779
5800
msgstr ""
5780
5801
···
5845
5866
msgid "Nudity or adult content not labeled as such"
5846
5867
msgstr ""
5847
5868
5848
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:11
5869
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:14
5849
5870
#: src/screens/Settings/NotificationSettings/index.tsx:291
5850
5871
msgid "Off"
5851
5872
msgstr ""
···
5926
5947
msgid "Only image files are supported"
5927
5948
msgstr ""
5928
5949
5929
-
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:40
5950
+
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:41
5930
5951
msgid "Only WebVTT (.vtt) files are supported"
5931
5952
msgstr ""
5932
5953
5933
-
#: src/components/Lists.tsx:95
5954
+
#: src/components/Lists.tsx:94
5934
5955
msgid "Oops, something went wrong!"
5935
5956
msgstr ""
5936
5957
5937
-
#: src/components/Lists.tsx:174
5958
+
#: src/components/Lists.tsx:173
5938
5959
#: src/components/StarterPack/ProfileStarterPacks.tsx:332
5939
5960
#: src/components/StarterPack/ProfileStarterPacks.tsx:341
5940
5961
#: src/screens/Settings/AppPasswords.tsx:59
···
5998
6019
msgid "Open pack"
5999
6020
msgstr ""
6000
6021
6001
-
#: src/components/PostControls/PostMenu/index.tsx:65
6022
+
#: src/components/PostControls/PostMenu/index.tsx:64
6002
6023
msgid "Open post options menu"
6003
6024
msgstr ""
6004
6025
···
6007
6028
msgid "Open profile"
6008
6029
msgstr ""
6009
6030
6010
-
#: src/components/PostControls/ShareMenu/index.tsx:90
6031
+
#: src/components/PostControls/ShareMenu/index.tsx:89
6011
6032
msgid "Open share menu"
6012
6033
msgstr ""
6013
6034
···
6091
6112
msgid "Opens list of invite codes"
6092
6113
msgstr ""
6093
6114
6094
-
#: src/view/com/util/UserAvatar.tsx:576
6115
+
#: src/view/com/util/UserAvatar.tsx:581
6095
6116
msgid "Opens live status dialog"
6096
6117
msgstr ""
6097
6118
···
6104
6125
msgstr ""
6105
6126
6106
6127
#: src/view/com/notifications/NotificationFeedItem.tsx:906
6107
-
#: src/view/com/util/UserAvatar.tsx:594
6128
+
#: src/view/com/util/UserAvatar.tsx:599
6108
6129
msgid "Opens this profile"
6109
6130
msgstr ""
6110
6131
···
6167
6188
msgid "Our moderators have reviewed reports and decided to disable your access to chats on Bluesky."
6168
6189
msgstr ""
6169
6190
6170
-
#: src/components/Lists.tsx:191
6191
+
#: src/components/Lists.tsx:190
6171
6192
#: src/view/screens/NotFound.tsx:47
6172
6193
msgid "Page not found"
6173
6194
msgstr ""
···
6211
6232
msgid "Pause video"
6212
6233
msgstr ""
6213
6234
6235
+
#: src/screens/ProfileList/index.tsx:166
6214
6236
#: src/screens/Search/SearchResults.tsx:67
6215
6237
#: src/screens/StarterPack/StarterPackScreen.tsx:189
6216
-
#: src/view/screens/ProfileList.tsx:170
6217
6238
msgid "People"
6218
6239
msgstr ""
6219
6240
···
6253
6274
6254
6275
#: src/screens/Profile/components/ProfileFeedHeader.tsx:523
6255
6276
#: src/screens/Profile/components/ProfileFeedHeader.tsx:530
6277
+
#: src/screens/SavedFeeds.tsx:351
6256
6278
msgid "Pin feed"
6257
6279
msgstr ""
6258
6280
···
6260
6282
msgid "Pin Feed"
6261
6283
msgstr ""
6262
6284
6263
-
#: src/view/screens/ProfileList.tsx:714
6285
+
#: src/screens/ProfileList/components/Header.tsx:156
6286
+
#: src/screens/ProfileList/components/Header.tsx:163
6264
6287
msgid "Pin to home"
6265
6288
msgstr ""
6266
6289
···
6282
6305
msgid "Pinned {0} to Home"
6283
6306
msgstr ""
6284
6307
6285
-
#: src/view/screens/SavedFeeds.tsx:131
6308
+
#: src/screens/SavedFeeds.tsx:142
6286
6309
msgid "Pinned Feeds"
6287
6310
msgstr ""
6288
6311
6289
-
#: src/view/screens/ProfileList.tsx:361
6312
+
#: src/screens/ProfileList/components/Header.tsx:74
6290
6313
msgid "Pinned to your feeds"
6291
6314
msgstr ""
6292
6315
···
6562
6585
6563
6586
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:250
6564
6587
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:262
6588
+
#: src/screens/ProfileList/index.tsx:166
6565
6589
#: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:213
6566
6590
#: src/screens/StarterPack/StarterPackScreen.tsx:191
6567
6591
#: src/view/screens/Profile.tsx:225
6568
-
#: src/view/screens/ProfileList.tsx:170
6569
6592
msgid "Posts"
6570
6593
msgstr ""
6571
6594
···
6594
6617
msgstr ""
6595
6618
6596
6619
#: src/components/Error.tsx:60
6597
-
#: src/components/Lists.tsx:100
6620
+
#: src/components/Lists.tsx:99
6598
6621
#: src/screens/Messages/components/MessageListError.tsx:24
6599
6622
#: src/screens/Signup/BackNextButtons.tsx:47
6600
6623
msgid "Press to retry"
···
6643
6666
#: src/Navigation.tsx:331
6644
6667
#: src/screens/Settings/AboutSettings.tsx:92
6645
6668
#: src/screens/Settings/AboutSettings.tsx:95
6646
-
#: src/view/screens/PrivacyPolicy.tsx:31
6669
+
#: src/view/screens/PrivacyPolicy.tsx:34
6647
6670
#: src/view/shell/Drawer.tsx:704
6648
6671
#: src/view/shell/Drawer.tsx:705
6649
6672
msgid "Privacy Policy"
···
6723
6746
msgid "Push, People you follow"
6724
6747
msgstr ""
6725
6748
6726
-
#: src/components/StarterPack/QrCodeDialog.tsx:134
6749
+
#: src/components/StarterPack/QrCodeDialog.tsx:137
6727
6750
msgid "QR code copied to your clipboard!"
6728
6751
msgstr ""
6729
6752
6730
-
#: src/components/StarterPack/QrCodeDialog.tsx:112
6753
+
#: src/components/StarterPack/QrCodeDialog.tsx:115
6731
6754
msgid "QR code has been downloaded!"
6732
6755
msgstr ""
6733
6756
6734
-
#: src/components/StarterPack/QrCodeDialog.tsx:113
6757
+
#: src/components/StarterPack/QrCodeDialog.tsx:116
6735
6758
msgid "QR code saved to your camera roll!"
6736
6759
msgstr ""
6737
6760
···
6766
6789
msgstr ""
6767
6790
6768
6791
#: src/lib/hooks/useNotificationHandler.ts:154
6769
-
#: src/screens/Post/PostQuotes.tsx:38
6792
+
#: src/screens/Post/PostQuotes.tsx:41
6770
6793
#: src/screens/Settings/NotificationSettings/index.tsx:170
6771
6794
#: src/screens/Settings/NotificationSettings/QuoteNotificationSettings.tsx:41
6772
6795
msgid "Quotes"
···
6929
6952
msgid "Remove Banner"
6930
6953
msgstr ""
6931
6954
6932
-
#: src/screens/Messages/components/MessageInputEmbed.tsx:209
6955
+
#: src/screens/Messages/components/MessageInputEmbed.tsx:212
6933
6956
msgid "Remove embed"
6934
6957
msgstr ""
6935
6958
···
6945
6968
6946
6969
#: src/screens/Profile/components/ProfileFeedHeader.tsx:319
6947
6970
#: src/screens/Profile/components/ProfileFeedHeader.tsx:325
6948
-
#: src/view/screens/ProfileList.tsx:528
6949
-
#: src/view/screens/SavedFeeds.tsx:350
6971
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:188
6972
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:191
6973
+
#: src/screens/SavedFeeds.tsx:340
6950
6974
msgid "Remove from my feeds"
6951
6975
msgstr ""
6952
6976
···
7040
7064
msgstr ""
7041
7065
7042
7066
#: src/screens/Profile/components/ProfileFeedHeader.tsx:122
7067
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:74
7043
7068
#: src/view/com/posts/FeedShutdownMsg.tsx:44
7044
-
#: src/view/screens/ProfileList.tsx:392
7045
7069
msgid "Removed from your feeds"
7046
7070
msgstr ""
7047
7071
···
7167
7191
msgid "Report feed"
7168
7192
msgstr ""
7169
7193
7170
-
#: src/view/screens/ProfileList.tsx:570
7194
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:222
7195
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:225
7171
7196
msgid "Report list"
7172
7197
msgstr ""
7173
7198
···
7189
7214
msgid "Report submitted"
7190
7215
msgstr ""
7191
7216
7192
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:41
7217
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:44
7193
7218
msgid "Report this content"
7194
7219
msgstr ""
7195
7220
7196
7221
#: src/components/moderation/ReportDialog/copy.ts:31
7197
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:54
7222
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:57
7198
7223
msgid "Report this feed"
7199
7224
msgstr ""
7200
7225
7201
7226
#: src/components/moderation/ReportDialog/copy.ts:25
7202
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:51
7227
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:54
7203
7228
msgid "Report this list"
7204
7229
msgstr ""
7205
7230
7206
7231
#: src/components/dms/ReportDialog.tsx:61
7207
7232
#: src/components/dms/ReportDialog.tsx:185
7208
7233
#: src/components/moderation/ReportDialog/copy.ts:43
7209
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:60
7234
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:63
7210
7235
msgid "Report this message"
7211
7236
msgstr ""
7212
7237
7213
7238
#: src/components/moderation/ReportDialog/copy.ts:19
7214
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:48
7239
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:51
7215
7240
msgid "Report this post"
7216
7241
msgstr ""
7217
7242
7218
7243
#: src/components/moderation/ReportDialog/copy.ts:37
7219
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:57
7244
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:60
7220
7245
msgid "Report this starter pack"
7221
7246
msgstr ""
7222
7247
7223
7248
#: src/components/moderation/ReportDialog/copy.ts:13
7224
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:45
7249
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:48
7225
7250
msgid "Report this user"
7226
7251
msgstr ""
7227
7252
···
7249
7274
msgid "Repost or quote post"
7250
7275
msgstr ""
7251
7276
7252
-
#: src/screens/Post/PostRepostedBy.tsx:38
7277
+
#: src/screens/Post/PostRepostedBy.tsx:41
7253
7278
msgid "Reposted By"
7254
7279
msgstr ""
7255
7280
···
7354
7379
7355
7380
#: src/components/dms/MessageItem.tsx:322
7356
7381
#: src/components/Error.tsx:65
7357
-
#: src/components/Lists.tsx:111
7382
+
#: src/components/Lists.tsx:110
7358
7383
#: src/components/moderation/ReportDialog/index.tsx:229
7359
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:55
7360
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:57
7384
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:54
7385
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoFallback.tsx:56
7361
7386
#: src/components/StarterPack/ProfileStarterPacks.tsx:346
7362
7387
#: src/screens/Login/LoginForm.tsx:323
7363
7388
#: src/screens/Login/LoginForm.tsx:330
···
7391
7416
msgstr ""
7392
7417
7393
7418
#: src/screens/Profile/ProfileFeed/index.tsx:93
7419
+
#: src/screens/ProfileList/components/ErrorScreen.tsx:35
7394
7420
#: src/screens/Settings/components/ChangeHandleDialog.tsx:569
7395
7421
#: src/screens/VideoFeed/index.tsx:1147
7396
7422
#: src/view/screens/NotFound.tsx:60
7397
-
#: src/view/screens/ProfileList.tsx:1039
7398
7423
msgid "Returns to previous page"
7399
7424
msgstr ""
7400
7425
···
7407
7432
#: src/components/dialogs/PostInteractionSettingsDialog.tsx:489
7408
7433
#: src/components/live/EditLiveDialog.tsx:216
7409
7434
#: src/components/live/EditLiveDialog.tsx:223
7410
-
#: src/components/StarterPack/QrCodeDialog.tsx:192
7435
+
#: src/components/StarterPack/QrCodeDialog.tsx:204
7411
7436
#: src/screens/Profile/Header/EditProfileDialog.tsx:238
7412
7437
#: src/screens/Profile/Header/EditProfileDialog.tsx:252
7438
+
#: src/screens/SavedFeeds.tsx:120
7413
7439
#: src/screens/Settings/components/ChangeHandleDialog.tsx:267
7414
7440
#: src/view/com/composer/GifAltText.tsx:193
7415
7441
#: src/view/com/composer/GifAltText.tsx:202
···
7418
7444
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:152
7419
7445
#: src/view/com/composer/photos/ImageAltTextDialog.tsx:162
7420
7446
#: src/view/com/modals/CreateOrEditList.tsx:315
7421
-
#: src/view/screens/SavedFeeds.tsx:117
7422
7447
msgid "Save"
7423
7448
msgstr ""
7424
7449
···
7434
7459
7435
7460
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:191
7436
7461
#: src/components/activity-notifications/SubscribeProfileDialog.tsx:200
7437
-
#: src/view/screens/SavedFeeds.tsx:113
7438
-
#: src/view/screens/SavedFeeds.tsx:117
7462
+
#: src/screens/SavedFeeds.tsx:116
7463
+
#: src/screens/SavedFeeds.tsx:120
7439
7464
msgid "Save changes"
7440
7465
msgstr ""
7441
7466
7442
-
#: src/components/StarterPack/ShareDialog.tsx:131
7443
7467
#: src/components/StarterPack/ShareDialog.tsx:138
7468
+
#: src/components/StarterPack/ShareDialog.tsx:144
7444
7469
msgid "Save image"
7445
7470
msgstr ""
7446
7471
···
7452
7477
msgid "Save new handle"
7453
7478
msgstr ""
7454
7479
7455
-
#: src/components/StarterPack/QrCodeDialog.tsx:186
7480
+
#: src/components/StarterPack/QrCodeDialog.tsx:196
7456
7481
msgid "Save QR code"
7457
7482
msgstr ""
7458
7483
···
7467
7492
msgid "Saved"
7468
7493
msgstr ""
7469
7494
7470
-
#: src/view/screens/SavedFeeds.tsx:172
7495
+
#: src/screens/SavedFeeds.tsx:184
7471
7496
msgid "Saved Feeds"
7472
7497
msgstr ""
7473
7498
···
7478
7503
msgstr ""
7479
7504
7480
7505
#: src/screens/Profile/components/ProfileFeedHeader.tsx:132
7481
-
#: src/view/screens/ProfileList.tsx:372
7506
+
#: src/screens/ProfileList/components/Header.tsx:85
7482
7507
msgid "Saved to your feeds"
7483
7508
msgstr ""
7484
7509
···
7487
7512
msgstr ""
7488
7513
7489
7514
#: src/components/dms/ChatEmptyPill.tsx:33
7490
-
#: src/components/NewskieDialog.tsx:105
7515
+
#: src/components/NewskieDialog.tsx:121
7491
7516
#: src/view/com/notifications/NotificationFeedItem.tsx:751
7492
7517
#: src/view/com/notifications/NotificationFeedItem.tsx:776
7493
7518
msgid "Say hello!"
···
7506
7531
msgid "Scroll right"
7507
7532
msgstr ""
7508
7533
7509
-
#: src/view/screens/ProfileList.tsx:996
7534
+
#: src/screens/ProfileList/AboutSection.tsx:130
7510
7535
msgid "Scroll to top"
7511
7536
msgstr ""
7512
7537
···
7639
7664
msgid "See more suggested profiles on the Explore page"
7640
7665
msgstr ""
7641
7666
7642
-
#: src/view/screens/SavedFeeds.tsx:213
7667
+
#: src/screens/SavedFeeds.tsx:220
7643
7668
msgid "See this guide"
7644
7669
msgstr ""
7645
7670
7646
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:197
7671
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/Scrubber.tsx:196
7647
7672
msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause"
7648
7673
msgstr ""
7649
7674
···
7743
7768
msgid "Select primary language"
7744
7769
msgstr ""
7745
7770
7746
-
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:59
7747
-
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:66
7771
+
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:60
7772
+
#: src/view/com/composer/videos/SubtitleFilePicker.tsx:67
7748
7773
msgid "Select subtitle file (.vtt)"
7749
7774
msgstr ""
7750
7775
···
7849
7874
7850
7875
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:101
7851
7876
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:107
7852
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:104
7853
-
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:110
7877
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:103
7878
+
#: src/components/PostControls/ShareMenu/ShareMenuItems.web.tsx:109
7854
7879
msgid "Send via direct message"
7855
7880
msgstr ""
7856
7881
···
7946
7971
msgid "Sexually Suggestive"
7947
7972
msgstr ""
7948
7973
7949
-
#: src/components/StarterPack/QrCodeDialog.tsx:182
7974
+
#: src/components/StarterPack/QrCodeDialog.tsx:192
7950
7975
#: src/screens/Hashtag.tsx:126
7951
7976
#: src/screens/StarterPack/StarterPackScreen.tsx:433
7952
7977
#: src/screens/Topic.tsx:102
7953
-
#: src/view/screens/ProfileList.tsx:513
7954
7978
msgid "Share"
7955
7979
msgstr ""
7956
7980
···
7978
8002
7979
8003
#: src/components/dialogs/LinkWarning.tsx:96
7980
8004
#: src/components/dialogs/LinkWarning.tsx:104
7981
-
#: src/components/StarterPack/ShareDialog.tsx:104
7982
-
#: src/components/StarterPack/ShareDialog.tsx:111
8005
+
#: src/components/StarterPack/ShareDialog.tsx:113
8006
+
#: src/components/StarterPack/ShareDialog.tsx:119
7983
8007
msgid "Share link"
7984
8008
msgstr ""
7985
8009
7986
-
#: src/components/StarterPack/ShareDialog.tsx:68
8010
+
#: src/components/StarterPack/ShareDialog.tsx:72
7987
8011
msgid "Share link dialog"
7988
8012
msgstr ""
7989
8013
···
7992
8016
msgid "Share post at:// URI"
7993
8017
msgstr ""
7994
8018
7995
-
#: src/components/StarterPack/ShareDialog.tsx:115
7996
-
#: src/components/StarterPack/ShareDialog.tsx:126
8019
+
#: src/components/StarterPack/ShareDialog.tsx:123
8020
+
#: src/components/StarterPack/ShareDialog.tsx:133
7997
8021
msgid "Share QR code"
7998
8022
msgstr ""
7999
8023
···
8005
8029
msgid "Share this starter pack"
8006
8030
msgstr ""
8007
8031
8008
-
#: src/components/StarterPack/ShareDialog.tsx:80
8032
+
#: src/components/StarterPack/ShareDialog.tsx:84
8009
8033
msgid "Share this starter pack and help people join your community on Bluesky."
8010
8034
msgstr ""
8011
8035
8012
8036
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:117
8013
8037
#: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:120
8038
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:172
8039
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:178
8014
8040
#: src/screens/StarterPack/StarterPackScreen.tsx:611
8015
8041
#: src/screens/StarterPack/StarterPackScreen.tsx:619
8016
8042
#: src/view/com/profile/ProfileMenu.tsx:246
···
8027
8053
msgstr ""
8028
8054
8029
8055
#: src/components/moderation/ContentHider.tsx:203
8030
-
#: src/components/moderation/LabelPreference.tsx:143
8056
+
#: src/components/moderation/LabelPreference.tsx:142
8031
8057
#: src/components/moderation/PostHider.tsx:134
8032
8058
msgid "Show"
8033
8059
msgstr ""
···
8044
8070
msgid "Show anyway"
8045
8071
msgstr ""
8046
8072
8047
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:27
8048
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:63
8073
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:30
8074
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:66
8049
8075
msgid "Show badge"
8050
8076
msgstr ""
8051
8077
8052
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:61
8078
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:64
8053
8079
msgid "Show badge and filter from feeds"
8054
8080
msgstr ""
8055
8081
···
8116
8142
msgid "Show samples of your saved feeds in your Following feed"
8117
8143
msgstr ""
8118
8144
8119
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:58
8145
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:61
8120
8146
msgid "Show warning"
8121
8147
msgstr ""
8122
8148
8123
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:56
8149
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:59
8124
8150
msgid "Show warning and filter from feeds"
8125
8151
msgstr ""
8126
8152
···
8299
8325
msgid "Something went wrong, please try again."
8300
8326
msgstr ""
8301
8327
8302
-
#: src/components/Lists.tsx:175
8328
+
#: src/components/Lists.tsx:174
8303
8329
msgid "Something went wrong!"
8304
8330
msgstr ""
8305
8331
···
8363
8389
msgid "Start a new chat"
8364
8390
msgstr ""
8365
8391
8366
-
#: src/view/screens/ProfileList.tsx:846
8367
-
#: src/view/screens/ProfileList.tsx:967
8392
+
#: src/screens/ProfileList/AboutSection.tsx:102
8393
+
#: src/screens/ProfileList/FeedSection.tsx:74
8368
8394
msgid "Start adding people"
8369
8395
msgstr ""
8370
8396
8371
-
#: src/view/screens/ProfileList.tsx:853
8372
-
#: src/view/screens/ProfileList.tsx:974
8397
+
#: src/screens/ProfileList/AboutSection.tsx:108
8398
+
#: src/screens/ProfileList/FeedSection.tsx:80
8373
8399
msgid "Start adding people!"
8374
8400
msgstr ""
8375
8401
···
8450
8476
msgid "Submit report"
8451
8477
msgstr ""
8452
8478
8453
-
#: src/view/screens/ProfileList.tsx:741
8479
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:81
8454
8480
msgid "Subscribe"
8455
8481
msgstr ""
8456
8482
···
8470
8496
msgid "Subscribe to this labeler"
8471
8497
msgstr ""
8472
8498
8473
-
#: src/view/screens/ProfileList.tsx:737
8499
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:69
8474
8500
msgid "Subscribe to this list"
8475
8501
msgstr ""
8476
8502
···
8513
8539
msgstr ""
8514
8540
8515
8541
#: src/Navigation.tsx:326
8516
-
#: src/view/screens/Support.tsx:31
8517
8542
#: src/view/screens/Support.tsx:34
8543
+
#: src/view/screens/Support.tsx:37
8518
8544
msgid "Support"
8519
8545
msgstr ""
8520
8546
···
8622
8648
#: src/Navigation.tsx:336
8623
8649
#: src/screens/Settings/AboutSettings.tsx:84
8624
8650
#: src/screens/Settings/AboutSettings.tsx:87
8625
-
#: src/view/screens/TermsOfService.tsx:31
8651
+
#: src/view/screens/TermsOfService.tsx:34
8626
8652
#: src/view/shell/Drawer.tsx:697
8627
8653
#: src/view/shell/Drawer.tsx:699
8628
8654
msgid "Terms of Service"
···
8717
8743
msgid "The Bluesky web application"
8718
8744
msgstr ""
8719
8745
8720
-
#: src/view/screens/CommunityGuidelines.tsx:38
8746
+
#: src/view/screens/CommunityGuidelines.tsx:41
8721
8747
msgid "The Community Guidelines have been moved to <0/>"
8722
8748
msgstr ""
8723
8749
8724
-
#: src/view/screens/CopyrightPolicy.tsx:35
8750
+
#: src/view/screens/CopyrightPolicy.tsx:38
8725
8751
msgid "The Copyright Policy has been moved to <0/>"
8726
8752
msgstr ""
8727
8753
···
8765
8791
msgid "The open social network."
8766
8792
msgstr ""
8767
8793
8768
-
#: src/view/screens/PrivacyPolicy.tsx:35
8794
+
#: src/view/screens/PrivacyPolicy.tsx:38
8769
8795
msgid "The Privacy Policy has been moved to <0/>"
8770
8796
msgstr ""
8771
8797
···
8783
8809
msgid "The starter pack that you are trying to view is invalid. You may delete this starter pack instead."
8784
8810
msgstr ""
8785
8811
8786
-
#: src/components/ContextMenu/index.tsx:433
8812
+
#: src/components/ContextMenu/index.tsx:434
8787
8813
msgid "The subject of the context menu"
8788
8814
msgstr ""
8789
8815
8790
-
#: src/view/screens/Support.tsx:37
8816
+
#: src/view/screens/Support.tsx:40
8791
8817
msgid "The support form has been moved. If you need help, please <0/> or visit {HELP_DESK_URL} to get in touch with us."
8792
8818
msgstr ""
8793
8819
8794
-
#: src/view/screens/TermsOfService.tsx:35
8820
+
#: src/view/screens/TermsOfService.tsx:38
8795
8821
msgid "The Terms of Service have been moved to"
8796
8822
msgstr ""
8797
8823
···
8812
8838
msgstr ""
8813
8839
8814
8840
#: src/screens/Profile/components/ProfileFeedHeader.tsx:178
8815
-
#: src/view/screens/ProfileList.tsx:375
8816
-
#: src/view/screens/ProfileList.tsx:394
8817
-
#: src/view/screens/SavedFeeds.tsx:93
8841
+
#: src/screens/ProfileList/components/Header.tsx:88
8842
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:76
8843
+
#: src/screens/SavedFeeds.tsx:97
8818
8844
msgid "There was an issue contacting the server"
8819
8845
msgstr ""
8820
8846
···
8828
8854
msgstr ""
8829
8855
8830
8856
#: src/screens/Search/Explore.tsx:986
8831
-
#: src/view/com/posts/PostFeed.tsx:701
8857
+
#: src/view/com/posts/PostFeed.tsx:709
8832
8858
msgid "There was an issue fetching posts. Tap here to try again."
8833
8859
msgstr ""
8834
8860
···
8885
8911
#: src/screens/List/ListHiddenScreen.tsx:63
8886
8912
#: src/screens/List/ListHiddenScreen.tsx:77
8887
8913
#: src/screens/List/ListHiddenScreen.tsx:99
8888
-
#: src/view/screens/ProfileList.tsx:411
8889
-
#: src/view/screens/ProfileList.tsx:429
8890
-
#: src/view/screens/ProfileList.tsx:447
8891
-
#: src/view/screens/ProfileList.tsx:465
8914
+
#: src/screens/ProfileList/components/Header.tsx:107
8915
+
#: src/screens/ProfileList/components/Header.tsx:125
8916
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:129
8917
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:147
8918
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:40
8919
+
#: src/screens/ProfileList/components/SubscribeMenu.tsx:59
8892
8920
msgid "There was an issue. Please check your internet connection and try again."
8893
8921
msgstr ""
8894
8922
···
8979
9007
msgid "This feature allows users to receive notifications for your new posts and replies. Who do you want to enable this for?"
8980
9008
msgstr ""
8981
9009
8982
-
#: src/screens/Settings/components/ExportCarDialog.tsx:96
9010
+
#: src/screens/Settings/components/ExportCarDialog.tsx:95
8983
9011
msgid "This feature is in beta. You can read more about repository exports in <0>this blogpost</0>."
8984
9012
msgstr ""
8985
9013
···
9001
9029
9002
9030
#: src/components/StarterPack/Main/PostsList.tsx:36
9003
9031
#: src/screens/Profile/ProfileFeed/index.tsx:192
9004
-
#: src/view/screens/ProfileList.tsx:843
9032
+
#: src/screens/ProfileList/FeedSection.tsx:71
9005
9033
msgid "This feed is empty."
9006
9034
msgstr ""
9007
9035
···
9046
9074
msgid "This list – created by you – contains possible violations of Bluesky's community guidelines in its name or description."
9047
9075
msgstr ""
9048
9076
9049
-
#: src/view/screens/ProfileList.tsx:962
9077
+
#: src/screens/ProfileList/AboutSection.tsx:98
9050
9078
msgid "This list is empty."
9051
9079
msgstr ""
9052
9080
···
9127
9155
msgid "This user is included in the <0>{0}</0> list which you have muted."
9128
9156
msgstr ""
9129
9157
9130
-
#: src/components/NewskieDialog.tsx:65
9158
+
#: src/components/NewskieDialog.tsx:47
9131
9159
msgid "This user is new here. Press for more info about when they joined."
9132
9160
msgstr ""
9133
9161
···
9315
9343
#: src/components/dms/MessagesListBlockedFooter.tsx:119
9316
9344
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:208
9317
9345
#: src/screens/Profile/Header/ProfileHeaderStandard.tsx:328
9346
+
#: src/screens/ProfileList/components/Header.tsx:171
9347
+
#: src/screens/ProfileList/components/Header.tsx:178
9318
9348
#: src/view/com/profile/ProfileMenu.tsx:490
9319
-
#: src/view/screens/ProfileList.tsx:723
9320
9349
msgid "Unblock"
9321
9350
msgstr ""
9322
9351
···
9337
9366
msgid "Unblock Account?"
9338
9367
msgstr ""
9339
9368
9340
-
#: src/view/screens/ProfileList.tsx:620
9369
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:254
9370
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:257
9341
9371
msgid "Unblock list"
9342
9372
msgstr ""
9343
9373
···
9396
9426
msgstr ""
9397
9427
9398
9428
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/VideoEmbedInnerNative.tsx:152
9399
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:95
9429
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:94
9400
9430
msgctxt "video"
9401
9431
msgid "Unmute"
9402
9432
msgstr ""
9403
9433
9404
-
#: src/view/screens/ProfileList.tsx:730
9434
+
#: src/screens/ProfileList/components/Header.tsx:185
9435
+
#: src/screens/ProfileList/components/Header.tsx:192
9405
9436
msgid "Unmute"
9406
9437
msgstr ""
9407
9438
···
9421
9452
msgid "Unmute conversation"
9422
9453
msgstr ""
9423
9454
9424
-
#: src/view/screens/ProfileList.tsx:605
9455
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:264
9456
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:267
9425
9457
msgid "Unmute list"
9426
9458
msgstr ""
9427
9459
···
9434
9466
msgid "Unmute video"
9435
9467
msgstr ""
9436
9468
9437
-
#: src/view/screens/ProfileList.tsx:714
9469
+
#: src/screens/ProfileList/components/Header.tsx:156
9470
+
#: src/screens/ProfileList/components/Header.tsx:163
9438
9471
msgid "Unpin"
9439
9472
msgstr ""
9440
9473
9441
9474
#: src/screens/Profile/components/ProfileFeedHeader.tsx:523
9442
9475
#: src/screens/Profile/components/ProfileFeedHeader.tsx:530
9476
+
#: src/screens/SavedFeeds.tsx:351
9443
9477
msgid "Unpin feed"
9444
9478
msgstr ""
9445
9479
···
9457
9491
msgid "Unpin from profile"
9458
9492
msgstr ""
9459
9493
9460
-
#: src/view/screens/ProfileList.tsx:585
9494
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:237
9495
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:240
9461
9496
msgid "Unpin moderation list"
9462
9497
msgstr ""
9463
9498
···
9465
9500
msgid "Unpinned {0} from Home"
9466
9501
msgstr ""
9467
9502
9468
-
#: src/view/screens/ProfileList.tsx:362
9503
+
#: src/screens/ProfileList/components/Header.tsx:75
9469
9504
msgid "Unpinned from your feeds"
9505
+
msgstr ""
9506
+
9507
+
#: src/screens/ProfileList/components/MoreOptionsMenu.tsx:109
9508
+
msgid "Unpinned list"
9470
9509
msgstr ""
9471
9510
9472
9511
#: src/screens/Settings/Settings.tsx:474
···
9604
9643
msgid "Use my default browser"
9605
9644
msgstr ""
9606
9645
9607
-
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:53
9646
+
#: src/screens/Feeds/NoSavedFeedsOfAnyType.tsx:56
9608
9647
msgid "Use recommended"
9609
9648
msgstr ""
9610
9649
···
9831
9870
msgid "Video is playing"
9832
9871
msgstr ""
9833
9872
9834
-
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:220
9873
+
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:219
9835
9874
msgid "Video not found."
9836
9875
msgstr ""
9837
9876
···
9880
9919
msgid "View blocked user's profile"
9881
9920
msgstr ""
9882
9921
9883
-
#: src/screens/Settings/components/ExportCarDialog.tsx:100
9922
+
#: src/screens/Settings/components/ExportCarDialog.tsx:99
9884
9923
msgid "View blogpost for more details"
9885
9924
msgstr ""
9886
9925
···
9888
9927
msgid "View debug entry"
9889
9928
msgstr ""
9890
9929
9891
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:137
9930
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:140
9892
9931
#: src/screens/VideoFeed/index.tsx:659
9893
9932
#: src/screens/VideoFeed/index.tsx:677
9894
9933
msgid "View details"
9895
9934
msgstr ""
9896
9935
9897
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:132
9936
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:135
9898
9937
msgid "View details for reporting a copyright violation"
9899
9938
msgstr ""
9900
9939
···
9902
9941
msgid "View full thread"
9903
9942
msgstr ""
9904
9943
9905
-
#: src/components/moderation/LabelsOnMe.tsx:46
9944
+
#: src/components/moderation/LabelsOnMe.tsx:51
9906
9945
msgid "View information about these labels"
9907
9946
msgstr ""
9908
9947
···
9925
9964
#: src/components/ProfileHoverCard/index.web.tsx:486
9926
9965
#: src/components/ProfileHoverCard/index.web.tsx:513
9927
9966
#: src/view/com/posts/PostFeedErrorMessage.tsx:179
9928
-
#: src/view/com/util/PostMeta.tsx:91
9929
-
#: src/view/com/util/PostMeta.tsx:128
9967
+
#: src/view/com/util/PostMeta.tsx:90
9968
+
#: src/view/com/util/PostMeta.tsx:127
9930
9969
msgid "View profile"
9931
9970
msgstr ""
9932
9971
···
9958
9997
msgid "View your default post interaction settings"
9959
9998
msgstr ""
9960
9999
9961
-
#: src/view/com/home/HomeHeaderLayout.web.tsx:56
9962
-
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:71
10000
+
#: src/view/com/home/HomeHeaderLayout.web.tsx:57
10001
+
#: src/view/com/home/HomeHeaderLayoutMobile.tsx:72
9963
10002
msgid "View your feeds and explore more"
9964
10003
msgstr ""
9965
10004
···
9993
10032
msgid "Visit your notification settings"
9994
10033
msgstr ""
9995
10034
9996
-
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:81
10035
+
#: src/components/Post/Embed/VideoEmbed/VideoEmbedInner/web-controls/VolumeControl.tsx:80
9997
10036
msgid "Volume"
9998
10037
msgstr ""
9999
10038
10000
-
#: src/components/moderation/LabelPreference.tsx:142
10001
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:17
10002
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:22
10039
+
#: src/components/moderation/LabelPreference.tsx:141
10040
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:20
10041
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:25
10003
10042
msgid "Warn"
10004
10043
msgstr ""
10005
10044
10006
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:48
10045
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:51
10007
10046
msgid "Warn content"
10008
10047
msgstr ""
10009
10048
10010
-
#: src/lib/moderation/useLabelBehaviorDescription.ts:46
10049
+
#: src/lib/moderation/useLabelBehaviorDescription.ts:49
10011
10050
msgid "Warn content and filter from feeds"
10012
10051
msgstr ""
10013
10052
···
10133
10172
msgid "We're sorry, but based on your device's location, you are currently located in a region where we cannot provide access at this time."
10134
10173
msgstr ""
10135
10174
10136
-
#: src/view/screens/ProfileList.tsx:117
10175
+
#: src/screens/ProfileList/index.tsx:87
10137
10176
msgid "We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @{handleOrDid}."
10138
10177
msgstr ""
10139
10178
···
10149
10188
msgid "We're sorry! The post you are replying to has been deleted."
10150
10189
msgstr ""
10151
10190
10152
-
#: src/components/Lists.tsx:195
10191
+
#: src/components/Lists.tsx:194
10153
10192
#: src/view/screens/NotFound.tsx:50
10154
10193
msgid "We're sorry! We can't find the page you were looking for."
10155
10194
msgstr ""
···
10182
10221
msgid "Welcome back!"
10183
10222
msgstr ""
10184
10223
10185
-
#: src/components/NewskieDialog.tsx:103
10224
+
#: src/components/NewskieDialog.tsx:121
10186
10225
msgid "Welcome, friend!"
10187
10226
msgstr ""
10188
10227
···
10235
10274
msgid "Why are you appealing?"
10236
10275
msgstr ""
10237
10276
10238
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:42
10277
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:45
10239
10278
msgid "Why should this content be reviewed?"
10240
10279
msgstr ""
10241
10280
10242
10281
#: src/components/moderation/ReportDialog/copy.ts:32
10243
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:55
10282
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:58
10244
10283
msgid "Why should this feed be reviewed?"
10245
10284
msgstr ""
10246
10285
10247
10286
#: src/components/moderation/ReportDialog/copy.ts:26
10248
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:52
10287
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:55
10249
10288
msgid "Why should this list be reviewed?"
10250
10289
msgstr ""
10251
10290
10252
10291
#: src/components/moderation/ReportDialog/copy.ts:44
10253
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:61
10292
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:64
10254
10293
msgid "Why should this message be reviewed?"
10255
10294
msgstr ""
10256
10295
10257
10296
#: src/components/moderation/ReportDialog/copy.ts:20
10258
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:49
10297
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:52
10259
10298
msgid "Why should this post be reviewed?"
10260
10299
msgstr ""
10261
10300
10262
10301
#: src/components/moderation/ReportDialog/copy.ts:38
10263
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:58
10302
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:61
10264
10303
msgid "Why should this starter pack be reviewed?"
10265
10304
msgstr ""
10266
10305
10267
10306
#: src/components/moderation/ReportDialog/copy.ts:14
10268
-
#: src/components/ReportDialog/SelectReportOptionView.tsx:46
10307
+
#: src/components/ReportDialog/SelectReportOptionView.tsx:49
10269
10308
msgid "Why should this user be reviewed?"
10270
10309
msgstr ""
10271
10310
···
10297
10336
msgid "www.mylivestream.tv"
10298
10337
msgstr ""
10299
10338
10300
-
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:100
10339
+
#: src/view/com/composer/select-language/SuggestedLanguage.tsx:102
10301
10340
msgid "Yes"
10302
10341
msgstr ""
10303
10342
···
10326
10365
msgid "Yesterday"
10327
10366
msgstr ""
10328
10367
10329
-
#: src/components/NewskieDialog.tsx:43
10368
+
#: src/components/NewskieDialog.tsx:91
10330
10369
msgid "You"
10331
10370
msgstr ""
10332
10371
···
10448
10487
msgid "You do not have any followers."
10449
10488
msgstr ""
10450
10489
10451
-
#: src/screens/Profile/KnownFollowers.tsx:109
10490
+
#: src/screens/Profile/KnownFollowers.tsx:112
10452
10491
msgid "You don't follow any users who follow @{name}."
10453
10492
msgstr ""
10454
10493
···
10460
10499
msgid "You don't have any invite codes yet! We'll send you some when you've been on Bluesky for a little longer."
10461
10500
msgstr ""
10462
10501
10463
-
#: src/view/screens/SavedFeeds.tsx:145
10502
+
#: src/screens/SavedFeeds.tsx:149
10464
10503
msgid "You don't have any pinned feeds."
10465
10504
msgstr ""
10466
10505
10467
-
#: src/view/screens/SavedFeeds.tsx:185
10506
+
#: src/screens/SavedFeeds.tsx:191
10468
10507
msgid "You don't have any saved feeds."
10469
10508
msgstr ""
10470
10509
···
10530
10569
msgid "You have not muted any accounts yet. To mute an account, go to their profile and select \"Mute account\" from the menu on their account."
10531
10570
msgstr ""
10532
10571
10533
-
#: src/components/Lists.tsx:58
10572
+
#: src/components/Lists.tsx:57
10534
10573
msgid "You have reached the end"
10535
10574
msgstr ""
10536
10575
···
10595
10634
msgid "You must complete age assurance in order to access this screen."
10596
10635
msgstr ""
10597
10636
10598
-
#: src/components/StarterPack/QrCodeDialog.tsx:61
10637
+
#: src/components/StarterPack/QrCodeDialog.tsx:65
10599
10638
msgid "You must grant access to your photo library to save a QR code"
10600
10639
msgstr ""
10601
10640
···
10771
10810
msgid "Your birth date"
10772
10811
msgstr ""
10773
10812
10774
-
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:224
10813
+
#: src/components/Post/Embed/VideoEmbed/index.web.tsx:223
10775
10814
msgid "Your browser does not support the video format. Please try a different browser."
10776
10815
msgstr ""
10777
10816
+1
-1
src/logger/__tests__/logDump.test.ts
+1
-1
src/logger/__tests__/logDump.test.ts
+1
-1
src/logger/logDump.ts
+1
-1
src/logger/logDump.ts
+1
-1
src/logger/transports/bitdrift.ts
+1
-1
src/logger/transports/bitdrift.ts
+1
-1
src/logger/transports/console.ts
+1
-1
src/logger/transports/console.ts
+1
-1
src/logger/util.ts
+1
-1
src/logger/util.ts
-49
src/platform/polyfills.ts
-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
+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
+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
+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
+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
+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
-1
src/screens/Login/FormContainer.tsx
+2
-2
src/screens/Login/ScreenTransition.tsx
+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
+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
+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
+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
+1
-1
src/screens/ModerationInteractionSettings/index.tsx
+1
-1
src/screens/Onboarding/StepInterests/InterestButton.tsx
+1
-1
src/screens/Onboarding/StepInterests/InterestButton.tsx
+1
-1
src/screens/Onboarding/StepProfile/AvatarCreatorCircle.tsx
+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
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
+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
+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
+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
+1
-1
src/screens/PostThread/components/ThreadItemAnchor.tsx
+1
-1
src/screens/Profile/ErrorState.tsx
+1
-1
src/screens/Profile/ErrorState.tsx
···
4
4
import {useLingui} from '@lingui/react'
5
5
import {useNavigation} from '@react-navigation/native'
6
6
7
-
import {NavigationProp} from '#/lib/routes/types'
7
+
import {type NavigationProp} from '#/lib/routes/types'
8
8
import {atoms as a, useTheme} from '#/alf'
9
9
import {Button, ButtonText} from '#/components/Button'
10
10
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
+3
-3
src/screens/Profile/Header/GrowableAvatar.tsx
+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
+3
-2
src/screens/Profile/Header/GrowableBanner.tsx
···
1
-
import React, {useEffect, useState} from 'react'
1
+
import {useEffect, useState} from 'react'
2
2
import {View} from 'react-native'
3
3
import {ActivityIndicator} from 'react-native'
4
4
import Animated, {
5
5
Extrapolation,
6
6
interpolate,
7
7
runOnJS,
8
-
SharedValue,
8
+
type SharedValue,
9
9
useAnimatedProps,
10
10
useAnimatedReaction,
11
11
useAnimatedStyle,
···
13
13
import {useSafeAreaInsets} from 'react-native-safe-area-context'
14
14
import {BlurView} from 'expo-blur'
15
15
import {useIsFetching} from '@tanstack/react-query'
16
+
import type React from 'react'
16
17
17
18
import {isIOS} from '#/platform/detection'
18
19
import {RQKEY_ROOT as STARTERPACK_RQKEY_ROOT} from '#/state/queries/actor-starter-packs'
+4
-1
src/screens/Profile/Header/StatusBarShadow.tsx
+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
+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
+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
+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
+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
+4
-1
src/screens/Profile/ProfileLabelerLikedBy.tsx
···
3
3
import {useLingui} from '@lingui/react'
4
4
import {useFocusEffect} from '@react-navigation/native'
5
5
6
-
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
6
+
import {
7
+
type CommonNavigatorParams,
8
+
type NativeStackScreenProps,
9
+
} from '#/lib/routes/types'
7
10
import {makeRecordUri} from '#/lib/strings/url-helpers'
8
11
import {useSetMinimalShellMode} from '#/state/shell'
9
12
import {ViewHeader} from '#/view/com/util/ViewHeader'
+136
src/screens/ProfileList/AboutSection.tsx
+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
+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
+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
+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
+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
+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
+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
+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
src/screens/Settings/AppIconSettings/AppIconImage.tsx
+1
-1
src/screens/Settings/components/AddAppPasswordDialog.tsx
+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
+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
+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
+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
src/screens/Settings/components/PwiOptOut.tsx
+1
-1
src/screens/Signup/StepCaptcha/CaptchaWebView.tsx
+1
-1
src/screens/Signup/StepCaptcha/CaptchaWebView.tsx
+1
-1
src/screens/Signup/StepInfo/Policies.tsx
+1
-1
src/screens/Signup/StepInfo/Policies.tsx
+1
-1
src/screens/Signup/StepInfo/index.tsx
+1
-1
src/screens/Signup/StepInfo/index.tsx
···
68
68
69
69
const [hasWarnedEmail, setHasWarnedEmail] = React.useState<boolean>(false)
70
70
71
-
const tldtsRef = React.useRef<typeof tldts>()
71
+
const tldtsRef = React.useRef<typeof tldts>(undefined)
72
72
React.useEffect(() => {
73
73
// @ts-expect-error - valid path
74
74
import('tldts/dist/index.cjs.min.js').then(tldts => {
+2
-2
src/screens/StarterPack/Wizard/StepFeeds.tsx
+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
+3
-3
src/screens/StarterPack/Wizard/StepProfiles.tsx
···
1
1
import {useState} from 'react'
2
-
import {ListRenderItemInfo, View} from 'react-native'
2
+
import {type ListRenderItemInfo, View} from 'react-native'
3
3
import {KeyboardAwareScrollView} from 'react-native-keyboard-controller'
4
-
import {AppBskyActorDefs, ModerationOpts} from '@atproto/api'
4
+
import {type AppBskyActorDefs, type ModerationOpts} from '@atproto/api'
5
5
import {Trans} from '@lingui/macro'
6
6
7
7
import {isNative} from '#/platform/detection'
···
16
16
import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition'
17
17
import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard'
18
18
import {Text} from '#/components/Typography'
19
-
import * as bsky from '#/types/bsky'
19
+
import type * as bsky from '#/types/bsky'
20
20
21
21
function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
22
22
return item?.did ?? ''
+4
-4
src/screens/VideoFeed/components/Header.tsx
+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
-1
src/screens/VideoFeed/types.ts
+5
-5
src/state/messages/convo/util.ts
+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
-1
src/state/messages/events/types.ts
+1
-1
src/state/messages/index.tsx
+1
-1
src/state/messages/index.tsx
+2
-2
src/state/persisted/index.web.ts
+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
-1
src/state/persisted/types.ts
+1
-1
src/state/persisted/util.ts
+1
-1
src/state/persisted/util.ts
+5
-1
src/state/queries/actor-autocomplete.ts
+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
-1
src/state/queries/app-passwords.ts
+1
-1
src/state/queries/invites.ts
+1
-1
src/state/queries/invites.ts
+7
-4
src/state/queries/known-followers.ts
+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
+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
+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
+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
-1
src/state/queries/nuxs/types.ts
+3
-3
src/state/queries/nuxs/util.ts
+3
-3
src/state/queries/nuxs/util.ts
+1
-1
src/state/queries/post-interaction-settings.ts
+1
-1
src/state/queries/post-interaction-settings.ts
+4
-4
src/state/queries/post-liked-by.ts
+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
+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
+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
+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
+3
-3
src/state/queries/preferences/types.ts
+9
-2
src/state/queries/profile-feedgens.ts
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+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
+1
-1
src/state/session/__tests__/session-test.ts
···
2
2
import {describe, expect, it, jest} from '@jest/globals'
3
3
4
4
import {agentToSessionAccountOrThrow} from '../agent'
5
-
import {Action, getInitialState, reducer, State} from '../reducer'
5
+
import {type Action, getInitialState, reducer, type State} from '../reducer'
6
6
7
7
jest.mock('jwt-decode', () => ({
8
8
jwtDecode(_token: string) {
+1
-1
src/state/session/util.ts
+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
-2
src/state/shell/index.tsx
+1
-1
src/state/shell/reminders.ts
+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
-1
src/types/bsky/index.ts
+1
-1
src/types/bsky/post.ts
+1
-1
src/types/bsky/post.ts
+1
-1
src/view/com/composer/AltTextCounterWrapper.tsx
+1
-1
src/view/com/composer/AltTextCounterWrapper.tsx
+1
-1
src/view/com/composer/Composer.tsx
+1
-1
src/view/com/composer/Composer.tsx
+1
-1
src/view/com/composer/KeyboardAccessory.tsx
+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
+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
-2
src/view/com/composer/photos/EditImageDialog.tsx
+1
-1
src/view/com/composer/photos/EditImageDialog.web.tsx
+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
+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
+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
-1
src/view/com/composer/text-input/text-input-util.ts
+1
-1
src/view/com/composer/text-input/web/LinkDecorator.ts
+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
+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
+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
+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
+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
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
-1
src/view/com/composer/videos/pickVideo.web.ts
+8
-1
src/view/com/feeds/FeedPage.tsx
+8
-1
src/view/com/feeds/FeedPage.tsx
···
1
-
import {useCallback, useEffect, useMemo, useRef, useState} from 'react'
1
+
import {
2
+
type JSX,
3
+
useCallback,
4
+
useEffect,
5
+
useMemo,
6
+
useRef,
7
+
useState,
8
+
} from 'react'
2
9
import {View} from 'react-native'
3
10
import {type AppBskyActorDefs, AppBskyFeedDefs} from '@atproto/api'
4
11
import {msg} from '@lingui/macro'
+2
-1
src/view/com/home/HomeHeaderLayout.web.tsx
+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
src/view/com/home/HomeHeaderLayoutMobile.tsx
+2
-2
src/view/com/lightbox/ImageViewing/@types/index.ts
+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
+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
+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
+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
+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
-1
src/view/com/lightbox/ImageViewing/transforms.ts
+1
-1
src/view/com/lists/ListMembers.tsx
+1
-1
src/view/com/lists/ListMembers.tsx
+5
-5
src/view/com/lists/MyLists.tsx
+5
-5
src/view/com/lists/MyLists.tsx
···
1
-
import React from 'react'
1
+
import React, {type JSX} from 'react'
2
2
import {
3
3
ActivityIndicator,
4
4
FlatList as RNFlatList,
5
5
RefreshControl,
6
-
StyleProp,
6
+
type StyleProp,
7
7
View,
8
-
ViewStyle,
8
+
type ViewStyle,
9
9
} from 'react-native'
10
-
import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
10
+
import {type AppBskyGraphDefs as GraphDefs} from '@atproto/api'
11
11
import {msg} from '@lingui/macro'
12
12
import {useLingui} from '@lingui/react'
13
13
···
16
16
import {s} from '#/lib/styles'
17
17
import {logger} from '#/logger'
18
18
import {useModerationOpts} from '#/state/preferences/moderation-opts'
19
-
import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
19
+
import {type MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
20
20
import {atoms as a, useTheme} from '#/alf'
21
21
import {BulletList_Stroke2_Corner0_Rounded as ListIcon} from '#/components/icons/BulletList'
22
22
import * as ListCard from '#/components/ListCard'
+3
-3
src/view/com/modals/InviteCodes.tsx
+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
+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
+1
-1
src/view/com/notifications/NotificationFeedItem.tsx
+1
src/view/com/pager/Pager.tsx
+1
src/view/com/pager/Pager.tsx
+1
src/view/com/pager/Pager.web.tsx
+1
src/view/com/pager/Pager.web.tsx
+1
-1
src/view/com/pager/PagerWithHeader.tsx
+1
-1
src/view/com/pager/PagerWithHeader.tsx
+8
-3
src/view/com/pager/PagerWithHeader.web.tsx
+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
+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
src/view/com/post-thread/PostLikedBy.tsx
+2
-2
src/view/com/post-thread/PostQuotes.tsx
+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
src/view/com/post-thread/PostRepostedBy.tsx
+2
-2
src/view/com/posts/CustomFeedEmptyState.tsx
+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
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
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
+9
-1
src/view/com/posts/PostFeed.tsx
+1
-1
src/view/com/profile/ProfileFollowers.tsx
+1
-1
src/view/com/profile/ProfileFollowers.tsx
+1
-1
src/view/com/profile/ProfileFollows.tsx
+1
-1
src/view/com/profile/ProfileFollows.tsx
+1
-1
src/view/com/util/Alert.web.tsx
+1
-1
src/view/com/util/Alert.web.tsx
+3
-2
src/view/com/util/BottomSheetCustomBackdrop.tsx
+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
+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
+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
+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
src/view/com/util/EventStopper.tsx
+2
-2
src/view/com/util/FeedInfoText.tsx
+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
-1
src/view/com/util/Link.tsx
+1
-1
src/view/com/util/List.tsx
+1
-1
src/view/com/util/List.tsx
+13
-6
src/view/com/util/List.web.tsx
+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
src/view/com/util/LoadMoreRetryBtn.tsx
-17
src/view/com/util/LoadingScreen.tsx
-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
src/view/com/util/MainScrollProvider.tsx
-1
src/view/com/util/PostMeta.tsx
-1
src/view/com/util/PostMeta.tsx
+8
-3
src/view/com/util/PressableWithHover.tsx
+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
+2
-2
src/view/com/util/TimeElapsed.tsx
+13
-7
src/view/com/util/UserAvatar.tsx
+13
-7
src/view/com/util/UserAvatar.tsx
···
1
-
import React, {memo, useCallback, useMemo, useState} from 'react'
1
+
import {memo, useCallback, useMemo, useState} from 'react'
2
2
import {
3
3
Image,
4
4
Pressable,
···
375
375
}
376
376
}, [circular, size])
377
377
378
-
const onOpenCamera = React.useCallback(async () => {
378
+
const onOpenCamera = useCallback(async () => {
379
379
if (!(await requestCameraAccessIfNeeded())) {
380
380
return
381
381
}
···
389
389
)
390
390
}, [onSelectNewAvatar, requestCameraAccessIfNeeded])
391
391
392
-
const onOpenLibrary = React.useCallback(async () => {
392
+
const onOpenLibrary = useCallback(async () => {
393
393
if (!(await requestPhotoAccessIfNeeded())) {
394
394
return
395
395
}
···
433
433
circular,
434
434
])
435
435
436
-
const onRemoveAvatar = React.useCallback(() => {
436
+
const onRemoveAvatar = useCallback(() => {
437
437
onSelectNewAvatar(null)
438
438
}, [onSelectNewAvatar])
439
439
···
542
542
disableNavigation,
543
543
onBeforePress,
544
544
live,
545
-
...rest
545
+
...props
546
546
}: PreviewableUserAvatarProps): React.ReactNode => {
547
547
const {_} = useLingui()
548
548
const queryClient = useQueryClient()
···
571
571
moderation={moderation}
572
572
type={profile.associated?.labeler ? 'labeler' : 'user'}
573
573
live={status.isActive || live}
574
-
{...rest}
574
+
{...props}
575
575
/>
576
576
)
577
+
578
+
const linkStyle =
579
+
props.type !== 'algo' && props.type !== 'list'
580
+
? a.rounded_full
581
+
: {borderRadius: props.size > 32 ? 8 : 3}
577
582
578
583
return (
579
584
<ProfileHoverCard did={profile.did} disable={disableHoverCard}>
···
610
615
did: profile.did,
611
616
handle: profile.handle,
612
617
})}
613
-
onPress={onPress}>
618
+
onPress={onPress}
619
+
style={linkStyle}>
614
620
{avatarEl}
615
621
</Link>
616
622
)}
+2
src/view/com/util/ViewHeader.tsx
+2
src/view/com/util/ViewHeader.tsx
+4
-4
src/view/com/util/ViewSelector.tsx
+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
+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
-1
src/view/com/util/WebAuxClickWrapper.tsx
+3
-3
src/view/com/util/error/ErrorMessage.tsx
+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
src/view/com/util/error/ErrorScreen.tsx
+1
-1
src/view/com/util/fab/FAB.web.tsx
+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
+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
+1
-1
src/view/com/util/forms/NativeDropdown.web.tsx
-1
src/view/com/util/images/Gallery.tsx
-1
src/view/com/util/images/Gallery.tsx
+1
-1
src/view/com/util/images/Image.tsx
+1
-1
src/view/com/util/images/Image.tsx
+1
-1
src/view/com/util/layouts/LoggedOutLayout.tsx
+1
-1
src/view/com/util/layouts/LoggedOutLayout.tsx
+1
-1
src/view/icons/Logomark.tsx
+1
-1
src/view/icons/Logomark.tsx
+4
-1
src/view/screens/CommunityGuidelines.tsx
+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
+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
+1
-1
src/view/screens/NotFound.tsx
···
9
9
} from '@react-navigation/native'
10
10
11
11
import {usePalette} from '#/lib/hooks/usePalette'
12
-
import {NavigationProp} from '#/lib/routes/types'
12
+
import {type NavigationProp} from '#/lib/routes/types'
13
13
import {s} from '#/lib/styles'
14
14
import {useSetMinimalShellMode} from '#/state/shell'
15
15
import {Button} from '#/view/com/util/forms/Button'
+4
-1
src/view/screens/ProfileFeedLikedBy.tsx
+4
-1
src/view/screens/ProfileFeedLikedBy.tsx
···
3
3
import {useLingui} from '@lingui/react'
4
4
import {useFocusEffect} from '@react-navigation/native'
5
5
6
-
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
6
+
import {
7
+
type CommonNavigatorParams,
8
+
type NativeStackScreenProps,
9
+
} from '#/lib/routes/types'
7
10
import {makeRecordUri} from '#/lib/strings/url-helpers'
8
11
import {useSetMinimalShellMode} from '#/state/shell'
9
12
import {PostLikedBy as PostLikedByComponent} from '#/view/com/post-thread/PostLikedBy'
-1079
src/view/screens/ProfileList.tsx
-1079
src/view/screens/ProfileList.tsx
···
1
-
import React, {useCallback, useMemo} from 'react'
2
-
import {StyleSheet, View} from 'react-native'
3
-
import {useAnimatedRef} from 'react-native-reanimated'
4
-
import {
5
-
AppBskyGraphDefs,
6
-
AtUri,
7
-
moderateUserList,
8
-
type ModerationOpts,
9
-
RichText as RichTextAPI,
10
-
} from '@atproto/api'
11
-
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
12
-
import {msg, Trans} from '@lingui/macro'
13
-
import {useLingui} from '@lingui/react'
14
-
import {useFocusEffect, useIsFocused} from '@react-navigation/native'
15
-
import {useNavigation} from '@react-navigation/native'
16
-
import {useQueryClient} from '@tanstack/react-query'
17
-
18
-
import {useHaptics} from '#/lib/haptics'
19
-
import {useOpenComposer} from '#/lib/hooks/useOpenComposer'
20
-
import {usePalette} from '#/lib/hooks/usePalette'
21
-
import {useSetTitle} from '#/lib/hooks/useSetTitle'
22
-
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
23
-
import {ComposeIcon2} from '#/lib/icons'
24
-
import {makeListLink} from '#/lib/routes/links'
25
-
import {
26
-
type CommonNavigatorParams,
27
-
type NativeStackScreenProps,
28
-
} from '#/lib/routes/types'
29
-
import {type NavigationProp} from '#/lib/routes/types'
30
-
import {shareUrl} from '#/lib/sharing'
31
-
import {cleanError} from '#/lib/strings/errors'
32
-
import {toShareUrl, toShareUrlBsky} from '#/lib/strings/url-helpers'
33
-
import {s} from '#/lib/styles'
34
-
import {logger} from '#/logger'
35
-
import {isNative, isWeb} from '#/platform/detection'
36
-
import {listenSoftReset} from '#/state/events'
37
-
import {useModalControls} from '#/state/modals'
38
-
import {useModerationOpts} from '#/state/preferences/moderation-opts'
39
-
import {
40
-
useListBlockMutation,
41
-
useListDeleteMutation,
42
-
useListMuteMutation,
43
-
useListQuery,
44
-
} from '#/state/queries/list'
45
-
import {type FeedDescriptor} from '#/state/queries/post-feed'
46
-
import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
47
-
import {
48
-
useAddSavedFeedsMutation,
49
-
usePreferencesQuery,
50
-
type UsePreferencesQueryResponse,
51
-
useRemoveFeedMutation,
52
-
useUpdateSavedFeedsMutation,
53
-
} from '#/state/queries/preferences'
54
-
import {useResolveUriQuery} from '#/state/queries/resolve-uri'
55
-
import {truncateAndInvalidate} from '#/state/queries/util'
56
-
import {useSession} from '#/state/session'
57
-
import {useSetMinimalShellMode} from '#/state/shell'
58
-
import {ListMembers} from '#/view/com/lists/ListMembers'
59
-
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
60
-
import {PostFeed} from '#/view/com/posts/PostFeed'
61
-
import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
62
-
import {EmptyState} from '#/view/com/util/EmptyState'
63
-
import {FAB} from '#/view/com/util/fab/FAB'
64
-
import {Button} from '#/view/com/util/forms/Button'
65
-
import {
66
-
type DropdownItem,
67
-
NativeDropdown,
68
-
} from '#/view/com/util/forms/NativeDropdown'
69
-
import {type ListRef} from '#/view/com/util/List'
70
-
import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
71
-
import {LoadingScreen} from '#/view/com/util/LoadingScreen'
72
-
import {Text} from '#/view/com/util/text/Text'
73
-
import * as Toast from '#/view/com/util/Toast'
74
-
import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
75
-
import {atoms as a} from '#/alf'
76
-
import {Button as NewButton, ButtonIcon, ButtonText} from '#/components/Button'
77
-
import {useDialogControl} from '#/components/Dialog'
78
-
import {ListAddRemoveUsersDialog} from '#/components/dialogs/lists/ListAddRemoveUsersDialog'
79
-
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
80
-
import * as Layout from '#/components/Layout'
81
-
import * as Hider from '#/components/moderation/Hider'
82
-
import {
83
-
ReportDialog,
84
-
useReportDialogControl,
85
-
} from '#/components/moderation/ReportDialog'
86
-
import * as Prompt from '#/components/Prompt'
87
-
import {RichText} from '#/components/RichText'
88
-
89
-
interface SectionRef {
90
-
scrollToTop: () => void
91
-
}
92
-
93
-
type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileList'>
94
-
export function ProfileListScreen(props: Props) {
95
-
return (
96
-
<Layout.Screen testID="profileListScreen">
97
-
<ProfileListScreenInner {...props} />
98
-
</Layout.Screen>
99
-
)
100
-
}
101
-
102
-
function ProfileListScreenInner(props: Props) {
103
-
const {_} = useLingui()
104
-
const {name: handleOrDid, rkey} = props.route.params
105
-
const {data: resolvedUri, error: resolveError} = useResolveUriQuery(
106
-
AtUri.make(handleOrDid, 'app.bsky.graph.list', rkey).toString(),
107
-
)
108
-
const {data: preferences} = usePreferencesQuery()
109
-
const {data: list, error: listError} = useListQuery(resolvedUri?.uri)
110
-
const moderationOpts = useModerationOpts()
111
-
112
-
if (resolveError) {
113
-
return (
114
-
<Layout.Content>
115
-
<ErrorScreen
116
-
error={_(
117
-
msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
118
-
)}
119
-
/>
120
-
</Layout.Content>
121
-
)
122
-
}
123
-
if (listError) {
124
-
return (
125
-
<Layout.Content>
126
-
<ErrorScreen error={cleanError(listError)} />
127
-
</Layout.Content>
128
-
)
129
-
}
130
-
131
-
return resolvedUri && list && moderationOpts && preferences ? (
132
-
<ProfileListScreenLoaded
133
-
{...props}
134
-
uri={resolvedUri.uri}
135
-
list={list}
136
-
moderationOpts={moderationOpts}
137
-
preferences={preferences}
138
-
/>
139
-
) : (
140
-
<LoadingScreen />
141
-
)
142
-
}
143
-
144
-
function ProfileListScreenLoaded({
145
-
route,
146
-
uri,
147
-
list,
148
-
moderationOpts,
149
-
preferences,
150
-
}: Props & {
151
-
uri: string
152
-
list: AppBskyGraphDefs.ListView
153
-
moderationOpts: ModerationOpts
154
-
preferences: UsePreferencesQueryResponse
155
-
}) {
156
-
const {_} = useLingui()
157
-
const queryClient = useQueryClient()
158
-
const {openComposer} = useOpenComposer()
159
-
const setMinimalShellMode = useSetMinimalShellMode()
160
-
const {currentAccount} = useSession()
161
-
const {rkey} = route.params
162
-
const feedSectionRef = React.useRef<SectionRef>(null)
163
-
const aboutSectionRef = React.useRef<SectionRef>(null)
164
-
const isCurateList = list.purpose === AppBskyGraphDefs.CURATELIST
165
-
const isScreenFocused = useIsFocused()
166
-
const isHidden = list.labels?.findIndex(l => l.val === '!hide') !== -1
167
-
const isOwner = currentAccount?.did === list.creator.did
168
-
const scrollElRef = useAnimatedRef()
169
-
const addUserDialogControl = useDialogControl()
170
-
const sectionTitlesCurate = [_(msg`Posts`), _(msg`People`)]
171
-
172
-
const moderation = React.useMemo(() => {
173
-
return moderateUserList(list, moderationOpts)
174
-
}, [list, moderationOpts])
175
-
176
-
useSetTitle(isHidden ? _(msg`List Hidden`) : list.name)
177
-
178
-
useFocusEffect(
179
-
useCallback(() => {
180
-
setMinimalShellMode(false)
181
-
}, [setMinimalShellMode]),
182
-
)
183
-
184
-
const onChangeMembers = useCallback(() => {
185
-
if (isCurateList) {
186
-
truncateAndInvalidate(queryClient, FEED_RQKEY(`list|${list.uri}`))
187
-
}
188
-
}, [list.uri, isCurateList, queryClient])
189
-
190
-
const onCurrentPageSelected = React.useCallback(
191
-
(index: number) => {
192
-
if (index === 0) {
193
-
feedSectionRef.current?.scrollToTop()
194
-
} else if (index === 1) {
195
-
aboutSectionRef.current?.scrollToTop()
196
-
}
197
-
},
198
-
[feedSectionRef],
199
-
)
200
-
201
-
const renderHeader = useCallback(() => {
202
-
return <Header rkey={rkey} list={list} preferences={preferences} />
203
-
}, [rkey, list, preferences])
204
-
205
-
if (isCurateList) {
206
-
return (
207
-
<Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}>
208
-
<Hider.Mask>
209
-
<ListHiddenScreen list={list} preferences={preferences} />
210
-
</Hider.Mask>
211
-
<Hider.Content>
212
-
<View style={s.hContentRegion}>
213
-
<PagerWithHeader
214
-
items={sectionTitlesCurate}
215
-
isHeaderReady={true}
216
-
renderHeader={renderHeader}
217
-
onCurrentPageSelected={onCurrentPageSelected}>
218
-
{({headerHeight, scrollElRef, isFocused}) => (
219
-
<FeedSection
220
-
ref={feedSectionRef}
221
-
feed={`list|${uri}`}
222
-
scrollElRef={scrollElRef as ListRef}
223
-
headerHeight={headerHeight}
224
-
isFocused={isScreenFocused && isFocused}
225
-
isOwner={isOwner}
226
-
onPressAddUser={addUserDialogControl.open}
227
-
/>
228
-
)}
229
-
{({headerHeight, scrollElRef}) => (
230
-
<AboutSection
231
-
ref={aboutSectionRef}
232
-
scrollElRef={scrollElRef as ListRef}
233
-
list={list}
234
-
onPressAddUser={addUserDialogControl.open}
235
-
headerHeight={headerHeight}
236
-
/>
237
-
)}
238
-
</PagerWithHeader>
239
-
<FAB
240
-
testID="composeFAB"
241
-
onPress={() => openComposer({})}
242
-
icon={
243
-
<ComposeIcon2
244
-
strokeWidth={1.5}
245
-
size={29}
246
-
style={{color: 'white'}}
247
-
/>
248
-
}
249
-
accessibilityRole="button"
250
-
accessibilityLabel={_(msg`New post`)}
251
-
accessibilityHint=""
252
-
/>
253
-
</View>
254
-
<ListAddRemoveUsersDialog
255
-
control={addUserDialogControl}
256
-
list={list}
257
-
onChange={onChangeMembers}
258
-
/>
259
-
</Hider.Content>
260
-
</Hider.Outer>
261
-
)
262
-
}
263
-
return (
264
-
<Hider.Outer modui={moderation.ui('contentView')} allowOverride={isOwner}>
265
-
<Hider.Mask>
266
-
<ListHiddenScreen list={list} preferences={preferences} />
267
-
</Hider.Mask>
268
-
<Hider.Content>
269
-
<View style={s.hContentRegion}>
270
-
<Layout.Center>{renderHeader()}</Layout.Center>
271
-
<AboutSection
272
-
list={list}
273
-
scrollElRef={scrollElRef as ListRef}
274
-
onPressAddUser={addUserDialogControl.open}
275
-
headerHeight={0}
276
-
/>
277
-
<FAB
278
-
testID="composeFAB"
279
-
onPress={() => openComposer({})}
280
-
icon={
281
-
<ComposeIcon2
282
-
strokeWidth={1.5}
283
-
size={29}
284
-
style={{color: 'white'}}
285
-
/>
286
-
}
287
-
accessibilityRole="button"
288
-
accessibilityLabel={_(msg`New post`)}
289
-
accessibilityHint=""
290
-
/>
291
-
</View>
292
-
<ListAddRemoveUsersDialog
293
-
control={addUserDialogControl}
294
-
list={list}
295
-
onChange={onChangeMembers}
296
-
/>
297
-
</Hider.Content>
298
-
</Hider.Outer>
299
-
)
300
-
}
301
-
302
-
function Header({
303
-
rkey,
304
-
list,
305
-
preferences,
306
-
}: {
307
-
rkey: string
308
-
list: AppBskyGraphDefs.ListView
309
-
preferences: UsePreferencesQueryResponse
310
-
}) {
311
-
const pal = usePalette('default')
312
-
const palInverted = usePalette('inverted')
313
-
const {_} = useLingui()
314
-
const navigation = useNavigation<NavigationProp>()
315
-
const {currentAccount} = useSession()
316
-
const reportDialogControl = useReportDialogControl()
317
-
const {openModal} = useModalControls()
318
-
const listMuteMutation = useListMuteMutation()
319
-
const listBlockMutation = useListBlockMutation()
320
-
const listDeleteMutation = useListDeleteMutation()
321
-
const isCurateList = list.purpose === 'app.bsky.graph.defs#curatelist'
322
-
const isModList = list.purpose === 'app.bsky.graph.defs#modlist'
323
-
const isBlocking = !!list.viewer?.blocked
324
-
const isMuting = !!list.viewer?.muted
325
-
const isOwner = list.creator.did === currentAccount?.did
326
-
const playHaptic = useHaptics()
327
-
328
-
const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
329
-
useAddSavedFeedsMutation()
330
-
const {mutateAsync: removeSavedFeed, isPending: isRemovePending} =
331
-
useRemoveFeedMutation()
332
-
const {mutateAsync: updateSavedFeeds, isPending: isUpdatingSavedFeeds} =
333
-
useUpdateSavedFeedsMutation()
334
-
335
-
const isPending =
336
-
isAddSavedFeedPending || isRemovePending || isUpdatingSavedFeeds
337
-
338
-
const deleteListPromptControl = useDialogControl()
339
-
const subscribeMutePromptControl = useDialogControl()
340
-
const subscribeBlockPromptControl = useDialogControl()
341
-
342
-
const savedFeedConfig = preferences?.savedFeeds?.find(
343
-
f => f.value === list.uri,
344
-
)
345
-
const isPinned = Boolean(savedFeedConfig?.pinned)
346
-
347
-
const onTogglePinned = React.useCallback(async () => {
348
-
playHaptic()
349
-
350
-
try {
351
-
if (savedFeedConfig) {
352
-
const pinned = !savedFeedConfig.pinned
353
-
await updateSavedFeeds([
354
-
{
355
-
...savedFeedConfig,
356
-
pinned,
357
-
},
358
-
])
359
-
Toast.show(
360
-
pinned
361
-
? _(msg`Pinned to your feeds`)
362
-
: _(msg`Unpinned from your feeds`),
363
-
)
364
-
} else {
365
-
await addSavedFeeds([
366
-
{
367
-
type: 'list',
368
-
value: list.uri,
369
-
pinned: true,
370
-
},
371
-
])
372
-
Toast.show(_(msg`Saved to your feeds`))
373
-
}
374
-
} catch (e) {
375
-
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
376
-
logger.error('Failed to toggle pinned feed', {message: e})
377
-
}
378
-
}, [
379
-
playHaptic,
380
-
addSavedFeeds,
381
-
updateSavedFeeds,
382
-
list.uri,
383
-
_,
384
-
savedFeedConfig,
385
-
])
386
-
387
-
const onRemoveFromSavedFeeds = React.useCallback(async () => {
388
-
playHaptic()
389
-
if (!savedFeedConfig) return
390
-
try {
391
-
await removeSavedFeed(savedFeedConfig)
392
-
Toast.show(_(msg`Removed from your feeds`))
393
-
} catch (e) {
394
-
Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
395
-
logger.error('Failed to remove pinned list', {message: e})
396
-
}
397
-
}, [playHaptic, removeSavedFeed, _, savedFeedConfig])
398
-
399
-
const onSubscribeMute = useCallback(async () => {
400
-
try {
401
-
await listMuteMutation.mutateAsync({uri: list.uri, mute: true})
402
-
Toast.show(_(msg({message: 'List muted', context: 'toast'})))
403
-
logger.metric(
404
-
'moderation:subscribedToList',
405
-
{listType: 'mute'},
406
-
{statsig: true},
407
-
)
408
-
} catch {
409
-
Toast.show(
410
-
_(
411
-
msg`There was an issue. Please check your internet connection and try again.`,
412
-
),
413
-
)
414
-
}
415
-
}, [list, listMuteMutation, _])
416
-
417
-
const onUnsubscribeMute = useCallback(async () => {
418
-
try {
419
-
await listMuteMutation.mutateAsync({uri: list.uri, mute: false})
420
-
Toast.show(_(msg({message: 'List unmuted', context: 'toast'})))
421
-
logger.metric(
422
-
'moderation:unsubscribedFromList',
423
-
{listType: 'mute'},
424
-
{statsig: true},
425
-
)
426
-
} catch {
427
-
Toast.show(
428
-
_(
429
-
msg`There was an issue. Please check your internet connection and try again.`,
430
-
),
431
-
)
432
-
}
433
-
}, [list, listMuteMutation, _])
434
-
435
-
const onSubscribeBlock = useCallback(async () => {
436
-
try {
437
-
await listBlockMutation.mutateAsync({uri: list.uri, block: true})
438
-
Toast.show(_(msg({message: 'List blocked', context: 'toast'})))
439
-
logger.metric(
440
-
'moderation:subscribedToList',
441
-
{listType: 'block'},
442
-
{statsig: true},
443
-
)
444
-
} catch {
445
-
Toast.show(
446
-
_(
447
-
msg`There was an issue. Please check your internet connection and try again.`,
448
-
),
449
-
)
450
-
}
451
-
}, [list, listBlockMutation, _])
452
-
453
-
const onUnsubscribeBlock = useCallback(async () => {
454
-
try {
455
-
await listBlockMutation.mutateAsync({uri: list.uri, block: false})
456
-
Toast.show(_(msg({message: 'List unblocked', context: 'toast'})))
457
-
logger.metric(
458
-
'moderation:unsubscribedFromList',
459
-
{listType: 'block'},
460
-
{statsig: true},
461
-
)
462
-
} catch {
463
-
Toast.show(
464
-
_(
465
-
msg`There was an issue. Please check your internet connection and try again.`,
466
-
),
467
-
)
468
-
}
469
-
}, [list, listBlockMutation, _])
470
-
471
-
const onPressEdit = useCallback(() => {
472
-
openModal({
473
-
name: 'create-or-edit-list',
474
-
list,
475
-
})
476
-
}, [openModal, list])
477
-
478
-
const onPressDelete = useCallback(async () => {
479
-
await listDeleteMutation.mutateAsync({uri: list.uri})
480
-
481
-
if (savedFeedConfig) {
482
-
await removeSavedFeed(savedFeedConfig)
483
-
}
484
-
485
-
Toast.show(_(msg({message: 'List deleted', context: 'toast'})))
486
-
if (navigation.canGoBack()) {
487
-
navigation.goBack()
488
-
} else {
489
-
navigation.navigate('Home')
490
-
}
491
-
}, [
492
-
list,
493
-
listDeleteMutation,
494
-
navigation,
495
-
_,
496
-
removeSavedFeed,
497
-
savedFeedConfig,
498
-
])
499
-
500
-
const onPressReport = useCallback(() => {
501
-
reportDialogControl.open()
502
-
}, [reportDialogControl])
503
-
504
-
const onPressShare = useCallback(() => {
505
-
const url = toShareUrl(`/profile/${list.creator.did}/lists/${rkey}`)
506
-
shareUrl(url)
507
-
}, [list, rkey])
508
-
509
-
const onPressShareBsky = useCallback(() => {
510
-
const url = toShareUrlBsky(`/profile/${list.creator.did}/lists/${rkey}`)
511
-
shareUrl(url)
512
-
}, [list, rkey])
513
-
514
-
const dropdownItems: DropdownItem[] = useMemo(() => {
515
-
let items: DropdownItem[] = [
516
-
{
517
-
testID: 'listHeaderDropdownShareBtn',
518
-
label: isWeb ? _(msg`Copy link to list`) : _(msg`Share`),
519
-
onPress: onPressShare,
520
-
icon: {
521
-
ios: {
522
-
name: 'square.and.arrow.up',
523
-
},
524
-
android: '',
525
-
web: 'share',
526
-
},
527
-
},
528
-
{
529
-
testID: 'listHeaderDropdownShareBtn',
530
-
label: isWeb ? _(msg`Copy via bsky.app`) : _(msg`Share`),
531
-
onPress: onPressShareBsky,
532
-
icon: {
533
-
ios: {
534
-
name: 'square.and.arrow.up',
535
-
},
536
-
android: '',
537
-
web: 'share',
538
-
},
539
-
},
540
-
]
541
-
542
-
if (savedFeedConfig) {
543
-
items.push({
544
-
testID: 'listHeaderDropdownRemoveFromFeedsBtn',
545
-
label: _(msg`Remove from my feeds`),
546
-
onPress: onRemoveFromSavedFeeds,
547
-
icon: {
548
-
ios: {
549
-
name: 'trash',
550
-
},
551
-
android: '',
552
-
web: ['far', 'trash-can'],
553
-
},
554
-
})
555
-
}
556
-
557
-
if (isOwner) {
558
-
items.push({label: 'separator'})
559
-
items.push({
560
-
testID: 'listHeaderDropdownEditBtn',
561
-
label: _(msg`Edit list details`),
562
-
onPress: onPressEdit,
563
-
icon: {
564
-
ios: {
565
-
name: 'pencil',
566
-
},
567
-
android: '',
568
-
web: 'pen',
569
-
},
570
-
})
571
-
items.push({
572
-
testID: 'listHeaderDropdownDeleteBtn',
573
-
label: _(msg`Delete list`),
574
-
onPress: deleteListPromptControl.open,
575
-
icon: {
576
-
ios: {
577
-
name: 'trash',
578
-
},
579
-
android: '',
580
-
web: ['far', 'trash-can'],
581
-
},
582
-
})
583
-
} else {
584
-
items.push({label: 'separator'})
585
-
items.push({
586
-
testID: 'listHeaderDropdownReportBtn',
587
-
label: _(msg`Report list`),
588
-
onPress: onPressReport,
589
-
icon: {
590
-
ios: {
591
-
name: 'exclamationmark.triangle',
592
-
},
593
-
android: '',
594
-
web: 'circle-exclamation',
595
-
},
596
-
})
597
-
}
598
-
if (isModList && isPinned) {
599
-
items.push({label: 'separator'})
600
-
items.push({
601
-
testID: 'listHeaderDropdownUnpinBtn',
602
-
label: _(msg`Unpin moderation list`),
603
-
onPress:
604
-
isPending || !savedFeedConfig
605
-
? undefined
606
-
: () => removeSavedFeed(savedFeedConfig),
607
-
icon: {
608
-
ios: {
609
-
name: 'pin',
610
-
},
611
-
android: '',
612
-
web: 'thumbtack',
613
-
},
614
-
})
615
-
}
616
-
if (isCurateList && (isBlocking || isMuting)) {
617
-
items.push({label: 'separator'})
618
-
619
-
if (isMuting) {
620
-
items.push({
621
-
testID: 'listHeaderDropdownMuteBtn',
622
-
label: _(msg`Unmute list`),
623
-
onPress: onUnsubscribeMute,
624
-
icon: {
625
-
ios: {
626
-
name: 'eye',
627
-
},
628
-
android: '',
629
-
web: 'eye',
630
-
},
631
-
})
632
-
}
633
-
634
-
if (isBlocking) {
635
-
items.push({
636
-
testID: 'listHeaderDropdownBlockBtn',
637
-
label: _(msg`Unblock list`),
638
-
onPress: onUnsubscribeBlock,
639
-
icon: {
640
-
ios: {
641
-
name: 'person.fill.xmark',
642
-
},
643
-
android: '',
644
-
web: 'user-slash',
645
-
},
646
-
})
647
-
}
648
-
}
649
-
return items
650
-
}, [
651
-
_,
652
-
onPressShare,
653
-
onPressShareBsky,
654
-
savedFeedConfig,
655
-
isOwner,
656
-
isModList,
657
-
isPinned,
658
-
isCurateList,
659
-
isBlocking,
660
-
isMuting,
661
-
onRemoveFromSavedFeeds,
662
-
onPressEdit,
663
-
deleteListPromptControl.open,
664
-
onPressReport,
665
-
isPending,
666
-
removeSavedFeed,
667
-
onUnsubscribeMute,
668
-
onUnsubscribeBlock,
669
-
])
670
-
671
-
const subscribeDropdownItems: DropdownItem[] = useMemo(() => {
672
-
return [
673
-
{
674
-
testID: 'subscribeDropdownMuteBtn',
675
-
label: _(msg`Mute accounts`),
676
-
onPress: subscribeMutePromptControl.open,
677
-
icon: {
678
-
ios: {
679
-
name: 'speaker.slash',
680
-
},
681
-
android: '',
682
-
web: 'user-slash',
683
-
},
684
-
},
685
-
{
686
-
testID: 'subscribeDropdownBlockBtn',
687
-
label: _(msg`Block accounts`),
688
-
onPress: subscribeBlockPromptControl.open,
689
-
icon: {
690
-
ios: {
691
-
name: 'person.fill.xmark',
692
-
},
693
-
android: '',
694
-
web: 'ban',
695
-
},
696
-
},
697
-
]
698
-
}, [_, subscribeMutePromptControl.open, subscribeBlockPromptControl.open])
699
-
700
-
const descriptionRT = useMemo(
701
-
() =>
702
-
list.description
703
-
? new RichTextAPI({
704
-
text: list.description,
705
-
facets: list.descriptionFacets,
706
-
})
707
-
: undefined,
708
-
[list],
709
-
)
710
-
711
-
return (
712
-
<>
713
-
<ProfileSubpageHeader
714
-
href={makeListLink(list.creator.handle || list.creator.did || '', rkey)}
715
-
title={list.name}
716
-
avatar={list.avatar}
717
-
isOwner={list.creator.did === currentAccount?.did}
718
-
creator={list.creator}
719
-
purpose={list.purpose}
720
-
avatarType="list">
721
-
<ReportDialog
722
-
control={reportDialogControl}
723
-
subject={{
724
-
...list,
725
-
$type: 'app.bsky.graph.defs#listView',
726
-
}}
727
-
/>
728
-
{isCurateList ? (
729
-
<Button
730
-
testID={isPinned ? 'unpinBtn' : 'pinBtn'}
731
-
type={isPinned ? 'default' : 'inverted'}
732
-
label={isPinned ? _(msg`Unpin`) : _(msg`Pin to home`)}
733
-
onPress={onTogglePinned}
734
-
disabled={isPending}
735
-
/>
736
-
) : isModList ? (
737
-
isBlocking ? (
738
-
<Button
739
-
testID="unblockBtn"
740
-
type="default"
741
-
label={_(msg`Unblock`)}
742
-
onPress={onUnsubscribeBlock}
743
-
/>
744
-
) : isMuting ? (
745
-
<Button
746
-
testID="unmuteBtn"
747
-
type="default"
748
-
label={_(msg`Unmute`)}
749
-
onPress={onUnsubscribeMute}
750
-
/>
751
-
) : (
752
-
<NativeDropdown
753
-
testID="subscribeBtn"
754
-
items={subscribeDropdownItems}
755
-
accessibilityLabel={_(msg`Subscribe to this list`)}
756
-
accessibilityHint="">
757
-
<View style={[palInverted.view, styles.btn]}>
758
-
<Text style={palInverted.text}>
759
-
<Trans>Subscribe</Trans>
760
-
</Text>
761
-
</View>
762
-
</NativeDropdown>
763
-
)
764
-
) : null}
765
-
<NativeDropdown
766
-
testID="headerDropdownBtn"
767
-
items={dropdownItems}
768
-
accessibilityLabel={_(msg`More options`)}
769
-
accessibilityHint="">
770
-
<View style={[pal.viewLight, styles.btn]}>
771
-
<FontAwesomeIcon
772
-
icon="ellipsis"
773
-
size={20}
774
-
color={pal.colors.text}
775
-
/>
776
-
</View>
777
-
</NativeDropdown>
778
-
779
-
<Prompt.Basic
780
-
control={deleteListPromptControl}
781
-
title={_(msg`Delete this list?`)}
782
-
description={_(
783
-
msg`If you delete this list, you won't be able to recover it.`,
784
-
)}
785
-
onConfirm={onPressDelete}
786
-
confirmButtonCta={_(msg`Delete`)}
787
-
confirmButtonColor="negative"
788
-
/>
789
-
790
-
<Prompt.Basic
791
-
control={subscribeMutePromptControl}
792
-
title={_(msg`Mute these accounts?`)}
793
-
description={_(
794
-
msg`Muting is private. Muted accounts can interact with you, but you will not see their posts or receive notifications from them.`,
795
-
)}
796
-
onConfirm={onSubscribeMute}
797
-
confirmButtonCta={_(msg`Mute list`)}
798
-
/>
799
-
800
-
<Prompt.Basic
801
-
control={subscribeBlockPromptControl}
802
-
title={_(msg`Block these accounts?`)}
803
-
description={_(
804
-
msg`Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you.`,
805
-
)}
806
-
onConfirm={onSubscribeBlock}
807
-
confirmButtonCta={_(msg`Block list`)}
808
-
confirmButtonColor="negative"
809
-
/>
810
-
</ProfileSubpageHeader>
811
-
{descriptionRT ? (
812
-
<View style={[a.px_lg, a.pt_sm, a.pb_sm, a.gap_md]}>
813
-
<RichText value={descriptionRT} style={[a.text_md, a.leading_snug]} />
814
-
</View>
815
-
) : null}
816
-
</>
817
-
)
818
-
}
819
-
820
-
interface FeedSectionProps {
821
-
feed: FeedDescriptor
822
-
headerHeight: number
823
-
scrollElRef: ListRef
824
-
isFocused: boolean
825
-
isOwner: boolean
826
-
onPressAddUser: () => void
827
-
}
828
-
const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
829
-
function FeedSectionImpl(
830
-
{feed, scrollElRef, headerHeight, isFocused, isOwner, onPressAddUser},
831
-
ref,
832
-
) {
833
-
const queryClient = useQueryClient()
834
-
const [hasNew, setHasNew] = React.useState(false)
835
-
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
836
-
const isScreenFocused = useIsFocused()
837
-
const {_} = useLingui()
838
-
839
-
const onScrollToTop = useCallback(() => {
840
-
scrollElRef.current?.scrollToOffset({
841
-
animated: isNative,
842
-
offset: -headerHeight,
843
-
})
844
-
queryClient.resetQueries({queryKey: FEED_RQKEY(feed)})
845
-
setHasNew(false)
846
-
}, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
847
-
React.useImperativeHandle(ref, () => ({
848
-
scrollToTop: onScrollToTop,
849
-
}))
850
-
851
-
React.useEffect(() => {
852
-
if (!isScreenFocused) {
853
-
return
854
-
}
855
-
return listenSoftReset(onScrollToTop)
856
-
}, [onScrollToTop, isScreenFocused])
857
-
858
-
const renderPostsEmpty = useCallback(() => {
859
-
return (
860
-
<View style={[a.gap_xl, a.align_center]}>
861
-
<EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
862
-
{isOwner && (
863
-
<NewButton
864
-
label={_(msg`Start adding people`)}
865
-
onPress={onPressAddUser}
866
-
color="primary"
867
-
size="small"
868
-
variant="solid">
869
-
<ButtonIcon icon={PersonPlusIcon} />
870
-
<ButtonText>
871
-
<Trans>Start adding people!</Trans>
872
-
</ButtonText>
873
-
</NewButton>
874
-
)}
875
-
</View>
876
-
)
877
-
}, [_, onPressAddUser, isOwner])
878
-
879
-
return (
880
-
<View>
881
-
<PostFeed
882
-
testID="listFeed"
883
-
enabled={isFocused}
884
-
feed={feed}
885
-
pollInterval={60e3}
886
-
disablePoll={hasNew}
887
-
scrollElRef={scrollElRef}
888
-
onHasNew={setHasNew}
889
-
onScrolledDownChange={setIsScrolledDown}
890
-
renderEmptyState={renderPostsEmpty}
891
-
headerOffset={headerHeight}
892
-
/>
893
-
{(isScrolledDown || hasNew) && (
894
-
<LoadLatestBtn
895
-
onPress={onScrollToTop}
896
-
label={_(msg`Load new posts`)}
897
-
showIndicator={hasNew}
898
-
/>
899
-
)}
900
-
</View>
901
-
)
902
-
},
903
-
)
904
-
905
-
interface AboutSectionProps {
906
-
list: AppBskyGraphDefs.ListView
907
-
onPressAddUser: () => void
908
-
headerHeight: number
909
-
scrollElRef: ListRef
910
-
}
911
-
const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>(
912
-
function AboutSectionImpl(
913
-
{list, onPressAddUser, headerHeight, scrollElRef},
914
-
ref,
915
-
) {
916
-
const {_} = useLingui()
917
-
const {currentAccount} = useSession()
918
-
const {isMobile} = useWebMediaQueries()
919
-
const [isScrolledDown, setIsScrolledDown] = React.useState(false)
920
-
const isOwner = list.creator.did === currentAccount?.did
921
-
922
-
const onScrollToTop = useCallback(() => {
923
-
scrollElRef.current?.scrollToOffset({
924
-
animated: isNative,
925
-
offset: -headerHeight,
926
-
})
927
-
}, [scrollElRef, headerHeight])
928
-
929
-
React.useImperativeHandle(ref, () => ({
930
-
scrollToTop: onScrollToTop,
931
-
}))
932
-
933
-
const renderHeader = React.useCallback(() => {
934
-
if (!isOwner) {
935
-
return <View />
936
-
}
937
-
if (isMobile) {
938
-
return (
939
-
<View style={[a.px_sm, a.py_sm]}>
940
-
<NewButton
941
-
testID="addUserBtn"
942
-
label={_(msg`Add a user to this list`)}
943
-
onPress={onPressAddUser}
944
-
color="primary"
945
-
size="small"
946
-
variant="outline"
947
-
style={[a.py_md]}>
948
-
<ButtonIcon icon={PersonPlusIcon} />
949
-
<ButtonText>
950
-
<Trans>Add people</Trans>
951
-
</ButtonText>
952
-
</NewButton>
953
-
</View>
954
-
)
955
-
}
956
-
return (
957
-
<View style={[a.px_lg, a.py_md, a.flex_row_reverse]}>
958
-
<NewButton
959
-
testID="addUserBtn"
960
-
label={_(msg`Add a user to this list`)}
961
-
onPress={onPressAddUser}
962
-
color="primary"
963
-
size="small"
964
-
variant="ghost"
965
-
style={[a.py_sm]}>
966
-
<ButtonIcon icon={PersonPlusIcon} />
967
-
<ButtonText>
968
-
<Trans>Add people</Trans>
969
-
</ButtonText>
970
-
</NewButton>
971
-
</View>
972
-
)
973
-
}, [isOwner, _, onPressAddUser, isMobile])
974
-
975
-
const renderEmptyState = useCallback(() => {
976
-
return (
977
-
<View style={[a.gap_xl, a.align_center]}>
978
-
<EmptyState
979
-
icon="users-slash"
980
-
message={_(msg`This list is empty.`)}
981
-
/>
982
-
{isOwner && (
983
-
<NewButton
984
-
testID="emptyStateAddUserBtn"
985
-
label={_(msg`Start adding people`)}
986
-
onPress={onPressAddUser}
987
-
color="primary"
988
-
size="small"
989
-
variant="solid">
990
-
<ButtonIcon icon={PersonPlusIcon} />
991
-
<ButtonText>
992
-
<Trans>Start adding people!</Trans>
993
-
</ButtonText>
994
-
</NewButton>
995
-
)}
996
-
</View>
997
-
)
998
-
}, [_, onPressAddUser, isOwner])
999
-
1000
-
return (
1001
-
<View>
1002
-
<ListMembers
1003
-
testID="listItems"
1004
-
list={list.uri}
1005
-
scrollElRef={scrollElRef}
1006
-
renderHeader={renderHeader}
1007
-
renderEmptyState={renderEmptyState}
1008
-
headerOffset={headerHeight}
1009
-
onScrolledDownChange={setIsScrolledDown}
1010
-
/>
1011
-
{isScrolledDown && (
1012
-
<LoadLatestBtn
1013
-
onPress={onScrollToTop}
1014
-
label={_(msg`Scroll to top`)}
1015
-
showIndicator={false}
1016
-
/>
1017
-
)}
1018
-
</View>
1019
-
)
1020
-
},
1021
-
)
1022
-
1023
-
function ErrorScreen({error}: {error: string}) {
1024
-
const pal = usePalette('default')
1025
-
const navigation = useNavigation<NavigationProp>()
1026
-
const {_} = useLingui()
1027
-
const onPressBack = useCallback(() => {
1028
-
if (navigation.canGoBack()) {
1029
-
navigation.goBack()
1030
-
} else {
1031
-
navigation.navigate('Home')
1032
-
}
1033
-
}, [navigation])
1034
-
1035
-
return (
1036
-
<View
1037
-
style={[
1038
-
pal.view,
1039
-
pal.border,
1040
-
{
1041
-
paddingHorizontal: 18,
1042
-
paddingVertical: 14,
1043
-
borderTopWidth: StyleSheet.hairlineWidth,
1044
-
},
1045
-
]}>
1046
-
<Text type="title-lg" style={[pal.text, s.mb10]}>
1047
-
<Trans>Could not load list</Trans>
1048
-
</Text>
1049
-
<Text type="md" style={[pal.text, s.mb20]}>
1050
-
{error}
1051
-
</Text>
1052
-
1053
-
<View style={{flexDirection: 'row'}}>
1054
-
<Button
1055
-
type="default"
1056
-
accessibilityLabel={_(msg`Go back`)}
1057
-
accessibilityHint={_(msg`Returns to previous page`)}
1058
-
onPress={onPressBack}
1059
-
style={{flexShrink: 1}}>
1060
-
<Text type="button" style={pal.text}>
1061
-
<Trans>Go Back</Trans>
1062
-
</Text>
1063
-
</Button>
1064
-
</View>
1065
-
</View>
1066
-
)
1067
-
}
1068
-
1069
-
const styles = StyleSheet.create({
1070
-
btn: {
1071
-
flexDirection: 'row',
1072
-
alignItems: 'center',
1073
-
gap: 6,
1074
-
paddingVertical: 7,
1075
-
paddingHorizontal: 14,
1076
-
borderRadius: 50,
1077
-
marginLeft: 6,
1078
-
},
1079
-
})
-450
src/view/screens/SavedFeeds.tsx
-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
+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
+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
+4
-1
src/view/screens/Support.tsx
···
5
5
6
6
import {HELP_DESK_URL} from '#/lib/constants'
7
7
import {usePalette} from '#/lib/hooks/usePalette'
8
-
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
8
+
import {
9
+
type CommonNavigatorParams,
10
+
type NativeStackScreenProps,
11
+
} from '#/lib/routes/types'
9
12
import {s} from '#/lib/styles'
10
13
import {useSetMinimalShellMode} from '#/state/shell'
11
14
import {TextLink} from '#/view/com/util/Link'
+1
-1
src/view/shell/Drawer.tsx
+1
-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
-1
src/view/shell/bottom-bar/BottomBar.tsx
+1
-1
src/view/shell/bottom-bar/BottomBarWeb.tsx
+1
-1
src/view/shell/bottom-bar/BottomBarWeb.tsx
+22
-41
yarn.lock
+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"