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