diff options
Diffstat (limited to 'src')
60 files changed, 2752 insertions, 1892 deletions
diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 5b7c5c87e..ae88f457d 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -70,9 +70,21 @@ export const atoms = { overflow_visible: { overflow: 'visible', }, + overflow_x_visible: { + overflowX: 'visible', + }, + overflow_y_visible: { + overflowY: 'visible', + }, overflow_hidden: { overflow: 'hidden', }, + overflow_x_hidden: { + overflowX: 'hidden', + }, + overflow_y_hidden: { + overflowY: 'hidden', + }, /** * @platform web */ @@ -363,6 +375,14 @@ export const atoms = { border_r_0: { borderRightWidth: 0, }, + border_x_0: { + borderLeftWidth: 0, + borderRightWidth: 0, + }, + border_y_0: { + borderTopWidth: 0, + borderBottomWidth: 0, + }, border: { borderWidth: StyleSheet.hairlineWidth, }, @@ -378,6 +398,14 @@ export const atoms = { border_r: { borderRightWidth: StyleSheet.hairlineWidth, }, + border_x: { + borderLeftWidth: StyleSheet.hairlineWidth, + borderRightWidth: StyleSheet.hairlineWidth, + }, + border_y: { + borderTopWidth: StyleSheet.hairlineWidth, + borderBottomWidth: StyleSheet.hairlineWidth, + }, border_transparent: { borderColor: 'transparent', }, @@ -988,6 +1016,9 @@ export const atoms = { block: web({ display: 'block', }), + contents: web({ + display: 'contents', + }), /* * Transition diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 4795385ee..de8287a53 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -12,9 +12,13 @@ import { import { KeyboardAwareScrollView, useKeyboardHandler, + useReanimatedKeyboardAnimation, } from 'react-native-keyboard-controller' -import {runOnJS} from 'react-native-reanimated' -import {type ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/hook/commonTypes' +import Animated, { + runOnJS, + type ScrollEvent, + useAnimatedStyle, +} from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -26,7 +30,7 @@ import {isAndroid, isIOS} from '#/platform/detection' import {useA11y} from '#/state/a11y' import {useDialogStateControlContext} from '#/state/dialogs' import {List, type ListMethods, type ListProps} from '#/view/com/util/List' -import {atoms as a, tokens, useTheme} from '#/alf' +import {atoms as a, ios, platform, tokens, useTheme} from '#/alf' import {useThemeName} from '#/alf/util/useColorModeTheme' import {Context, useDialogContext} from '#/components/Dialog/context' import { @@ -256,6 +260,7 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>( contentContainerStyle, ]} ref={ref} + showsVerticalScrollIndicator={isAndroid ? false : undefined} {...props} bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} bottomOffset={30} @@ -275,12 +280,15 @@ export const InnerFlatList = React.forwardRef< ListProps<any> & { webInnerStyle?: StyleProp<ViewStyle> webInnerContentContainerStyle?: StyleProp<ViewStyle> + footer?: React.ReactNode } ->(function InnerFlatList({style, ...props}, ref) { +>(function InnerFlatList({footer, style, ...props}, ref) { const insets = useSafeAreaInsets() const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext() - const onScroll = (e: ReanimatedScrollEvent) => { + useEnableKeyboardController(isIOS) + + const onScroll = (e: ScrollEvent) => { 'worklet' if (!isAndroid) { return @@ -300,13 +308,54 @@ export const InnerFlatList = React.forwardRef< bounces={nativeSnapPoint === BottomSheetSnapPoint.Full} ListFooterComponent={<View style={{height: insets.bottom + 100}} />} ref={ref} + showsVerticalScrollIndicator={isAndroid ? false : undefined} {...props} - style={[style]} + style={[a.h_full, style]} /> + {footer} </ScrollProvider> ) }) +export function FlatListFooter({children}: {children: React.ReactNode}) { + const t = useTheme() + const {top, bottom} = useSafeAreaInsets() + const {height} = useReanimatedKeyboardAnimation() + + const animatedStyle = useAnimatedStyle(() => { + if (!isIOS) return {} + return { + transform: [{translateY: Math.min(0, height.get() + bottom - 10)}], + } + }) + + return ( + <Animated.View + style={[ + a.absolute, + a.bottom_0, + a.w_full, + a.z_10, + a.border_t, + t.atoms.bg, + t.atoms.border_contrast_low, + a.px_lg, + a.pt_md, + { + paddingBottom: platform({ + ios: tokens.space.md + bottom, + android: tokens.space.md + bottom + top, + }), + }, + // TODO: had to admit defeat here, but we should + // try and get this to work for Android as well -sfn + ios(animatedStyle), + ]}> + {children} + </Animated.View> + ) +} + export function Handle({difference = false}: {difference?: boolean}) { const t = useTheme() const {_} = useLingui() diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index 7e10dfadc..1d62cbfdc 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -33,6 +33,9 @@ export * from '#/components/Dialog/types' export * from '#/components/Dialog/utils' export {Input} from '#/components/forms/TextField' +// 100 minus 10vh of paddingVertical +export const WEB_DIALOG_HEIGHT = '80vh' + const stopPropagation = (e: any) => e.stopPropagation() const preventDefault = (e: any) => e.preventDefault() @@ -215,9 +218,17 @@ export const InnerFlatList = React.forwardRef< FlatListProps<any> & {label: string} & { webInnerStyle?: StyleProp<ViewStyle> webInnerContentContainerStyle?: StyleProp<ViewStyle> + footer?: React.ReactNode } >(function InnerFlatList( - {label, style, webInnerStyle, webInnerContentContainerStyle, ...props}, + { + label, + style, + webInnerStyle, + webInnerContentContainerStyle, + footer, + ...props + }, ref, ) { const {gtMobile} = useBreakpoints() @@ -227,8 +238,7 @@ export const InnerFlatList = React.forwardRef< style={[ a.overflow_hidden, a.px_0, - // 100 minus 10vh of paddingVertical - web({maxHeight: '80vh'}), + web({maxHeight: WEB_DIALOG_HEIGHT}), webInnerStyle, ]} contentContainerStyle={[a.h_full, a.px_0, webInnerContentContainerStyle]}> @@ -237,10 +247,32 @@ export const InnerFlatList = React.forwardRef< style={[a.h_full, gtMobile ? a.px_2xl : a.px_xl, flatten(style)]} {...props} /> + {footer} </Inner> ) }) +export function FlatListFooter({children}: {children: React.ReactNode}) { + const t = useTheme() + + return ( + <View + style={[ + a.absolute, + a.bottom_0, + a.w_full, + a.z_10, + t.atoms.bg, + a.border_t, + t.atoms.border_contrast_low, + a.px_lg, + a.py_md, + ]}> + {children} + </View> + ) +} + export function Close() { const {_} = useLingui() const {close} = React.useContext(Context) diff --git a/src/components/InterestTabs.tsx b/src/components/InterestTabs.tsx new file mode 100644 index 000000000..b61157ed8 --- /dev/null +++ b/src/components/InterestTabs.tsx @@ -0,0 +1,390 @@ +import {useEffect, useRef, useState} from 'react' +import { + type ScrollView, + type StyleProp, + View, + type ViewStyle, +} from 'react-native' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {isWeb} from '#/platform/detection' +import {DraggableScrollView} from '#/view/com/pager/DraggableScrollView' +import {atoms as a, tokens, useTheme, web} from '#/alf' +import {transparentifyColor} from '#/alf/util/colorGeneration' +import {Button, ButtonIcon} from '#/components/Button' +import { + ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft, + ArrowRight_Stroke2_Corner0_Rounded as ArrowRight, +} from '#/components/icons/Arrow' +import {Text} from '#/components/Typography' + +/** + * Tab component that automatically scrolls the selected tab into view - used for interests + * in the Find Follows dialog, Explore screen, etc. + */ +export function InterestTabs({ + onSelectTab, + interests, + selectedInterest, + disabled, + interestsDisplayNames, + TabComponent = Tab, + contentContainerStyle, + gutterWidth = tokens.space.lg, +}: { + onSelectTab: (tab: string) => void + interests: string[] + selectedInterest: string + interestsDisplayNames: Record<string, string> + /** still allows changing tab, but removes the active state from the selected tab */ + disabled?: boolean + TabComponent?: React.ComponentType<React.ComponentProps<typeof Tab>> + contentContainerStyle?: StyleProp<ViewStyle> + gutterWidth?: number +}) { + const t = useTheme() + const {_} = useLingui() + const listRef = useRef<ScrollView>(null) + const [totalWidth, setTotalWidth] = useState(0) + const [scrollX, setScrollX] = useState(0) + const [contentWidth, setContentWidth] = useState(0) + const pendingTabOffsets = useRef<{x: number; width: number}[]>([]) + const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([]) + + const onInitialLayout = useNonReactiveCallback(() => { + const index = interests.indexOf(selectedInterest) + scrollIntoViewIfNeeded(index) + }) + + useEffect(() => { + if (tabOffsets) { + onInitialLayout() + } + }, [tabOffsets, onInitialLayout]) + + function scrollIntoViewIfNeeded(index: number) { + const btnLayout = tabOffsets[index] + if (!btnLayout) return + listRef.current?.scrollTo({ + // centered + x: btnLayout.x - (totalWidth / 2 - btnLayout.width / 2), + animated: true, + }) + } + + function handleSelectTab(index: number) { + const tab = interests[index] + onSelectTab(tab) + scrollIntoViewIfNeeded(index) + } + + function handleTabLayout(index: number, x: number, width: number) { + if (!tabOffsets.length) { + pendingTabOffsets.current[index] = {x, width} + if (pendingTabOffsets.current.length === interests.length) { + setTabOffsets(pendingTabOffsets.current) + } + } + } + + const canScrollLeft = scrollX > 0 + const canScrollRight = scrollX < contentWidth - totalWidth + + const cleanupRef = useRef<(() => void) | null>(null) + + function scrollLeft() { + if (isContinuouslyScrollingRef.current) { + return + } + if (listRef.current && canScrollLeft) { + const newScrollX = Math.max(0, scrollX - 200) + listRef.current.scrollTo({x: newScrollX, animated: true}) + } + } + + function scrollRight() { + if (isContinuouslyScrollingRef.current) { + return + } + if (listRef.current && canScrollRight) { + const maxScroll = contentWidth - totalWidth + const newScrollX = Math.min(maxScroll, scrollX + 200) + listRef.current.scrollTo({x: newScrollX, animated: true}) + } + } + + const isContinuouslyScrollingRef = useRef(false) + + function startContinuousScroll(direction: 'left' | 'right') { + // Clear any existing continuous scroll + if (cleanupRef.current) { + cleanupRef.current() + } + + let holdTimeout: NodeJS.Timeout | null = null + let animationFrame: number | null = null + let isActive = true + isContinuouslyScrollingRef.current = false + + const cleanup = () => { + isActive = false + if (holdTimeout) clearTimeout(holdTimeout) + if (animationFrame) cancelAnimationFrame(animationFrame) + cleanupRef.current = null + // Reset flag after a delay to prevent onPress from firing + setTimeout(() => { + isContinuouslyScrollingRef.current = false + }, 100) + } + + cleanupRef.current = cleanup + + // Start continuous scrolling after hold delay + holdTimeout = setTimeout(() => { + if (!isActive) return + + isContinuouslyScrollingRef.current = true + let currentScrollPosition = scrollX + + const scroll = () => { + if (!isActive || !listRef.current) return + + const scrollAmount = 3 + const maxScroll = contentWidth - totalWidth + + let newScrollX: number + let canContinue = false + + if (direction === 'left' && currentScrollPosition > 0) { + newScrollX = Math.max(0, currentScrollPosition - scrollAmount) + canContinue = newScrollX > 0 + } else if (direction === 'right' && currentScrollPosition < maxScroll) { + newScrollX = Math.min(maxScroll, currentScrollPosition + scrollAmount) + canContinue = newScrollX < maxScroll + } else { + return + } + + currentScrollPosition = newScrollX + listRef.current.scrollTo({x: newScrollX, animated: false}) + + if (canContinue && isActive) { + animationFrame = requestAnimationFrame(scroll) + } + } + + scroll() + }, 500) + } + + function stopContinuousScroll() { + if (cleanupRef.current) { + cleanupRef.current() + } + } + + useEffect(() => { + return () => { + if (cleanupRef.current) { + cleanupRef.current() + } + } + }, []) + + return ( + <View style={[a.relative, a.flex_row]}> + <DraggableScrollView + ref={listRef} + contentContainerStyle={[ + a.gap_sm, + {paddingHorizontal: gutterWidth}, + contentContainerStyle, + ]} + showsHorizontalScrollIndicator={false} + decelerationRate="fast" + snapToOffsets={ + tabOffsets.length === interests.length + ? tabOffsets.map(o => o.x - tokens.space.xl) + : undefined + } + onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)} + onContentSizeChange={width => setContentWidth(width)} + onScroll={evt => { + const newScrollX = evt.nativeEvent.contentOffset.x + setScrollX(newScrollX) + }} + scrollEventThrottle={16}> + {interests.map((interest, i) => { + const active = interest === selectedInterest && !disabled + return ( + <TabComponent + key={interest} + onSelectTab={handleSelectTab} + active={active} + index={i} + interest={interest} + interestsDisplayName={interestsDisplayNames[interest]} + onLayout={handleTabLayout} + /> + ) + })} + </DraggableScrollView> + {isWeb && canScrollLeft && ( + <View + style={[ + a.absolute, + a.top_0, + a.left_0, + a.bottom_0, + a.justify_center, + {paddingLeft: gutterWidth}, + a.pr_md, + a.z_10, + web({ + background: `linear-gradient(to right, ${t.atoms.bg.backgroundColor} 0%, ${t.atoms.bg.backgroundColor} 70%, ${transparentifyColor(t.atoms.bg.backgroundColor, 0)} 100%)`, + }), + ]}> + <Button + label={_(msg`Scroll left`)} + onPress={scrollLeft} + onPressIn={() => startContinuousScroll('left')} + onPressOut={stopContinuousScroll} + color="secondary" + size="small" + style={[ + a.border, + t.atoms.border_contrast_low, + t.atoms.bg, + a.h_full, + {aspectRatio: 1}, + a.rounded_full, + ]}> + <ButtonIcon icon={ArrowLeft} /> + </Button> + </View> + )} + {isWeb && canScrollRight && ( + <View + style={[ + a.absolute, + a.top_0, + a.right_0, + a.bottom_0, + a.justify_center, + {paddingRight: gutterWidth}, + a.pl_md, + a.z_10, + web({ + background: `linear-gradient(to left, ${t.atoms.bg.backgroundColor} 0%, ${t.atoms.bg.backgroundColor} 70%, ${transparentifyColor(t.atoms.bg.backgroundColor, 0)} 100%)`, + }), + ]}> + <Button + label={_(msg`Scroll right`)} + onPress={scrollRight} + onPressIn={() => startContinuousScroll('right')} + onPressOut={stopContinuousScroll} + color="secondary" + size="small" + style={[ + a.border, + t.atoms.border_contrast_low, + t.atoms.bg, + a.h_full, + {aspectRatio: 1}, + a.rounded_full, + ]}> + <ButtonIcon icon={ArrowRight} /> + </Button> + </View> + )} + </View> + ) +} + +function Tab({ + onSelectTab, + interest, + active, + index, + interestsDisplayName, + onLayout, +}: { + onSelectTab: (index: number) => void + interest: string + active: boolean + index: number + interestsDisplayName: string + onLayout: (index: number, x: number, width: number) => void +}) { + const t = useTheme() + const {_} = useLingui() + const label = active + ? _( + msg({ + message: `"${interestsDisplayName}" category (active)`, + comment: + 'Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is currently selected.', + }), + ) + : _( + msg({ + message: `Select "${interestsDisplayName}" category`, + comment: + 'Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is not currently active and can be selected.', + }), + ) + + return ( + <View + key={interest} + onLayout={e => + onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width) + }> + <Button + label={label} + onPress={() => onSelectTab(index)} + // disable focus ring, we handle it + style={web({outline: 'none'})}> + {({hovered, pressed, focused}) => ( + <View + style={[ + a.rounded_full, + a.px_lg, + a.py_sm, + a.border, + active || hovered || pressed + ? [t.atoms.bg_contrast_25, t.atoms.border_contrast_medium] + : focused + ? { + borderColor: t.palette.primary_300, + backgroundColor: t.palette.primary_25, + } + : [t.atoms.bg, t.atoms.border_contrast_low], + ]}> + <Text + style={[ + a.font_medium, + active || hovered || pressed + ? t.atoms.text + : t.atoms.text_contrast_medium, + ]}> + {interestsDisplayName} + </Text> + </View> + )} + </Button> + </View> + ) +} + +export function boostInterests(boosts?: string[]) { + return (_a: string, _b: string) => { + const indexA = boosts?.indexOf(_a) ?? -1 + const indexB = boosts?.indexOf(_b) ?? -1 + const rankA = indexA === -1 ? Infinity : indexA + const rankB = indexB === -1 ? Infinity : indexB + return rankA - rankB + } +} diff --git a/src/components/LoggedOutCTA.tsx b/src/components/LoggedOutCTA.tsx index 7ec8c2264..0bafbd45f 100644 --- a/src/components/LoggedOutCTA.tsx +++ b/src/components/LoggedOutCTA.tsx @@ -1,5 +1,6 @@ import {View, type ViewStyle} from 'react-native' -import {Trans} from '@lingui/macro' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' import {type Gate} from '#/lib/statsig/gates' import {useGate} from '#/lib/statsig/statsig' @@ -21,6 +22,7 @@ export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) { const {requestSwitchToAccount} = useLoggedOutViewControls() const gate = useGate() const t = useTheme() + const {_} = useLingui() // Only show for logged-out users on web if (hasSession || !isWeb) { @@ -66,7 +68,7 @@ export function LoggedOutCTA({style, gateName}: LoggedOutCTAProps) { onPress={() => { requestSwitchToAccount({requestedAccount: 'new'}) }} - label="Create account" + label={_(msg`Create account`)} size="small" variant="solid" color="primary"> diff --git a/src/components/ProgressGuide/FollowDialog.tsx b/src/components/ProgressGuide/FollowDialog.tsx index 20ebb0abf..a2ec4df13 100644 --- a/src/components/ProgressGuide/FollowDialog.tsx +++ b/src/components/ProgressGuide/FollowDialog.tsx @@ -1,17 +1,9 @@ import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' -import { - ScrollView, - type StyleProp, - TextInput, - useWindowDimensions, - View, - type ViewStyle, -} from 'react-native' +import {TextInput, useWindowDimensions, View} from 'react-native' import {type ModerationOpts} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {logEvent} from '#/lib/statsig/statsig' import {isWeb} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' @@ -28,7 +20,6 @@ import { import { atoms as a, native, - tokens, useBreakpoints, useTheme, type ViewStyleProp, @@ -40,6 +31,7 @@ import {useInteractionState} from '#/components/hooks/useInteractionState' import {MagnifyingGlass2_Stroke2_Corner0_Rounded as SearchIcon} from '#/components/icons/MagnifyingGlass2' import {PersonGroup_Stroke2_Corner2_Rounded as PersonGroupIcon} from '#/components/icons/Person' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' +import {boostInterests, InterestTabs} from '#/components/InterestTabs' import * as ProfileCard from '#/components/ProfileCard' import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' @@ -337,12 +329,13 @@ let Header = ({ }} onEscape={control.close} /> - <Tabs + <InterestTabs onSelectTab={onSelectTab} interests={interests} selectedInterest={selectedInterest} - hasSearchText={!!searchText} + disabled={!!searchText} interestsDisplayNames={interestsDisplayNames} + TabComponent={Tab} /> </View> </View> @@ -403,99 +396,6 @@ function HeaderTop({guide}: {guide: Follow10ProgressGuide}) { ) } -let Tabs = ({ - onSelectTab, - interests, - selectedInterest, - hasSearchText, - interestsDisplayNames, - TabComponent = Tab, - contentContainerStyle, -}: { - onSelectTab: (tab: string) => void - interests: string[] - selectedInterest: string - hasSearchText: boolean - interestsDisplayNames: Record<string, string> - TabComponent?: React.ComponentType<React.ComponentProps<typeof Tab>> - contentContainerStyle?: StyleProp<ViewStyle> -}): React.ReactNode => { - const listRef = useRef<ScrollView>(null) - const [totalWidth, setTotalWidth] = useState(0) - const pendingTabOffsets = useRef<{x: number; width: number}[]>([]) - const [tabOffsets, setTabOffsets] = useState<{x: number; width: number}[]>([]) - - const onInitialLayout = useNonReactiveCallback(() => { - const index = interests.indexOf(selectedInterest) - scrollIntoViewIfNeeded(index) - }) - - useEffect(() => { - if (tabOffsets) { - onInitialLayout() - } - }, [tabOffsets, onInitialLayout]) - - function scrollIntoViewIfNeeded(index: number) { - const btnLayout = tabOffsets[index] - if (!btnLayout) return - listRef.current?.scrollTo({ - // centered - x: btnLayout.x - (totalWidth / 2 - btnLayout.width / 2), - animated: true, - }) - } - - function handleSelectTab(index: number) { - const tab = interests[index] - onSelectTab(tab) - scrollIntoViewIfNeeded(index) - } - - function handleTabLayout(index: number, x: number, width: number) { - if (!tabOffsets.length) { - pendingTabOffsets.current[index] = {x, width} - if (pendingTabOffsets.current.length === interests.length) { - setTabOffsets(pendingTabOffsets.current) - } - } - } - - return ( - <ScrollView - ref={listRef} - horizontal - contentContainerStyle={[a.gap_sm, a.px_lg, contentContainerStyle]} - showsHorizontalScrollIndicator={false} - decelerationRate="fast" - snapToOffsets={ - tabOffsets.length === interests.length - ? tabOffsets.map(o => o.x - tokens.space.xl) - : undefined - } - onLayout={evt => setTotalWidth(evt.nativeEvent.layout.width)} - scrollEventThrottle={200} // big throttle - > - {interests.map((interest, i) => { - const active = interest === selectedInterest && !hasSearchText - return ( - <TabComponent - key={interest} - onSelectTab={handleSelectTab} - active={active} - index={i} - interest={interest} - interestsDisplayName={interestsDisplayNames[interest]} - onLayout={handleTabLayout} - /> - ) - })} - </ScrollView> - ) -} -Tabs = memo(Tabs) -export {Tabs} - let Tab = ({ onSelectTab, interest, @@ -513,24 +413,36 @@ let Tab = ({ }): React.ReactNode => { const t = useTheme() const {_} = useLingui() - const activeText = active ? _(msg` (active)`) : '' + const label = active + ? _( + msg({ + message: `Search for "${interestsDisplayName}" (active)`, + comment: + 'Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is currently selected.', + }), + ) + : _( + msg({ + message: `Search for "${interestsDisplayName}`, + comment: + 'Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is not currently active and can be selected.', + }), + ) return ( <View key={interest} onLayout={e => onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width) }> - <Button - label={_(msg`Search for "${interestsDisplayName}"${activeText}`)} - onPress={() => onSelectTab(index)}> - {({hovered, pressed, focused}) => ( + <Button label={label} onPress={() => onSelectTab(index)}> + {({hovered, pressed}) => ( <View style={[ a.rounded_full, a.px_lg, a.py_sm, a.border, - active || hovered || pressed || focused + active || hovered || pressed ? [ t.atoms.bg_contrast_25, {borderColor: t.atoms.bg_contrast_25.backgroundColor}, @@ -540,7 +452,7 @@ let Tab = ({ <Text style={[ a.font_medium, - active || hovered || pressed || focused + active || hovered || pressed ? t.atoms.text : t.atoms.text_contrast_medium, ]}> @@ -759,13 +671,3 @@ function Empty({message}: {message: string}) { </View> ) } - -export function boostInterests(boosts?: string[]) { - return (_a: string, _b: string) => { - const indexA = boosts?.indexOf(_a) ?? -1 - const indexB = boosts?.indexOf(_b) ?? -1 - const rankA = indexA === -1 ? Infinity : indexA - const rankB = indexB === -1 ? Infinity : indexB - return rankA - rankB - } -} diff --git a/src/components/Toast/Toast.tsx b/src/components/Toast/Toast.tsx index 53d5e5115..ac5bc4889 100644 --- a/src/components/Toast/Toast.tsx +++ b/src/components/Toast/Toast.tsx @@ -16,19 +16,6 @@ import {dismiss} from '#/components/Toast/sonner' import {type ToastType} from '#/components/Toast/types' import {Text as BaseText} from '#/components/Typography' -type ToastConfigContextType = { - id: string -} - -type ToastThemeContextType = { - type: ToastType -} - -export type ToastComponentProps = { - type?: ToastType - content: string -} - export const ICONS = { default: CircleCheck, success: CircleCheck, @@ -37,81 +24,67 @@ export const ICONS = { info: CircleInfo, } -const ToastConfigContext = createContext<ToastConfigContextType>({ +const ToastConfigContext = createContext<{ + id: string + type: ToastType +}>({ id: '', + type: 'default', }) ToastConfigContext.displayName = 'ToastConfigContext' export function ToastConfigProvider({ children, id, + type, }: { children: React.ReactNode id: string + type: ToastType }) { return ( - <ToastConfigContext.Provider value={useMemo(() => ({id}), [id])}> + <ToastConfigContext.Provider + value={useMemo(() => ({id, type}), [id, type])}> {children} </ToastConfigContext.Provider> ) } -const ToastThemeContext = createContext<ToastThemeContextType>({ - type: 'default', -}) -ToastThemeContext.displayName = 'ToastThemeContext' - -export function Default({type = 'default', content}: ToastComponentProps) { - return ( - <Outer type={type}> - <Icon /> - <Text>{content}</Text> - </Outer> - ) -} - -export function Outer({ - children, - type = 'default', -}: { - children: React.ReactNode - type?: ToastType -}) { +export function Outer({children}: {children: React.ReactNode}) { const t = useTheme() + const {type} = useContext(ToastConfigContext) const styles = useToastStyles({type}) return ( - <ToastThemeContext.Provider value={useMemo(() => ({type}), [type])}> - <View - style={[ - a.flex_1, - a.p_lg, - a.rounded_md, - a.border, - a.flex_row, - a.gap_sm, - t.atoms.shadow_sm, - { - paddingVertical: 14, // 16 seems too big - backgroundColor: styles.backgroundColor, - borderColor: styles.borderColor, - }, - ]}> - {children} - </View> - </ToastThemeContext.Provider> + <View + style={[ + a.flex_1, + a.p_lg, + a.rounded_md, + a.border, + a.flex_row, + a.gap_sm, + t.atoms.shadow_sm, + { + paddingVertical: 14, // 16 seems too big + backgroundColor: styles.backgroundColor, + borderColor: styles.borderColor, + }, + ]}> + {children} + </View> ) } export function Icon({icon}: {icon?: React.ComponentType<SVGIconProps>}) { - const {type} = useContext(ToastThemeContext) + const {type} = useContext(ToastConfigContext) const styles = useToastStyles({type}) const IconComponent = icon || ICONS[type] return <IconComponent size="md" fill={styles.iconColor} /> } export function Text({children}: {children: React.ReactNode}) { - const {type} = useContext(ToastThemeContext) + const {type} = useContext(ToastConfigContext) const {textColor} = useToastStyles({type}) const {fontScaleCompensation} = useToastFontScaleCompensation() return ( @@ -142,12 +115,12 @@ export function Text({children}: {children: React.ReactNode}) { export function Action( props: Omit<ButtonProps, UninheritableButtonProps | 'children'> & { - children: string + children: React.ReactNode }, ) { const t = useTheme() const {fontScaleCompensation} = useToastFontScaleCompensation() - const {type} = useContext(ToastThemeContext) + const {type} = useContext(ToastConfigContext) const {id} = useContext(ToastConfigContext) const styles = useMemo(() => { const base = { diff --git a/src/components/Toast/index.e2e.tsx b/src/components/Toast/index.e2e.tsx index 357bd8dda..a4056323d 100644 --- a/src/components/Toast/index.e2e.tsx +++ b/src/components/Toast/index.e2e.tsx @@ -1,3 +1,11 @@ +export const DURATION = 0 + +export const Action = () => null +export const Icon = () => null +export const Outer = () => null +export const Text = () => null +export const ToastConfigProvider = () => null + export function ToastOutlet() { return null } diff --git a/src/components/Toast/index.tsx b/src/components/Toast/index.tsx index 0d1c661d2..d70a8ad16 100644 --- a/src/components/Toast/index.tsx +++ b/src/components/Toast/index.tsx @@ -6,15 +6,15 @@ import {toast as sonner, Toaster} from 'sonner-native' import {atoms as a} from '#/alf' import {DURATION} from '#/components/Toast/const' import { - Default as DefaultToast, + Icon as ToastIcon, Outer as BaseOuter, - type ToastComponentProps, + Text as ToastText, ToastConfigProvider, } from '#/components/Toast/Toast' import {type BaseToastOptions} from '#/components/Toast/types' export {DURATION} from '#/components/Toast/const' -export {Action, Icon, Text} from '#/components/Toast/Toast' +export {Action, Icon, Text, ToastConfigProvider} from '#/components/Toast/Toast' export {type ToastType} from '#/components/Toast/types' /** @@ -25,27 +25,10 @@ export function ToastOutlet() { return <Toaster pauseWhenPageIsHidden gap={a.gap_sm.gap} /> } -/** - * The toast UI component - */ -export function Default({type, content}: ToastComponentProps) { +export function Outer({children}: {children: React.ReactNode}) { return ( <View style={[a.px_xl, a.w_full]}> - <DefaultToast content={content} type={type} /> - </View> - ) -} - -export function Outer({ - children, - type = 'default', -}: { - children: React.ReactNode - type?: ToastComponentProps['type'] -}) { - return ( - <View style={[a.px_xl, a.w_full]}> - <BaseOuter type={type}>{children}</BaseOuter> + <BaseOuter>{children}</BaseOuter> </View> ) } @@ -60,14 +43,17 @@ export const api = sonner */ export function show( content: React.ReactNode, - {type, ...options}: BaseToastOptions = {}, + {type = 'default', ...options}: BaseToastOptions = {}, ) { const id = nanoid() if (typeof content === 'string') { sonner.custom( - <ToastConfigProvider id={id}> - <Default content={content} type={type} /> + <ToastConfigProvider id={id} type={type}> + <Outer> + <ToastIcon /> + <ToastText>{content}</ToastText> + </Outer> </ToastConfigProvider>, { ...options, @@ -77,7 +63,9 @@ export function show( ) } else if (React.isValidElement(content)) { sonner.custom( - <ToastConfigProvider id={id}>{content}</ToastConfigProvider>, + <ToastConfigProvider id={id} type={type}> + {content} + </ToastConfigProvider>, { ...options, id, diff --git a/src/components/Toast/index.web.tsx b/src/components/Toast/index.web.tsx index c4db20dca..8b2028db9 100644 --- a/src/components/Toast/index.web.tsx +++ b/src/components/Toast/index.web.tsx @@ -5,7 +5,9 @@ import {toast as sonner, Toaster} from 'sonner' import {atoms as a} from '#/alf' import {DURATION} from '#/components/Toast/const' import { - Default as DefaultToast, + Icon as ToastIcon, + Outer as ToastOuter, + Text as ToastText, ToastConfigProvider, } from '#/components/Toast/Toast' import {type BaseToastOptions} from '#/components/Toast/types' @@ -39,14 +41,17 @@ export const api = sonner */ export function show( content: React.ReactNode, - {type, ...options}: BaseToastOptions = {}, + {type = 'default', ...options}: BaseToastOptions = {}, ) { const id = nanoid() if (typeof content === 'string') { sonner( - <ToastConfigProvider id={id}> - <DefaultToast content={content} type={type} /> + <ToastConfigProvider id={id} type={type}> + <ToastOuter> + <ToastIcon /> + <ToastText>{content}</ToastText> + </ToastOuter> </ToastConfigProvider>, { ...options, @@ -56,12 +61,17 @@ export function show( }, ) } else if (React.isValidElement(content)) { - sonner(<ToastConfigProvider id={id}>{content}</ToastConfigProvider>, { - ...options, - unstyled: true, // required on web - id, - duration: options?.duration ?? DURATION, - }) + sonner( + <ToastConfigProvider id={id} type={type}> + {content} + </ToastConfigProvider>, + { + ...options, + unstyled: true, // required on web + id, + duration: options?.duration ?? DURATION, + }, + ) } else { throw new Error( `Toast can be a string or a React element, got ${typeof content}`, diff --git a/src/components/dialogs/StarterPackDialog.tsx b/src/components/dialogs/StarterPackDialog.tsx index ec041d401..c4b8a72c4 100644 --- a/src/components/dialogs/StarterPackDialog.tsx +++ b/src/components/dialogs/StarterPackDialog.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import {useCallback, useState} from 'react' import {View} from 'react-native' import { type AppBskyGraphGetStarterPacksWithMembership, @@ -22,16 +22,16 @@ import { } from '#/state/queries/list-memberships' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useTheme} from '#/alf' +import {AvatarStack} from '#/components/AvatarStack' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {Divider} from '#/components/Divider' +import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' +import {StarterPack} from '#/components/icons/StarterPack' +import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import * as bsky from '#/types/bsky' -import {AvatarStack} from '../AvatarStack' -import {PlusLarge_Stroke2_Corner0_Rounded} from '../icons/Plus' -import {StarterPack} from '../icons/StarterPack' -import {TimesLarge_Stroke2_Corner0_Rounded} from '../icons/Times' type StarterPackWithMembership = AppBskyGraphGetStarterPacksWithMembership.StarterPackWithMembership @@ -51,7 +51,7 @@ export function StarterPackDialog({ const navigation = useNavigation<NavigationProp>() const requireEmailVerification = useRequireEmailVerification() - const navToWizard = React.useCallback(() => { + const navToWizard = useCallback(() => { control.close() navigation.navigate('StarterPackWizard', { fromDialog: true, @@ -91,7 +91,6 @@ function Empty({onStartWizard}: {onStartWizard: () => void}) { const {_} = useLingui() const t = useTheme() - isWeb return ( <View style={[a.gap_2xl, {paddingTop: isWeb ? 100 : 64}]}> <View style={[a.gap_xs, a.align_center]}> @@ -115,7 +114,7 @@ function Empty({onStartWizard}: {onStartWizard: () => void}) { Create </Trans> </ButtonText> - <ButtonIcon icon={PlusLarge_Stroke2_Corner0_Rounded} /> + <ButtonIcon icon={PlusIcon} /> </Button> </View> </View> @@ -134,7 +133,6 @@ function StarterPackList({ enabled?: boolean }) { const {_} = useLingui() - const t = useTheme() const { data, @@ -149,7 +147,7 @@ function StarterPackList({ const membershipItems = data?.pages.flatMap(page => page.starterPacksWithMembership) || [] - const _onRefresh = React.useCallback(async () => { + const _onRefresh = useCallback(async () => { try { await refetch() } catch (err) { @@ -157,7 +155,7 @@ function StarterPackList({ } }, [refetch]) - const _onEndReached = React.useCallback(async () => { + const _onEndReached = useCallback(async () => { if (isFetchingNextPage || !hasNextPage || isError) return try { await fetchNextPage() @@ -166,25 +164,17 @@ function StarterPackList({ } }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) - const renderItem = React.useCallback( + const renderItem = useCallback( ({item}: {item: StarterPackWithMembership}) => ( <StarterPackItem starterPackWithMembership={item} targetDid={targetDid} /> ), [targetDid], ) - const onClose = React.useCallback(() => { + const onClose = useCallback(() => { control.close() }, [control]) - const XIcon = React.useMemo(() => { - return ( - <TimesLarge_Stroke2_Corner0_Rounded - fill={t.atoms.text_contrast_medium.color} - /> - ) - }, [t]) - const listHeader = ( <> <View @@ -196,8 +186,14 @@ function StarterPackList({ <Text style={[a.text_lg, a.font_bold]}> <Trans>Add to starter packs</Trans> </Text> - <Button label={_(msg`Close`)} onPress={onClose}> - <ButtonIcon icon={() => XIcon} /> + <Button + label={_(msg`Close`)} + onPress={onClose} + variant="ghost" + color="secondary" + size="small" + shape="round"> + <ButtonIcon icon={XIcon} /> </Button> </View> {membershipItems.length > 0 && ( @@ -217,7 +213,7 @@ function StarterPackList({ Create </Trans> </ButtonText> - <ButtonIcon icon={PlusLarge_Stroke2_Corner0_Rounded} /> + <ButtonIcon icon={PlusIcon} /> </Button> </View> <Divider /> @@ -268,7 +264,7 @@ function StarterPackItem({ const starterPack = starterPackWithMembership.starterPack const isInPack = !!starterPackWithMembership.listItem - const [isPendingRefresh, setIsPendingRefresh] = React.useState(false) + const [isPendingRefresh, setIsPendingRefresh] = useState(false) const {mutate: addMembership} = useListMembershipAddMutation({ onSuccess: () => { diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index 9c3564aa5..bb9fde2e1 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -1,5 +1,5 @@ import React from 'react' -import {Pressable, View, type ViewStyle} from 'react-native' +import {Pressable, type StyleProp, View, type ViewStyle} from 'react-native' import Animated, {LinearTransition} from 'react-native-reanimated' import {HITSLOP_10} from '#/lib/constants' @@ -59,6 +59,7 @@ export type GroupProps = React.PropsWithChildren<{ disabled?: boolean onChange: (value: string[]) => void label: string + style?: StyleProp<ViewStyle> }> export type ItemProps = ViewStyleProp & { @@ -84,6 +85,7 @@ export function Group({ type = 'checkbox', maxSelections, label, + style, }: GroupProps) { const groupRole = type === 'radio' ? 'radiogroup' : undefined const values = type === 'radio' ? providedValues.slice(0, 1) : providedValues @@ -136,7 +138,7 @@ export function Group({ return ( <GroupContext.Provider value={context}> <View - style={[a.w_full]} + style={[a.w_full, style]} role={groupRole} {...(groupRole === 'radiogroup' ? { diff --git a/src/env/common.ts b/src/env/common.ts index fbf477726..69451fd7e 100644 --- a/src/env/common.ts +++ b/src/env/common.ts @@ -63,6 +63,12 @@ export const LOG_LEVEL = (process.env.EXPO_PUBLIC_LOG_LEVEL || 'info') as export const LOG_DEBUG: string = process.env.EXPO_PUBLIC_LOG_DEBUG || '' /** + * The DID of the Bluesky appview to proxy to + */ +export const BLUESKY_PROXY_DID: Did = + process.env.EXPO_PUBLIC_BLUESKY_PROXY_DID || 'did:web:api.bsky.app' + +/** * The DID of the chat service to proxy to */ export const CHAT_PROXY_DID: Did = diff --git a/src/lib/constants.ts b/src/lib/constants.ts index d81b68db6..727d4b052 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -1,6 +1,9 @@ import {type Insets, Platform} from 'react-native' import {type AppBskyActorDefs} from '@atproto/api' +import {type ProxyHeaderValue} from '#/state/session/agent' +import {BLUESKY_PROXY_DID, CHAT_PROXY_DID} from '#/env' + export const LOCAL_DEV_SERVICE = Platform.OS === 'android' ? 'http://10.0.2.2:2583' : 'http://localhost:2583' export const STAGING_SERVICE = 'https://staging.bsky.dev' @@ -211,6 +214,16 @@ export const PUBLIC_STAGING_APPVIEW_DID = 'did:web:api.staging.bsky.dev' export const DEV_ENV_APPVIEW = `http://localhost:2584` // always the same +export const BLUESKY_PROXY_HEADER: ProxyHeaderValue = `${BLUESKY_PROXY_DID}#bsky_appview` + +export const BLUESKY_SERVICE_HEADERS = { + 'atproto-proxy': BLUESKY_PROXY_HEADER, +} + +export const DM_SERVICE_HEADERS = { + 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, +} + export const webLinks = { tos: `https://bsky.social/about/support/tos`, privacy: `https://bsky.social/about/support/privacy-policy`, diff --git a/src/lib/hooks/useDraggableScrollView.ts b/src/lib/hooks/useDraggableScrollView.ts index 05fda9a9f..d4d35ccda 100644 --- a/src/lib/hooks/useDraggableScrollView.ts +++ b/src/lib/hooks/useDraggableScrollView.ts @@ -20,9 +20,6 @@ export function useDraggableScroll<Scrollable extends ScrollView = ScrollView>({ return } const slider = ref.current as unknown as HTMLDivElement - if (!slider) { - return - } let isDragging = false let isMouseDown = false let startX = 0 @@ -61,6 +58,9 @@ export function useDraggableScroll<Scrollable extends ScrollView = ScrollView>({ e.preventDefault() const walk = x - startX slider.scrollLeft = scrollLeft - walk + + if (slider.contains(document.activeElement)) + (document.activeElement as HTMLElement)?.blur?.() } slider.addEventListener('mousedown', mouseDown) diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx index 6e0be9d0a..4bea1ebef 100644 --- a/src/lib/icons.tsx +++ b/src/lib/icons.tsx @@ -7,17 +7,19 @@ export function MagnifyingGlassIcon({ style, size, strokeWidth = 2, + color = 'currentColor', }: { style?: StyleProp<ViewStyle> size?: string | number strokeWidth?: number + color?: string }) { return ( <Svg fill="none" viewBox="0 0 24 24" strokeWidth={strokeWidth} - stroke="currentColor" + stroke={color} width={size || 24} height={size || 24} style={style}> diff --git a/src/lib/statsig/gates.ts b/src/lib/statsig/gates.ts index 114048ab7..391314162 100644 --- a/src/lib/statsig/gates.ts +++ b/src/lib/statsig/gates.ts @@ -9,6 +9,8 @@ export type Gate = | 'explore_show_suggested_feeds' | 'old_postonboarding' | 'onboarding_add_video_feed' + | 'onboarding_suggested_accounts' + | 'onboarding_value_prop' | 'post_follow_profile_suggested_accounts' | 'remove_show_latest_button' | 'test_gate_1' diff --git a/src/locale/locales/en/messages.po b/src/locale/locales/en/messages.po index a6c98211c..c48ea6c64 100644 --- a/src/locale/locales/en/messages.po +++ b/src/locale/locales/en/messages.po @@ -13,16 +13,16 @@ msgstr "" "Language-Team: \n" "Plural-Forms: \n" -#: src/components/ProgressGuide/FollowDialog.tsx:516 -#: src/screens/Search/modules/ExploreSuggestedAccounts.tsx:127 -msgid "(active)" +#. Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is currently selected. +#: src/components/InterestTabs.tsx:325 +msgid "\"{interestsDisplayName}\" category (active)" msgstr "" #: src/screens/Messages/components/ChatListItem.tsx:160 msgid "(contains embedded content)" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:69 +#: src/screens/Settings/AccountSettings.tsx:71 msgid "(no email)" msgstr "" @@ -95,8 +95,7 @@ msgstr "" msgid "{0, plural, one {following} other {following}}" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:469 -#: src/view/com/post-thread/PostThreadItem.tsx:538 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:473 msgid "{0, plural, one {like} other {likes}}" msgstr "" @@ -104,13 +103,11 @@ msgstr "" msgid "{0, plural, one {post} other {posts}}" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:453 -#: src/view/com/post-thread/PostThreadItem.tsx:522 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:457 msgid "{0, plural, one {quote} other {quotes}}" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:435 -#: src/view/com/post-thread/PostThreadItem.tsx:504 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:439 msgid "{0, plural, one {repost} other {reposts}}" msgstr "" @@ -123,6 +120,10 @@ msgstr "" msgid "{0, plural, other {# people have}} used this starter pack!" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:370 +msgid "{0, plural, other {+# more}}" +msgstr "" + #. Pattern: {wordValue} in tags #: src/components/dialogs/MutedWords.tsx:475 msgid "{0} <0>in <1>tags</1></0>" @@ -211,20 +212,20 @@ msgstr "" msgid "{0}s" msgstr "" -#: src/view/shell/desktop/LeftNav.tsx:402 +#: src/view/shell/desktop/LeftNav.tsx:454 msgid "{count, plural, one {# unread item} other {# unread items}}" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:386 +#: src/screens/Profile/Header/EditProfileDialog.tsx:380 msgid "{DESCRIPTION_MAX_GRAPHEMES, plural, other {Description is too long. The maximum number of characters is #.}}" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:340 +#: src/screens/Profile/Header/EditProfileDialog.tsx:334 msgid "{DISPLAY_NAME_MAX_GRAPHEMES, plural, other {Display name is too long. The maximum number of characters is #.}}" msgstr "" -#: src/lib/generate-starterpack.ts:111 -#: src/screens/StarterPack/Wizard/index.tsx:184 +#: src/lib/generate-starterpack.ts:104 +#: src/screens/StarterPack/Wizard/index.tsx:201 msgid "{displayName}'s Starter Pack" msgstr "" @@ -453,12 +454,12 @@ msgstr "" msgid "+{computedTotal}" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:488 +#: src/screens/StarterPack/Wizard/index.tsx:524 msgctxt "profiles" msgid "<0>{0}, </0><1>{1}, </1>and {2, plural, one {# other} other {# others}} are included in your starter pack" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:541 +#: src/screens/StarterPack/Wizard/index.tsx:577 msgctxt "feeds" msgid "<0>{0}, </0><1>{1}, </1>and {2, plural, one {# other} other {# others}} are included in your starter pack" msgstr "" @@ -471,11 +472,12 @@ msgstr "" msgid "<0>{0}</0> {1, plural, one {following} other {following}}" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:529 +#: src/screens/StarterPack/Wizard/index.tsx:511 +#: src/screens/StarterPack/Wizard/index.tsx:565 msgid "<0>{0}</0> and<1> </1><2>{1} </2>are included in your starter pack" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:522 +#: src/screens/StarterPack/Wizard/index.tsx:558 msgid "<0>{0}</0> is included in your starter pack" msgstr "" @@ -491,7 +493,7 @@ msgstr "" msgid "<0>Sign in</0><1> or </1><2>create an account</2><3> </3><4>to search for news, sports, politics, and everything else happening on Bluesky.</4>" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:479 +#: src/screens/StarterPack/Wizard/index.tsx:502 msgid "<0>You</0> and<1> </1><2>{0} </2>are included in your starter pack" msgstr "" @@ -515,6 +517,10 @@ msgstr "" msgid "7 days" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:341 +msgid "A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens" +msgstr "" + #. If last message does not contain text, fall back to "{user} reacted to {a message}" #: src/screens/Messages/components/ChatListItem.tsx:210 msgid "a message" @@ -565,26 +571,26 @@ msgstr "" #: src/Navigation.tsx:398 #: src/screens/Login/LoginForm.tsx:194 -#: src/screens/Settings/AccountSettings.tsx:49 +#: src/screens/Settings/AccountSettings.tsx:51 #: src/screens/Settings/Settings.tsx:174 #: src/screens/Settings/Settings.tsx:177 msgid "Account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:357 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:361 #: src/screens/Messages/components/RequestButtons.tsx:91 -#: src/view/com/profile/ProfileMenu.tsx:158 +#: src/view/com/profile/ProfileMenu.tsx:161 msgctxt "toast" msgid "Account blocked" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:171 +#: src/view/com/profile/ProfileMenu.tsx:174 msgctxt "toast" msgid "Account followed" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:380 -#: src/view/com/profile/ProfileMenu.tsx:134 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:384 +#: src/view/com/profile/ProfileMenu.tsx:137 msgctxt "toast" msgid "Account muted" msgstr "" @@ -607,18 +613,18 @@ msgid "Account removed from quick access" msgstr "" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:132 -#: src/view/com/profile/ProfileMenu.tsx:148 +#: src/view/com/profile/ProfileMenu.tsx:151 msgctxt "toast" msgid "Account unblocked" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:183 +#: src/view/com/profile/ProfileMenu.tsx:186 msgctxt "toast" msgid "Account unfollowed" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:370 -#: src/view/com/profile/ProfileMenu.tsx:124 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:374 +#: src/view/com/profile/ProfileMenu.tsx:127 msgctxt "toast" msgid "Account unmuted" msgstr "" @@ -639,11 +645,13 @@ msgstr "" #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:169 #: src/components/dialogs/MutedWords.tsx:328 +#: src/components/dialogs/StarterPackDialog.tsx:384 +#: src/components/dialogs/StarterPackDialog.tsx:390 #: src/view/com/modals/UserAddRemoveLists.tsx:235 msgid "Add" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:572 +#: src/screens/StarterPack/Wizard/index.tsx:608 msgid "Add {0} more to continue" msgstr "" @@ -686,12 +694,12 @@ msgstr "" #: src/screens/Settings/Settings.tsx:564 #: src/screens/Settings/Settings.tsx:567 -#: src/view/shell/desktop/LeftNav.tsx:259 -#: src/view/shell/desktop/LeftNav.tsx:263 +#: src/view/shell/desktop/LeftNav.tsx:261 +#: src/view/shell/desktop/LeftNav.tsx:265 msgid "Add another account" msgstr "" -#: src/view/com/composer/Composer.tsx:788 +#: src/view/com/composer/Composer.tsx:810 msgid "Add another post" msgstr "" @@ -709,7 +717,7 @@ msgstr "" msgid "Add emoji reaction" msgstr "" -#. Accessibility label for button in composer to add photos or a video to a post +#. Accessibility label for button in composer to add images, a video, or a GIF to a post #: src/view/com/composer/SelectMediaButton.tsx:482 msgid "Add media to post" msgstr "" @@ -727,7 +735,7 @@ msgstr "" msgid "Add muted words and tags" msgstr "" -#: src/view/com/composer/Composer.tsx:1421 +#: src/view/com/composer/Composer.tsx:1443 msgid "Add new post" msgstr "" @@ -748,7 +756,7 @@ msgstr "" msgid "Add recommended feeds" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:510 +#: src/screens/StarterPack/Wizard/index.tsx:546 msgid "Add some feeds to your starter pack!" msgstr "" @@ -764,11 +772,17 @@ msgstr "" msgid "Add this feed to your feeds" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:305 -#: src/view/com/profile/ProfileMenu.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:317 +#: src/view/com/profile/ProfileMenu.tsx:320 msgid "Add to lists" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:187 +#: src/view/com/profile/ProfileMenu.tsx:308 +#: src/view/com/profile/ProfileMenu.tsx:311 +msgid "Add to starter packs" +msgstr "" + #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:156 msgid "Add user to list" msgstr "" @@ -778,6 +792,10 @@ msgstr "" msgid "Added to list" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:271 +msgid "Added to starter pack" +msgstr "" + #: src/components/ageAssurance/AgeAssuranceAppealDialog.tsx:112 msgid "Additional details (limit 1000 characters)" msgstr "" @@ -835,6 +853,8 @@ msgstr "" msgid "alice@example.com" msgstr "" +#. the default tab in the interests tab bar +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:154 #: src/view/screens/Notifications.tsx:88 msgid "All" msgstr "" @@ -846,6 +866,7 @@ msgstr "" #: src/screens/Search/components/SearchLanguageDropdown.tsx:64 #: src/screens/Search/components/SearchLanguageDropdown.tsx:99 #: src/screens/Search/components/SearchLanguageDropdown.tsx:101 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:280 msgid "All languages" msgstr "" @@ -881,7 +902,8 @@ msgid "Allows access to direct messages" msgstr "" #: src/screens/Login/ForgotPasswordForm.tsx:171 -#: src/view/com/modals/ChangePassword.tsx:182 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:235 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:241 msgid "Already have a code?" msgstr "" @@ -923,6 +945,7 @@ msgid "An email has been sent to {0}. It includes a confirmation code which you msgstr "" #: src/components/dialogs/GifSelect.tsx:264 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:362 msgid "An error has occurred" msgstr "" @@ -934,6 +957,10 @@ msgstr "" msgid "An error occurred while compressing the video." msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:176 +msgid "An error occurred while fetching suggested accounts." +msgstr "" + #: src/state/queries/explore-feed-previews.tsx:173 msgid "An error occurred while fetching the feed." msgstr "" @@ -963,6 +990,10 @@ msgstr "" msgid "An error occurred while uploading the video." msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:359 +msgid "An illustration of several Bluesky posts alongside repost, like, and comment icons" +msgstr "" + #: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:84 #: src/components/verification/VerifierDialog.tsx:86 msgid "An illustration showing that Bluesky selects trusted verifiers, and trusted verifiers in turn verify individual user accounts." @@ -994,7 +1025,7 @@ msgstr "" msgid "An mockup of a iPhone showing the Bluesky app open to the profile of a verified user with a blue checkmark next to their display name." msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:185 +#: src/screens/Onboarding/StepInterests/index.tsx:194 msgid "an unknown error occurred" msgstr "" @@ -1007,8 +1038,8 @@ msgstr "" msgid "and" msgstr "" -#: src/screens/Onboarding/index.tsx:29 -#: src/screens/Onboarding/state.ts:97 +#: src/screens/Onboarding/index.tsx:43 +#: src/screens/Onboarding/state.ts:102 msgid "Animals" msgstr "" @@ -1130,15 +1161,12 @@ msgstr "" msgid "Apply Pull Request" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:639 -#: src/view/com/post-thread/PostThreadItem.tsx:954 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:643 msgid "Archived from {0}" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:608 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:647 -#: src/view/com/post-thread/PostThreadItem.tsx:923 -#: src/view/com/post-thread/PostThreadItem.tsx:962 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:612 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:651 msgid "Archived post" msgstr "" @@ -1154,7 +1182,7 @@ msgstr "" msgid "Are you sure you want to delete this starter pack?" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:87 +#: src/screens/Profile/Header/EditProfileDialog.tsx:81 msgid "Are you sure you want to discard your changes?" msgstr "" @@ -1170,11 +1198,11 @@ msgstr "" msgid "Are you sure you want to remove this from your feeds?" msgstr "" -#: src/view/com/composer/Composer.tsx:737 +#: src/view/com/composer/Composer.tsx:759 msgid "Are you sure you'd like to discard this draft?" msgstr "" -#: src/view/com/composer/Composer.tsx:927 +#: src/view/com/composer/Composer.tsx:949 msgid "Are you sure you'd like to discard this post?" msgstr "" @@ -1186,8 +1214,8 @@ msgstr "" msgid "Are you writing in <0>{suggestedLanguageName}</0>?" msgstr "" -#: src/screens/Onboarding/index.tsx:23 -#: src/screens/Onboarding/state.ts:98 +#: src/screens/Onboarding/index.tsx:37 +#: src/screens/Onboarding/state.ts:103 msgid "Art" msgstr "" @@ -1199,11 +1227,15 @@ msgstr "" msgid "As a small team, we cannot justify building the expensive infrastructure this requirement demands while legal challenges to this law are pending." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:491 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:493 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:497 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:499 msgid "Assign topic for algo" msgstr "" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:208 +msgid "At least 8 characters" +msgstr "" + #: src/screens/Settings/AppIconSettings/useAppIconSets.ts:58 msgctxt "Name of app icon variant" msgid "Aurora" @@ -1232,8 +1264,10 @@ msgstr "" #: src/screens/Messages/components/ChatDisabled.tsx:133 #: src/screens/Messages/components/ChatDisabled.tsx:134 #: src/screens/Profile/Header/Shell.tsx:158 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:271 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:280 #: src/screens/Signup/BackNextButtons.tsx:41 -#: src/screens/StarterPack/Wizard/index.tsx:303 +#: src/screens/StarterPack/Wizard/index.tsx:323 msgid "Back" msgstr "" @@ -1250,6 +1284,7 @@ msgstr "" msgid "Before creating a post or replying, you must first verify your email." msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:71 #: src/components/StarterPack/ProfileStarterPacks.tsx:235 #: src/components/StarterPack/ProfileStarterPacks.tsx:245 msgid "Before creating a starter pack, you must first verify your email." @@ -1283,29 +1318,29 @@ msgid "Begin the age assurance process by completing the fields below." msgstr "" #: src/components/dialogs/BirthDateSettings.tsx:103 -#: src/screens/Settings/AccountSettings.tsx:140 +#: src/screens/Settings/AccountSettings.tsx:142 msgid "Birthday" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:753 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:760 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:328 -#: src/view/com/profile/ProfileMenu.tsx:473 +#: src/view/com/profile/ProfileMenu.tsx:490 msgid "Block" msgstr "" #: src/components/dms/ConvoMenu.tsx:247 #: src/components/dms/ConvoMenu.tsx:250 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:638 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:640 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:645 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:647 #: src/screens/Messages/components/RequestButtons.tsx:144 #: src/screens/Messages/components/RequestButtons.tsx:146 -#: src/view/com/profile/ProfileMenu.tsx:384 -#: src/view/com/profile/ProfileMenu.tsx:391 +#: src/view/com/profile/ProfileMenu.tsx:396 +#: src/view/com/profile/ProfileMenu.tsx:403 msgid "Block account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:748 -#: src/view/com/profile/ProfileMenu.tsx:456 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:755 +#: src/view/com/profile/ProfileMenu.tsx:473 msgid "Block Account?" msgstr "" @@ -1355,8 +1390,8 @@ msgstr "" msgid "Blocked Accounts" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:750 -#: src/view/com/profile/ProfileMenu.tsx:468 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:757 +#: src/view/com/profile/ProfileMenu.tsx:485 msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" @@ -1364,10 +1399,6 @@ msgstr "" msgid "Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you. You will not see their content and they will be prevented from seeing yours." msgstr "" -#: src/view/com/post-thread/PostThread.tsx:489 -msgid "Blocked post." -msgstr "" - #: src/screens/Profile/Sections/Labels.tsx:203 msgid "Blocking does not prevent this labeler from placing labels on your account." msgstr "" @@ -1376,7 +1407,7 @@ msgstr "" msgid "Blocking is public. Blocked accounts cannot reply in your threads, mention you, or otherwise interact with you." msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:465 +#: src/view/com/profile/ProfileMenu.tsx:482 msgid "Blocking will not prevent labels from being applied on your account, but it will stop this account from replying in your threads or interacting with you." msgstr "" @@ -1389,8 +1420,7 @@ msgstr "" msgid "Bluesky" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:664 -#: src/view/com/post-thread/PostThreadItem.tsx:979 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:668 msgid "Bluesky cannot confirm the authenticity of the claimed date." msgstr "" @@ -1452,25 +1482,25 @@ msgstr "" msgid "Blur images and filter from feeds" msgstr "" -#: src/screens/Onboarding/index.tsx:30 -#: src/screens/Onboarding/state.ts:99 +#: src/screens/Onboarding/index.tsx:44 +#: src/screens/Onboarding/state.ts:104 msgid "Books" msgstr "" -#: src/components/FeedInterstitials.tsx:428 +#: src/components/FeedInterstitials.tsx:431 msgid "Browse more accounts on the Explore page" msgstr "" -#: src/components/FeedInterstitials.tsx:556 +#: src/components/FeedInterstitials.tsx:559 msgid "Browse more feeds on the Explore page" msgstr "" -#: src/components/FeedInterstitials.tsx:537 #: src/components/FeedInterstitials.tsx:540 +#: src/components/FeedInterstitials.tsx:543 msgid "Browse more suggestions" msgstr "" -#: src/components/FeedInterstitials.tsx:565 +#: src/components/FeedInterstitials.tsx:568 msgid "Browse more suggestions on the Explore page" msgstr "" @@ -1556,22 +1586,22 @@ msgstr "" #: src/components/Prompt.tsx:144 #: src/components/Prompt.tsx:146 #: src/screens/Deactivated.tsx:158 -#: src/screens/Profile/Header/EditProfileDialog.tsx:226 -#: src/screens/Profile/Header/EditProfileDialog.tsx:234 +#: src/screens/Profile/Header/EditProfileDialog.tsx:220 +#: src/screens/Profile/Header/EditProfileDialog.tsx:228 #: src/screens/Search/Shell.tsx:349 #: src/screens/Settings/AppIconSettings/index.tsx:44 #: src/screens/Settings/AppIconSettings/index.tsx:225 #: src/screens/Settings/components/ChangeHandleDialog.tsx:78 #: src/screens/Settings/components/ChangeHandleDialog.tsx:85 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:246 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:252 #: src/screens/Settings/Settings.tsx:289 #: src/screens/Takendown.tsx:99 #: src/screens/Takendown.tsx:102 -#: src/view/com/composer/Composer.tsx:982 -#: src/view/com/composer/Composer.tsx:993 +#: src/view/com/composer/Composer.tsx:1004 +#: src/view/com/composer/Composer.tsx:1015 #: src/view/com/composer/photos/EditImageDialog.web.tsx:43 #: src/view/com/composer/photos/EditImageDialog.web.tsx:52 -#: src/view/com/modals/ChangePassword.tsx:279 -#: src/view/com/modals/ChangePassword.tsx:282 #: src/view/com/modals/CreateOrEditList.tsx:333 #: src/view/com/modals/CropImage.web.tsx:97 #: src/view/shell/desktop/LeftNav.tsx:212 @@ -1648,8 +1678,13 @@ msgstr "" msgid "Change moderation service" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:153 -msgid "Change Password" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:260 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:266 +msgid "Change password" +msgstr "" + +#: src/screens/Settings/components/ChangePasswordDialog.tsx:164 +msgid "Change password dialog" msgstr "" #: src/view/com/composer/select-language/SuggestedLanguage.tsx:98 @@ -1660,6 +1695,10 @@ msgstr "" msgid "Change report reason" msgstr "" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:57 +msgid "Change your password" +msgstr "" + #: src/screens/Settings/AppIconSettings/index.tsx:216 msgid "Changes app icon" msgstr "" @@ -1676,7 +1715,7 @@ msgstr "" #: src/lib/hooks/useNotificationHandler.ts:99 #: src/Navigation.tsx:548 #: src/view/shell/bottom-bar/BottomBar.tsx:221 -#: src/view/shell/desktop/LeftNav.tsx:554 +#: src/view/shell/desktop/LeftNav.tsx:606 #: src/view/shell/Drawer.tsx:455 msgid "Chat" msgstr "" @@ -1750,7 +1789,7 @@ msgstr "" msgid "Choose domain verification method" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:200 +#: src/screens/StarterPack/Wizard/index.tsx:217 msgid "Choose Feeds" msgstr "" @@ -1758,11 +1797,15 @@ msgstr "" msgid "Choose for me" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:196 +#: src/screens/StarterPack/Wizard/index.tsx:213 msgid "Choose People" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:298 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:226 +msgid "Choose Post Languages" +msgstr "" + +#: src/screens/Onboarding/StepFinished.tsx:575 msgid "Choose the algorithms that power your custom feeds." msgstr "" @@ -1835,7 +1878,7 @@ msgstr "" msgid "Click to retry failed message" msgstr "" -#: src/screens/Onboarding/index.tsx:32 +#: src/screens/Onboarding/index.tsx:46 msgid "Climate" msgstr "" @@ -1853,6 +1896,7 @@ msgstr "" #: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:178 #: src/components/dialogs/nuxs/InitialVerificationAnnouncement.tsx:187 #: src/components/dialogs/SearchablePeopleList.tsx:295 +#: src/components/dialogs/StarterPackDialog.tsx:190 #: src/components/dms/EmojiPopup.android.tsx:58 #: src/components/dms/ReportDialog.tsx:381 #: src/components/dms/ReportDialog.tsx:390 @@ -1861,22 +1905,23 @@ msgstr "" #: src/components/NewskieDialog.tsx:146 #: src/components/NewskieDialog.tsx:153 #: src/components/Post/Embed/ExternalEmbed/Gif.tsx:197 -#: src/components/ProgressGuide/FollowDialog.tsx:386 -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:123 -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:129 +#: src/components/ProgressGuide/FollowDialog.tsx:379 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:118 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:124 #: src/components/verification/VerificationsDialog.tsx:144 #: src/components/verification/VerifierDialog.tsx:144 #: src/components/WhoCanReply.tsx:202 #: src/components/WhoCanReply.tsx:209 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:286 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:291 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:377 #: src/view/com/feeds/MissingFeed.tsx:208 #: src/view/com/feeds/MissingFeed.tsx:215 -#: src/view/com/modals/ChangePassword.tsx:279 -#: src/view/com/modals/ChangePassword.tsx:282 msgid "Close" msgstr "" -#: src/components/Dialog/index.web.tsx:111 -#: src/components/Dialog/index.web.tsx:259 +#: src/components/Dialog/index.web.tsx:118 +#: src/components/Dialog/index.web.tsx:295 msgid "Close active dialog" msgstr "" @@ -1893,6 +1938,9 @@ msgstr "" #: src/components/dialogs/GifSelect.tsx:274 #: src/components/verification/VerificationsDialog.tsx:136 #: src/components/verification/VerifierDialog.tsx:136 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:246 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:340 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:372 msgid "Close dialog" msgstr "" @@ -1931,7 +1979,7 @@ msgstr "" msgid "Closes password update alert" msgstr "" -#: src/view/com/composer/Composer.tsx:990 +#: src/view/com/composer/Composer.tsx:1012 msgid "Closes post composer and discards post draft" msgstr "" @@ -1961,13 +2009,13 @@ msgstr "" msgid "Color theme" msgstr "" -#: src/screens/Onboarding/index.tsx:38 -#: src/screens/Onboarding/state.ts:100 +#: src/screens/Onboarding/index.tsx:52 +#: src/screens/Onboarding/state.ts:105 msgid "Comedy" msgstr "" -#: src/screens/Onboarding/index.tsx:24 -#: src/screens/Onboarding/state.ts:101 +#: src/screens/Onboarding/index.tsx:38 +#: src/screens/Onboarding/state.ts:106 msgid "Comics" msgstr "" @@ -1977,7 +2025,8 @@ msgstr "" msgid "Community Guidelines" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:311 +#: src/screens/Onboarding/StepFinished.tsx:473 +#: src/screens/Onboarding/StepFinished.tsx:587 msgid "Complete onboarding and start using your account" msgstr "" @@ -1985,19 +2034,19 @@ msgstr "" msgid "Complete the challenge" msgstr "" -#: src/view/shell/desktop/LeftNav.tsx:519 +#: src/view/shell/desktop/LeftNav.tsx:571 msgid "Compose new post" msgstr "" -#: src/view/com/composer/Composer.tsx:891 +#: src/view/com/composer/Composer.tsx:913 msgid "Compose posts up to {0, plural, other {# characters}} in length" msgstr "" -#: src/view/com/post-thread/PostThreadComposePrompt.tsx:62 +#: src/screens/PostThread/components/ThreadComposePrompt.tsx:62 msgid "Compose reply" msgstr "" -#: src/view/com/composer/Composer.tsx:1815 +#: src/view/com/composer/Composer.tsx:1837 msgid "Compressing video..." msgstr "" @@ -2034,6 +2083,8 @@ msgstr "" #: src/components/dialogs/EmailDialog/components/TokenField.tsx:36 #: src/screens/Login/LoginForm.tsx:274 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:186 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:190 #: src/screens/Settings/components/DisableEmail2FADialog.tsx:144 #: src/screens/Settings/components/DisableEmail2FADialog.tsx:150 #: src/view/com/modals/DeleteAccount.tsx:220 @@ -2117,8 +2168,9 @@ msgstr "" #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:162 #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:170 -#: src/screens/Onboarding/StepInterests/index.tsx:244 +#: src/screens/Onboarding/StepInterests/index.tsx:253 #: src/screens/Onboarding/StepProfile/index.tsx:277 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:246 msgid "Continue" msgstr "" @@ -2131,12 +2183,12 @@ msgid "Continue thread" msgstr "" #: src/screens/PostThread/components/ThreadItemReadMoreUp.tsx:63 -#: src/view/com/post-thread/PostThreadLoadMore.tsx:60 msgid "Continue thread..." msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:241 +#: src/screens/Onboarding/StepInterests/index.tsx:250 #: src/screens/Onboarding/StepProfile/index.tsx:274 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:243 #: src/screens/Signup/BackNextButtons.tsx:60 msgid "Continue to next step" msgstr "" @@ -2155,7 +2207,7 @@ msgstr "" msgid "Conversation deleted" msgstr "" -#: src/screens/Onboarding/index.tsx:41 +#: src/screens/Onboarding/index.tsx:55 msgid "Cooking" msgstr "" @@ -2194,8 +2246,8 @@ msgstr "" msgid "Copy App Password" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:421 -#: src/view/com/profile/ProfileMenu.tsx:424 +#: src/view/com/profile/ProfileMenu.tsx:433 +#: src/view/com/profile/ProfileMenu.tsx:436 msgid "Copy at:// URI" msgstr "" @@ -2210,8 +2262,8 @@ msgid "Copy code" msgstr "" #: src/screens/Settings/components/ChangeHandleDialog.tsx:491 -#: src/view/com/profile/ProfileMenu.tsx:430 -#: src/view/com/profile/ProfileMenu.tsx:433 +#: src/view/com/profile/ProfileMenu.tsx:442 +#: src/view/com/profile/ProfileMenu.tsx:445 msgid "Copy DID" msgstr "" @@ -2239,8 +2291,8 @@ msgstr "" msgid "Copy link to post" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:243 -#: src/view/com/profile/ProfileMenu.tsx:254 +#: src/view/com/profile/ProfileMenu.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:257 msgid "Copy link to profile" msgstr "" @@ -2258,8 +2310,8 @@ msgstr "" msgid "Copy post at:// URI" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:448 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:450 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:452 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:454 msgid "Copy post text" msgstr "" @@ -2319,6 +2371,10 @@ msgstr "" msgid "Could not update notification settings" msgstr "" +#. Text on button to create a new starter pack +#. Text on button to create a new starter pack +#: src/components/dialogs/StarterPackDialog.tsx:113 +#: src/components/dialogs/StarterPackDialog.tsx:212 #: src/components/StarterPack/ProfileStarterPacks.tsx:300 msgid "Create" msgstr "" @@ -2337,6 +2393,8 @@ msgstr "" msgid "Create a starter pack for me" msgstr "" +#: src/components/LoggedOutCTA.tsx:71 +#: src/components/LoggedOutCTA.tsx:76 #: src/view/com/auth/SplashScreen.tsx:55 #: src/view/com/auth/SplashScreen.web.tsx:117 #: src/view/shell/bottom-bar/BottomBar.tsx:345 @@ -2381,6 +2439,11 @@ msgstr "" msgid "Create report for {0}" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:108 +#: src/components/dialogs/StarterPackDialog.tsx:207 +msgid "Create starter pack" +msgstr "" + #: src/screens/Settings/AppPasswords.tsx:174 msgid "Created {0}" msgstr "" @@ -2389,8 +2452,8 @@ msgstr "" msgid "Creator has been blocked" msgstr "" -#: src/screens/Onboarding/index.tsx:26 -#: src/screens/Onboarding/state.ts:102 +#: src/screens/Onboarding/index.tsx:40 +#: src/screens/Onboarding/state.ts:107 msgid "Culture" msgstr "" @@ -2407,7 +2470,7 @@ msgstr "" msgid "Customize who can interact with this post." msgstr "" -#: src/screens/Onboarding/Layout.tsx:56 +#: src/screens/Onboarding/Layout.tsx:61 msgid "Customizes your Bluesky experience" msgstr "" @@ -2423,7 +2486,7 @@ msgctxt "Name of app icon variant" msgid "Dark" msgstr "" -#: src/view/screens/Debug.tsx:69 +#: src/view/screens/Debug.tsx:68 msgid "Dark mode" msgstr "" @@ -2435,8 +2498,8 @@ msgstr "" msgid "Date of birth" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:159 -#: src/screens/Settings/AccountSettings.tsx:164 +#: src/screens/Settings/AccountSettings.tsx:161 +#: src/screens/Settings/AccountSettings.tsx:166 #: src/screens/Settings/components/DeactivateAccountDialog.tsx:73 msgid "Deactivate account" msgstr "" @@ -2445,7 +2508,7 @@ msgstr "" msgid "Debug Moderation" msgstr "" -#: src/view/screens/Debug.tsx:89 +#: src/view/screens/Debug.tsx:88 msgid "Debug panel" msgstr "" @@ -2458,7 +2521,7 @@ msgid "Default icons" msgstr "" #: src/components/dms/MessageContextMenu.tsx:185 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:697 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:704 #: src/screens/Messages/components/ChatStatusInfo.tsx:55 #: src/screens/Settings/AppPasswords.tsx:212 #: src/screens/StarterPack/StarterPackScreen.tsx:599 @@ -2468,8 +2531,8 @@ msgstr "" msgid "Delete" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:169 -#: src/screens/Settings/AccountSettings.tsx:174 +#: src/screens/Settings/AccountSettings.tsx:171 +#: src/screens/Settings/AccountSettings.tsx:176 msgid "Delete account" msgstr "" @@ -2525,9 +2588,9 @@ msgstr "" msgid "Delete my account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:678 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:680 -#: src/view/com/composer/Composer.tsx:901 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:685 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:687 +#: src/view/com/composer/Composer.tsx:923 msgid "Delete post" msgstr "" @@ -2544,7 +2607,7 @@ msgstr "" msgid "Delete this list?" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:692 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:699 msgid "Delete this post?" msgstr "" @@ -2564,11 +2627,7 @@ msgstr "" msgid "Deleted list" msgstr "" -#: src/view/com/post-thread/PostThread.tsx:475 -msgid "Deleted post." -msgstr "" - -#: src/screens/Profile/Header/EditProfileDialog.tsx:366 +#: src/screens/Profile/Header/EditProfileDialog.tsx:360 #: src/view/com/modals/CreateOrEditList.tsx:278 #: src/view/com/modals/CreateOrEditList.tsx:299 msgid "Description" @@ -2579,12 +2638,12 @@ msgstr "" msgid "Descriptive alt text" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:582 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:592 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:589 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:599 msgid "Detach quote" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:728 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:735 msgid "Detach quote post?" msgstr "" @@ -2642,21 +2701,21 @@ msgstr "" msgid "Disabled" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:89 -#: src/view/com/composer/Composer.tsx:739 -#: src/view/com/composer/Composer.tsx:934 +#: src/screens/Profile/Header/EditProfileDialog.tsx:83 +#: src/view/com/composer/Composer.tsx:761 +#: src/view/com/composer/Composer.tsx:956 msgid "Discard" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:86 +#: src/screens/Profile/Header/EditProfileDialog.tsx:80 msgid "Discard changes?" msgstr "" -#: src/view/com/composer/Composer.tsx:736 +#: src/view/com/composer/Composer.tsx:758 msgid "Discard draft?" msgstr "" -#: src/view/com/composer/Composer.tsx:926 +#: src/view/com/composer/Composer.tsx:948 msgid "Discard post?" msgstr "" @@ -2665,24 +2724,21 @@ msgstr "" msgid "Discourage apps from showing my account to logged-out users" msgstr "" -#: src/screens/Search/Explore.tsx:434 -msgid "Discover Feeds" -msgstr "" - #: src/view/com/posts/FollowingEmptyState.tsx:70 #: src/view/com/posts/FollowingEndOfFeed.tsx:71 msgid "Discover new custom feeds" msgstr "" +#: src/screens/Search/Explore.tsx:434 #: src/view/screens/Feeds.tsx:730 msgid "Discover New Feeds" msgstr "" -#: src/components/Dialog/index.tsx:321 +#: src/components/Dialog/index.tsx:370 msgid "Dismiss" msgstr "" -#: src/view/com/composer/Composer.tsx:1739 +#: src/view/com/composer/Composer.tsx:1761 msgid "Dismiss error" msgstr "" @@ -2703,12 +2759,16 @@ msgstr "" msgid "Display larger alt text badges" msgstr "" +#: src/screens/Profile/Header/EditProfileDialog.tsx:315 #: src/screens/Profile/Header/EditProfileDialog.tsx:321 -#: src/screens/Profile/Header/EditProfileDialog.tsx:327 -#: src/screens/Profile/Header/EditProfileDialog.tsx:373 +#: src/screens/Profile/Header/EditProfileDialog.tsx:367 msgid "Display name" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:347 +msgid "Ditch the trolls and clickbait. Find real people and conversations that matter to you." +msgstr "" + #: src/screens/Settings/components/ChangeHandleDialog.tsx:392 #: src/screens/Settings/components/ChangeHandleDialog.tsx:394 msgid "DNS Panel" @@ -2758,6 +2818,7 @@ msgstr "" #: src/view/com/auth/server-input/index.tsx:233 #: src/view/com/composer/labels/LabelsBtn.tsx:223 #: src/view/com/composer/labels/LabelsBtn.tsx:230 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:345 #: src/view/com/composer/videos/SubtitleDialog.tsx:168 #: src/view/com/composer/videos/SubtitleDialog.tsx:178 #: src/view/com/modals/CropImage.web.tsx:112 @@ -2780,11 +2841,11 @@ msgstr "" msgid "Double tap or long press the message to add a reaction" msgstr "" -#: src/components/Dialog/index.tsx:322 +#: src/components/Dialog/index.tsx:371 msgid "Double tap to close the dialog" msgstr "" -#: src/screens/VideoFeed/index.tsx:1081 +#: src/screens/VideoFeed/index.tsx:1084 msgid "Double tap to like" msgstr "" @@ -2809,7 +2870,7 @@ msgstr "" msgid "e.g. alice" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:328 +#: src/screens/Profile/Header/EditProfileDialog.tsx:322 msgid "e.g. Alice Lastname" msgstr "" @@ -2841,11 +2902,11 @@ msgstr "" msgid "Each code works once. You'll receive more invite codes periodically." msgstr "" -#: src/screens/Settings/AccountSettings.tsx:143 +#: src/screens/Settings/AccountSettings.tsx:145 #: src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx:249 #: src/screens/StarterPack/StarterPackScreen.tsx:588 -#: src/screens/StarterPack/Wizard/index.tsx:319 -#: src/screens/StarterPack/Wizard/index.tsx:324 +#: src/screens/StarterPack/Wizard/index.tsx:339 +#: src/screens/StarterPack/Wizard/index.tsx:344 msgid "Edit" msgstr "" @@ -2860,7 +2921,7 @@ msgstr "" msgid "Edit avatar" msgstr "" -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:117 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:112 msgid "Edit Feeds" msgstr "" @@ -2870,8 +2931,8 @@ msgstr "" msgid "Edit image" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:659 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:672 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:666 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:679 msgid "Edit interaction settings" msgstr "" @@ -2884,8 +2945,8 @@ msgstr "" msgid "Edit list details" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:317 -#: src/view/com/profile/ProfileMenu.tsx:323 +#: src/view/com/profile/ProfileMenu.tsx:329 +#: src/view/com/profile/ProfileMenu.tsx:335 msgid "Edit live status" msgstr "" @@ -2902,7 +2963,7 @@ msgstr "" msgid "Edit notifications from {0}" msgstr "" -#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:115 +#: src/components/StarterPack/Wizard/WizardEditListDialog.tsx:110 msgid "Edit People" msgstr "" @@ -2911,8 +2972,8 @@ msgstr "" msgid "Edit post interaction settings" msgstr "" +#: src/screens/Profile/Header/EditProfileDialog.tsx:270 #: src/screens/Profile/Header/EditProfileDialog.tsx:276 -#: src/screens/Profile/Header/EditProfileDialog.tsx:282 #: src/screens/Profile/Header/ProfileHeaderLabeler.tsx:183 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:190 msgid "Edit profile" @@ -2939,8 +3000,8 @@ msgstr "" msgid "Edit your starter pack" msgstr "" -#: src/screens/Onboarding/index.tsx:31 -#: src/screens/Onboarding/state.ts:104 +#: src/screens/Onboarding/index.tsx:45 +#: src/screens/Onboarding/state.ts:109 msgid "Education" msgstr "" @@ -2948,7 +3009,7 @@ msgstr "" msgid "Either the creator of this list has blocked you or you have blocked the creator." msgstr "" -#: src/screens/Settings/AccountSettings.tsx:64 +#: src/screens/Settings/AccountSettings.tsx:66 #: src/screens/Signup/StepInfo/index.tsx:197 msgid "Email" msgstr "" @@ -3080,6 +3141,7 @@ msgstr "" #: src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx:201 #: src/components/dialogs/EmailDialog/screens/Verify.tsx:320 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:63 msgid "Enter code" msgstr "" @@ -3087,10 +3149,6 @@ msgstr "" msgid "Enter fullscreen" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:165 -msgid "Enter the code you received to change your password." -msgstr "" - #: src/screens/Settings/components/ChangeHandleDialog.tsx:370 msgid "Enter the domain you want to use" msgstr "" @@ -3128,7 +3186,7 @@ msgstr "" msgid "Entertainment" msgstr "" -#: src/view/com/composer/Composer.tsx:1824 +#: src/view/com/composer/Composer.tsx:1846 #: src/view/com/util/error/ErrorScreen.tsx:42 msgid "Error" msgstr "" @@ -3153,7 +3211,7 @@ msgstr "" msgid "Error receiving captcha response." msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:183 +#: src/screens/Onboarding/StepInterests/index.tsx:192 msgid "Error:" msgstr "" @@ -3235,7 +3293,7 @@ msgstr "" msgid "Expand post text" msgstr "" -#: src/screens/VideoFeed/index.tsx:966 +#: src/screens/VideoFeed/index.tsx:969 msgid "Expands or collapses post text" msgstr "" @@ -3244,7 +3302,6 @@ msgid "Expected uri to resolve to a record" msgstr "" #: src/screens/Settings/FollowingFeedPreferences.tsx:126 -#: src/screens/Settings/ThreadPreferences.tsx:271 msgid "Experimental" msgstr "" @@ -3276,13 +3333,13 @@ msgstr "" #: src/Navigation.tsx:750 #: src/screens/Search/Shell.tsx:307 -#: src/view/shell/desktop/LeftNav.tsx:636 +#: src/view/shell/desktop/LeftNav.tsx:688 #: src/view/shell/Drawer.tsx:403 msgid "Explore" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:150 -#: src/screens/Settings/AccountSettings.tsx:154 +#: src/screens/Settings/AccountSettings.tsx:152 +#: src/screens/Settings/AccountSettings.tsx:156 msgid "Export my data" msgstr "" @@ -3320,6 +3377,10 @@ msgstr "" msgid "Failed to add emoji reaction" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:283 +msgid "Failed to add to starter pack" +msgstr "" + #: src/screens/Settings/components/ChangeHandleDialog.tsx:587 msgid "Failed to change handle. Please try again." msgstr "" @@ -3332,8 +3393,8 @@ msgstr "" msgid "Failed to create conversation" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:239 -#: src/screens/StarterPack/Wizard/index.tsx:247 +#: src/screens/StarterPack/Wizard/index.tsx:262 +#: src/screens/StarterPack/Wizard/index.tsx:270 msgid "Failed to create starter pack" msgstr "" @@ -3359,6 +3420,10 @@ msgstr "" msgid "Failed to delete starter pack" msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:126 +msgid "Failed to follow all suggested accounts, please try again" +msgstr "" + #: src/screens/Messages/ChatList.tsx:270 #: src/screens/Messages/Inbox.tsx:208 msgid "Failed to load conversations" @@ -3422,6 +3487,10 @@ msgstr "" msgid "Failed to remove emoji reaction" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:302 +msgid "Failed to remove from starter pack" +msgstr "" + #: src/components/verification/VerificationRemovePrompt.tsx:34 msgid "Failed to remove verification" msgstr "" @@ -3522,16 +3591,16 @@ msgstr "" msgid "Feed unavailable" msgstr "" -#: src/view/shell/desktop/RightNav.tsx:102 -#: src/view/shell/desktop/RightNav.tsx:103 +#: src/view/shell/desktop/RightNav.tsx:106 +#: src/view/shell/desktop/RightNav.tsx:107 #: src/view/shell/Drawer.tsx:357 msgid "Feedback" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:269 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:285 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:270 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:288 msgctxt "toast" -msgid "Feedback sent!" +msgid "Feedback sent to feed operator" msgstr "" #: src/Navigation.tsx:573 @@ -3540,7 +3609,7 @@ msgstr "" #: src/view/screens/Feeds.tsx:511 #: src/view/screens/Profile.tsx:230 #: src/view/screens/SavedFeeds.tsx:104 -#: src/view/shell/desktop/LeftNav.tsx:674 +#: src/view/shell/desktop/LeftNav.tsx:726 #: src/view/shell/Drawer.tsx:519 msgid "Feeds" msgstr "" @@ -3587,7 +3656,8 @@ msgstr "" msgid "Filter who you receive notifications from" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:314 +#: src/screens/Onboarding/StepFinished.tsx:479 +#: src/screens/Onboarding/StepFinished.tsx:590 msgid "Finalizing" msgstr "" @@ -3597,9 +3667,9 @@ msgstr "" msgid "Find accounts to follow" msgstr "" -#: src/components/ProgressGuide/FollowDialog.tsx:77 -#: src/components/ProgressGuide/FollowDialog.tsx:87 -#: src/components/ProgressGuide/FollowDialog.tsx:374 +#: src/components/ProgressGuide/FollowDialog.tsx:69 +#: src/components/ProgressGuide/FollowDialog.tsx:79 +#: src/components/ProgressGuide/FollowDialog.tsx:367 msgid "Find people to follow" msgstr "" @@ -3607,11 +3677,15 @@ msgstr "" msgid "Find posts, users, and feeds on Bluesky" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:201 +#: src/screens/Onboarding/StepFinished.tsx:345 +msgid "Find your people" +msgstr "" + +#: src/screens/StarterPack/Wizard/index.tsx:218 msgid "Finish" msgstr "" -#: src/screens/Onboarding/index.tsx:35 +#: src/screens/Onboarding/index.tsx:49 msgid "Fitness" msgstr "" @@ -3630,7 +3704,7 @@ msgctxt "Name of app icon variant" msgid "Flat White" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:294 +#: src/screens/Onboarding/StepFinished.tsx:571 msgid "Flexible" msgstr "" @@ -3638,9 +3712,9 @@ msgstr "" #: src/components/ProfileCard.tsx:524 #: src/components/ProfileHoverCard/index.web.tsx:496 #: src/components/ProfileHoverCard/index.web.tsx:507 +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:131 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:252 -#: src/screens/VideoFeed/index.tsx:851 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:131 +#: src/screens/VideoFeed/index.tsx:854 msgid "Follow" msgstr "" @@ -3649,12 +3723,12 @@ msgctxt "action" msgid "Follow" msgstr "" +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:113 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:237 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:113 msgid "Follow {0}" msgstr "" -#: src/screens/VideoFeed/index.tsx:828 +#: src/screens/VideoFeed/index.tsx:831 msgid "Follow {handle}" msgstr "" @@ -3667,20 +3741,25 @@ msgstr "" msgid "Follow 7 accounts" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:284 -#: src/view/com/profile/ProfileMenu.tsx:295 +#: src/view/com/profile/ProfileMenu.tsx:287 +#: src/view/com/profile/ProfileMenu.tsx:298 msgid "Follow account" msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:235 #: src/screens/StarterPack/StarterPackScreen.tsx:438 #: src/screens/StarterPack/StarterPackScreen.tsx:446 msgid "Follow all" msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:232 +msgid "Follow all accounts" +msgstr "" + #. User is not following this account, click to follow back #: src/components/ProfileCard.tsx:518 +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:129 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:250 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:129 msgid "Follow back" msgstr "" @@ -3689,6 +3768,10 @@ msgctxt "action" msgid "Follow back" msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:121 +msgid "Followed all accounts!" +msgstr "" + #: src/components/KnownFollowers.tsx:238 msgid "Followed by <0>{0}</0>" msgstr "" @@ -3718,9 +3801,9 @@ msgstr "" #: src/components/ProfileCard.tsx:511 #: src/components/ProfileHoverCard/index.web.tsx:495 #: src/components/ProfileHoverCard/index.web.tsx:506 +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:134 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:248 -#: src/screens/VideoFeed/index.tsx:849 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:134 +#: src/screens/VideoFeed/index.tsx:852 msgid "Following" msgstr "" @@ -3735,7 +3818,7 @@ msgstr "" msgid "Following {0}" msgstr "" -#: src/screens/VideoFeed/index.tsx:827 +#: src/screens/VideoFeed/index.tsx:830 msgid "Following {handle}" msgstr "" @@ -3765,8 +3848,8 @@ msgstr "" msgid "Font size" msgstr "" -#: src/screens/Onboarding/index.tsx:40 -#: src/screens/Onboarding/state.ts:105 +#: src/screens/Onboarding/index.tsx:54 +#: src/screens/Onboarding/state.ts:110 msgid "Food" msgstr "" @@ -3786,6 +3869,7 @@ msgstr "" msgid "For the best experience, we recommend using the theme font." msgstr "" +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:293 #: src/screens/Search/modules/ExploreSuggestedAccounts.tsx:94 msgid "For You" msgstr "" @@ -3794,6 +3878,10 @@ msgstr "" msgid "Forever" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:354 +msgid "Forget the noise" +msgstr "" + #: src/screens/Login/index.tsx:153 #: src/screens/Login/index.tsx:168 msgid "Forgot Password" @@ -3807,6 +3895,10 @@ msgstr "" msgid "Forgot?" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:336 +msgid "Free your feed" +msgstr "" + #: src/components/moderation/ReportDialog/utils/useReportOptions.ts:54 #: src/lib/moderation/useReportOptions.ts:54 msgid "Frequently Posts Unwanted Content" @@ -3919,8 +4011,8 @@ msgstr "" #: src/screens/Messages/Inbox.tsx:249 #: src/screens/Profile/ProfileFeed/index.tsx:92 #: src/screens/VideoFeed/components/Header.tsx:163 -#: src/screens/VideoFeed/index.tsx:1142 -#: src/screens/VideoFeed/index.tsx:1146 +#: src/screens/VideoFeed/index.tsx:1145 +#: src/screens/VideoFeed/index.tsx:1149 #: src/view/com/auth/LoggedOut.tsx:72 #: src/view/screens/NotFound.tsx:57 #: src/view/screens/ProfileList.tsx:1038 @@ -3941,8 +4033,8 @@ msgstr "" #: src/components/dms/ReportDialog.tsx:192 #: src/components/ReportDialog/SelectReportOptionView.tsx:78 #: src/components/ReportDialog/SubmitView.tsx:110 -#: src/screens/Onboarding/Layout.tsx:99 -#: src/screens/Onboarding/Layout.tsx:188 +#: src/screens/Onboarding/Layout.tsx:121 +#: src/screens/Onboarding/Layout.tsx:214 #: src/screens/Signup/BackNextButtons.tsx:35 msgid "Go back to previous step" msgstr "" @@ -3955,8 +4047,8 @@ msgstr "" msgid "Go Home" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:318 -#: src/view/com/profile/ProfileMenu.tsx:325 +#: src/view/com/profile/ProfileMenu.tsx:330 +#: src/view/com/profile/ProfileMenu.tsx:337 msgid "Go live" msgstr "" @@ -3987,11 +4079,12 @@ msgid "Go to conversation with {0}" msgstr "" #: src/screens/Login/ForgotPasswordForm.tsx:165 -#: src/view/com/modals/ChangePassword.tsx:179 msgid "Go to next" msgstr "" #: src/components/dms/ConvoMenu.tsx:227 +#: src/view/shell/desktop/LeftNav.tsx:316 +#: src/view/shell/desktop/LeftNav.tsx:322 msgid "Go to profile" msgstr "" @@ -4011,8 +4104,8 @@ msgstr "" msgid "Half way there!" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:128 -#: src/screens/Settings/AccountSettings.tsx:133 +#: src/screens/Settings/AccountSettings.tsx:130 +#: src/screens/Settings/AccountSettings.tsx:135 msgid "Handle" msgstr "" @@ -4057,8 +4150,8 @@ msgstr "" #: src/screens/Settings/Settings.tsx:236 #: src/screens/Settings/Settings.tsx:240 -#: src/view/shell/desktop/RightNav.tsx:120 -#: src/view/shell/desktop/RightNav.tsx:121 +#: src/view/shell/desktop/RightNav.tsx:124 +#: src/view/shell/desktop/RightNav.tsx:125 #: src/view/shell/Drawer.tsx:370 msgid "Help" msgstr "" @@ -4081,7 +4174,7 @@ msgstr "" msgid "Hidden" msgstr "" -#: src/screens/VideoFeed/index.tsx:625 +#: src/screens/VideoFeed/index.tsx:628 msgid "Hidden by your moderation settings." msgstr "" @@ -4094,12 +4187,12 @@ msgstr "" #: src/components/moderation/ContentHider.tsx:203 #: src/components/moderation/LabelPreference.tsx:141 #: src/components/moderation/PostHider.tsx:134 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:708 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:715 #: src/lib/moderation/useLabelBehaviorDescription.ts:15 #: src/lib/moderation/useLabelBehaviorDescription.ts:20 #: src/lib/moderation/useLabelBehaviorDescription.ts:25 #: src/lib/moderation/useLabelBehaviorDescription.ts:30 -#: src/view/shell/desktop/SidebarTrendingTopics.tsx:110 +#: src/view/shell/desktop/SidebarTrendingTopics.tsx:111 msgid "Hide" msgstr "" @@ -4112,18 +4205,18 @@ msgstr "" msgid "Hide customization options" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:539 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:545 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:546 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:552 msgid "Hide post for me" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:556 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:566 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:563 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:573 msgid "Hide reply for everyone" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:538 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:544 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:545 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:551 msgid "Hide reply for me" msgstr "" @@ -4131,12 +4224,12 @@ msgstr "" msgid "Hide this card" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:703 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:710 msgid "Hide this post?" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:703 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:738 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:710 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:745 msgid "Hide this reply?" msgstr "" @@ -4146,7 +4239,7 @@ msgid "Hide trending topics" msgstr "" #: src/components/interstitials/Trending.tsx:129 -#: src/view/shell/desktop/SidebarTrendingTopics.tsx:108 +#: src/view/shell/desktop/SidebarTrendingTopics.tsx:109 msgid "Hide trending topics?" msgstr "" @@ -4203,7 +4296,7 @@ msgstr "" #: src/Navigation.tsx:745 #: src/Navigation.tsx:765 #: src/view/shell/bottom-bar/BottomBar.tsx:178 -#: src/view/shell/desktop/LeftNav.tsx:618 +#: src/view/shell/desktop/LeftNav.tsx:670 #: src/view/shell/Drawer.tsx:429 msgid "Home" msgstr "" @@ -4221,13 +4314,6 @@ msgstr "" msgid "Hot" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:201 -#: src/screens/Settings/ThreadPreferences.tsx:204 -#: src/view/com/post-thread/PostThread.tsx:680 -#: src/view/com/post-thread/PostThread.tsx:685 -msgid "Hot replies first" -msgstr "" - #: src/components/dialogs/InAppBrowserConsent.tsx:62 #: src/components/dialogs/InAppBrowserConsent.tsx:66 msgid "How should we open this link?" @@ -4268,7 +4354,7 @@ msgstr "" msgid "If you need to update your email, <0>click here</0>." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:694 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:701 msgid "If you remove this post, you won't be able to recover it." msgstr "" @@ -4276,7 +4362,7 @@ msgstr "" msgid "If you update your email address, email 2FA will be disabled." msgstr "" -#: src/view/com/modals/ChangePassword.tsx:160 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:59 msgid "If you want to change your password, we will send you a code to verify that this is your account." msgstr "" @@ -4411,10 +4497,6 @@ msgstr "" msgid "Invalid handle. Please try a different one." msgstr "" -#: src/view/com/post-thread/PostThreadItem.tsx:348 -msgid "Invalid or unsupported post record" -msgstr "" - #: src/components/moderation/ReportDialog/index.tsx:73 msgid "Invalid report subject" msgstr "" @@ -4459,11 +4541,15 @@ msgstr "" msgid "It's correct" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:474 +#: src/screens/StarterPack/Wizard/index.tsx:491 +msgid "It's just <0>{0} </0>right now! Add more people to your starter pack by searching above." +msgstr "" + +#: src/screens/StarterPack/Wizard/index.tsx:486 msgid "It's just you right now! Add more people to your starter pack by searching above." msgstr "" -#: src/view/com/composer/Composer.tsx:1758 +#: src/view/com/composer/Composer.tsx:1780 msgid "Job ID: {0}" msgstr "" @@ -4472,6 +4558,7 @@ msgstr "" msgid "Jobs" msgstr "" +#: src/components/LoggedOutCTA.tsx:54 #: src/screens/StarterPack/StarterPackLandingScreen.tsx:205 #: src/screens/StarterPack/StarterPackLandingScreen.tsx:211 #: src/screens/StarterPack/StarterPackScreen.tsx:466 @@ -4484,8 +4571,8 @@ msgstr "" msgid "Join the conversation" msgstr "" -#: src/screens/Onboarding/index.tsx:21 -#: src/screens/Onboarding/state.ts:107 +#: src/screens/Onboarding/index.tsx:35 +#: src/screens/Onboarding/state.ts:112 msgid "Journalism" msgstr "" @@ -4534,10 +4621,6 @@ msgstr "" msgid "Labels on your content" msgstr "" -#: src/view/com/composer/select-language/SelectLangBtn.tsx:107 -msgid "Language selection" -msgstr "" - #: src/Navigation.tsx:217 msgid "Language Settings" msgstr "" @@ -4573,7 +4656,7 @@ msgstr "" #: src/components/verification/VerificationsDialog.tsx:165 #: src/components/verification/VerifierDialog.tsx:132 #: src/screens/Moderation/VerificationSettings.tsx:47 -#: src/screens/Profile/Header/EditProfileDialog.tsx:356 +#: src/screens/Profile/Header/EditProfileDialog.tsx:350 #: src/screens/Settings/components/ChangeHandleDialog.tsx:212 msgid "Learn more" msgstr "" @@ -4673,7 +4756,8 @@ msgstr "" msgid "Let's get your password reset!" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:314 +#: src/screens/Onboarding/StepFinished.tsx:481 +#: src/screens/Onboarding/StepFinished.tsx:590 msgid "Let's go!" msgstr "" @@ -4758,15 +4842,12 @@ msgstr "" msgid "Likes of your reposts notifications" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:462 -#: src/view/com/post-thread/PostThreadItem.tsx:243 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:466 msgid "Likes on this post" msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:47 #: src/screens/PostThread/components/HeaderDropdown.tsx:52 -#: src/view/com/post-thread/PostThread.tsx:654 -#: src/view/com/post-thread/PostThread.tsx:659 msgid "Linear" msgstr "" @@ -4836,7 +4917,7 @@ msgstr "" #: src/view/screens/Lists.tsx:65 #: src/view/screens/Profile.tsx:224 #: src/view/screens/Profile.tsx:232 -#: src/view/shell/desktop/LeftNav.tsx:692 +#: src/view/shell/desktop/LeftNav.tsx:744 #: src/view/shell/Drawer.tsx:534 msgid "Lists" msgstr "" @@ -4893,11 +4974,11 @@ msgstr "" msgid "Logged-out visibility" msgstr "" -#: src/view/shell/desktop/RightNav.tsx:130 +#: src/view/shell/desktop/RightNav.tsx:134 msgid "Logo by @sawaratsuki.bsky.social" msgstr "" -#: src/view/shell/desktop/RightNav.tsx:127 +#: src/view/shell/desktop/RightNav.tsx:131 #: src/view/shell/Drawer.tsx:672 msgid "Logo by <0>@sawaratsuki.bsky.social</0>" msgstr "" @@ -5117,36 +5198,22 @@ msgstr "" msgid "Moderator has chosen to set a general warning on the content." msgstr "" -#: src/view/com/post-thread/PostThreadItem.tsx:728 -msgid "More" -msgstr "" - -#: src/view/shell/desktop/Feeds.tsx:102 -#: src/view/shell/desktop/Feeds.tsx:105 +#: src/view/shell/desktop/Feeds.tsx:108 +#: src/view/shell/desktop/Feeds.tsx:118 msgid "More feeds" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:220 -#: src/view/com/profile/ProfileMenu.tsx:226 +#: src/view/com/profile/ProfileMenu.tsx:223 +#: src/view/com/profile/ProfileMenu.tsx:229 #: src/view/screens/ProfileList.tsx:750 msgid "More options" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:228 -msgid "Most-liked first" -msgstr "" - -#: src/screens/Settings/ThreadPreferences.tsx:225 -#: src/view/com/post-thread/PostThread.tsx:710 -#: src/view/com/post-thread/PostThread.tsx:715 -msgid "Most-liked replies first" -msgstr "" - -#: src/screens/Onboarding/state.ts:108 +#: src/screens/Onboarding/state.ts:113 msgid "Movies" msgstr "" -#: src/screens/Onboarding/state.ts:109 +#: src/screens/Onboarding/state.ts:114 msgid "Music" msgstr "" @@ -5161,10 +5228,10 @@ msgstr "" msgid "Mute {tag}" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:621 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:627 -#: src/view/com/profile/ProfileMenu.tsx:363 -#: src/view/com/profile/ProfileMenu.tsx:370 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:628 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:634 +#: src/view/com/profile/ProfileMenu.tsx:375 +#: src/view/com/profile/ProfileMenu.tsx:382 msgid "Mute account" msgstr "" @@ -5213,13 +5280,13 @@ msgstr "" msgid "Mute this word until you unmute it" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:505 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:509 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:512 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:516 msgid "Mute thread" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:519 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:521 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:526 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:528 msgid "Mute words & tags" msgstr "" @@ -5276,8 +5343,8 @@ msgstr "" msgid "Name or Description Violates Community Standards" msgstr "" -#: src/screens/Onboarding/index.tsx:22 -#: src/screens/Onboarding/state.ts:110 +#: src/screens/Onboarding/index.tsx:36 +#: src/screens/Onboarding/state.ts:115 msgid "Nature" msgstr "" @@ -5292,7 +5359,6 @@ msgstr "" #: src/screens/Login/ForgotPasswordForm.tsx:166 #: src/screens/Login/LoginForm.tsx:344 -#: src/view/com/modals/ChangePassword.tsx:180 msgid "Navigates to the next screen" msgstr "" @@ -5309,7 +5375,7 @@ msgstr "" msgid "Need to report a copyright violation?" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:282 +#: src/screens/Onboarding/StepFinished.tsx:559 msgid "Never lose access to your followers or data." msgstr "" @@ -5380,14 +5446,11 @@ msgid "New Moderation List" msgstr "" #: src/screens/Login/SetNewPasswordForm.tsx:141 -#: src/view/com/modals/ChangePassword.tsx:224 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:203 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:207 msgid "New password" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:229 -msgid "New Password" -msgstr "" - #: src/screens/Profile/ProfileFeed/index.tsx:241 #: src/view/screens/Feeds.tsx:552 #: src/view/screens/Notifications.tsx:167 @@ -5402,7 +5465,7 @@ msgctxt "action" msgid "New post" msgstr "" -#: src/view/shell/desktop/LeftNav.tsx:527 +#: src/view/shell/desktop/LeftNav.tsx:579 msgctxt "action" msgid "New Post" msgstr "" @@ -5415,6 +5478,10 @@ msgstr "" msgid "New posts from {firstAuthorName} and {additionalAuthorsCount, plural, one {{formattedAuthorsCount} other} other {{formattedAuthorsCount} others}}" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:204 +msgid "New starter pack" +msgstr "" + #: src/components/NewskieDialog.tsx:83 msgid "New user info dialog" msgstr "" @@ -5425,17 +5492,13 @@ msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:93 #: src/screens/PostThread/components/HeaderDropdown.tsx:98 -#: src/screens/Settings/ThreadPreferences.tsx:96 -#: src/screens/Settings/ThreadPreferences.tsx:99 -#: src/screens/Settings/ThreadPreferences.tsx:217 -#: src/screens/Settings/ThreadPreferences.tsx:220 -#: src/view/com/post-thread/PostThread.tsx:700 -#: src/view/com/post-thread/PostThread.tsx:705 +#: src/screens/Settings/ThreadPreferences.tsx:80 +#: src/screens/Settings/ThreadPreferences.tsx:83 msgid "Newest replies first" msgstr "" -#: src/screens/Onboarding/index.tsx:20 -#: src/screens/Onboarding/state.ts:111 +#: src/screens/Onboarding/index.tsx:34 +#: src/screens/Onboarding/state.ts:116 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:238 msgid "News" msgstr "" @@ -5446,15 +5509,15 @@ msgstr "" #: src/screens/Login/LoginForm.tsx:350 #: src/screens/Login/SetNewPasswordForm.tsx:182 #: src/screens/Login/SetNewPasswordForm.tsx:188 +#: src/screens/Onboarding/StepFinished.tsx:474 +#: src/screens/Onboarding/StepFinished.tsx:483 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:157 #: src/screens/Settings/components/AddAppPasswordDialog.tsx:165 #: src/screens/Signup/BackNextButtons.tsx:67 -#: src/screens/StarterPack/Wizard/index.tsx:193 -#: src/screens/StarterPack/Wizard/index.tsx:197 -#: src/screens/StarterPack/Wizard/index.tsx:376 -#: src/screens/StarterPack/Wizard/index.tsx:383 -#: src/view/com/modals/ChangePassword.tsx:265 -#: src/view/com/modals/ChangePassword.tsx:267 +#: src/screens/StarterPack/Wizard/index.tsx:210 +#: src/screens/StarterPack/Wizard/index.tsx:214 +#: src/screens/StarterPack/Wizard/index.tsx:392 +#: src/screens/StarterPack/Wizard/index.tsx:399 msgid "Next" msgstr "" @@ -5467,6 +5530,10 @@ msgstr "" msgid "Next image" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:356 +msgid "No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention." +msgstr "" + #: src/screens/Settings/AppPasswords.tsx:108 msgid "No app passwords yet" msgstr "" @@ -5506,6 +5573,10 @@ msgstr "" msgid "No messages yet" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:338 +msgid "No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you." +msgstr "" + #: src/view/com/notifications/NotificationFeed.tsx:122 msgid "No notifications yet!" msgstr "" @@ -5544,7 +5615,7 @@ msgid "No result" msgstr "" #: src/components/dialogs/SearchablePeopleList.tsx:223 -#: src/components/ProgressGuide/FollowDialog.tsx:212 +#: src/components/ProgressGuide/FollowDialog.tsx:204 msgid "No results" msgstr "" @@ -5617,7 +5688,7 @@ msgstr "" msgid "Not Found" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:480 +#: src/view/com/profile/ProfileMenu.tsx:497 msgid "Note about sharing" msgstr "" @@ -5665,7 +5736,7 @@ msgstr "" #: src/screens/Settings/Settings.tsx:199 #: src/view/screens/Notifications.tsx:130 #: src/view/shell/bottom-bar/BottomBar.tsx:252 -#: src/view/shell/desktop/LeftNav.tsx:655 +#: src/view/shell/desktop/LeftNav.tsx:707 #: src/view/shell/Drawer.tsx:482 msgid "Notifications" msgstr "" @@ -5698,6 +5769,7 @@ msgid "Off" msgstr "" #: src/components/dialogs/GifSelect.tsx:267 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:365 #: src/view/com/util/ErrorBoundary.tsx:57 msgid "Oh no!" msgstr "" @@ -5715,19 +5787,14 @@ msgid "OK" msgstr "" #: src/screens/Login/PasswordUpdatedForm.tsx:37 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:669 -#: src/view/com/post-thread/PostThreadItem.tsx:984 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:673 msgid "Okay" msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:83 #: src/screens/PostThread/components/HeaderDropdown.tsx:88 -#: src/screens/Settings/ThreadPreferences.tsx:88 -#: src/screens/Settings/ThreadPreferences.tsx:91 -#: src/screens/Settings/ThreadPreferences.tsx:209 -#: src/screens/Settings/ThreadPreferences.tsx:212 -#: src/view/com/post-thread/PostThread.tsx:690 -#: src/view/com/post-thread/PostThread.tsx:695 +#: src/screens/Settings/ThreadPreferences.tsx:72 +#: src/screens/Settings/ThreadPreferences.tsx:75 msgid "Oldest replies first" msgstr "" @@ -5739,11 +5806,11 @@ msgstr "" msgid "Onboarding reset" msgstr "" -#: src/view/com/composer/Composer.tsx:354 +#: src/view/com/composer/Composer.tsx:358 msgid "One or more GIFs is missing alt text." msgstr "" -#: src/view/com/composer/Composer.tsx:351 +#: src/view/com/composer/Composer.tsx:355 msgid "One or more images is missing alt text." msgstr "" @@ -5752,10 +5819,10 @@ msgid "One or more of your selected files are not supported." msgstr "" #: src/view/com/composer/SelectMediaButton.tsx:413 -msgid "One or more of your selected files is too large. Maximum size is 100 MB." +msgid "One or more of your selected files are too large. Maximum size is 100 MB." msgstr "" -#: src/view/com/composer/Composer.tsx:361 +#: src/view/com/composer/Composer.tsx:365 msgid "One or more videos is missing alt text." msgstr "" @@ -5794,7 +5861,7 @@ msgstr "" msgid "Oops!" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:278 +#: src/screens/Onboarding/StepFinished.tsx:555 msgid "Open" msgstr "" @@ -5812,7 +5879,7 @@ msgid "Open drawer menu" msgstr "" #: src/screens/Messages/components/MessageInput.web.tsx:181 -#: src/view/com/composer/Composer.tsx:1406 +#: src/view/com/composer/Composer.tsx:1428 msgid "Open emoji picker" msgstr "" @@ -5875,10 +5942,6 @@ msgstr "" msgid "Open system log" msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:167 -msgid "Opens {numItems} options" -msgstr "" - #: src/view/com/composer/labels/LabelsBtn.tsx:62 msgid "Opens a dialog to add a content warning to your post" msgstr "" @@ -5903,25 +5966,20 @@ msgstr "" msgid "Opens captions and alt text dialog" msgstr "" -#: src/screens/Settings/AccountSettings.tsx:129 +#: src/screens/Settings/AccountSettings.tsx:131 msgid "Opens change handle dialog" msgstr "" -#: src/view/com/post-thread/PostThreadComposePrompt.tsx:63 +#: src/screens/PostThread/components/ThreadComposePrompt.tsx:63 msgid "Opens composer" msgstr "" -#. Accessibility hint on web for button in composer to add images, a video, or a GIF to a post. Maximum number of images that can be selected is currently 4 but may change. -#: src/view/com/composer/SelectMediaButton.tsx:501 +#. Accessibility hint for button in composer to add images, a video, or a GIF to a post. Maximum number of images that can be selected is currently 4 but may change. +#: src/view/com/composer/SelectMediaButton.tsx:488 msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video or GIF." msgstr "" -#. Accessibility hint on native for button in composer to add images or a video to a post. Maximum number of images that can be selected is currently 4 but may change. -#: src/view/com/composer/SelectMediaButton.tsx:490 -msgid "Opens device gallery to select up to {MAX_IMAGES, plural, other {# images}}, or a single video." -msgstr "" - -#: src/view/com/composer/Composer.tsx:1407 +#: src/view/com/composer/Composer.tsx:1429 msgid "Opens emoji picker" msgstr "" @@ -5959,6 +6017,10 @@ msgstr "" msgid "Opens password reset form" msgstr "" +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:58 +msgid "Opens post language settings" +msgstr "" + #: src/view/com/notifications/NotificationFeedItem.tsx:906 #: src/view/com/util/UserAvatar.tsx:594 msgid "Opens this profile" @@ -6011,10 +6073,6 @@ msgstr "" msgid "Other account" msgstr "" -#: src/view/com/composer/select-language/SelectLangBtn.tsx:93 -msgid "Other..." -msgstr "" - #: src/components/PolicyUpdateOverlay/updates/202508/index.tsx:50 msgid "Our blog post" msgstr "" @@ -6037,19 +6095,19 @@ msgid "Page Not Found" msgstr "" #: src/screens/Login/LoginForm.tsx:228 -#: src/screens/Settings/AccountSettings.tsx:119 -#: src/screens/Settings/AccountSettings.tsx:123 +#: src/screens/Settings/AccountSettings.tsx:121 +#: src/screens/Settings/AccountSettings.tsx:125 #: src/screens/Signup/StepInfo/index.tsx:232 #: src/view/com/modals/DeleteAccount.tsx:239 #: src/view/com/modals/DeleteAccount.tsx:246 msgid "Password" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:154 -msgid "Password Changed" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:69 +msgid "Password changed" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:99 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:124 msgid "Password must be at least 8 characters long." msgstr "" @@ -6098,12 +6156,12 @@ msgstr "" msgid "Person toggle" msgstr "" -#: src/screens/Onboarding/index.tsx:28 -#: src/screens/Onboarding/state.ts:112 +#: src/screens/Onboarding/index.tsx:42 +#: src/screens/Onboarding/state.ts:117 msgid "Pets" msgstr "" -#: src/screens/Onboarding/state.ts:113 +#: src/screens/Onboarding/state.ts:118 msgid "Photography" msgstr "" @@ -6128,8 +6186,8 @@ msgstr "" msgid "Pin to Home" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:416 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:423 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:420 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:427 msgid "Pin to your profile" msgstr "" @@ -6219,7 +6277,7 @@ msgstr "" msgid "Please enter a password." msgstr "" -#: src/view/com/modals/ChangePassword.tsx:94 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:119 msgid "Please enter a password. It must be at least 8 characters long." msgstr "" @@ -6250,6 +6308,10 @@ msgstr "" msgid "Please enter the code we sent to <0>{0}</0> below." msgstr "" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:65 +msgid "Please enter the code you received and the new password you would like to use." +msgstr "" + #: src/components/dialogs/EmailDialog/screens/Update.tsx:247 msgid "Please enter the security code we sent to your previous email address." msgstr "" @@ -6321,8 +6383,8 @@ msgstr "" msgid "Please write your message below:" msgstr "" -#: src/screens/Onboarding/index.tsx:34 -#: src/screens/Onboarding/state.ts:114 +#: src/screens/Onboarding/index.tsx:48 +#: src/screens/Onboarding/state.ts:119 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:232 msgid "Politics" msgstr "" @@ -6331,18 +6393,17 @@ msgstr "" msgid "Porn" msgstr "" -#: src/screens/PostThread/index.tsx:496 -#: src/view/com/post-thread/PostThread.tsx:562 +#: src/screens/PostThread/index.tsx:502 msgctxt "description" msgid "Post" msgstr "" -#: src/view/com/composer/Composer.tsx:1053 +#: src/view/com/composer/Composer.tsx:1075 msgctxt "action" msgid "Post" msgstr "" -#: src/view/com/composer/Composer.tsx:1051 +#: src/view/com/composer/Composer.tsx:1073 msgctxt "action" msgid "Post All" msgstr "" @@ -6351,10 +6412,6 @@ msgstr "" msgid "Post blocked" msgstr "" -#: src/view/com/post-thread/PostThreadItem.tsx:235 -msgid "Post by {0}" -msgstr "" - #: src/Navigation.tsx:263 #: src/Navigation.tsx:270 #: src/Navigation.tsx:277 @@ -6371,17 +6428,13 @@ msgstr "" msgid "Post failed to upload. Please check your Internet connection and try again." msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:132 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:133 #: src/screens/PostThread/components/ThreadItemPost.tsx:110 #: src/screens/PostThread/components/ThreadItemTreePost.tsx:107 -#: src/screens/VideoFeed/index.tsx:529 +#: src/screens/VideoFeed/index.tsx:532 msgid "Post has been deleted" msgstr "" -#: src/view/com/post-thread/PostThread.tsx:271 -msgid "Post hidden" -msgstr "" - #: src/components/moderation/ModerationDetailsDialog.tsx:107 #: src/lib/moderation/useModerationCauseDescription.ts:106 msgid "Post Hidden by Muted Word" @@ -6401,18 +6454,13 @@ msgstr "" msgid "Post Interaction Settings" msgstr "" -#: src/view/com/composer/select-language/SelectLangBtn.tsx:89 -msgid "Post language" -msgstr "" - -#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:77 -msgid "Post Languages" +#. Accessibility label for button that opens dialog to choose post language settings +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:53 +msgid "Post language selection" msgstr "" #: src/screens/PostThread/components/ThreadError.tsx:32 #: src/screens/PostThread/components/ThreadItemPostTombstone.tsx:25 -#: src/view/com/post-thread/PostThread.tsx:266 -#: src/view/com/post-thread/PostThread.tsx:278 msgid "Post not found" msgstr "" @@ -6478,15 +6526,13 @@ msgstr "" msgid "Primary Language" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:110 -#: src/screens/Settings/ThreadPreferences.tsx:115 -#: src/screens/Settings/ThreadPreferences.tsx:246 -#: src/screens/Settings/ThreadPreferences.tsx:251 +#: src/screens/Settings/ThreadPreferences.tsx:94 +#: src/screens/Settings/ThreadPreferences.tsx:99 msgid "Prioritize your Follows" msgstr "" -#: src/view/shell/desktop/RightNav.tsx:110 -#: src/view/shell/desktop/RightNav.tsx:111 +#: src/view/shell/desktop/RightNav.tsx:114 +#: src/view/shell/desktop/RightNav.tsx:115 msgid "Privacy" msgstr "" @@ -6517,7 +6563,7 @@ msgstr "" msgid "Privacy Policy" msgstr "" -#: src/view/com/composer/Composer.tsx:1821 +#: src/view/com/composer/Composer.tsx:1843 msgid "Processing video..." msgstr "" @@ -6526,24 +6572,24 @@ msgstr "" msgid "Processing..." msgstr "" -#: src/view/screens/DebugMod.tsx:927 +#: src/view/screens/DebugMod.tsx:934 #: src/view/screens/Profile.tsx:364 msgid "profile" msgstr "" #: src/view/shell/bottom-bar/BottomBar.tsx:316 -#: src/view/shell/desktop/LeftNav.tsx:710 +#: src/view/shell/desktop/LeftNav.tsx:762 #: src/view/shell/Drawer.tsx:76 #: src/view/shell/Drawer.tsx:559 msgid "Profile" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:197 +#: src/screens/Profile/Header/EditProfileDialog.tsx:191 msgctxt "toast" msgid "Profile updated" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:264 +#: src/screens/Onboarding/StepFinished.tsx:541 msgid "Public" msgstr "" @@ -6556,22 +6602,22 @@ msgid "Public, sharable lists which can be used to drive feeds." msgstr "" #. Accessibility label for button to publish a single post -#: src/view/com/composer/Composer.tsx:1033 +#: src/view/com/composer/Composer.tsx:1055 msgid "Publish post" msgstr "" #. Accessibility label for button to publish multiple posts in a thread -#: src/view/com/composer/Composer.tsx:1026 +#: src/view/com/composer/Composer.tsx:1048 msgid "Publish posts" msgstr "" #. Accessibility label for button to publish multiple replies in a thread -#: src/view/com/composer/Composer.tsx:1011 +#: src/view/com/composer/Composer.tsx:1033 msgid "Publish replies" msgstr "" #. Accessibility label for button to publish a single reply -#: src/view/com/composer/Composer.tsx:1018 +#: src/view/com/composer/Composer.tsx:1040 msgid "Publish reply" msgstr "" @@ -6614,11 +6660,11 @@ msgstr "" msgid "Quote post" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:304 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:308 msgid "Quote post was re-attached" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:303 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:307 msgid "Quote post was successfully detached" msgstr "" @@ -6640,24 +6686,16 @@ msgstr "" msgid "Quotes" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:446 -#: src/view/com/post-thread/PostThreadItem.tsx:269 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:450 msgid "Quotes of this post" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:233 -#: src/screens/Settings/ThreadPreferences.tsx:236 -#: src/view/com/post-thread/PostThread.tsx:720 -#: src/view/com/post-thread/PostThread.tsx:725 -msgid "Random (aka \"Poster's Roulette\")" -msgstr "" - #: src/screens/Settings/components/ChangeHandleDialog.tsx:600 msgid "Rate limit exceeded – you've tried to change your handle too many times in a short period. Please wait a minute before trying again." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:581 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:591 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:588 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:598 msgid "Re-attach quote" msgstr "" @@ -6678,11 +6716,11 @@ msgstr "" msgid "Read blog post" msgstr "" -#: src/screens/VideoFeed/index.tsx:967 +#: src/screens/VideoFeed/index.tsx:970 msgid "Read less" msgstr "" -#: src/screens/VideoFeed/index.tsx:967 +#: src/screens/VideoFeed/index.tsx:970 msgid "Read more" msgstr "" @@ -6730,6 +6768,10 @@ msgstr "" msgid "Recent Searches" msgstr "" +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:274 +msgid "Recently used" +msgstr "" + #: src/screens/Search/modules/ExploreRecommendations.tsx:54 msgid "Recommended" msgstr "" @@ -6754,6 +6796,8 @@ msgstr "" #: src/components/dialogs/lists/ListAddRemoveUsersDialog.tsx:171 #: src/components/dialogs/MutedWords.tsx:438 +#: src/components/dialogs/StarterPackDialog.tsx:384 +#: src/components/dialogs/StarterPackDialog.tsx:390 #: src/components/FeedCard.tsx:343 #: src/components/StarterPack/Wizard/WizardListCard.tsx:104 #: src/components/StarterPack/Wizard/WizardListCard.tsx:111 @@ -6860,8 +6904,8 @@ msgstr "" #: src/components/verification/VerificationRemovePrompt.tsx:46 #: src/components/verification/VerificationsDialog.tsx:247 -#: src/view/com/profile/ProfileMenu.tsx:336 -#: src/view/com/profile/ProfileMenu.tsx:339 +#: src/view/com/profile/ProfileMenu.tsx:348 +#: src/view/com/profile/ProfileMenu.tsx:351 msgid "Remove verification" msgstr "" @@ -6886,6 +6930,10 @@ msgstr "" msgid "Removed from saved feeds" msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:290 +msgid "Removed from starter pack" +msgstr "" + #: src/screens/Profile/components/ProfileFeedHeader.tsx:122 #: src/view/com/posts/FeedShutdownMsg.tsx:44 #: src/view/screens/ProfileList.tsx:392 @@ -6939,7 +6987,7 @@ msgstr "" msgid "Replies to this post are disabled." msgstr "" -#: src/view/com/composer/Composer.tsx:1049 +#: src/view/com/composer/Composer.tsx:1071 msgctxt "action" msgid "Reply" msgstr "" @@ -6972,16 +7020,15 @@ msgid "Reply settings are chosen by the author of the thread" msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:69 -#: src/view/com/post-thread/PostThread.tsx:676 msgid "Reply sorting" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:335 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:339 msgctxt "toast" msgid "Reply visibility updated" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:334 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:338 msgid "Reply was successfully hidden" msgstr "" @@ -6991,8 +7038,8 @@ msgstr "" msgid "Report" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:403 -#: src/view/com/profile/ProfileMenu.tsx:406 +#: src/view/com/profile/ProfileMenu.tsx:415 +#: src/view/com/profile/ProfileMenu.tsx:418 msgid "Report account" msgstr "" @@ -7023,8 +7070,8 @@ msgstr "" msgid "Report message" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:647 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:649 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:654 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:656 msgid "Report post" msgstr "" @@ -7120,8 +7167,7 @@ msgstr "" msgid "Reposts" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:428 -#: src/view/com/post-thread/PostThreadItem.tsx:248 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:432 msgid "Reposts of this post" msgstr "" @@ -7135,9 +7181,9 @@ msgstr "" msgid "Reposts of your reposts notifications" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:253 -#: src/view/com/modals/ChangePassword.tsx:255 -msgid "Request Code" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:224 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:230 +msgid "Request code" msgstr "" #: src/screens/Settings/AccessibilitySettings.tsx:58 @@ -7180,14 +7226,9 @@ msgid "Reset activity subscription nudge" msgstr "" #: src/screens/Login/SetNewPasswordForm.tsx:116 -#: src/view/com/modals/ChangePassword.tsx:197 msgid "Reset code" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:204 -msgid "Reset Code" -msgstr "" - #: src/screens/Settings/Settings.tsx:467 #: src/screens/Settings/Settings.tsx:469 msgid "Reset onboarding state" @@ -7218,8 +7259,10 @@ msgstr "" #: src/screens/Messages/ChatList.tsx:280 #: src/screens/Messages/components/MessageListError.tsx:25 #: src/screens/Messages/Inbox.tsx:218 -#: src/screens/Onboarding/StepInterests/index.tsx:217 -#: src/screens/Onboarding/StepInterests/index.tsx:220 +#: src/screens/Onboarding/StepInterests/index.tsx:226 +#: src/screens/Onboarding/StepInterests/index.tsx:229 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:209 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:212 #: src/screens/PostThread/components/ThreadError.tsx:75 #: src/screens/PostThread/components/ThreadError.tsx:81 #: src/screens/Signup/BackNextButtons.tsx:53 @@ -7244,13 +7287,13 @@ msgstr "" #: src/screens/Profile/ProfileFeed/index.tsx:93 #: src/screens/Settings/components/ChangeHandleDialog.tsx:559 -#: src/screens/VideoFeed/index.tsx:1143 +#: src/screens/VideoFeed/index.tsx:1146 #: src/view/screens/NotFound.tsx:60 #: src/view/screens/ProfileList.tsx:1039 msgid "Returns to previous page" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:304 +#: src/screens/StarterPack/Wizard/index.tsx:324 msgid "Returns to the previous step" msgstr "" @@ -7260,8 +7303,8 @@ msgstr "" #: src/components/live/EditLiveDialog.tsx:216 #: src/components/live/EditLiveDialog.tsx:223 #: src/components/StarterPack/QrCodeDialog.tsx:192 -#: src/screens/Profile/Header/EditProfileDialog.tsx:244 -#: src/screens/Profile/Header/EditProfileDialog.tsx:258 +#: src/screens/Profile/Header/EditProfileDialog.tsx:238 +#: src/screens/Profile/Header/EditProfileDialog.tsx:252 #: src/screens/Settings/components/ChangeHandleDialog.tsx:262 #: src/view/com/composer/GifAltText.tsx:193 #: src/view/com/composer/GifAltText.tsx:202 @@ -7333,11 +7376,19 @@ msgstr "" msgid "Say hello!" msgstr "" -#: src/screens/Onboarding/index.tsx:33 -#: src/screens/Onboarding/state.ts:115 +#: src/screens/Onboarding/index.tsx:47 +#: src/screens/Onboarding/state.ts:120 msgid "Science" msgstr "" +#: src/components/InterestTabs.tsx:250 +msgid "Scroll left" +msgstr "" + +#: src/components/InterestTabs.tsx:284 +msgid "Scroll right" +msgstr "" + #: src/view/screens/ProfileList.tsx:996 msgid "Scroll to top" msgstr "" @@ -7356,7 +7407,7 @@ msgstr "" msgid "Search @{0}'s posts" msgstr "" -#: src/components/ProgressGuide/FollowDialog.tsx:683 +#: src/components/ProgressGuide/FollowDialog.tsx:595 msgid "Search by name or interest" msgstr "" @@ -7364,9 +7415,14 @@ msgstr "" msgid "Search feeds" msgstr "" -#: src/components/ProgressGuide/FollowDialog.tsx:524 -#: src/screens/Search/modules/ExploreSuggestedAccounts.tsx:135 -msgid "Search for \"{interestsDisplayName}\"{activeText}" +#. Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is not currently active and can be selected. +#: src/components/ProgressGuide/FollowDialog.tsx:425 +msgid "Search for \"{interestsDisplayName}" +msgstr "" + +#. Accessibility label for a tab that searches for accounts in a category (e.g. Art, Video Games, Sports, etc.) that are suggested for the user to follow. The tab is currently selected. +#: src/components/ProgressGuide/FollowDialog.tsx:418 +msgid "Search for \"{interestsDisplayName}\" (active)" msgstr "" #: src/view/shell/desktop/Search.tsx:131 @@ -7377,7 +7433,7 @@ msgstr "" msgid "Search for \"{searchText}\"" msgstr "" -#: src/screens/StarterPack/Wizard/index.tsx:513 +#: src/screens/StarterPack/Wizard/index.tsx:549 msgid "Search for feeds that you want to suggest to others." msgstr "" @@ -7401,17 +7457,22 @@ msgstr "" msgid "Search is currently unavailable when logged out" msgstr "" +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:257 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:258 +msgid "Search languages" +msgstr "" + #: src/screens/Profile/ProfileSearch.tsx:36 msgid "Search my posts" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:263 #: src/view/com/profile/ProfileMenu.tsx:266 +#: src/view/com/profile/ProfileMenu.tsx:269 msgid "Search posts" msgstr "" #: src/components/dialogs/SearchablePeopleList.tsx:534 -#: src/components/ProgressGuide/FollowDialog.tsx:702 +#: src/components/ProgressGuide/FollowDialog.tsx:614 msgid "Search profiles" msgstr "" @@ -7424,7 +7485,7 @@ msgid "Search..." msgstr "" #: src/components/dialogs/SearchablePeopleList.tsx:535 -#: src/components/ProgressGuide/FollowDialog.tsx:703 +#: src/components/ProgressGuide/FollowDialog.tsx:615 msgid "Searches for profiles" msgstr "" @@ -7452,15 +7513,12 @@ msgstr "" msgid "See jobs at Bluesky" msgstr "" -#: src/components/FeedInterstitials.tsx:393 +#: src/components/FeedInterstitials.tsx:394 +#: src/components/FeedInterstitials.tsx:445 msgid "See more" msgstr "" -#: src/components/FeedInterstitials.tsx:437 -msgid "See more accounts you might like" -msgstr "" - -#: src/components/FeedInterstitials.tsx:391 +#: src/components/FeedInterstitials.tsx:392 msgid "See more suggested profiles on the Explore page" msgstr "" @@ -7472,6 +7530,11 @@ msgstr "" msgid "Seek slider. Use the arrow keys to seek forwards and backwards, and space to play/pause" msgstr "" +#. Accessibility label for a category (e.g. Art, Video Games, Sports, etc.) that shows suggested accounts for the user to follow. The tab is not currently active and can be selected. +#: src/components/InterestTabs.tsx:332 +msgid "Select \"{interestsDisplayName}\" category" +msgstr "" + #. Accessibility label for a username suggestion in the account creation flow #: src/screens/Signup/StepHandle/HandleSuggestions.tsx:42 msgid "Select {0}" @@ -7539,6 +7602,7 @@ msgid "Select language..." msgstr "" #: src/screens/Settings/LanguageSettings.tsx:178 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:290 msgid "Select languages" msgstr "" @@ -7567,6 +7631,10 @@ msgstr "" msgid "Select the moderation service(s) to report to" msgstr "" +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:236 +msgid "Select up to 3 languages used in this post" +msgstr "" + #: src/components/dialogs/MutedWords.tsx:242 msgid "Select what content this mute word should apply to." msgstr "" @@ -7583,7 +7651,7 @@ msgstr "" msgid "Select your date of birth" msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:192 +#: src/screens/Onboarding/StepInterests/index.tsx:201 #: src/screens/Settings/InterestsSettings.tsx:161 msgid "Select your interests from the options below" msgstr "" @@ -7600,10 +7668,6 @@ msgstr "" msgid "Selecting multiple media types is not supported." msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:302 -msgid "Selects option {0} of {numItems}" -msgstr "" - #: src/components/dms/ChatEmptyPill.tsx:38 msgid "Send a neat website!" msgstr "" @@ -7685,7 +7749,7 @@ msgstr "" msgid "Set new password" msgstr "" -#: src/screens/Onboarding/Layout.tsx:47 +#: src/screens/Onboarding/Layout.tsx:50 msgid "Set up your account" msgstr "" @@ -7695,7 +7759,7 @@ msgstr "" #: src/Navigation.tsx:212 #: src/screens/Settings/Settings.tsx:99 -#: src/view/shell/desktop/LeftNav.tsx:728 +#: src/view/shell/desktop/LeftNav.tsx:780 #: src/view/shell/Drawer.tsx:572 msgid "Settings" msgstr "" @@ -7778,7 +7842,7 @@ msgstr "" msgid "Share a fun fact!" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:485 +#: src/view/com/profile/ProfileMenu.tsx:502 msgid "Share anyway" msgstr "" @@ -7824,8 +7888,8 @@ msgstr "" #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:120 #: src/screens/StarterPack/StarterPackScreen.tsx:611 #: src/screens/StarterPack/StarterPackScreen.tsx:619 -#: src/view/com/profile/ProfileMenu.tsx:243 -#: src/view/com/profile/ProfileMenu.tsx:256 +#: src/view/com/profile/ProfileMenu.tsx:246 +#: src/view/com/profile/ProfileMenu.tsx:259 msgid "Share via..." msgstr "" @@ -7850,8 +7914,8 @@ msgstr "" #: src/components/moderation/ScreenHider.tsx:172 #: src/components/moderation/ScreenHider.tsx:175 #: src/screens/List/ListHiddenScreen.tsx:190 -#: src/screens/VideoFeed/index.tsx:628 -#: src/screens/VideoFeed/index.tsx:634 +#: src/screens/VideoFeed/index.tsx:631 +#: src/screens/VideoFeed/index.tsx:637 msgid "Show anyway" msgstr "" @@ -7868,12 +7932,8 @@ msgstr "" msgid "Show customization options" msgstr "" -#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:22 -msgid "Show hidden replies" -msgstr "" - -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:479 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:481 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:483 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:485 msgid "Show less like this" msgstr "" @@ -7885,8 +7945,8 @@ msgstr "" msgid "Show More" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:471 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:473 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:475 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:477 msgid "Show more like this" msgstr "" @@ -7894,11 +7954,7 @@ msgstr "" msgid "Show more replies" msgstr "" -#: src/view/com/post-thread/PostThreadShowHiddenReplies.tsx:22 -msgid "Show muted replies" -msgstr "" - -#: src/screens/Settings/ThreadPreferences.tsx:143 +#: src/screens/Settings/ThreadPreferences.tsx:127 msgid "Show post replies in a threaded tree view" msgstr "" @@ -7913,21 +7969,15 @@ msgid "Show replies" msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:43 -#: src/view/com/post-thread/PostThread.tsx:650 msgid "Show replies as" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:285 -msgid "Show replies as threaded" -msgstr "" - -#: src/screens/Settings/ThreadPreferences.tsx:120 -#: src/screens/Settings/ThreadPreferences.tsx:260 +#: src/screens/Settings/ThreadPreferences.tsx:104 msgid "Show replies by people you follow before all other replies" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:555 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:565 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:562 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:572 msgid "Show reply for everyone" msgstr "" @@ -7949,8 +7999,7 @@ msgstr "" msgid "Show warning and filter from feeds" msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:610 -#: src/view/com/post-thread/PostThreadItem.tsx:925 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:614 msgid "Shows information about when this post was created" msgstr "" @@ -8007,8 +8056,8 @@ msgstr "" msgid "Sign in to Bluesky or create a new account" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:457 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:459 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:461 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:463 msgid "Sign in to view post" msgstr "" @@ -8019,8 +8068,8 @@ msgstr "" #: src/screens/SignupQueued.tsx:96 #: src/screens/Takendown.tsx:85 #: src/view/shell/desktop/LeftNav.tsx:211 -#: src/view/shell/desktop/LeftNav.tsx:266 -#: src/view/shell/desktop/LeftNav.tsx:269 +#: src/view/shell/desktop/LeftNav.tsx:268 +#: src/view/shell/desktop/LeftNav.tsx:271 msgid "Sign out" msgstr "" @@ -8043,16 +8092,25 @@ msgstr "" msgid "Signed in as @{0}" msgstr "" -#: src/components/FeedInterstitials.tsx:386 +#: src/components/FeedInterstitials.tsx:387 msgid "Similar accounts" msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:231 -#: src/screens/StarterPack/Wizard/index.tsx:201 +#: src/screens/Onboarding/StepFinished.tsx:380 +#: src/screens/Onboarding/StepFinished.tsx:462 +#: src/screens/Onboarding/StepInterests/index.tsx:240 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:222 +#: src/screens/StarterPack/Wizard/index.tsx:218 msgid "Skip" msgstr "" -#: src/screens/Onboarding/StepInterests/index.tsx:228 +#: src/screens/Onboarding/StepFinished.tsx:373 +#: src/screens/Onboarding/StepFinished.tsx:459 +msgid "Skip introduction and start using your account" +msgstr "" + +#: src/screens/Onboarding/StepInterests/index.tsx:237 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:219 msgid "Skip this flow" msgstr "" @@ -8064,8 +8122,8 @@ msgstr "" msgid "Snoozes the reminder" msgstr "" -#: src/screens/Onboarding/index.tsx:37 -#: src/screens/Onboarding/state.ts:103 +#: src/screens/Onboarding/index.tsx:51 +#: src/screens/Onboarding/state.ts:108 msgid "Software Dev" msgstr "" @@ -8073,7 +8131,7 @@ msgstr "" msgid "Some of your verifications are invalid." msgstr "" -#: src/components/FeedInterstitials.tsx:519 +#: src/components/FeedInterstitials.tsx:522 msgid "Some other feeds you might like" msgstr "" @@ -8131,18 +8189,15 @@ msgstr "" msgid "Sorry! Your session expired. Please sign in again." msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:68 -#: src/screens/Settings/ThreadPreferences.tsx:189 +#: src/screens/Settings/ThreadPreferences.tsx:52 msgid "Sort replies" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:75 -#: src/screens/Settings/ThreadPreferences.tsx:196 +#: src/screens/Settings/ThreadPreferences.tsx:59 msgid "Sort replies by" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:72 -#: src/screens/Settings/ThreadPreferences.tsx:193 +#: src/screens/Settings/ThreadPreferences.tsx:56 msgid "Sort replies to the same post by:" msgstr "" @@ -8163,8 +8218,8 @@ msgstr "" msgid "Spam; excessive mentions or replies" msgstr "" -#: src/screens/Onboarding/index.tsx:27 -#: src/screens/Onboarding/state.ts:116 +#: src/screens/Onboarding/index.tsx:41 +#: src/screens/Onboarding/state.ts:121 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:230 msgid "Sports" msgstr "" @@ -8193,7 +8248,7 @@ msgstr "" #: src/Navigation.tsx:578 #: src/Navigation.tsx:583 -#: src/screens/StarterPack/Wizard/index.tsx:192 +#: src/screens/StarterPack/Wizard/index.tsx:209 msgid "Starter Pack" msgstr "" @@ -8305,7 +8360,9 @@ msgstr "" msgid "Suggested Accounts" msgstr "" -#: src/components/FeedInterstitials.tsx:384 +#. Accounts suggested to the user for them to follow +#: src/components/FeedInterstitials.tsx:385 +#: src/screens/Onboarding/StepSuggestedAccounts/index.tsx:137 msgid "Suggested for you" msgstr "" @@ -8333,7 +8390,7 @@ msgstr "" #: src/screens/Settings/Settings.tsx:123 #: src/screens/Settings/Settings.tsx:137 #: src/screens/Settings/Settings.tsx:604 -#: src/view/shell/desktop/LeftNav.tsx:245 +#: src/view/shell/desktop/LeftNav.tsx:246 msgid "Switch account" msgstr "" @@ -8346,7 +8403,7 @@ msgstr "" msgid "Switch accounts" msgstr "" -#: src/view/shell/desktop/LeftNav.tsx:293 +#: src/view/shell/desktop/LeftNav.tsx:345 msgid "Switch to {0}" msgstr "" @@ -8402,8 +8459,8 @@ msgstr "" msgid "Teach our algorithm what you like" msgstr "" -#: src/screens/Onboarding/index.tsx:36 -#: src/screens/Onboarding/state.ts:117 +#: src/screens/Onboarding/index.tsx:50 +#: src/screens/Onboarding/state.ts:122 msgid "Tech" msgstr "" @@ -8411,7 +8468,7 @@ msgstr "" msgid "Tell a joke!" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:374 +#: src/screens/Profile/Header/EditProfileDialog.tsx:368 msgid "Tell us a bit about yourself" msgstr "" @@ -8419,8 +8476,8 @@ msgstr "" msgid "Tell us a little more" msgstr "" -#: src/view/shell/desktop/RightNav.tsx:116 -#: src/view/shell/desktop/RightNav.tsx:117 +#: src/view/shell/desktop/RightNav.tsx:120 +#: src/view/shell/desktop/RightNav.tsx:121 msgid "Terms" msgstr "" @@ -8479,8 +8536,8 @@ msgstr "" #: src/screens/StarterPack/StarterPackScreen.tsx:111 #: src/screens/StarterPack/StarterPackScreen.tsx:155 #: src/screens/StarterPack/StarterPackScreen.tsx:156 -#: src/screens/StarterPack/Wizard/index.tsx:110 -#: src/screens/StarterPack/Wizard/index.tsx:120 +#: src/screens/StarterPack/Wizard/index.tsx:117 +#: src/screens/StarterPack/Wizard/index.tsx:127 msgid "That starter pack could not be found." msgstr "" @@ -8492,12 +8549,12 @@ msgstr "" msgid "That's all, folks!" msgstr "" -#: src/screens/VideoFeed/index.tsx:1115 +#: src/screens/VideoFeed/index.tsx:1118 msgid "That's everything!" msgstr "" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:324 -#: src/view/com/profile/ProfileMenu.tsx:461 +#: src/view/com/profile/ProfileMenu.tsx:478 msgid "The account will be able to interact with you after unblocking." msgstr "" @@ -8559,9 +8616,8 @@ msgstr "" msgid "The laws in your location require you to verify you're an adult to access certain features. Tap to learn more." msgstr "" -#: src/view/com/post-thread/PostThread.tsx:267 -#: src/view/com/post-thread/PostThread.tsx:279 -msgid "The post may have been deleted." +#: src/components/LoggedOutCTA.tsx:63 +msgid "The open social network." msgstr "" #: src/view/screens/PrivacyPolicy.tsx:35 @@ -8663,20 +8719,20 @@ msgstr "" msgid "There was an issue updating your feeds, please check your internet connection and try again." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:361 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:374 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:384 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:365 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:378 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:388 +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:90 +#: src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx:101 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:101 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:123 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:136 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:90 -#: src/view/com/post-thread/PostThreadFollowBtn.tsx:101 -#: src/view/com/profile/ProfileMenu.tsx:128 -#: src/view/com/profile/ProfileMenu.tsx:138 -#: src/view/com/profile/ProfileMenu.tsx:152 -#: src/view/com/profile/ProfileMenu.tsx:162 -#: src/view/com/profile/ProfileMenu.tsx:175 -#: src/view/com/profile/ProfileMenu.tsx:187 +#: src/view/com/profile/ProfileMenu.tsx:131 +#: src/view/com/profile/ProfileMenu.tsx:141 +#: src/view/com/profile/ProfileMenu.tsx:155 +#: src/view/com/profile/ProfileMenu.tsx:165 +#: src/view/com/profile/ProfileMenu.tsx:178 +#: src/view/com/profile/ProfileMenu.tsx:190 msgid "There was an issue! {0}" msgstr "" @@ -8692,6 +8748,7 @@ msgid "There was an issue. Please check your internet connection and try again." msgstr "" #: src/components/dialogs/GifSelect.tsx:269 +#: src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx:367 #: src/view/com/util/ErrorBoundary.tsx:59 msgid "There was an unexpected issue in the application. Please let us know if this happened to you!" msgstr "" @@ -8740,6 +8797,10 @@ msgstr "" msgid "This chat was disconnected" msgstr "" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:144 +msgid "This confirmation code is not valid. Please try again." +msgstr "" + #: src/lib/moderation/useGlobalLabelStrings.ts:19 msgid "This content has been hidden by the moderators." msgstr "" @@ -8848,8 +8909,7 @@ msgstr "" msgid "This moderation service is unavailable. See below for more details. If this issue persists, contact us." msgstr "" -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:650 -#: src/view/com/post-thread/PostThreadItem.tsx:965 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:654 msgid "This post claims to have been created on <0>{0}</0>, but was first seen by Bluesky on <1>{1}</1>." msgstr "" @@ -8857,27 +8917,23 @@ msgstr "" msgid "This post has an unknown type of threadgate on it. Your app may be out of date." msgstr "" -#: src/view/com/post-thread/PostThreadItem.tsx:171 -msgid "This post has been deleted." -msgstr "" - #: src/components/PostControls/ShareMenu/ShareMenuItems.tsx:140 msgid "This post is only visible to logged-in users." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:705 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:712 msgid "This post will be hidden from feeds and threads. This cannot be undone." msgstr "" -#: src/view/com/composer/Composer.tsx:470 +#: src/view/com/composer/Composer.tsx:474 msgid "This post's author has disabled quote posts." msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:482 +#: src/view/com/profile/ProfileMenu.tsx:499 msgid "This profile is only visible to logged-in users. It won't be visible to people who aren't signed in." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:740 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:747 msgid "This reply will be sorted into a hidden section at the bottom of your thread and will mute notifications for subsequent replies - both for yourself and others." msgstr "" @@ -8938,14 +8994,12 @@ msgstr "" msgid "This will remove @{0} from the quick access list." msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:730 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:737 msgid "This will remove your post from this quote post for all users, and replace it with a placeholder." msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:23 #: src/screens/PostThread/components/HeaderDropdown.tsx:26 -#: src/view/com/post-thread/PostThread.tsx:634 -#: src/view/com/post-thread/PostThread.tsx:637 msgid "Thread options" msgstr "" @@ -8954,22 +9008,15 @@ msgstr "" msgid "Thread preferences" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:58 -#: src/screens/Settings/ThreadPreferences.tsx:179 +#: src/screens/Settings/ThreadPreferences.tsx:42 msgid "Thread Preferences" msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:57 #: src/screens/PostThread/components/HeaderDropdown.tsx:62 -#: src/view/com/post-thread/PostThread.tsx:664 -#: src/view/com/post-thread/PostThread.tsx:669 msgid "Threaded" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:276 -msgid "Threaded mode" -msgstr "" - #: src/Navigation.tsx:368 msgid "Threads Preferences" msgstr "" @@ -8999,10 +9046,6 @@ msgstr "" msgid "Today" msgstr "" -#: src/view/com/util/forms/DropdownButton.tsx:263 -msgid "Toggle dropdown" -msgstr "" - #: src/screens/Moderation/index.tsx:403 msgid "Toggle to enable or disable adult content" msgstr "" @@ -9019,8 +9062,8 @@ msgstr "" #: src/screens/PostThread/components/HeaderDropdown.tsx:73 #: src/screens/PostThread/components/HeaderDropdown.tsx:78 -#: src/screens/Settings/ThreadPreferences.tsx:80 -#: src/screens/Settings/ThreadPreferences.tsx:83 +#: src/screens/Settings/ThreadPreferences.tsx:64 +#: src/screens/Settings/ThreadPreferences.tsx:67 msgid "Top replies first" msgstr "" @@ -9030,17 +9073,15 @@ msgstr "" #: src/components/dms/MessageContextMenu.tsx:137 #: src/components/dms/MessageContextMenu.tsx:139 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:440 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:442 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:572 -#: src/screens/PostThread/components/ThreadItemAnchor.tsx:575 -#: src/view/com/post-thread/PostThreadItem.tsx:887 -#: src/view/com/post-thread/PostThreadItem.tsx:890 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:444 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:446 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:576 +#: src/screens/PostThread/components/ThreadItemAnchor.tsx:579 msgid "Translate" msgstr "" -#: src/screens/Settings/ThreadPreferences.tsx:131 -#: src/screens/Settings/ThreadPreferences.tsx:136 +#: src/screens/Settings/ThreadPreferences.tsx:115 +#: src/screens/Settings/ThreadPreferences.tsx:120 msgid "Tree view" msgstr "" @@ -9061,7 +9102,7 @@ msgctxt "action" msgid "Try again" msgstr "" -#: src/screens/Onboarding/state.ts:118 +#: src/screens/Onboarding/state.ts:123 msgid "TV" msgstr "" @@ -9082,13 +9123,16 @@ msgstr "" msgid "Unable to connect. Please check your internet connection and try again." msgstr "" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:95 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:140 +msgid "Unable to contact your service. Please check your internet connection and try again." +msgstr "" + #: src/screens/Login/ForgotPasswordForm.tsx:68 #: src/screens/Login/index.tsx:79 #: src/screens/Login/LoginForm.tsx:169 #: src/screens/Login/SetNewPasswordForm.tsx:81 #: src/screens/Signup/index.tsx:76 -#: src/view/com/modals/ChangePassword.tsx:71 -#: src/view/com/modals/ChangePassword.tsx:117 msgid "Unable to contact your service. Please check your Internet connection." msgstr "" @@ -9118,7 +9162,7 @@ msgstr "" #: src/components/dms/MessagesListBlockedFooter.tsx:119 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:208 #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:328 -#: src/view/com/profile/ProfileMenu.tsx:473 +#: src/view/com/profile/ProfileMenu.tsx:490 #: src/view/screens/ProfileList.tsx:723 msgid "Unblock" msgstr "" @@ -9130,13 +9174,13 @@ msgstr "" #: src/components/dms/ConvoMenu.tsx:247 #: src/components/dms/ConvoMenu.tsx:250 -#: src/view/com/profile/ProfileMenu.tsx:383 -#: src/view/com/profile/ProfileMenu.tsx:389 +#: src/view/com/profile/ProfileMenu.tsx:395 +#: src/view/com/profile/ProfileMenu.tsx:401 msgid "Unblock account" msgstr "" #: src/screens/Profile/Header/ProfileHeaderStandard.tsx:322 -#: src/view/com/profile/ProfileMenu.tsx:455 +#: src/view/com/profile/ProfileMenu.tsx:472 msgid "Unblock Account?" msgstr "" @@ -9163,12 +9207,12 @@ msgstr "" msgid "Unfollow {0}" msgstr "" -#: src/view/com/profile/ProfileMenu.tsx:283 -#: src/view/com/profile/ProfileMenu.tsx:293 +#: src/view/com/profile/ProfileMenu.tsx:286 +#: src/view/com/profile/ProfileMenu.tsx:296 msgid "Unfollow account" msgstr "" -#: src/screens/VideoFeed/index.tsx:832 +#: src/screens/VideoFeed/index.tsx:835 msgid "Unfollows the user" msgstr "" @@ -9208,10 +9252,10 @@ msgstr "" msgid "Unmute {tag}" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:620 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:626 -#: src/view/com/profile/ProfileMenu.tsx:362 -#: src/view/com/profile/ProfileMenu.tsx:368 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:627 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:633 +#: src/view/com/profile/ProfileMenu.tsx:374 +#: src/view/com/profile/ProfileMenu.tsx:380 msgid "Unmute account" msgstr "" @@ -9223,8 +9267,8 @@ msgstr "" msgid "Unmute list" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:505 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:509 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:512 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:516 msgid "Unmute thread" msgstr "" @@ -9250,8 +9294,8 @@ msgstr "" msgid "Unpin from home" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:415 -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:422 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:419 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:426 msgid "Unpin from profile" msgstr "" @@ -9289,7 +9333,7 @@ msgstr "" msgid "Unsubscribed from list" msgstr "" -#: src/view/com/composer/Composer.tsx:829 +#: src/view/com/composer/Composer.tsx:851 msgid "Unsupported video type: {mimeType}" msgstr "" @@ -9311,8 +9355,8 @@ msgstr "" #: src/components/dialogs/EmailDialog/screens/Update.tsx:300 #: src/components/dialogs/EmailDialog/screens/Update.tsx:312 -#: src/screens/Settings/AccountSettings.tsx:105 -#: src/screens/Settings/AccountSettings.tsx:113 +#: src/screens/Settings/AccountSettings.tsx:107 +#: src/screens/Settings/AccountSettings.tsx:115 msgid "Update email" msgstr "" @@ -9325,12 +9369,12 @@ msgstr "" msgid "Update your email" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:308 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:312 msgctxt "toast" msgid "Updating quote attachment failed" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:339 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:343 msgctxt "toast" msgid "Updating reply visibility failed" msgstr "" @@ -9375,7 +9419,7 @@ msgstr "" msgid "Uploading link thumbnail..." msgstr "" -#: src/view/com/composer/Composer.tsx:1818 +#: src/view/com/composer/Composer.tsx:1840 msgid "Uploading video..." msgstr "" @@ -9530,8 +9574,8 @@ msgstr "" #: src/components/verification/VerificationCreatePrompt.tsx:84 #: src/components/verification/VerificationCreatePrompt.tsx:86 -#: src/view/com/profile/ProfileMenu.tsx:346 -#: src/view/com/profile/ProfileMenu.tsx:349 +#: src/view/com/profile/ProfileMenu.tsx:358 +#: src/view/com/profile/ProfileMenu.tsx:361 msgid "Verify account" msgstr "" @@ -9580,8 +9624,8 @@ msgid "Verify your age" msgstr "" #: src/components/dialogs/EmailDialog/screens/Verify.tsx:211 -#: src/screens/Settings/AccountSettings.tsx:79 -#: src/screens/Settings/AccountSettings.tsx:99 +#: src/screens/Settings/AccountSettings.tsx:81 +#: src/screens/Settings/AccountSettings.tsx:101 msgid "Verify your email" msgstr "" @@ -9615,17 +9659,17 @@ msgstr "" msgid "Video from {0}: {text}" msgstr "" -#: src/screens/Onboarding/index.tsx:39 -#: src/screens/Onboarding/state.ts:106 +#: src/screens/Onboarding/index.tsx:53 +#: src/screens/Onboarding/state.ts:111 #: src/screens/Search/modules/ExploreTrendingTopics.tsx:234 msgid "Video Games" msgstr "" -#: src/screens/VideoFeed/index.tsx:1073 +#: src/screens/VideoFeed/index.tsx:1076 msgid "Video is paused" msgstr "" -#: src/screens/VideoFeed/index.tsx:1073 +#: src/screens/VideoFeed/index.tsx:1076 msgid "Video is playing" msgstr "" @@ -9637,7 +9681,7 @@ msgstr "" msgid "Video settings" msgstr "" -#: src/view/com/composer/Composer.tsx:1828 +#: src/view/com/composer/Composer.tsx:1850 msgid "Video uploaded" msgstr "" @@ -9653,6 +9697,11 @@ msgstr "" msgid "Videos must be less than 3 minutes long." msgstr "" +#: src/view/com/composer/Composer.tsx:545 +msgctxt "Action to view the post the user just created" +msgid "View" +msgstr "" + #: src/screens/Profile/Header/Shell.tsx:229 msgid "View {0}'s avatar" msgstr "" @@ -9660,7 +9709,7 @@ msgstr "" #: src/components/ProfileCard.tsx:124 #: src/screens/Profile/components/ProfileFeedHeader.tsx:454 #: src/screens/Search/components/SearchProfileCard.tsx:36 -#: src/screens/VideoFeed/index.tsx:791 +#: src/screens/VideoFeed/index.tsx:794 #: src/view/com/notifications/NotificationFeedItem.tsx:599 msgid "View {0}'s profile" msgstr "" @@ -9682,8 +9731,8 @@ msgid "View debug entry" msgstr "" #: src/components/ReportDialog/SelectReportOptionView.tsx:137 -#: src/screens/VideoFeed/index.tsx:656 -#: src/screens/VideoFeed/index.tsx:674 +#: src/screens/VideoFeed/index.tsx:659 +#: src/screens/VideoFeed/index.tsx:677 msgid "View details" msgstr "" @@ -9710,6 +9759,10 @@ msgstr "" msgid "View more trending videos" msgstr "" +#: src/view/com/composer/Composer.tsx:540 +msgid "View post" +msgstr "" + #: src/components/ProfileHoverCard/index.web.tsx:466 #: src/components/ProfileHoverCard/index.web.tsx:486 #: src/components/ProfileHoverCard/index.web.tsx:513 @@ -9837,7 +9890,7 @@ msgstr "" msgid "We have sent another verification email to <0>{0}</0>." msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:256 +#: src/screens/Onboarding/StepFinished.tsx:533 msgid "We hope you have a wonderful time. Remember, Bluesky is:" msgstr "" @@ -9902,7 +9955,7 @@ msgid "We're having issues initializing the age assurance process for your accou msgstr "" #: src/components/dialogs/SearchablePeopleList.tsx:107 -#: src/components/ProgressGuide/FollowDialog.tsx:172 +#: src/components/ProgressGuide/FollowDialog.tsx:164 msgid "We're having network issues, try again" msgstr "" @@ -9926,7 +9979,7 @@ msgstr "" msgid "We're sorry, but your search could not be completed. Please try again in a few minutes." msgstr "" -#: src/view/com/composer/Composer.tsx:467 +#: src/view/com/composer/Composer.tsx:471 msgid "We're sorry! The post you are replying to has been deleted." msgstr "" @@ -9977,7 +10030,7 @@ msgstr "" #: src/view/com/auth/SplashScreen.tsx:38 #: src/view/com/auth/SplashScreen.web.tsx:99 -#: src/view/com/composer/Composer.tsx:789 +#: src/view/com/composer/Composer.tsx:811 msgid "What's up?" msgstr "" @@ -9985,10 +10038,6 @@ msgstr "" msgid "When you tap on a check, you’ll see which organizations have granted verification." msgstr "" -#: src/view/com/modals/lang-settings/PostLanguagesSettings.tsx:80 -msgid "Which languages are used in this post?" -msgstr "" - #: src/view/com/modals/lang-settings/ContentLanguagesSettings.tsx:79 msgid "Which languages would you like to see in your algorithmic feeds?" msgstr "" @@ -10059,17 +10108,17 @@ msgstr "" msgid "Write a message" msgstr "" -#: src/view/com/composer/Composer.tsx:889 +#: src/view/com/composer/Composer.tsx:911 msgid "Write post" msgstr "" -#: src/view/com/composer/Composer.tsx:787 -#: src/view/com/post-thread/PostThreadComposePrompt.tsx:90 +#: src/screens/PostThread/components/ThreadComposePrompt.tsx:90 +#: src/view/com/composer/Composer.tsx:809 msgid "Write your reply" msgstr "" -#: src/screens/Onboarding/index.tsx:25 -#: src/screens/Onboarding/state.ts:119 +#: src/screens/Onboarding/index.tsx:39 +#: src/screens/Onboarding/state.ts:124 msgid "Writers" msgstr "" @@ -10095,11 +10144,11 @@ msgstr "" msgid "Yes, delete this starter pack" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:733 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:740 msgid "Yes, detach" msgstr "" -#: src/components/PostControls/PostMenu/PostMenuItems.tsx:743 +#: src/components/PostControls/PostMenu/PostMenuItems.tsx:750 msgid "Yes, hide" msgstr "" @@ -10164,7 +10213,7 @@ msgstr "" msgid "You are verified" msgstr "" -#: src/screens/Profile/Header/EditProfileDialog.tsx:352 +#: src/screens/Profile/Header/EditProfileDialog.tsx:346 msgid "You are verified. You will lose your verification status if you change your display name. <0>Learn more.</0>" msgstr "" @@ -10225,7 +10274,7 @@ msgstr "" #: src/components/interstitials/Trending.tsx:130 #: src/components/interstitials/TrendingVideos.tsx:137 -#: src/view/shell/desktop/SidebarTrendingTopics.tsx:109 +#: src/view/shell/desktop/SidebarTrendingTopics.tsx:110 msgid "You can update this later from your settings." msgstr "" @@ -10253,10 +10302,6 @@ msgstr "" msgid "You don't have any saved feeds." msgstr "" -#: src/view/com/post-thread/PostThread.tsx:273 -msgid "You have blocked the author or you have been blocked by the author." -msgstr "" - #: src/components/dms/MessagesListBlockedFooter.tsx:66 msgid "You have blocked this user" msgstr "" @@ -10273,8 +10318,7 @@ msgstr "" #: src/screens/Login/SetNewPasswordForm.tsx:49 #: src/screens/Login/SetNewPasswordForm.tsx:95 -#: src/view/com/modals/ChangePassword.tsx:87 -#: src/view/com/modals/ChangePassword.tsx:133 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:112 msgid "You have entered an invalid code. It should look like XXXXX-XXXXX." msgstr "" @@ -10308,6 +10352,10 @@ msgstr "" msgid "You have no lists." msgstr "" +#: src/components/dialogs/StarterPackDialog.tsx:102 +msgid "You have no starter packs." +msgstr "" + #: src/view/screens/ModerationBlockedAccounts.tsx:164 msgid "You have not blocked any accounts yet. To block an account, go to their profile and select \"Block account\" from the menu on their account." msgstr "" @@ -10475,7 +10523,7 @@ msgstr "" msgid "You're in line" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:253 +#: src/screens/Onboarding/StepFinished.tsx:530 msgid "You're ready to go!" msgstr "" @@ -10517,7 +10565,7 @@ msgstr "" msgid "You've reached your daily limit for video uploads (too many videos)" msgstr "" -#: src/screens/VideoFeed/index.tsx:1124 +#: src/screens/VideoFeed/index.tsx:1127 msgid "You've run out of videos to watch. Maybe it's a good time to take a break?" msgstr "" @@ -10580,9 +10628,9 @@ msgid "Your email" msgstr "" #: src/screens/Login/ForgotPasswordForm.tsx:51 +#: src/screens/Settings/components/ChangePasswordDialog.tsx:81 #: src/screens/Signup/state.ts:271 #: src/screens/Signup/StepInfo/index.tsx:101 -#: src/view/com/modals/ChangePassword.tsx:55 msgid "Your email appears to be invalid." msgstr "" @@ -10627,23 +10675,23 @@ msgstr "" msgid "Your muted words" msgstr "" -#: src/view/com/modals/ChangePassword.tsx:169 -msgid "Your password has been changed successfully!" +#: src/screens/Settings/components/ChangePasswordDialog.tsx:71 +msgid "Your password has been changed successfully! Please use your new password when you sign in to Bluesky from now on." msgstr "" #: src/screens/Signup/StepInfo/index.tsx:130 msgid "Your password must be at least 8 characters long." msgstr "" -#: src/view/com/composer/Composer.tsx:529 -msgid "Your post has been published" +#: src/view/com/composer/Composer.tsx:536 +msgid "Your post was sent" msgstr "" -#: src/view/com/composer/Composer.tsx:526 -msgid "Your posts have been published" +#: src/view/com/composer/Composer.tsx:533 +msgid "Your posts were sent" msgstr "" -#: src/screens/Onboarding/StepFinished.tsx:268 +#: src/screens/Onboarding/StepFinished.tsx:545 msgid "Your posts, likes, and blocks are public. Mutes are private." msgstr "" @@ -10651,12 +10699,20 @@ msgstr "" msgid "Your preferred language" msgstr "" +#: src/screens/Onboarding/StepFinished.tsx:422 +msgid "Your profile picture" +msgstr "" + +#: src/screens/Onboarding/StepFinished.tsx:350 +msgid "Your profile picture surrounded by concentric circles of other users' profile pictures" +msgstr "" + #: src/screens/Settings/components/DeactivateAccountDialog.tsx:75 msgid "Your profile, posts, feeds, and lists will no longer be visible to other Bluesky users. You can reactivate your account at any time by logging in." msgstr "" -#: src/view/com/composer/Composer.tsx:528 -msgid "Your reply has been published" +#: src/view/com/composer/Composer.tsx:535 +msgid "Your reply was sent" msgstr "" #: src/components/moderation/ReportDialog/index.tsx:394 diff --git a/src/logger/metrics.ts b/src/logger/metrics.ts index e51905f84..1cb4eb9d3 100644 --- a/src/logger/metrics.ts +++ b/src/logger/metrics.ts @@ -90,6 +90,13 @@ export type MetricEvents = { selectedInterests: string[] selectedInterestsLength: number } + 'onboarding:suggestedAccounts:tabPressed': { + tab: string + } + 'onboarding:suggestedAccounts:followAllPressed': { + tab: string + numAccounts: number + } 'onboarding:suggestedAccounts:nextPressed': { selectedAccountsLength: number skipped: boolean @@ -118,6 +125,9 @@ export type MetricEvents = { 'onboarding:finished:avatarResult': { avatarResult: 'default' | 'created' | 'uploaded' } + 'onboarding:valueProp:stepOne:nextPressed': {} + 'onboarding:valueProp:stepTwo:nextPressed': {} + 'onboarding:valueProp:skipPressed': {} 'home:feedDisplayed': { feedUrl: string feedType: string @@ -242,6 +252,7 @@ export type MetricEvents = { | 'PostOnboardingFindFollows' | 'ImmersiveVideo' | 'ExploreSuggestedAccounts' + | 'OnboardingSuggestedAccounts' } 'suggestedUser:follow': { logContext: @@ -249,12 +260,17 @@ export type MetricEvents = { | 'InterstitialDiscover' | 'InterstitialProfile' | 'Profile' + | 'Onboarding' location: 'Card' | 'Profile' recId?: number position: number } 'suggestedUser:press': { - logContext: 'Explore' | 'InterstitialDiscover' | 'InterstitialProfile' + logContext: + | 'Explore' + | 'InterstitialDiscover' + | 'InterstitialProfile' + | 'Onboarding' recId?: number position: number } @@ -280,6 +296,7 @@ export type MetricEvents = { | 'PostOnboardingFindFollows' | 'ImmersiveVideo' | 'ExploreSuggestedAccounts' + | 'OnboardingSuggestedAccounts' } 'chat:create': { logContext: 'ProfileHeader' | 'NewChatDialog' | 'SendViaChatDialog' diff --git a/src/screens/Login/ForgotPasswordForm.tsx b/src/screens/Login/ForgotPasswordForm.tsx index e8582f46f..d3b5a4f10 100644 --- a/src/screens/Login/ForgotPasswordForm.tsx +++ b/src/screens/Login/ForgotPasswordForm.tsx @@ -1,7 +1,6 @@ import React, {useState} from 'react' import {ActivityIndicator, Keyboard, View} from 'react-native' -import {ComAtprotoServerDescribeServer} from '@atproto/api' -import {BskyAgent} from '@atproto/api' +import {type ComAtprotoServerDescribeServer} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import * as EmailValidator from 'email-validator' @@ -9,6 +8,7 @@ import * as EmailValidator from 'email-validator' import {isNetworkError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' +import {Agent} from '#/state/session/agent' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {FormError} from '#/components/forms/FormError' @@ -55,7 +55,7 @@ export const ForgotPasswordForm = ({ setIsProcessing(true) try { - const agent = new BskyAgent({service: serviceUrl}) + const agent = new Agent(null, {service: serviceUrl}) await agent.com.atproto.server.requestPasswordReset({email}) onEmailSent() } catch (e: any) { diff --git a/src/screens/Login/SetNewPasswordForm.tsx b/src/screens/Login/SetNewPasswordForm.tsx index d2fa0f9c1..be72b558b 100644 --- a/src/screens/Login/SetNewPasswordForm.tsx +++ b/src/screens/Login/SetNewPasswordForm.tsx @@ -1,6 +1,5 @@ import {useState} from 'react' import {ActivityIndicator, View} from 'react-native' -import {BskyAgent} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -9,6 +8,7 @@ import {isNetworkError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors' import {checkAndFormatResetCode} from '#/lib/strings/password' import {logger} from '#/logger' +import {Agent} from '#/state/session/agent' import {atoms as a, useTheme} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {FormError} from '#/components/forms/FormError' @@ -63,7 +63,7 @@ export const SetNewPasswordForm = ({ setIsProcessing(true) try { - const agent = new BskyAgent({service: serviceUrl}) + const agent = new Agent(null, {service: serviceUrl}) await agent.com.atproto.server.resetPassword({ token: formattedCode, password, diff --git a/src/screens/Onboarding/Layout.tsx b/src/screens/Onboarding/Layout.tsx index 16c37358f..6394d9c96 100644 --- a/src/screens/Onboarding/Layout.tsx +++ b/src/screens/Onboarding/Layout.tsx @@ -1,4 +1,4 @@ -import React from 'react' +import React, {useState} from 'react' import {ScrollView, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' @@ -11,20 +11,23 @@ import { atoms as a, flatten, native, - TextStyleProp, + type TextStyleProp, + tokens, useBreakpoints, useTheme, web, } from '#/alf' import {leading} from '#/alf/typography' import {Button, ButtonIcon, ButtonText} from '#/components/Button' -import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' +import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeft} from '#/components/icons/Arrow' +import {HEADER_SLOT_SIZE} from '#/components/Layout' import {createPortalGroup} from '#/components/Portal' import {P, Text} from '#/components/Typography' -const COL_WIDTH = 420 +const ONBOARDING_COL_WIDTH = 420 export const OnboardingControls = createPortalGroup() +export const OnboardingHeaderSlot = createPortalGroup() export function Layout({children}: React.PropsWithChildren<{}>) { const {_} = useLingui() @@ -46,6 +49,8 @@ export function Layout({children}: React.PropsWithChildren<{}>) { const paddingTop = gtMobile ? a.py_5xl : a.py_lg const dialogLabel = _(msg`Set up your account`) + const [footerHeight, setFooterHeight] = useState(0) + return ( <View aria-modal @@ -62,45 +67,67 @@ export function Layout({children}: React.PropsWithChildren<{}>) { t.atoms.bg, ]}> {__DEV__ && ( - <View style={[a.absolute, a.p_xl, a.z_10, {right: 0, top: insets.top}]}> - <Button - variant="ghost" - color="negative" - size="small" - onPress={() => onboardDispatch({type: 'skip'})} - // DEV ONLY - label="Clear onboarding state"> - <ButtonText>Clear</ButtonText> - </Button> - </View> + <Button + variant="ghost" + color="negative" + size="tiny" + onPress={() => onboardDispatch({type: 'skip'})} + // DEV ONLY + label="Clear onboarding state" + style={[ + a.absolute, + a.z_10, + { + left: '50%', + top: insets.top + 2, + transform: [{translateX: '-50%'}], + }, + ]}> + <ButtonText>[DEV] Clear</ButtonText> + </Button> )} - {!gtMobile && state.hasPrev && ( + {!gtMobile && ( <View + pointerEvents="box-none" style={[ web(a.fixed), native(a.absolute), + a.left_0, + a.right_0, a.flex_row, a.w_full, a.justify_center, a.z_20, a.px_xl, - { - top: paddingTop.paddingTop + insets.top - 1, - }, + {top: paddingTop.paddingTop + insets.top - 1}, ]}> - <View style={[a.w_full, a.align_start, {maxWidth: COL_WIDTH}]}> - <Button - key={state.activeStep} // remove focus state on nav - variant="ghost" - color="secondary" - size="small" - shape="round" - label={_(msg`Go back to previous step`)} - style={[a.absolute]} - onPress={() => dispatch({type: 'prev'})}> - <ButtonIcon icon={ChevronLeft} /> - </Button> + <View + pointerEvents="box-none" + style={[ + a.w_full, + a.align_start, + a.flex_row, + a.justify_between, + {maxWidth: ONBOARDING_COL_WIDTH}, + ]}> + {state.hasPrev ? ( + <Button + key={state.activeStep} // remove focus state on nav + color="secondary" + variant="ghost" + shape="square" + size="small" + label={_(msg`Go back to previous step`)} + onPress={() => dispatch({type: 'prev'})} + style={[a.bg_transparent]}> + <ButtonIcon icon={ArrowLeft} size="lg" /> + </Button> + ) : ( + <View /> + )} + + <OnboardingHeaderSlot.Outlet /> </View> </View> )} @@ -109,22 +136,24 @@ export function Layout({children}: React.PropsWithChildren<{}>) { ref={scrollview} style={[a.h_full, a.w_full, {paddingTop: insets.top}]} contentContainerStyle={{borderWidth: 0}} - // @ts-ignore web only --prf + scrollIndicatorInsets={{bottom: footerHeight - insets.bottom}} + // @ts-expect-error web only --prf dataSet={{'stable-gutters': 1}}> <View style={[a.flex_row, a.justify_center, gtMobile ? a.px_5xl : a.px_xl]}> - <View style={[a.flex_1, {maxWidth: COL_WIDTH}]}> + <View style={[a.flex_1, {maxWidth: ONBOARDING_COL_WIDTH}]}> <View style={[a.w_full, a.align_center, paddingTop]}> <View style={[ a.flex_row, a.gap_sm, a.w_full, - {paddingTop: 17, maxWidth: '60%'}, + a.align_center, + {height: HEADER_SLOT_SIZE, maxWidth: '60%'}, ]}> {Array(state.totalSteps) .fill(0) - .map((_, i) => ( + .map((__, i) => ( <View key={i} style={[ @@ -144,19 +173,16 @@ export function Layout({children}: React.PropsWithChildren<{}>) { </View> </View> - <View - style={[a.w_full, a.mb_5xl, {paddingTop: gtMobile ? 20 : 40}]}> - {children} - </View> + <View style={[a.w_full, a.mb_5xl, a.pt_md]}>{children}</View> - <View style={{height: 400}} /> + <View style={{height: 100 + footerHeight}} /> </View> </View> </ScrollView> <View + onLayout={evt => setFooterHeight(evt.nativeEvent.layout.height)} style={[ - // @ts-ignore web only -prf isWeb ? a.fixed : a.absolute, {bottom: 0, left: 0, right: 0}, t.atoms.bg, @@ -167,30 +193,30 @@ export function Layout({children}: React.PropsWithChildren<{}>) { isWeb ? a.py_2xl : { - paddingTop: a.pt_lg.paddingTop, - paddingBottom: insets.bottom + 10, + paddingTop: tokens.space.md, + paddingBottom: insets.bottom + tokens.space.md, }, ]}> <View style={[ a.w_full, - {maxWidth: COL_WIDTH}, - gtMobile && [a.flex_row, a.justify_between], + {maxWidth: ONBOARDING_COL_WIDTH}, + gtMobile && [a.flex_row, a.justify_between, a.align_center], ]}> {gtMobile && (state.hasPrev ? ( <Button key={state.activeStep} // remove focus state on nav - variant="solid" color="secondary" - size="large" - shape="round" + variant="ghost" + shape="square" + size="small" label={_(msg`Go back to previous step`)} onPress={() => dispatch({type: 'prev'})}> - <ButtonIcon icon={ChevronLeft} /> + <ButtonIcon icon={ArrowLeft} size="lg" /> </Button> ) : ( - <View style={{height: 54}} /> + <View style={{height: 33}} /> ))} <OnboardingControls.Outlet /> </View> diff --git a/src/screens/Onboarding/StepFinished.tsx b/src/screens/Onboarding/StepFinished.tsx index 54d282a5e..f8040f3a5 100644 --- a/src/screens/Onboarding/StepFinished.tsx +++ b/src/screens/Onboarding/StepFinished.tsx @@ -1,5 +1,12 @@ -import React from 'react' +import {useCallback, useContext, useState} from 'react' import {View} from 'react-native' +import Animated, { + Easing, + LayoutAnimationConfig, + SlideInRight, + SlideOutLeft, +} from 'react-native-reanimated' +import {Image} from 'expo-image' import { type AppBskyActorDefs, type AppBskyActorProfile, @@ -22,6 +29,7 @@ import { import {useRequestNotificationsPermission} from '#/lib/notifications/notifications' import {logEvent, useGate} from '#/lib/statsig/statsig' import {logger} from '#/logger' +import {isNative} from '#/platform/detection' import {useSetHasCheckedForStarterPack} from '#/state/preferences/used-starter-packs' import {getAllListMembers} from '#/state/queries/list-members' import {preferencesQueryKey} from '#/state/queries/preferences' @@ -36,13 +44,22 @@ import { import { DescriptionText, OnboardingControls, + OnboardingHeaderSlot, TitleText, } from '#/screens/Onboarding/Layout' -import {Context} from '#/screens/Onboarding/state' +import {Context, type OnboardingState} from '#/screens/Onboarding/state' import {bulkWriteFollows} from '#/screens/Onboarding/util' -import {atoms as a, useTheme} from '#/alf' +import { + atoms as a, + native, + platform, + tokens, + useBreakpoints, + useTheme, +} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {IconCircle} from '#/components/IconCircle' +import {ArrowRight_Stroke2_Corner0_Rounded as ArrowRight} from '#/components/icons/Arrow' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth' import {News2_Stroke2_Corner0_Rounded as News} from '#/components/icons/News2' @@ -53,10 +70,9 @@ import * as bsky from '#/types/bsky' export function StepFinished() { const {_} = useLingui() - const t = useTheme() - const {state, dispatch} = React.useContext(Context) + const {state, dispatch} = useContext(Context) const onboardDispatch = useOnboardingDispatch() - const [saving, setSaving] = React.useState(false) + const [saving, setSaving] = useState(false) const queryClient = useQueryClient() const agent = useAgent() const requestNotificationsPermission = useRequestNotificationsPermission() @@ -66,7 +82,7 @@ export function StepFinished() { const {startProgressGuide} = useProgressGuideControls() const gate = useGate() - const finishOnboarding = React.useCallback(async () => { + const finishOnboarding = useCallback(async () => { setSaving(true) let starterPack: AppBskyGraphDefs.StarterPackView | undefined @@ -245,6 +261,267 @@ export function StepFinished() { gate, ]) + return state.experiments?.onboarding_value_prop ? ( + <ValueProposition + finishOnboarding={finishOnboarding} + saving={saving} + state={state} + /> + ) : ( + <LegacyFinalStep + finishOnboarding={finishOnboarding} + saving={saving} + state={state} + /> + ) +} + +const PROP_1 = { + light: platform({ + native: require('../../../assets/images/onboarding/value_prop_1_light.webp'), + web: require('../../../assets/images/onboarding/value_prop_1_light_borderless.webp'), + }), + dim: platform({ + native: require('../../../assets/images/onboarding/value_prop_1_dim.webp'), + web: require('../../../assets/images/onboarding/value_prop_1_dim_borderless.webp'), + }), + dark: platform({ + native: require('../../../assets/images/onboarding/value_prop_1_dark.webp'), + web: require('../../../assets/images/onboarding/value_prop_1_dark_borderless.webp'), + }), +} as const + +const PROP_2 = { + light: require('../../../assets/images/onboarding/value_prop_2_light.webp'), + dim: require('../../../assets/images/onboarding/value_prop_2_dim.webp'), + dark: require('../../../assets/images/onboarding/value_prop_2_dark.webp'), +} as const + +const PROP_3 = { + light: require('../../../assets/images/onboarding/value_prop_3_light.webp'), + dim: require('../../../assets/images/onboarding/value_prop_3_dim.webp'), + dark: require('../../../assets/images/onboarding/value_prop_3_dark.webp'), +} as const + +function ValueProposition({ + finishOnboarding, + saving, + state, +}: { + finishOnboarding: () => void + saving: boolean + state: OnboardingState +}) { + const [subStep, setSubStep] = useState<0 | 1 | 2>(0) + const t = useTheme() + const {_} = useLingui() + const {gtMobile} = useBreakpoints() + + const image = [PROP_1[t.name], PROP_2[t.name], PROP_3[t.name]][subStep] + + const onPress = () => { + if (subStep === 2) { + finishOnboarding() // has its own metrics + } else if (subStep === 1) { + setSubStep(2) + logger.metric('onboarding:valueProp:stepTwo:nextPressed', {}) + } else if (subStep === 0) { + setSubStep(1) + logger.metric('onboarding:valueProp:stepOne:nextPressed', {}) + } + } + + const {title, description, alt} = [ + { + title: _(msg`Free your feed`), + description: _( + msg`No more doomscrolling junk-filled algorithms. Find feeds that work for you, not against you.`, + ), + alt: _( + msg`A collection of popular feeds you can find on Bluesky, including News, Booksky, Game Dev, Blacksky, and Fountain Pens`, + ), + }, + { + title: _(msg`Find your people`), + description: _( + msg`Ditch the trolls and clickbait. Find real people and conversations that matter to you.`, + ), + alt: _( + msg`Your profile picture surrounded by concentric circles of other users' profile pictures`, + ), + }, + { + title: _(msg`Forget the noise`), + description: _( + msg`No ads, no invasive tracking, no engagement traps. Bluesky respects your time and attention.`, + ), + alt: _( + msg`An illustration of several Bluesky posts alongside repost, like, and comment icons`, + ), + }, + ][subStep] + + return ( + <> + {!gtMobile && ( + <OnboardingHeaderSlot.Portal> + <Button + disabled={saving} + variant="ghost" + color="secondary" + size="small" + label={_(msg`Skip introduction and start using your account`)} + onPress={() => { + logger.metric('onboarding:valueProp:skipPressed', {}) + finishOnboarding() + }} + style={[a.bg_transparent]}> + <ButtonText> + <Trans>Skip</Trans> + </ButtonText> + </Button> + </OnboardingHeaderSlot.Portal> + )} + + <LayoutAnimationConfig skipEntering skipExiting> + <Animated.View + key={subStep} + entering={native( + SlideInRight.easing(Easing.out(Easing.exp)).duration(500), + )} + exiting={native( + SlideOutLeft.easing(Easing.out(Easing.exp)).duration(500), + )}> + <View + style={[ + a.relative, + a.align_center, + a.justify_center, + isNative && {marginHorizontal: tokens.space.xl * -1}, + a.pointer_events_none, + ]}> + <Image + source={image} + style={[a.w_full, {aspectRatio: 1}]} + alt={alt} + accessibilityIgnoresInvertColors={false} // I guess we do need it to blend into the background + /> + {subStep === 1 && ( + <Image + source={state.profileStepResults.imageUri} + style={[ + a.z_10, + a.absolute, + a.rounded_full, + { + width: `${(80 / 393) * 100}%`, + height: `${(80 / 393) * 100}%`, + }, + ]} + accessibilityIgnoresInvertColors + alt={_(msg`Your profile picture`)} + /> + )} + </View> + + <View style={[a.mt_4xl, a.gap_2xl, a.align_center]}> + <View style={[a.flex_row, a.gap_sm]}> + <Dot active={subStep === 0} /> + <Dot active={subStep === 1} /> + <Dot active={subStep === 2} /> + </View> + + <View style={[a.gap_sm]}> + <Text style={[a.font_heavy, a.text_3xl, a.text_center]}> + {title} + </Text> + <Text + style={[ + t.atoms.text_contrast_medium, + a.text_md, + a.leading_snug, + a.text_center, + ]}> + {description} + </Text> + </View> + </View> + </Animated.View> + </LayoutAnimationConfig> + + <OnboardingControls.Portal> + <View style={gtMobile && [a.gap_md, a.flex_row]}> + {gtMobile && ( + <Button + disabled={saving} + color="secondary" + size="large" + label={_(msg`Skip introduction and start using your account`)} + onPress={() => finishOnboarding()}> + <ButtonText> + <Trans>Skip</Trans> + </ButtonText> + </Button> + )} + <Button + disabled={saving} + key={state.activeStep} // remove focus state on nav + color="primary" + size="large" + label={ + subStep === 2 + ? _(msg`Complete onboarding and start using your account`) + : _(msg`Next`) + } + onPress={onPress}> + <ButtonText> + {saving ? ( + <Trans>Finalizing</Trans> + ) : subStep === 2 ? ( + <Trans>Let's go!</Trans> + ) : ( + <Trans>Next</Trans> + )} + </ButtonText> + {subStep === 2 && ( + <ButtonIcon icon={saving ? Loader : ArrowRight} /> + )} + </Button> + </View> + </OnboardingControls.Portal> + </> + ) +} + +function Dot({active}: {active: boolean}) { + const t = useTheme() + const {_} = useLingui() + + return ( + <View + style={[ + a.rounded_full, + {width: 8, height: 8}, + active + ? {backgroundColor: t.palette.primary_500} + : t.atoms.bg_contrast_50, + ]} + /> + ) +} + +function LegacyFinalStep({ + finishOnboarding, + saving, + state, +}: { + finishOnboarding: () => void + saving: boolean + state: OnboardingState +}) { + const t = useTheme() + const {_} = useLingui() + return ( <View style={[a.align_start]}> <IconCircle icon={Check} style={[a.mb_2xl]} /> @@ -305,7 +582,6 @@ export function StepFinished() { <Button disabled={saving} key={state.activeStep} // remove focus state on nav - variant="solid" color="primary" size="large" label={_(msg`Complete onboarding and start using your account`)} diff --git a/src/screens/Onboarding/StepInterests/index.tsx b/src/screens/Onboarding/StepInterests/index.tsx index 2a121cac6..3bde22136 100644 --- a/src/screens/Onboarding/StepInterests/index.tsx +++ b/src/screens/Onboarding/StepInterests/index.tsx @@ -160,7 +160,16 @@ export function StepInterests() { <View style={[a.w_full, a.pt_2xl]}> {isLoading ? ( - <Loader size="xl" /> + <View + style={[ + a.flex_1, + a.mt_md, + a.align_center, + a.justify_center, + {minHeight: 400}, + ]}> + <Loader size="xl" /> + </View> ) : isError || !data ? ( <View style={[ diff --git a/src/screens/Onboarding/StepProfile/index.tsx b/src/screens/Onboarding/StepProfile/index.tsx index 30da5cbb5..fd5f9b6fb 100644 --- a/src/screens/Onboarding/StepProfile/index.tsx +++ b/src/screens/Onboarding/StepProfile/index.tsx @@ -266,7 +266,7 @@ export function StepProfile() { </View> <OnboardingControls.Portal> - <View style={[a.gap_md, gtMobile && {flexDirection: 'row-reverse'}]}> + <View style={[a.gap_md, gtMobile && a.flex_row_reverse]}> <Button variant="solid" color="primary" diff --git a/src/screens/Onboarding/StepSuggestedAccounts/index.tsx b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx new file mode 100644 index 000000000..5a9d3464c --- /dev/null +++ b/src/screens/Onboarding/StepSuggestedAccounts/index.tsx @@ -0,0 +1,356 @@ +import {useCallback, useContext, useMemo, useState} from 'react' +import {View} from 'react-native' +import {type ModerationOpts} from '@atproto/api' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' +import {useMutation, useQueryClient} from '@tanstack/react-query' +import * as bcp47Match from 'bcp-47-match' + +import {wait} from '#/lib/async/wait' +import {isBlockedOrBlocking, isMuted} from '#/lib/moderation/blocked-and-muted' +import {logger} from '#/logger' +import {isWeb} from '#/platform/detection' +import {updateProfileShadow} from '#/state/cache/profile-shadow' +import {useLanguagePrefs} from '#/state/preferences' +import {useModerationOpts} from '#/state/preferences/moderation-opts' +import {useAgent, useSession} from '#/state/session' +import {useOnboardingDispatch} from '#/state/shell' +import {OnboardingControls} from '#/screens/Onboarding/Layout' +import { + Context, + popularInterests, + useInterestsDisplayNames, +} from '#/screens/Onboarding/state' +import {useSuggestedUsers} from '#/screens/Search/util/useSuggestedUsers' +import {atoms as a, tokens, useBreakpoints, useTheme} from '#/alf' +import {Admonition} from '#/components/Admonition' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as ArrowRotateCounterClockwiseIcon} from '#/components/icons/ArrowRotateCounterClockwise' +import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' +import {boostInterests, InterestTabs} from '#/components/InterestTabs' +import {Loader} from '#/components/Loader' +import * as ProfileCard from '#/components/ProfileCard' +import * as toast from '#/components/Toast' +import {Text} from '#/components/Typography' +import type * as bsky from '#/types/bsky' +import {bulkWriteFollows} from '../util' + +export function StepSuggestedAccounts() { + const {_} = useLingui() + const t = useTheme() + const {gtMobile} = useBreakpoints() + const moderationOpts = useModerationOpts() + const agent = useAgent() + const {currentAccount} = useSession() + const queryClient = useQueryClient() + + const {state, dispatch} = useContext(Context) + const onboardDispatch = useOnboardingDispatch() + + const [selectedInterest, setSelectedInterest] = useState<string | null>(null) + // keeping track of who was followed via the follow all button + // so we can enable/disable the button without having to dig through the shadow cache + const [followedUsers, setFollowedUsers] = useState<string[]>([]) + + /* + * Special language handling copied wholesale from the Explore screen + */ + const {contentLanguages} = useLanguagePrefs() + const useFullExperience = useMemo(() => { + if (contentLanguages.length === 0) return true + return bcp47Match.basicFilter('en', contentLanguages).length > 0 + }, [contentLanguages]) + const interestsDisplayNames = useInterestsDisplayNames() + const interests = Object.keys(interestsDisplayNames) + .sort(boostInterests(popularInterests)) + .sort(boostInterests(state.interestsStepResults.selectedInterests)) + const { + data: suggestedUsers, + isLoading, + error, + isRefetching, + refetch, + } = useSuggestedUsers({ + category: selectedInterest || (useFullExperience ? null : interests[0]), + search: !useFullExperience, + overrideInterests: state.interestsStepResults.selectedInterests, + }) + + const isError = !!error + + const skipOnboarding = useCallback(() => { + onboardDispatch({type: 'finish'}) + dispatch({type: 'finish'}) + }, [onboardDispatch, dispatch]) + + const followableDids = + suggestedUsers?.actors + .filter( + user => + user.did !== currentAccount?.did && + !isBlockedOrBlocking(user) && + !isMuted(user) && + !user.viewer?.following && + !followedUsers.includes(user.did), + ) + .map(user => user.did) ?? [] + + const {mutate: followAll, isPending: isFollowingAll} = useMutation({ + onMutate: () => { + logger.metric('onboarding:suggestedAccounts:followAllPressed', { + tab: selectedInterest ?? 'all', + numAccounts: followableDids.length, + }) + }, + mutationFn: async () => { + for (const did of followableDids) { + updateProfileShadow(queryClient, did, { + followingUri: 'pending', + }) + } + const uris = await wait(1e3, bulkWriteFollows(agent, followableDids)) + for (const did of followableDids) { + const uri = uris.get(did) + updateProfileShadow(queryClient, did, { + followingUri: uri, + }) + } + return followableDids + }, + onSuccess: newlyFollowed => { + toast.show(_(msg`Followed all accounts!`), {type: 'success'}) + setFollowedUsers(followed => [...followed, ...newlyFollowed]) + }, + onError: () => { + toast.show( + _(msg`Failed to follow all suggested accounts, please try again`), + {type: 'error'}, + ) + }, + }) + + const canFollowAll = followableDids.length > 0 && !isFollowingAll + + return ( + <View style={[a.align_start]} testID="onboardingInterests"> + <Text style={[a.font_heavy, a.text_3xl]}> + <Trans comment="Accounts suggested to the user for them to follow"> + Suggested for you + </Trans> + </Text> + + <View + style={[ + a.overflow_hidden, + a.mt_lg, + isWeb ? a.max_w_full : {marginHorizontal: tokens.space.xl * -1}, + a.flex_1, + a.justify_start, + ]}> + <TabBar + selectedInterest={selectedInterest} + onSelectInterest={setSelectedInterest} + defaultTabLabel={_( + msg({ + message: 'All', + comment: 'the default tab in the interests tab bar', + }), + )} + selectedInterests={state.interestsStepResults.selectedInterests} + /> + + {isLoading || !moderationOpts ? ( + <View + style={[ + a.flex_1, + a.mt_md, + a.align_center, + a.justify_center, + {minHeight: 400}, + ]}> + <Loader size="xl" /> + </View> + ) : isError ? ( + <View style={[a.flex_1, a.px_xl, a.pt_5xl]}> + <Admonition type="error"> + <Trans> + An error occurred while fetching suggested accounts. + </Trans> + </Admonition> + </View> + ) : ( + <View + style={[ + a.flex_1, + a.mt_md, + a.border_y, + t.atoms.border_contrast_low, + isWeb && [a.border_x, a.rounded_sm, a.overflow_hidden], + ]}> + {suggestedUsers?.actors.map((user, index) => ( + <SuggestedProfileCard + key={user.did} + profile={user} + moderationOpts={moderationOpts} + position={index} + /> + ))} + </View> + )} + </View> + + <OnboardingControls.Portal> + {isError ? ( + <View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}> + <Button + disabled={isRefetching} + color="secondary" + size="large" + label={_(msg`Retry`)} + onPress={() => refetch()}> + <ButtonText> + <Trans>Retry</Trans> + </ButtonText> + <ButtonIcon icon={ArrowRotateCounterClockwiseIcon} /> + </Button> + <Button + color="secondary" + size="large" + label={_(msg`Skip this flow`)} + onPress={skipOnboarding}> + <ButtonText> + <Trans>Skip</Trans> + </ButtonText> + </Button> + </View> + ) : ( + <View style={[a.gap_md, gtMobile ? a.flex_row : a.flex_col]}> + <Button + disabled={!canFollowAll} + color="secondary" + size="large" + label={_(msg`Follow all accounts`)} + onPress={() => followAll()}> + <ButtonText> + <Trans>Follow all</Trans> + </ButtonText> + <ButtonIcon icon={isFollowingAll ? Loader : PlusIcon} /> + </Button> + <Button + disabled={isFollowingAll} + color="primary" + size="large" + label={_(msg`Continue to next step`)} + onPress={() => dispatch({type: 'next'})}> + <ButtonText> + <Trans>Continue</Trans> + </ButtonText> + </Button> + </View> + )} + </OnboardingControls.Portal> + </View> + ) +} + +function TabBar({ + selectedInterest, + onSelectInterest, + selectedInterests, + hideDefaultTab, + defaultTabLabel, +}: { + selectedInterest: string | null + onSelectInterest: (interest: string | null) => void + selectedInterests: string[] + hideDefaultTab?: boolean + defaultTabLabel?: string +}) { + const {_} = useLingui() + const interestsDisplayNames = useInterestsDisplayNames() + const interests = Object.keys(interestsDisplayNames) + .sort(boostInterests(popularInterests)) + .sort(boostInterests(selectedInterests)) + + return ( + <InterestTabs + interests={hideDefaultTab ? interests : ['all', ...interests]} + selectedInterest={ + selectedInterest || (hideDefaultTab ? interests[0] : 'all') + } + onSelectTab={tab => { + logger.metric( + 'onboarding:suggestedAccounts:tabPressed', + {tab: tab}, + {statsig: true}, + ) + onSelectInterest(tab === 'all' ? null : tab) + }} + interestsDisplayNames={ + hideDefaultTab + ? interestsDisplayNames + : { + all: defaultTabLabel || _(msg`For You`), + ...interestsDisplayNames, + } + } + gutterWidth={isWeb ? 0 : tokens.space.xl} + /> + ) +} + +function SuggestedProfileCard({ + profile, + moderationOpts, + position, +}: { + profile: bsky.profile.AnyProfileView + moderationOpts: ModerationOpts + position: number +}) { + const t = useTheme() + return ( + <View + style={[ + a.flex_1, + a.w_full, + a.py_lg, + a.px_xl, + position !== 0 && a.border_t, + t.atoms.border_contrast_low, + ]}> + <ProfileCard.Outer> + <ProfileCard.Header> + <ProfileCard.Avatar + profile={profile} + moderationOpts={moderationOpts} + disabledPreview + /> + <ProfileCard.NameAndHandle + profile={profile} + moderationOpts={moderationOpts} + /> + <ProfileCard.FollowButton + profile={profile} + moderationOpts={moderationOpts} + withIcon={false} + logContext="OnboardingSuggestedAccounts" + onFollow={() => { + logger.metric( + 'suggestedUser:follow', + { + logContext: 'Onboarding', + location: 'Card', + recId: undefined, + position, + }, + {statsig: true}, + ) + }} + /> + </ProfileCard.Header> + <ProfileCard.Description profile={profile} numberOfLines={3} /> + </ProfileCard.Outer> + </View> + ) +} diff --git a/src/screens/Onboarding/index.tsx b/src/screens/Onboarding/index.tsx index a5c423ca1..2291e5e4f 100644 --- a/src/screens/Onboarding/index.tsx +++ b/src/screens/Onboarding/index.tsx @@ -1,21 +1,35 @@ -import React from 'react' +import {useMemo, useReducer} from 'react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {Layout, OnboardingControls} from '#/screens/Onboarding/Layout' +import {useGate} from '#/lib/statsig/statsig' +import { + Layout, + OnboardingControls, + OnboardingHeaderSlot, +} from '#/screens/Onboarding/Layout' import {Context, initialState, reducer} from '#/screens/Onboarding/state' import {StepFinished} from '#/screens/Onboarding/StepFinished' import {StepInterests} from '#/screens/Onboarding/StepInterests' import {StepProfile} from '#/screens/Onboarding/StepProfile' import {Portal} from '#/components/Portal' +import {StepSuggestedAccounts} from './StepSuggestedAccounts' export function Onboarding() { const {_} = useLingui() - const [state, dispatch] = React.useReducer(reducer, { + const gate = useGate() + const showValueProp = gate('onboarding_value_prop') + const showSuggestedAccounts = gate('onboarding_suggested_accounts') + const [state, dispatch] = useReducer(reducer, { ...initialState, + totalSteps: showSuggestedAccounts ? 4 : 3, + experiments: { + onboarding_suggested_accounts: showSuggestedAccounts, + onboarding_value_prop: showValueProp, + }, }) - const interestsDisplayNames = React.useMemo(() => { + const interestsDisplayNames = useMemo(() => { return { news: _(msg`News`), journalism: _(msg`Journalism`), @@ -45,17 +59,22 @@ export function Onboarding() { return ( <Portal> <OnboardingControls.Provider> - <Context.Provider - value={React.useMemo( - () => ({state, dispatch, interestsDisplayNames}), - [state, dispatch, interestsDisplayNames], - )}> - <Layout> - {state.activeStep === 'profile' && <StepProfile />} - {state.activeStep === 'interests' && <StepInterests />} - {state.activeStep === 'finished' && <StepFinished />} - </Layout> - </Context.Provider> + <OnboardingHeaderSlot.Provider> + <Context.Provider + value={useMemo( + () => ({state, dispatch, interestsDisplayNames}), + [state, dispatch, interestsDisplayNames], + )}> + <Layout> + {state.activeStep === 'profile' && <StepProfile />} + {state.activeStep === 'interests' && <StepInterests />} + {state.activeStep === 'suggested-accounts' && ( + <StepSuggestedAccounts /> + )} + {state.activeStep === 'finished' && <StepFinished />} + </Layout> + </Context.Provider> + </OnboardingHeaderSlot.Provider> </OnboardingControls.Provider> </Portal> ) diff --git a/src/screens/Onboarding/state.ts b/src/screens/Onboarding/state.ts index cbb466245..31f6eb039 100644 --- a/src/screens/Onboarding/state.ts +++ b/src/screens/Onboarding/state.ts @@ -11,7 +11,7 @@ import { export type OnboardingState = { hasPrev: boolean totalSteps: number - activeStep: 'profile' | 'interests' | 'finished' + activeStep: 'profile' | 'interests' | 'suggested-accounts' | 'finished' activeStepIndex: number interestsStepResults: { @@ -34,6 +34,11 @@ export type OnboardingState = { backgroundColor: AvatarColor } } + + experiments?: { + onboarding_suggested_accounts?: boolean + onboarding_value_prop?: boolean + } } export type OnboardingAction = @@ -160,22 +165,49 @@ export function reducer( switch (a.type) { case 'next': { - if (s.activeStep === 'profile') { - next.activeStep = 'interests' - next.activeStepIndex = 2 - } else if (s.activeStep === 'interests') { - next.activeStep = 'finished' - next.activeStepIndex = 3 + if (s.experiments?.onboarding_suggested_accounts) { + if (s.activeStep === 'profile') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } else if (s.activeStep === 'interests') { + next.activeStep = 'suggested-accounts' + next.activeStepIndex = 3 + } + if (s.activeStep === 'suggested-accounts') { + next.activeStep = 'finished' + next.activeStepIndex = 4 + } + } else { + if (s.activeStep === 'profile') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } else if (s.activeStep === 'interests') { + next.activeStep = 'finished' + next.activeStepIndex = 3 + } } break } case 'prev': { - if (s.activeStep === 'interests') { - next.activeStep = 'profile' - next.activeStepIndex = 1 - } else if (s.activeStep === 'finished') { - next.activeStep = 'interests' - next.activeStepIndex = 2 + if (s.experiments?.onboarding_suggested_accounts) { + if (s.activeStep === 'interests') { + next.activeStep = 'profile' + next.activeStepIndex = 1 + } else if (s.activeStep === 'suggested-accounts') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } else if (s.activeStep === 'finished') { + next.activeStep = 'suggested-accounts' + next.activeStepIndex = 3 + } + } else { + if (s.activeStep === 'interests') { + next.activeStep = 'profile' + next.activeStepIndex = 1 + } else if (s.activeStep === 'finished') { + next.activeStep = 'interests' + next.activeStepIndex = 2 + } } break } diff --git a/src/screens/Onboarding/util.ts b/src/screens/Onboarding/util.ts index d14c9562e..b08f0408e 100644 --- a/src/screens/Onboarding/util.ts +++ b/src/screens/Onboarding/util.ts @@ -1,9 +1,9 @@ import { - $Typed, - AppBskyGraphFollow, - AppBskyGraphGetFollows, - BskyAgent, - ComAtprotoRepoApplyWrites, + type $Typed, + type AppBskyGraphFollow, + type AppBskyGraphGetFollows, + type BskyAgent, + type ComAtprotoRepoApplyWrites, } from '@atproto/api' import {TID} from '@atproto/common-web' import chunk from 'lodash.chunk' @@ -42,10 +42,10 @@ export async function bulkWriteFollows(agent: BskyAgent, dids: string[]) { } await whenFollowsIndexed(agent, session.did, res => !!res.data.follows.length) - const followUris = new Map() + const followUris = new Map<string, string>() for (const r of followWrites) { followUris.set( - r.value.subject, + r.value.subject as string, `at://${session.did}/app.bsky.graph.follow/${r.rkey}`, ) } diff --git a/src/screens/Profile/Header/SuggestedFollows.tsx b/src/screens/Profile/Header/SuggestedFollows.tsx index d005d888e..58a507e08 100644 --- a/src/screens/Profile/Header/SuggestedFollows.tsx +++ b/src/screens/Profile/Header/SuggestedFollows.tsx @@ -28,7 +28,6 @@ export function AnimatedProfileHeaderSuggestedFollows({ actorDid: string }) { const gate = useGate() - if (!gate('post_follow_profile_suggested_accounts')) return null /* NOTE (caidanw): * Android does not work well with this feature yet. @@ -37,6 +36,8 @@ export function AnimatedProfileHeaderSuggestedFollows({ **/ if (isAndroid) return null + if (!gate('post_follow_profile_suggested_accounts')) return null + return ( <AccordionAnimation isExpanded={isExpanded}> <ProfileHeaderSuggestedFollows actorDid={actorDid} /> diff --git a/src/screens/Search/Explore.tsx b/src/screens/Search/Explore.tsx index baf69cd7f..cefe68b01 100644 --- a/src/screens/Search/Explore.tsx +++ b/src/screens/Search/Explore.tsx @@ -66,9 +66,9 @@ import { import {ListSparkle_Stroke2_Corner0_Rounded as ListSparkle} from '#/components/icons/ListSparkle' import {StarterPack} from '#/components/icons/StarterPack' import {UserCircle_Stroke2_Corner0_Rounded as Person} from '#/components/icons/UserCircle' +import {boostInterests} from '#/components/InterestTabs' import {Loader} from '#/components/Loader' import * as ProfileCard from '#/components/ProfileCard' -import {boostInterests} from '#/components/ProgressGuide/FollowDialog' import {SubtleHover} from '#/components/SubtleHover' import {Text} from '#/components/Typography' import * as ModuleHeader from './components/ModuleHeader' diff --git a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx index fd37544f4..71bfd6547 100644 --- a/src/screens/Search/modules/ExploreSuggestedAccounts.tsx +++ b/src/screens/Search/modules/ExploreSuggestedAccounts.tsx @@ -14,11 +14,9 @@ import { } from '#/screens/Onboarding/state' import {useTheme} from '#/alf' import {atoms as a} from '#/alf' -import {Button} from '#/components/Button' +import {boostInterests, InterestTabs} from '#/components/InterestTabs' import * as ProfileCard from '#/components/ProfileCard' -import {boostInterests, Tabs} from '#/components/ProgressGuide/FollowDialog' import {SubtleHover} from '#/components/SubtleHover' -import {Text} from '#/components/Typography' import type * as bsky from '#/types/bsky' export function useLoadEnoughProfiles({ @@ -59,10 +57,12 @@ export function SuggestedAccountsTabBar({ selectedInterest, onSelectInterest, hideDefaultTab, + defaultTabLabel, }: { selectedInterest: string | null onSelectInterest: (interest: string | null) => void hideDefaultTab?: boolean + defaultTabLabel?: string }) { const {_} = useLingui() const interestsDisplayNames = useInterestsDisplayNames() @@ -71,9 +71,10 @@ export function SuggestedAccountsTabBar({ const interests = Object.keys(interestsDisplayNames) .sort(boostInterests(popularInterests)) .sort(boostInterests(personalizedInterests)) + return ( <BlockDrawerGesture> - <Tabs + <InterestTabs interests={hideDefaultTab ? interests : ['all', ...interests]} selectedInterest={ selectedInterest || (hideDefaultTab ? interests[0] : 'all') @@ -86,82 +87,19 @@ export function SuggestedAccountsTabBar({ ) onSelectInterest(tab === 'all' ? null : tab) }} - hasSearchText={false} interestsDisplayNames={ hideDefaultTab ? interestsDisplayNames : { - all: _(msg`For You`), + all: defaultTabLabel || _(msg`For You`), ...interestsDisplayNames, } } - TabComponent={Tab} - contentContainerStyle={[ - { - // visual alignment - paddingLeft: a.px_md.paddingLeft, - }, - ]} /> </BlockDrawerGesture> ) } -let Tab = ({ - onSelectTab, - interest, - active, - index, - interestsDisplayName, - onLayout, -}: { - onSelectTab: (index: number) => void - interest: string - active: boolean - index: number - interestsDisplayName: string - onLayout: (index: number, x: number, width: number) => void -}): React.ReactNode => { - const t = useTheme() - const {_} = useLingui() - const activeText = active ? _(msg` (active)`) : '' - return ( - <View - key={interest} - onLayout={e => - onLayout(index, e.nativeEvent.layout.x, e.nativeEvent.layout.width) - }> - <Button - label={_(msg`Search for "${interestsDisplayName}"${activeText}`)} - onPress={() => onSelectTab(index)}> - {({hovered, pressed, focused}) => ( - <View - style={[ - a.rounded_full, - a.px_lg, - a.py_sm, - a.border, - active || hovered || pressed || focused - ? [t.atoms.bg_contrast_25, t.atoms.border_contrast_medium] - : [t.atoms.bg, t.atoms.border_contrast_low], - ]}> - <Text - style={[ - a.font_medium, - active || hovered || pressed || focused - ? t.atoms.text - : t.atoms.text_contrast_medium, - ]}> - {interestsDisplayName} - </Text> - </View> - )} - </Button> - </View> - ) -} -Tab = memo(Tab) - /** * Profile card for suggested accounts. Note: border is on the bottom edge */ diff --git a/src/screens/Search/util/useSuggestedUsers.ts b/src/screens/Search/util/useSuggestedUsers.ts index aa29dad8c..9ca2c558a 100644 --- a/src/screens/Search/util/useSuggestedUsers.ts +++ b/src/screens/Search/util/useSuggestedUsers.ts @@ -11,6 +11,7 @@ import {useInterestsDisplayNames} from '#/screens/Onboarding/state' export function useSuggestedUsers({ category = null, search = false, + overrideInterests, }: { category?: string | null /** @@ -18,11 +19,17 @@ export function useSuggestedUsers({ * based on the user's "app language setting */ search?: boolean + /** + * In onboarding, interests haven't been saved to prefs yet, so we need to + * pass them down through here + */ + overrideInterests?: string[] }) { const interestsDisplayNames = useInterestsDisplayNames() const curated = useGetSuggestedUsersQuery({ enabled: !search, category, + overrideInterests, }) const searched = useActorSearchPaginated({ enabled: !!search, @@ -43,6 +50,7 @@ export function useSuggestedUsers({ isLoading: searched.isLoading, error: searched.error, isRefetching: searched.isRefetching, + refetch: searched.refetch, } } else { return { @@ -50,6 +58,7 @@ export function useSuggestedUsers({ isLoading: curated.isLoading, error: curated.error, isRefetching: curated.isRefetching, + refetch: curated.refetch, } } }, [curated, searched, search]) diff --git a/src/state/messages/convo/agent.ts b/src/state/messages/convo/agent.ts index 2ad4c592e..168002b1f 100644 --- a/src/state/messages/convo/agent.ts +++ b/src/state/messages/convo/agent.ts @@ -10,6 +10,7 @@ import EventEmitter from 'eventemitter3' import {nanoid} from 'nanoid/non-secure' import {networkRetry} from '#/lib/async/retry' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {isNetworkError} from '#/lib/strings/errors' import {Logger} from '#/logger' import {isNative} from '#/platform/detection' @@ -33,7 +34,6 @@ import { } from '#/state/messages/convo/types' import {type MessagesEventBus} from '#/state/messages/events/agent' import {type MessagesEventBusError} from '#/state/messages/events/types' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' const logger = Logger.create(Logger.Context.ConversationAgent) diff --git a/src/state/messages/events/agent.ts b/src/state/messages/events/agent.ts index fb3047bf6..e54ea1c77 100644 --- a/src/state/messages/events/agent.ts +++ b/src/state/messages/events/agent.ts @@ -3,6 +3,7 @@ import EventEmitter from 'eventemitter3' import {nanoid} from 'nanoid/non-secure' import {networkRetry} from '#/lib/async/retry' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {isNetworkError} from '#/lib/strings/errors' import {Logger} from '#/logger' import { @@ -17,7 +18,6 @@ import { type MessagesEventBusParams, MessagesEventBusStatus, } from '#/state/messages/events/types' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' const logger = Logger.create(Logger.Context.DMsAgent) diff --git a/src/state/modals/index.tsx b/src/state/modals/index.tsx index 3ebbd1732..c6070e97b 100644 --- a/src/state/modals/index.tsx +++ b/src/state/modals/index.tsx @@ -35,10 +35,6 @@ export interface ContentLanguagesSettingsModal { name: 'content-languages-settings' } -export interface PostLanguagesSettingsModal { - name: 'post-languages-settings' -} - /** * @deprecated DO NOT ADD NEW MODALS */ @@ -48,7 +44,6 @@ export type Modal = // Curation | ContentLanguagesSettingsModal - | PostLanguagesSettingsModal // Lists | CreateOrEditListModal diff --git a/src/state/queries/handle-availability.ts b/src/state/queries/handle-availability.ts index 9391f5d09..06fc6eebb 100644 --- a/src/state/queries/handle-availability.ts +++ b/src/state/queries/handle-availability.ts @@ -1,4 +1,4 @@ -import {Agent, ComAtprotoTempCheckHandleAvailability} from '@atproto/api' +import {ComAtprotoTempCheckHandleAvailability} from '@atproto/api' import {useQuery} from '@tanstack/react-query' import { @@ -10,6 +10,7 @@ import {createFullHandle} from '#/lib/strings/handles' import {logger} from '#/logger' import {useDebouncedValue} from '#/components/live/utils' import * as bsky from '#/types/bsky' +import {Agent} from '../session/agent' export const RQKEY_handleAvailability = ( handle: string, @@ -74,7 +75,7 @@ export async function checkHandleAvailability( }, ) { if (serviceDid === BSKY_SERVICE_DID) { - const agent = new Agent({service: BSKY_SERVICE}) + const agent = new Agent(null, {service: BSKY_SERVICE}) // entryway has a special API for handle availability const {data} = await agent.com.atproto.temp.checkHandleAvailability({ handle, @@ -109,7 +110,7 @@ export async function checkHandleAvailability( } } else { // 3rd party PDSes won't have this API so just try and resolve the handle - const agent = new Agent({service: PUBLIC_BSKY_SERVICE}) + const agent = new Agent(null, {service: PUBLIC_BSKY_SERVICE}) try { const res = await agent.resolveHandle({ handle, diff --git a/src/state/queries/messages/accept-conversation.ts b/src/state/queries/messages/accept-conversation.ts index 82acb33c8..0c06055b5 100644 --- a/src/state/queries/messages/accept-conversation.ts +++ b/src/state/queries/messages/accept-conversation.ts @@ -1,9 +1,12 @@ -import {ChatBskyConvoAcceptConvo, ChatBskyConvoListConvos} from '@atproto/api' +import { + type ChatBskyConvoAcceptConvo, + type ChatBskyConvoListConvos, +} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' import {useAgent} from '#/state/session' -import {DM_SERVICE_HEADERS} from './const' import { RQKEY as CONVO_LIST_KEY, RQKEY_ROOT as CONVO_LIST_ROOT_KEY, diff --git a/src/state/queries/messages/const.ts b/src/state/queries/messages/const.ts deleted file mode 100644 index 1c5519a63..000000000 --- a/src/state/queries/messages/const.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {CHAT_PROXY_DID} from '#/env' - -export const DM_SERVICE_HEADERS = { - 'atproto-proxy': `${CHAT_PROXY_DID}#bsky_chat`, -} diff --git a/src/state/queries/messages/conversation.ts b/src/state/queries/messages/conversation.ts index de5a90571..393bf9e52 100644 --- a/src/state/queries/messages/conversation.ts +++ b/src/state/queries/messages/conversation.ts @@ -1,17 +1,17 @@ -import {ChatBskyConvoDefs} from '@atproto/api' +import {type ChatBskyConvoDefs} from '@atproto/api' import { - QueryClient, + type QueryClient, useMutation, useQuery, useQueryClient, } from '@tanstack/react-query' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {STALE} from '#/state/queries' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useOnMarkAsRead} from '#/state/queries/messages/list-conversations' import {useAgent} from '#/state/session' import { - ConvoListQueryData, + type ConvoListQueryData, getConvoFromQueryData, RQKEY_ROOT as LIST_CONVOS_KEY, } from './list-conversations' diff --git a/src/state/queries/messages/get-convo-availability.ts b/src/state/queries/messages/get-convo-availability.ts index f545c3bba..2392edb09 100644 --- a/src/state/queries/messages/get-convo-availability.ts +++ b/src/state/queries/messages/get-convo-availability.ts @@ -1,6 +1,6 @@ import {useQuery} from '@tanstack/react-query' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useAgent} from '#/state/session' import {STALE} from '..' diff --git a/src/state/queries/messages/get-convo-for-members.ts b/src/state/queries/messages/get-convo-for-members.ts index 3f45c2328..58c1ab524 100644 --- a/src/state/queries/messages/get-convo-for-members.ts +++ b/src/state/queries/messages/get-convo-for-members.ts @@ -1,8 +1,8 @@ -import {ChatBskyConvoGetConvoForMembers} from '@atproto/api' +import {type ChatBskyConvoGetConvoForMembers} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent} from '#/state/session' import {precacheConvoQuery} from './conversation' diff --git a/src/state/queries/messages/leave-conversation.ts b/src/state/queries/messages/leave-conversation.ts index b17e515be..986351a07 100644 --- a/src/state/queries/messages/leave-conversation.ts +++ b/src/state/queries/messages/leave-conversation.ts @@ -1,13 +1,16 @@ import {useMemo} from 'react' -import {ChatBskyConvoLeaveConvo, ChatBskyConvoListConvos} from '@atproto/api' +import { + type ChatBskyConvoLeaveConvo, + type ChatBskyConvoListConvos, +} from '@atproto/api' import { useMutation, useMutationState, useQueryClient, } from '@tanstack/react-query' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent} from '#/state/session' import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations' diff --git a/src/state/queries/messages/list-conversations.tsx b/src/state/queries/messages/list-conversations.tsx index 3f8252519..c5457d1cb 100644 --- a/src/state/queries/messages/list-conversations.tsx +++ b/src/state/queries/messages/list-conversations.tsx @@ -13,10 +13,10 @@ import { } from '@tanstack/react-query' import throttle from 'lodash.throttle' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useCurrentConvoId} from '#/state/messages/current-convo-id' import {useMessagesEventBus} from '#/state/messages/events' import {useModerationOpts} from '#/state/preferences/moderation-opts' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent, useSession} from '#/state/session' import {useLeftConvos} from './leave-conversation' diff --git a/src/state/queries/messages/mute-conversation.ts b/src/state/queries/messages/mute-conversation.ts index da9644145..d668e36cb 100644 --- a/src/state/queries/messages/mute-conversation.ts +++ b/src/state/queries/messages/mute-conversation.ts @@ -1,11 +1,15 @@ import { - ChatBskyConvoDefs, - ChatBskyConvoListConvos, - ChatBskyConvoMuteConvo, + type ChatBskyConvoDefs, + type ChatBskyConvoListConvos, + type ChatBskyConvoMuteConvo, } from '@atproto/api' -import {InfiniteData, useMutation, useQueryClient} from '@tanstack/react-query' +import { + type InfiniteData, + useMutation, + useQueryClient, +} from '@tanstack/react-query' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useAgent} from '#/state/session' import {RQKEY as CONVO_KEY} from './conversation' import {RQKEY_ROOT as CONVO_LIST_KEY} from './list-conversations' diff --git a/src/state/queries/messages/update-all-read.ts b/src/state/queries/messages/update-all-read.ts index 72fa65ee6..3d0fd3a45 100644 --- a/src/state/queries/messages/update-all-read.ts +++ b/src/state/queries/messages/update-all-read.ts @@ -1,8 +1,8 @@ -import {ChatBskyConvoListConvos} from '@atproto/api' +import {type ChatBskyConvoListConvos} from '@atproto/api' import {useMutation, useQueryClient} from '@tanstack/react-query' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {logger} from '#/logger' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent} from '#/state/session' import {RQKEY as CONVO_LIST_KEY} from './list-conversations' diff --git a/src/state/queries/service.ts b/src/state/queries/service.ts index 6bfd0b011..e9661db9e 100644 --- a/src/state/queries/service.ts +++ b/src/state/queries/service.ts @@ -1,6 +1,7 @@ -import {BskyAgent} from '@atproto/api' import {useQuery} from '@tanstack/react-query' +import {Agent} from '../session/agent' + const RQKEY_ROOT = 'service' export const RQKEY = (serviceUrl: string) => [RQKEY_ROOT, serviceUrl] @@ -8,7 +9,7 @@ export function useServiceQuery(serviceUrl: string) { return useQuery({ queryKey: RQKEY(serviceUrl), queryFn: async () => { - const agent = new BskyAgent({service: serviceUrl}) + const agent = new Agent(null, {service: serviceUrl}) const res = await agent.com.atproto.server.describeServer() return res.data }, diff --git a/src/state/queries/trending/useGetSuggestedUsersQuery.ts b/src/state/queries/trending/useGetSuggestedUsersQuery.ts index 05cc4d74d..898029398 100644 --- a/src/state/queries/trending/useGetSuggestedUsersQuery.ts +++ b/src/state/queries/trending/useGetSuggestedUsersQuery.ts @@ -17,6 +17,7 @@ export type QueryProps = { category?: string | null limit?: number enabled?: boolean + overrideInterests?: string[] } export const getSuggestedUsersQueryKeyRoot = 'unspecced-suggested-users' @@ -24,6 +25,7 @@ export const createGetSuggestedUsersQueryKey = (props: QueryProps) => [ getSuggestedUsersQueryKeyRoot, props.category, props.limit, + props.overrideInterests?.join(','), ] export function useGetSuggestedUsersQuery(props: QueryProps) { @@ -36,6 +38,7 @@ export function useGetSuggestedUsersQuery(props: QueryProps) { queryKey: createGetSuggestedUsersQueryKey(props), queryFn: async () => { const contentLangs = getContentLanguages().join(',') + const interests = aggregateUserInterests(preferences) const {data} = await agent.app.bsky.unspecced.getSuggestedUsers( { category: props.category ?? undefined, @@ -43,7 +46,11 @@ export function useGetSuggestedUsersQuery(props: QueryProps) { }, { headers: { - ...createBskyTopicsHeader(aggregateUserInterests(preferences)), + ...createBskyTopicsHeader( + props.overrideInterests && props.overrideInterests.length > 0 + ? props.overrideInterests.join(',') + : interests, + ), 'Accept-Language': contentLangs, }, }, diff --git a/src/state/session/agent.ts b/src/state/session/agent.ts index 531e285ab..d063a09a2 100644 --- a/src/state/session/agent.ts +++ b/src/state/session/agent.ts @@ -1,8 +1,19 @@ -import {AtpSessionData, AtpSessionEvent, BskyAgent} from '@atproto/api' +import { + Agent as BaseAgent, + type AtprotoServiceType, + type AtpSessionData, + type AtpSessionEvent, + BskyAgent, + type Did, +} from '@atproto/api' +import {type FetchHandler} from '@atproto/api/dist/agent' +import {type SessionManager} from '@atproto/api/dist/session-manager' import {TID} from '@atproto/common-web' +import {type FetchHandlerOptions} from '@atproto/xrpc' import {networkRetry} from '#/lib/async/retry' import { + BLUESKY_PROXY_HEADER, BSKY_SERVICE, DISCOVER_SAVED_FEED, IS_PROD_SERVICE, @@ -19,12 +30,17 @@ import { configureModerationForAccount, configureModerationForGuest, } from './moderation' -import {SessionAccount} from './types' +import {type SessionAccount} from './types' import {isSessionExpired, isSignupQueued} from './util' +export type ProxyHeaderValue = `${Did}#${AtprotoServiceType}` + export function createPublicAgent() { configureModerationForGuest() // Side effect but only relevant for tests - return new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) + + const agent = new BskyAppAgent({service: PUBLIC_BSKY_SERVICE}) + agent.configureProxy(BLUESKY_PROXY_HEADER) + return agent } export async function createAgentAndResume( @@ -61,6 +77,8 @@ export async function createAgentAndResume( } } + agent.configureProxy(BLUESKY_PROXY_HEADER) + return agent.prepare(gates, moderation, onSessionChange) } @@ -93,6 +111,9 @@ export async function createAgentAndLogin( const account = agentToSessionAccountOrThrow(agent) const gates = tryFetchGates(account.did, 'prefer-fresh-gates') const moderation = configureModerationForAccount(agent, account) + + agent.configureProxy(BLUESKY_PROXY_HEADER) + return agent.prepare(gates, moderation, onSessionChange) } @@ -180,6 +201,8 @@ export async function createAgentAndCreateAccount( logger.error(e, {message: `session: failed snoozeEmailConfirmationPrompt`}) } + agent.configureProxy(BLUESKY_PROXY_HEADER) + return agent.prepare(gates, moderation, onSessionChange) } @@ -234,7 +257,22 @@ export function sessionAccountToSession( } } +export class Agent extends BaseAgent { + constructor( + proxyHeader: ProxyHeaderValue | null, + options: SessionManager | FetchHandler | FetchHandlerOptions, + ) { + super(options) + if (proxyHeader) { + this.configureProxy(proxyHeader) + } + } +} + // Not exported. Use factories above to create it. +// WARN: In the factories above, we _manually set a proxy header_ for the agent after we do whatever it is we are supposed to do. +// Ideally, we wouldn't be doing this. However, since there is so much logic that requires making calls to the PDS right now, it +// feels safer to just let those run as-is and set the header afterward. let realFetch = globalThis.fetch class BskyAppAgent extends BskyAgent { persistSessionHandler: ((event: AtpSessionEvent) => void) | undefined = diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx index 7d4eb8ca7..b533510ec 100644 --- a/src/view/com/composer/Composer.tsx +++ b/src/view/com/composer/Composer.tsx @@ -110,7 +110,7 @@ import {LabelsBtn} from '#/view/com/composer/labels/LabelsBtn' import {Gallery} from '#/view/com/composer/photos/Gallery' import {OpenCameraBtn} from '#/view/com/composer/photos/OpenCameraBtn' import {SelectGifBtn} from '#/view/com/composer/photos/SelectGifBtn' -import {SelectLangBtn} from '#/view/com/composer/select-language/SelectLangBtn' +import {SelectPostLanguagesBtn} from '#/view/com/composer/select-language/SelectPostLanguagesDialog' import {SuggestedLanguage} from '#/view/com/composer/select-language/SuggestedLanguage' // TODO: Prevent naming components that coincide with RN primitives // due to linting false positives @@ -126,7 +126,6 @@ import {Text} from '#/view/com/util/text/Text' import {UserAvatar} from '#/view/com/util/UserAvatar' import {atoms as a, native, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' -import {CircleCheck_Stroke2_Corner0_Rounded as CircleCheckIcon} from '#/components/icons/CircleCheck' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {EmojiArc_Stroke2_Corner0_Rounded as EmojiSmile} from '#/components/icons/Emoji' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' @@ -527,8 +526,8 @@ export const ComposePost = ({ } onClose() Toast.show( - <Toast.Outer type="success"> - <Toast.Icon icon={CircleCheckIcon} /> + <Toast.Outer> + <Toast.Icon /> <Toast.Text> {thread.posts.length > 1 ? _(msg`Your posts were sent`) @@ -543,7 +542,9 @@ export const ComposePost = ({ const {host: name, rkey} = new AtUri(postUri) navigation.navigate('PostThread', {name, rkey}) }}> - View + <Trans context="Action to view the post the user just created"> + View + </Trans> </Toast.Action> )} </Toast.Outer>, @@ -1452,7 +1453,7 @@ function ComposerFooter({ /> </Button> )} - <SelectLangBtn /> + <SelectPostLanguagesBtn /> <CharProgress count={post.shortenedGraphemeLength} style={{width: 65}} diff --git a/src/view/com/composer/select-language/SelectLangBtn.tsx b/src/view/com/composer/select-language/SelectLangBtn.tsx deleted file mode 100644 index f487b1244..000000000 --- a/src/view/com/composer/select-language/SelectLangBtn.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import {useCallback, useMemo} from 'react' -import {Keyboard, StyleSheet} from 'react-native' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants' -import {usePalette} from '#/lib/hooks/usePalette' -import {isNative} from '#/platform/detection' -import {useModalControls} from '#/state/modals' -import { - hasPostLanguage, - toPostLanguages, - useLanguagePrefs, - useLanguagePrefsApi, -} from '#/state/preferences/languages' -import { - DropdownButton, - DropdownItem, - DropdownItemButton, -} from '#/view/com/util/forms/DropdownButton' -import {Text} from '#/view/com/util/text/Text' -import {codeToLanguageName} from '../../../../locale/helpers' - -export function SelectLangBtn() { - const pal = usePalette('default') - const {_} = useLingui() - const {openModal} = useModalControls() - const langPrefs = useLanguagePrefs() - const setLangPrefs = useLanguagePrefsApi() - - const onPressMore = useCallback(async () => { - if (isNative) { - if (Keyboard.isVisible()) { - Keyboard.dismiss() - } - } - openModal({name: 'post-languages-settings'}) - }, [openModal]) - - const postLanguagesPref = toPostLanguages(langPrefs.postLanguage) - const items: DropdownItem[] = useMemo(() => { - let arr: DropdownItemButton[] = [] - - function add(commaSeparatedLangCodes: string) { - const langCodes = commaSeparatedLangCodes.split(',') - const langName = langCodes - .map(code => codeToLanguageName(code, langPrefs.appLanguage)) - .join(' + ') - - /* - * Filter out any duplicates - */ - if (arr.find((item: DropdownItemButton) => item.label === langName)) { - return - } - - arr.push({ - icon: - langCodes.every(code => - hasPostLanguage(langPrefs.postLanguage, code), - ) && langCodes.length === postLanguagesPref.length - ? ['fas', 'circle-dot'] - : ['far', 'circle'], - label: langName, - onPress() { - setLangPrefs.setPostLanguage(commaSeparatedLangCodes) - }, - }) - } - - if (postLanguagesPref.length) { - /* - * Re-join here after sanitization bc postLanguageHistory is an array of - * comma-separated strings too - */ - add(langPrefs.postLanguage) - } - - // comma-separted strings of lang codes that have been used in the past - for (const lang of langPrefs.postLanguageHistory) { - add(lang) - } - - return [ - {heading: true, label: _(msg`Post language`)}, - ...arr.slice(0, 6), - {sep: true}, - { - label: _(msg`Other...`), - onPress: onPressMore, - }, - ] - }, [onPressMore, langPrefs, setLangPrefs, postLanguagesPref, _]) - - return ( - <DropdownButton - type="bare" - testID="selectLangBtn" - items={items} - openUpwards - style={styles.button} - hitSlop={LANG_DROPDOWN_HITSLOP} - accessibilityLabel={_(msg`Language selection`)} - accessibilityHint=""> - {postLanguagesPref.length > 0 ? ( - <Text type="lg-bold" style={[pal.link, styles.label]} numberOfLines={1}> - {postLanguagesPref - .map(lang => codeToLanguageName(lang, langPrefs.appLanguage)) - .join(', ')} - </Text> - ) : ( - <FontAwesomeIcon - icon="language" - style={pal.link as FontAwesomeIconStyle} - size={26} - /> - )} - </DropdownButton> - ) -} - -const styles = StyleSheet.create({ - button: { - marginHorizontal: 15, - }, - label: { - maxWidth: 100, - }, -}) diff --git a/src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx b/src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx new file mode 100644 index 000000000..c8ecc2b89 --- /dev/null +++ b/src/view/com/composer/select-language/SelectPostLanguagesDialog.tsx @@ -0,0 +1,382 @@ +import {useCallback, useMemo, useState} from 'react' +import {Keyboard, useWindowDimensions, View} from 'react-native' +import {useSafeAreaInsets} from 'react-native-safe-area-context' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {LANG_DROPDOWN_HITSLOP} from '#/lib/constants' +import {languageName} from '#/locale/helpers' +import {codeToLanguageName} from '#/locale/helpers' +import {type Language, LANGUAGES, LANGUAGES_MAP_CODE2} from '#/locale/languages' +import {isNative, isWeb} from '#/platform/detection' +import { + toPostLanguages, + useLanguagePrefs, + useLanguagePrefsApi, +} from '#/state/preferences/languages' +import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' +import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' +import {atoms as a, useTheme, web} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' +import * as Dialog from '#/components/Dialog' +import {SearchInput} from '#/components/forms/SearchInput' +import * as Toggle from '#/components/forms/Toggle' +import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' +import {TimesLarge_Stroke2_Corner0_Rounded as XIcon} from '#/components/icons/Times' +import {Text} from '#/components/Typography' + +export function SelectPostLanguagesBtn() { + const {_} = useLingui() + const langPrefs = useLanguagePrefs() + const t = useTheme() + const control = Dialog.useDialogControl() + + const onPressMore = useCallback(async () => { + if (isNative) { + if (Keyboard.isVisible()) { + Keyboard.dismiss() + } + } + control.open() + }, [control]) + + const postLanguagesPref = toPostLanguages(langPrefs.postLanguage) + + return ( + <> + <Button + testID="selectLangBtn" + onPress={onPressMore} + size="small" + hitSlop={LANG_DROPDOWN_HITSLOP} + label={_( + msg({ + message: `Post language selection`, + comment: `Accessibility label for button that opens dialog to choose post language settings`, + }), + )} + accessibilityHint={_(msg`Opens post language settings`)} + style={[a.mx_md]}> + {({pressed, hovered, focused}) => { + const color = + pressed || hovered || focused + ? t.palette.primary_300 + : t.palette.primary_500 + if (postLanguagesPref.length > 0) { + return ( + <Text + style={[ + {color}, + a.font_bold, + a.text_sm, + a.leading_snug, + {maxWidth: 100}, + ]} + numberOfLines={1}> + {postLanguagesPref + .map(lang => codeToLanguageName(lang, langPrefs.appLanguage)) + .join(', ')} + </Text> + ) + } else { + return <GlobeIcon size="xs" style={{color}} /> + } + }} + </Button> + + <LanguageDialog control={control} /> + </> + ) +} + +function LanguageDialog({control}: {control: Dialog.DialogControlProps}) { + const {height} = useWindowDimensions() + const insets = useSafeAreaInsets() + + const renderErrorBoundary = useCallback( + (error: any) => <DialogError details={String(error)} />, + [], + ) + + return ( + <Dialog.Outer + control={control} + nativeOptions={{minHeight: height - insets.top}}> + <Dialog.Handle /> + <ErrorBoundary renderError={renderErrorBoundary}> + <PostLanguagesSettingsDialogInner /> + </ErrorBoundary> + </Dialog.Outer> + ) +} + +export function PostLanguagesSettingsDialogInner() { + const control = Dialog.useDialogContext() + const [headerHeight, setHeaderHeight] = useState(0) + + const allowedLanguages = useMemo(() => { + const uniqueLanguagesMap = LANGUAGES.filter(lang => !!lang.code2).reduce( + (acc, lang) => { + acc[lang.code2] = lang + return acc + }, + {} as Record<string, Language>, + ) + + return Object.values(uniqueLanguagesMap) + }, []) + + const langPrefs = useLanguagePrefs() + const [checkedLanguagesCode2, setCheckedLanguagesCode2] = useState<string[]>( + langPrefs.postLanguage.split(',') || [langPrefs.primaryLanguage], + ) + const [search, setSearch] = useState('') + + const setLangPrefs = useLanguagePrefsApi() + const t = useTheme() + const {_} = useLingui() + + const handleClose = () => { + control.close(() => { + let langsString = checkedLanguagesCode2.join(',') + if (!langsString) { + langsString = langPrefs.primaryLanguage + } + setLangPrefs.setPostLanguage(langsString) + }) + } + + // NOTE(@elijaharita): Displayed languages are split into 3 lists for + // ordering. + const displayedLanguages = useMemo(() => { + function mapCode2List(code2List: string[]) { + return code2List.map(code2 => LANGUAGES_MAP_CODE2[code2]).filter(Boolean) + } + + // NOTE(@elijaharita): Get recent language codes and map them to language + // objects. Both the user account's saved language history and the current + // checked languages are displayed here. + const recentLanguagesCode2 = + Array.from( + new Set([...checkedLanguagesCode2, ...langPrefs.postLanguageHistory]), + ).slice(0, 5) || [] + const recentLanguages = mapCode2List(recentLanguagesCode2) + + // NOTE(@elijaharita): helper functions + const matchesSearch = (lang: Language) => + lang.name.toLowerCase().includes(search.toLowerCase()) + const isChecked = (lang: Language) => + checkedLanguagesCode2.includes(lang.code2) + const isInRecents = (lang: Language) => + recentLanguagesCode2.includes(lang.code2) + + const checkedRecent = recentLanguages.filter(isChecked) + + if (search) { + // NOTE(@elijaharita): if a search is active, we ALWAYS show checked + // items, as well as any items that match the search. + const uncheckedRecent = recentLanguages + .filter(lang => !isChecked(lang)) + .filter(matchesSearch) + const unchecked = allowedLanguages.filter(lang => !isChecked(lang)) + const all = unchecked + .filter(matchesSearch) + .filter(lang => !isInRecents(lang)) + + return { + all, + checkedRecent, + uncheckedRecent, + } + } else { + // NOTE(@elijaharita): if no search is active, we show everything. + const uncheckedRecent = recentLanguages.filter(lang => !isChecked(lang)) + const all = allowedLanguages + .filter(lang => !recentLanguagesCode2.includes(lang.code2)) + .filter(lang => !isInRecents(lang)) + + return { + all, + checkedRecent, + uncheckedRecent, + } + } + }, [ + allowedLanguages, + search, + langPrefs.postLanguageHistory, + checkedLanguagesCode2, + ]) + + const listHeader = ( + <View + style={[a.pb_xs, t.atoms.bg, isNative && a.pt_2xl]} + onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}> + <View style={[a.flex_row, a.w_full, a.justify_between]}> + <View> + <Text + nativeID="dialog-title" + style={[ + t.atoms.text, + a.text_left, + a.font_bold, + a.text_xl, + a.mb_sm, + ]}> + <Trans>Choose Post Languages</Trans> + </Text> + <Text + nativeID="dialog-description" + style={[ + t.atoms.text_contrast_medium, + a.text_left, + a.text_md, + a.mb_lg, + ]}> + <Trans>Select up to 3 languages used in this post</Trans> + </Text> + </View> + + {isWeb && ( + <Button + variant="ghost" + size="small" + color="secondary" + shape="round" + label={_(msg`Close dialog`)} + onPress={handleClose}> + <ButtonIcon icon={XIcon} /> + </Button> + )} + </View> + + <View style={[a.w_full, a.flex_row, a.align_stretch, a.gap_xs, a.pb_0]}> + <SearchInput + value={search} + onChangeText={setSearch} + placeholder={_(msg`Search languages`)} + label={_(msg`Search languages`)} + maxLength={50} + onClearText={() => setSearch('')} + /> + </View> + </View> + ) + + const isCheckedRecentEmpty = + displayedLanguages.checkedRecent.length > 0 || + displayedLanguages.uncheckedRecent.length > 0 + + const isDisplayedLanguagesEmpty = displayedLanguages.all.length === 0 + + const flatListData = [ + ...(isCheckedRecentEmpty + ? [{type: 'header', label: _(msg`Recently used`)}] + : []), + ...displayedLanguages.checkedRecent.map(lang => ({type: 'item', lang})), + ...displayedLanguages.uncheckedRecent.map(lang => ({type: 'item', lang})), + ...(isDisplayedLanguagesEmpty + ? [] + : [{type: 'header', label: _(msg`All languages`)}]), + ...displayedLanguages.all.map(lang => ({type: 'item', lang})), + ] + + return ( + <Toggle.Group + values={checkedLanguagesCode2} + onChange={setCheckedLanguagesCode2} + type="checkbox" + maxSelections={3} + label={_(msg`Select languages`)} + style={web([a.contents])}> + <Dialog.InnerFlatList + data={flatListData} + ListHeaderComponent={listHeader} + stickyHeaderIndices={[0]} + contentContainerStyle={[a.gap_0]} + style={[isNative && a.px_lg, web({paddingBottom: 120})]} + scrollIndicatorInsets={{top: headerHeight}} + renderItem={({item, index}) => { + if (item.type === 'header') { + return ( + <Text + key={index} + style={[ + a.px_0, + a.py_md, + a.font_bold, + a.text_xs, + t.atoms.text_contrast_low, + a.pt_3xl, + ]}> + {item.label} + </Text> + ) + } + const lang = item.lang + + return ( + <Toggle.Item + key={lang.code2} + name={lang.code2} + label={languageName(lang, langPrefs.appLanguage)} + style={[ + t.atoms.border_contrast_low, + a.border_b, + a.rounded_0, + a.px_0, + a.py_md, + ]}> + <Toggle.LabelText style={[a.flex_1]}> + {languageName(lang, langPrefs.appLanguage)} + </Toggle.LabelText> + <Toggle.Checkbox /> + </Toggle.Item> + ) + }} + footer={ + <Dialog.FlatListFooter> + <Button + label={_(msg`Close dialog`)} + onPress={handleClose} + color="primary" + size="large"> + <ButtonText> + <Trans>Done</Trans> + </ButtonText> + </Button> + </Dialog.FlatListFooter> + } + /> + </Toggle.Group> + ) +} + +function DialogError({details}: {details?: string}) { + const {_} = useLingui() + const control = Dialog.useDialogContext() + + return ( + <Dialog.ScrollableInner + style={a.gap_md} + label={_(msg`An error has occurred`)}> + <Dialog.Close /> + <ErrorScreen + title={_(msg`Oh no!`)} + message={_( + msg`There was an unexpected issue in the application. Please let us know if this happened to you!`, + )} + details={details} + /> + <Button + label={_(msg`Close dialog`)} + onPress={() => control.close()} + color="primary" + size="large"> + <ButtonText> + <Trans>Close</Trans> + </ButtonText> + </Button> + </Dialog.ScrollableInner> + ) +} diff --git a/src/view/com/modals/DeleteAccount.tsx b/src/view/com/modals/DeleteAccount.tsx index 5e188ee06..80ff15768 100644 --- a/src/view/com/modals/DeleteAccount.tsx +++ b/src/view/com/modals/DeleteAccount.tsx @@ -10,6 +10,7 @@ import {LinearGradient} from 'expo-linear-gradient' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {usePalette} from '#/lib/hooks/usePalette' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {cleanError} from '#/lib/strings/errors' @@ -17,7 +18,6 @@ import {colors, gradients, s} from '#/lib/styles' import {useTheme} from '#/lib/ThemeContext' import {isAndroid, isWeb} from '#/platform/detection' import {useModalControls} from '#/state/modals' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {useAgent, useSession, useSessionApi} from '#/state/session' import {atoms as a, useTheme as useNewTheme} from '#/alf' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index c3628f939..79971e660 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -11,7 +11,6 @@ import * as CreateOrEditListModal from './CreateOrEditList' import * as DeleteAccountModal from './DeleteAccount' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' -import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' import * as UserAddRemoveListsModal from './UserAddRemoveLists' const DEFAULT_SNAPPOINTS = ['90%'] @@ -60,9 +59,6 @@ export function ModalsContainer() { } else if (activeModal?.name === 'content-languages-settings') { snapPoints = ContentLanguagesSettingsModal.snapPoints element = <ContentLanguagesSettingsModal.Component /> - } else if (activeModal?.name === 'post-languages-settings') { - snapPoints = PostLanguagesSettingsModal.snapPoints - element = <PostLanguagesSettingsModal.Component /> } else { return null } diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx index 08f0e2f85..d0799a390 100644 --- a/src/view/com/modals/Modal.web.tsx +++ b/src/view/com/modals/Modal.web.tsx @@ -10,7 +10,6 @@ import * as CreateOrEditListModal from './CreateOrEditList' import * as DeleteAccountModal from './DeleteAccount' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' -import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' import * as UserAddRemoveLists from './UserAddRemoveLists' export function ModalsContainer() { @@ -59,8 +58,6 @@ function Modal({modal}: {modal: ModalIface}) { element = <InviteCodesModal.Component /> } else if (modal.name === 'content-languages-settings') { element = <ContentLanguagesSettingsModal.Component /> - } else if (modal.name === 'post-languages-settings') { - element = <PostLanguagesSettingsModal.Component /> } else { return null } diff --git a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx b/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx deleted file mode 100644 index 8c2969674..000000000 --- a/src/view/com/modals/lang-settings/PostLanguagesSettings.tsx +++ /dev/null @@ -1,145 +0,0 @@ -import React from 'react' -import {StyleSheet, View} from 'react-native' -import {Trans} from '@lingui/macro' - -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' -import {deviceLanguageCodes} from '#/locale/deviceLocales' -import {languageName} from '#/locale/helpers' -import {useModalControls} from '#/state/modals' -import { - hasPostLanguage, - useLanguagePrefs, - useLanguagePrefsApi, -} from '#/state/preferences/languages' -import {ToggleButton} from '#/view/com/util/forms/ToggleButton' -import {LANGUAGES, LANGUAGES_MAP_CODE2} from '../../../../locale/languages' -import {Text} from '../../util/text/Text' -import {ScrollView} from '../util' -import {ConfirmLanguagesButton} from './ConfirmLanguagesButton' - -export const snapPoints = ['100%'] - -export function Component() { - const {closeModal} = useModalControls() - const langPrefs = useLanguagePrefs() - const setLangPrefs = useLanguagePrefsApi() - const pal = usePalette('default') - const {isMobile} = useWebMediaQueries() - const onPressDone = React.useCallback(() => { - closeModal() - }, [closeModal]) - - const languages = React.useMemo(() => { - const langs = LANGUAGES.filter( - lang => - !!lang.code2.trim() && - LANGUAGES_MAP_CODE2[lang.code2].code3 === lang.code3, - ) - // sort so that device & selected languages are on top, then alphabetically - langs.sort((a, b) => { - const hasA = - hasPostLanguage(langPrefs.postLanguage, a.code2) || - deviceLanguageCodes.includes(a.code2) - const hasB = - hasPostLanguage(langPrefs.postLanguage, b.code2) || - deviceLanguageCodes.includes(b.code2) - if (hasA === hasB) return a.name.localeCompare(b.name) - if (hasA) return -1 - return 1 - }) - return langs - }, [langPrefs]) - - const onPress = React.useCallback( - (code2: string) => { - setLangPrefs.togglePostLanguage(code2) - }, - [setLangPrefs], - ) - - return ( - <View - testID="postLanguagesModal" - style={[ - pal.view, - styles.container, - // @ts-ignore vh is on web only - isMobile - ? { - paddingTop: 20, - } - : { - maxHeight: '90vh', - }, - ]}> - <Text style={[pal.text, styles.title]}> - <Trans>Post Languages</Trans> - </Text> - <Text style={[pal.text, styles.description]}> - <Trans>Which languages are used in this post?</Trans> - </Text> - <ScrollView style={styles.scrollContainer}> - {languages.map(lang => { - const isSelected = hasPostLanguage(langPrefs.postLanguage, lang.code2) - - // enforce a max of 3 selections for post languages - let isDisabled = false - if (langPrefs.postLanguage.split(',').length >= 3 && !isSelected) { - isDisabled = true - } - - return ( - <ToggleButton - key={lang.code2} - label={languageName(lang, langPrefs.appLanguage)} - isSelected={isSelected} - onPress={() => (isDisabled ? undefined : onPress(lang.code2))} - style={[ - pal.border, - styles.languageToggle, - isDisabled && styles.dimmed, - ]} - /> - ) - })} - <View - style={{ - height: isMobile ? 60 : 0, - }} - /> - </ScrollView> - <ConfirmLanguagesButton onPress={onPressDone} /> - </View> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - title: { - textAlign: 'center', - fontWeight: '600', - fontSize: 24, - marginBottom: 12, - }, - description: { - textAlign: 'center', - paddingHorizontal: 16, - marginBottom: 10, - }, - scrollContainer: { - flex: 1, - paddingHorizontal: 10, - }, - languageToggle: { - borderTopWidth: 1, - borderRadius: 0, - paddingHorizontal: 6, - paddingVertical: 12, - }, - dimmed: { - opacity: 0.5, - }, -}) diff --git a/src/view/com/notifications/NotificationFeedItem.tsx b/src/view/com/notifications/NotificationFeedItem.tsx index dc048bd26..ce774e888 100644 --- a/src/view/com/notifications/NotificationFeedItem.tsx +++ b/src/view/com/notifications/NotificationFeedItem.tsx @@ -31,6 +31,7 @@ import {useNavigation} from '@react-navigation/native' import {useQueryClient} from '@tanstack/react-query' import {MAX_POST_LINES} from '#/lib/constants' +import {DM_SERVICE_HEADERS} from '#/lib/constants' import {useAnimatedValue} from '#/lib/hooks/useAnimatedValue' import {usePalette} from '#/lib/hooks/usePalette' import {makeProfileLink} from '#/lib/routes/links' @@ -41,7 +42,6 @@ import {sanitizeHandle} from '#/lib/strings/handles' import {niceDate} from '#/lib/strings/time' import {s} from '#/lib/styles' import {logger} from '#/logger' -import {DM_SERVICE_HEADERS} from '#/state/queries/messages/const' import {type FeedNotification} from '#/state/queries/notifications/feed' import {unstableCacheProfileView} from '#/state/queries/unstable-profile-cache' import {useAgent} from '#/state/session' diff --git a/src/view/com/util/forms/DropdownButton.tsx b/src/view/com/util/forms/DropdownButton.tsx deleted file mode 100644 index e20dadb49..000000000 --- a/src/view/com/util/forms/DropdownButton.tsx +++ /dev/null @@ -1,397 +0,0 @@ -import {type PropsWithChildren} from 'react' -import {useMemo, useRef} from 'react' -import { - Dimensions, - type GestureResponderEvent, - type Insets, - type StyleProp, - StyleSheet, - TouchableOpacity, - TouchableWithoutFeedback, - useWindowDimensions, - View, - type ViewStyle, -} from 'react-native' -import Animated, {FadeIn, FadeInDown, FadeInUp} from 'react-native-reanimated' -import RootSiblings from 'react-native-root-siblings' -import {type IconProp} from '@fortawesome/fontawesome-svg-core' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import type React from 'react' - -import {HITSLOP_10} from '#/lib/constants' -import {usePalette} from '#/lib/hooks/usePalette' -import {colors} from '#/lib/styles' -import {useTheme} from '#/lib/ThemeContext' -import {isWeb} from '#/platform/detection' -import {native} from '#/alf' -import {FullWindowOverlay} from '#/components/FullWindowOverlay' -import {Text} from '../text/Text' -import {Button, type ButtonType} from './Button' - -const ESTIMATED_BTN_HEIGHT = 50 -const ESTIMATED_SEP_HEIGHT = 16 -const ESTIMATED_HEADING_HEIGHT = 60 - -export interface DropdownItemButton { - testID?: string - icon?: IconProp - label: string - onPress: () => void -} -export interface DropdownItemSeparator { - sep: true -} -export interface DropdownItemHeading { - heading: true - label: string -} -export type DropdownItem = - | DropdownItemButton - | DropdownItemSeparator - | DropdownItemHeading -type MaybeDropdownItem = DropdownItem | false | undefined - -export type DropdownButtonType = ButtonType | 'bare' - -interface DropdownButtonProps { - testID?: string - type?: DropdownButtonType - style?: StyleProp<ViewStyle> - items: MaybeDropdownItem[] - label?: string - menuWidth?: number - children?: React.ReactNode - openToRight?: boolean - openUpwards?: boolean - rightOffset?: number - bottomOffset?: number - hitSlop?: Insets - accessibilityLabel?: string - accessibilityHint?: string -} - -/** - * @deprecated use Menu from `#/components/Menu.tsx` instead - */ -export function DropdownButton({ - testID, - type = 'bare', - style, - items, - label, - menuWidth, - children, - openToRight = false, - openUpwards = false, - rightOffset = 0, - bottomOffset = 0, - hitSlop = HITSLOP_10, - accessibilityLabel, -}: PropsWithChildren<DropdownButtonProps>) { - const {_} = useLingui() - - const ref1 = useRef<View>(null) - const ref2 = useRef<View>(null) - - const onPress = (e: GestureResponderEvent) => { - const ref = ref1.current || ref2.current - const {height: winHeight} = Dimensions.get('window') - const pressY = e.nativeEvent.pageY - ref?.measure( - ( - _x: number, - _y: number, - width: number, - _height: number, - pageX: number, - pageY: number, - ) => { - if (!menuWidth) { - menuWidth = 200 - } - let estimatedMenuHeight = 0 - for (const item of items) { - if (item && isSep(item)) { - estimatedMenuHeight += ESTIMATED_SEP_HEIGHT - } else if (item && isBtn(item)) { - estimatedMenuHeight += ESTIMATED_BTN_HEIGHT - } else if (item && isHeading(item)) { - estimatedMenuHeight += ESTIMATED_HEADING_HEIGHT - } - } - const newX = openToRight - ? pageX + width + rightOffset - : pageX + width - menuWidth - - // Add a bit of additional room - let newY = pressY + bottomOffset + 20 - if (openUpwards || newY + estimatedMenuHeight > winHeight) { - newY -= estimatedMenuHeight - } - createDropdownMenu( - newX, - newY, - pageY, - menuWidth, - items.filter(v => !!v) as DropdownItem[], - openUpwards, - ) - }, - ) - } - - const numItems = useMemo( - () => - items.filter(item => { - if (item === undefined || item === false) { - return false - } - - return isBtn(item) - }).length, - [items], - ) - - if (type === 'bare') { - return ( - <TouchableOpacity - testID={testID} - style={style} - onPress={onPress} - hitSlop={hitSlop} - ref={ref1} - accessibilityRole="button" - accessibilityLabel={ - accessibilityLabel || _(msg`Opens ${numItems} options`) - } - accessibilityHint=""> - {children} - </TouchableOpacity> - ) - } - return ( - <View ref={ref2}> - <Button - type={type} - testID={testID} - onPress={onPress} - style={style} - label={label}> - {children} - </Button> - </View> - ) -} - -function createDropdownMenu( - x: number, - y: number, - pageY: number, - width: number, - items: DropdownItem[], - opensUpwards = false, -): RootSiblings { - const onPressItem = (index: number) => { - sibling.destroy() - const item = items[index] - if (isBtn(item)) { - item.onPress() - } - } - const onOuterPress = () => sibling.destroy() - const sibling = new RootSiblings( - ( - <DropdownItems - onOuterPress={onOuterPress} - x={x} - y={y} - pageY={pageY} - width={width} - items={items} - onPressItem={onPressItem} - openUpwards={opensUpwards} - /> - ), - ) - return sibling -} - -type DropDownItemProps = { - onOuterPress: () => void - x: number - y: number - pageY: number - width: number - items: DropdownItem[] - onPressItem: (index: number) => void - openUpwards: boolean -} - -const DropdownItems = ({ - onOuterPress, - x, - y, - pageY, - width, - items, - onPressItem, - openUpwards, -}: DropDownItemProps) => { - const pal = usePalette('default') - const theme = useTheme() - const {_} = useLingui() - const {height: screenHeight} = useWindowDimensions() - const dropDownBackgroundColor = - theme.colorScheme === 'dark' ? pal.btn : pal.view - const separatorColor = - theme.colorScheme === 'dark' ? pal.borderDark : pal.border - - const numItems = items.filter(isBtn).length - - // TODO: Refactor dropdown components to: - // - (On web, if not handled by React Native) use semantic <select /> - // and <option /> elements for keyboard navigation out of the box - // - (On mobile) be buttons by default, accept `label` and `nativeID` - // props, and always have an explicit label - return ( - <FullWindowOverlay> - {/* This TouchableWithoutFeedback renders the background so if the user clicks outside, the dropdown closes */} - <TouchableWithoutFeedback - onPress={onOuterPress} - accessibilityLabel={_(msg`Toggle dropdown`)} - accessibilityHint=""> - <Animated.View - entering={FadeIn} - style={[ - styles.bg, - // On web we need to adjust the top and bottom relative to the scroll position - isWeb - ? { - top: -pageY, - bottom: pageY - screenHeight, - } - : { - top: 0, - bottom: 0, - }, - ]} - /> - </TouchableWithoutFeedback> - <Animated.View - entering={native( - openUpwards ? FadeInDown.springify(1000) : FadeInUp.springify(1000), - )} - style={[ - styles.menu, - {left: x, top: y, width}, - dropDownBackgroundColor, - ]}> - {items.map((item, index) => { - if (isBtn(item)) { - return ( - <TouchableOpacity - testID={item.testID} - key={index} - style={[styles.menuItem]} - onPress={() => onPressItem(index)} - accessibilityRole="button" - accessibilityLabel={item.label} - accessibilityHint={_( - msg`Selects option ${index + 1} of ${numItems}`, - )}> - {item.icon && ( - <FontAwesomeIcon - style={styles.icon} - icon={item.icon} - color={pal.text.color as string} - /> - )} - <Text style={[styles.label, pal.text]}>{item.label}</Text> - </TouchableOpacity> - ) - } else if (isSep(item)) { - return ( - <View key={index} style={[styles.separator, separatorColor]} /> - ) - } else if (isHeading(item)) { - return ( - <View style={[styles.heading, pal.border]} key={index}> - <Text style={[pal.text, styles.headingLabel]}> - {item.label} - </Text> - </View> - ) - } - return null - })} - </Animated.View> - </FullWindowOverlay> - ) -} - -function isSep(item: DropdownItem): item is DropdownItemSeparator { - return 'sep' in item && item.sep -} -function isHeading(item: DropdownItem): item is DropdownItemHeading { - return 'heading' in item && item.heading -} -function isBtn(item: DropdownItem): item is DropdownItemButton { - return !isSep(item) && !isHeading(item) -} - -const styles = StyleSheet.create({ - bg: { - position: 'absolute', - left: 0, - width: '100%', - backgroundColor: 'rgba(0, 0, 0, 0.1)', - }, - menu: { - position: 'absolute', - backgroundColor: '#fff', - borderRadius: 14, - paddingVertical: 6, - }, - menuItem: { - flexDirection: 'row', - alignItems: 'center', - paddingVertical: 10, - paddingLeft: 15, - paddingRight: 40, - }, - menuItemBorder: { - borderTopWidth: 1, - borderTopColor: colors.gray1, - marginTop: 4, - paddingTop: 12, - }, - icon: { - marginLeft: 2, - marginRight: 8, - flexShrink: 0, - }, - label: { - fontSize: 18, - flexShrink: 1, - flexGrow: 1, - }, - separator: { - borderTopWidth: 1, - marginVertical: 8, - }, - heading: { - flexDirection: 'row', - justifyContent: 'center', - paddingVertical: 10, - paddingLeft: 15, - paddingRight: 20, - borderBottomWidth: 1, - marginBottom: 6, - }, - headingLabel: { - fontSize: 18, - fontWeight: '600', - }, -}) diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index 1a236f8bc..8b81cee10 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -4,17 +4,16 @@ import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {usePalette} from '#/lib/hooks/usePalette' -import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' +import { + type CommonNavigatorParams, + type NativeStackScreenProps, +} from '#/lib/routes/types' import {s} from '#/lib/styles' -import {PaletteColorName, ThemeProvider} from '#/lib/ThemeContext' +import {type PaletteColorName, ThemeProvider} from '#/lib/ThemeContext' import {EmptyState} from '#/view/com/util/EmptyState' import {ErrorMessage} from '#/view/com/util/error/ErrorMessage' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' import {Button} from '#/view/com/util/forms/Button' -import { - DropdownButton, - DropdownItem, -} from '#/view/com/util/forms/DropdownButton' import {ToggleButton} from '#/view/com/util/forms/ToggleButton' import * as LoadingPlaceholder from '#/view/com/util/LoadingPlaceholder' import {Text} from '#/view/com/util/text/Text' @@ -134,8 +133,6 @@ function ControlsView() { <ScrollView style={[s.pl10, s.pr10]}> <Heading label="Buttons" /> <ButtonsView /> - <Heading label="Dropdown Buttons" /> - <DropdownButtonsView /> <Heading label="Toggle Buttons" /> <ToggleButtonsView /> <View style={s.footerSpacer} /> @@ -396,44 +393,6 @@ function ButtonsView() { ) } -const DROPDOWN_ITEMS: DropdownItem[] = [ - { - icon: ['far', 'paste'], - label: 'Copy post text', - onPress() {}, - }, - { - icon: 'share', - label: 'Share...', - onPress() {}, - }, - { - icon: 'circle-exclamation', - label: 'Report post', - onPress() {}, - }, -] -function DropdownButtonsView() { - const defaultPal = usePalette('default') - return ( - <View style={[defaultPal.view]}> - <View style={s.mb5}> - <DropdownButton - type="primary" - items={DROPDOWN_ITEMS} - menuWidth={200} - label="Primary button" - /> - </View> - <View style={s.mb5}> - <DropdownButton type="bare" items={DROPDOWN_ITEMS} menuWidth={200}> - <Text>Bare</Text> - </DropdownButton> - </View> - </View> - ) -} - function ToggleButtonsView() { const defaultPal = usePalette('default') const buttonStyles = s.mb5 diff --git a/src/view/screens/Storybook/Toasts.tsx b/src/view/screens/Storybook/Toasts.tsx index 319f88e21..91fe0d970 100644 --- a/src/view/screens/Storybook/Toasts.tsx +++ b/src/view/screens/Storybook/Toasts.tsx @@ -4,12 +4,28 @@ import {show as deprecatedShow} from '#/view/com/util/Toast' import {atoms as a} from '#/alf' import {Globe_Stroke2_Corner0_Rounded as GlobeIcon} from '#/components/icons/Globe' import * as Toast from '#/components/Toast' -import {Default} from '#/components/Toast/Toast' import {H1} from '#/components/Typography' -function ToastWithAction({type = 'default'}: {type?: Toast.ToastType}) { +function DefaultToast({ + content, + type = 'default', +}: { + content: string + type?: Toast.ToastType +}) { return ( - <Toast.Outer type={type}> + <Toast.ToastConfigProvider id="default-toast" type={type}> + <Toast.Outer> + <Toast.Icon icon={GlobeIcon} /> + <Toast.Text>{content}</Toast.Text> + </Toast.Outer> + </Toast.ToastConfigProvider> + ) +} + +function ToastWithAction() { + return ( + <Toast.Outer> <Toast.Icon icon={GlobeIcon} /> <Toast.Text>This toast has an action button</Toast.Text> <Toast.Action @@ -21,9 +37,9 @@ function ToastWithAction({type = 'default'}: {type?: Toast.ToastType}) { ) } -function LongToastWithAction({type = 'default'}: {type?: Toast.ToastType}) { +function LongToastWithAction() { return ( - <Toast.Outer type={type}> + <Toast.Outer> <Toast.Icon icon={GlobeIcon} /> <Toast.Text> This is a longer message to test how the toast handles multiple lines of @@ -44,33 +60,25 @@ export function Toasts() { <H1>Toast Examples</H1> <View style={[a.gap_md]}> - <View style={[a.gap_md, {marginHorizontal: a.px_xl.paddingLeft * -1}]}> - <Pressable - accessibilityRole="button" - onPress={() => Toast.show(<ToastWithAction />)}> - <ToastWithAction /> - </Pressable> - <Pressable - accessibilityRole="button" - onPress={() => Toast.show(<LongToastWithAction />)}> - <LongToastWithAction /> - </Pressable> - <Pressable - accessibilityRole="button" - onPress={() => Toast.show(<ToastWithAction type="success" />)}> - <ToastWithAction type="success" /> - </Pressable> - <Pressable - accessibilityRole="button" - onPress={() => Toast.show(<ToastWithAction type="error" />)}> - <ToastWithAction type="error" /> - </Pressable> - </View> - + <Pressable + accessibilityRole="button" + onPress={() => Toast.show(<ToastWithAction />, {type: 'success'})}> + <ToastWithAction /> + </Pressable> + <Pressable + accessibilityRole="button" + onPress={() => Toast.show(<ToastWithAction />, {type: 'error'})}> + <ToastWithAction /> + </Pressable> + <Pressable + accessibilityRole="button" + onPress={() => Toast.show(<LongToastWithAction />)}> + <LongToastWithAction /> + </Pressable> <Pressable accessibilityRole="button" onPress={() => Toast.show(`Hey I'm a toast!`)}> - <Default content="Hey I'm a toast!" /> + <DefaultToast content="Hey I'm a toast!" /> </Pressable> <Pressable accessibilityRole="button" @@ -79,7 +87,7 @@ export function Toasts() { duration: 6e3, }) }> - <Default content="This toast will disappear after 6 seconds" /> + <DefaultToast content="This toast will disappear after 6 seconds" /> </Pressable> <Pressable accessibilityRole="button" @@ -88,7 +96,7 @@ export function Toasts() { `This is a longer message to test how the toast handles multiple lines of text content.`, ) }> - <Default content="This is a longer message to test how the toast handles multiple lines of text content." /> + <DefaultToast content="This is a longer message to test how the toast handles multiple lines of text content." /> </Pressable> <Pressable accessibilityRole="button" @@ -97,7 +105,7 @@ export function Toasts() { type: 'success', }) }> - <Default content="Success! Yayyyyyyy :)" type="success" /> + <DefaultToast content="Success! Yayyyyyyy :)" type="success" /> </Pressable> <Pressable accessibilityRole="button" @@ -106,7 +114,7 @@ export function Toasts() { type: 'info', }) }> - <Default content="I'm providing info!" type="info" /> + <DefaultToast content="I'm providing info!" type="info" /> </Pressable> <Pressable accessibilityRole="button" @@ -115,7 +123,7 @@ export function Toasts() { type: 'warning', }) }> - <Default content="This is a warning toast" type="warning" /> + <DefaultToast content="This is a warning toast" type="warning" /> </Pressable> <Pressable accessibilityRole="button" @@ -124,7 +132,7 @@ export function Toasts() { type: 'error', }) }> - <Default content="This is an error toast :(" type="error" /> + <DefaultToast content="This is an error toast :(" type="error" /> </Pressable> <Pressable @@ -135,7 +143,7 @@ export function Toasts() { 'exclamation-circle', ) }> - <Default + <DefaultToast content="This is a test of the deprecated API" type="warning" /> |