deer social fork for personal usage. but you might see a use idk. github mirror

Merge remote-tracking branch 'upstream/main'

aylac.top 67e360cc 17d7e791

verified
+1
.eslintrc.js
··· 36 36 'Admonition.Admonition', 37 37 'Toast.Action', 38 38 'Span', 39 + 'StackedButton', 39 40 ], 40 41 impliedTextProps: [], 41 42 suggestedTextWrappers: {
+8 -12
jest/test-utils.tsx
··· 1 - import React from 'react' 2 - import {render} from '@testing-library/react-native' 3 1 import {GestureHandlerRootView} from 'react-native-gesture-handler' 4 - import {RootSiblingParent} from 'react-native-root-siblings' 5 2 import {SafeAreaProvider} from 'react-native-safe-area-context' 6 - import {RootStoreProvider, RootStoreModel} from '../src/state' 3 + import {render} from '@testing-library/react-native' 4 + 7 5 import {ThemeProvider} from '../src/lib/ThemeContext' 6 + import {type RootStoreModel, RootStoreProvider} from '../src/state' 8 7 9 8 const customRender = (ui: any, rootStore: RootStoreModel) => 10 9 render( 11 - // eslint-disable-next-line react-native/no-inline-styles 12 10 <GestureHandlerRootView style={{flex: 1}}> 13 - <RootSiblingParent> 14 - <RootStoreProvider value={rootStore}> 15 - <ThemeProvider theme="light"> 16 - <SafeAreaProvider>{ui}</SafeAreaProvider> 17 - </ThemeProvider> 18 - </RootStoreProvider> 19 - </RootSiblingParent> 11 + <RootStoreProvider value={rootStore}> 12 + <ThemeProvider theme="light"> 13 + <SafeAreaProvider>{ui}</SafeAreaProvider> 14 + </ThemeProvider> 15 + </RootStoreProvider> 20 16 </GestureHandlerRootView>, 21 17 ) 22 18
-1
package.json
··· 195 195 "react-native-progress": "bluesky-social/react-native-progress", 196 196 "react-native-qrcode-styled": "^0.3.3", 197 197 "react-native-reanimated": "^3.19.1", 198 - "react-native-root-siblings": "^5.0.1", 199 198 "react-native-safe-area-context": "~5.6.0", 200 199 "react-native-screens": "~4.16.0", 201 200 "react-native-svg": "15.12.1",
+59 -58
src/App.native.tsx
··· 2 2 3 3 import React, {useEffect, useState} from 'react' 4 4 import {GestureHandlerRootView} from 'react-native-gesture-handler' 5 - import {RootSiblingParent} from 'react-native-root-siblings' 6 5 import { 7 6 initialWindowMetrics, 8 7 SafeAreaProvider, ··· 75 74 } 76 75 if (isAndroid) { 77 76 // iOS is handled by the config plugin -sfn 78 - ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP) 77 + ScreenOrientation.lockAsync( 78 + ScreenOrientation.OrientationLock.PORTRAIT_UP, 79 + ).catch(error => 80 + logger.debug('Could not lock orientation', {safeMessage: error}), 81 + ) 79 82 } 80 83 81 84 function InnerApp() { ··· 120 123 <ThemeProvider theme={theme}> 121 124 <ContextMenuProvider> 122 125 <Splash isReady={isReady && hasCheckedReferrer}> 123 - <RootSiblingParent> 124 - <VideoVolumeProvider> 125 - <React.Fragment 126 - // Resets the entire tree below when it changes: 127 - key={currentAccount?.did}> 128 - <QueryProvider currentDid={currentAccount?.did}> 129 - <PolicyUpdateOverlayProvider> 130 - <StatsigProvider> 131 - <ComposerProvider> 132 - <MessagesProvider> 133 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 134 - <LabelDefsProvider> 135 - <ModerationOptsProvider> 136 - <LoggedOutViewProvider> 137 - <SelectedFeedProvider> 138 - <HiddenRepliesProvider> 139 - <HomeBadgeProvider> 140 - <UnreadNotifsProvider> 141 - <BackgroundNotificationPreferencesProvider> 142 - <MutedThreadsProvider> 143 - <ProgressGuideProvider> 144 - <ServiceAccountManager> 145 - <EmailVerificationProvider> 146 - <HideBottomBarBorderProvider> 147 - <GestureHandlerRootView 148 - style={s.h100pct}> 149 - <GlobalGestureEventsProvider> 150 - <IntentDialogProvider> 151 - <TestCtrls /> 152 - <Shell /> 153 - <NuxDialogs /> 154 - <ToastOutlet /> 155 - </IntentDialogProvider> 156 - </GlobalGestureEventsProvider> 157 - </GestureHandlerRootView> 158 - </HideBottomBarBorderProvider> 159 - </EmailVerificationProvider> 160 - </ServiceAccountManager> 161 - </ProgressGuideProvider> 162 - </MutedThreadsProvider> 163 - </BackgroundNotificationPreferencesProvider> 164 - </UnreadNotifsProvider> 165 - </HomeBadgeProvider> 166 - </HiddenRepliesProvider> 167 - </SelectedFeedProvider> 168 - </LoggedOutViewProvider> 169 - </ModerationOptsProvider> 170 - </LabelDefsProvider> 171 - </MessagesProvider> 172 - </ComposerProvider> 173 - </StatsigProvider> 174 - </PolicyUpdateOverlayProvider> 175 - </QueryProvider> 176 - </React.Fragment> 177 - </VideoVolumeProvider> 178 - </RootSiblingParent> 126 + <VideoVolumeProvider> 127 + <React.Fragment 128 + // Resets the entire tree below when it changes: 129 + key={currentAccount?.did}> 130 + <QueryProvider currentDid={currentAccount?.did}> 131 + <PolicyUpdateOverlayProvider> 132 + <StatsigProvider> 133 + <ComposerProvider> 134 + <MessagesProvider> 135 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 136 + <LabelDefsProvider> 137 + <ModerationOptsProvider> 138 + <LoggedOutViewProvider> 139 + <SelectedFeedProvider> 140 + <HiddenRepliesProvider> 141 + <HomeBadgeProvider> 142 + <UnreadNotifsProvider> 143 + <BackgroundNotificationPreferencesProvider> 144 + <MutedThreadsProvider> 145 + <ProgressGuideProvider> 146 + <ServiceAccountManager> 147 + <EmailVerificationProvider> 148 + <HideBottomBarBorderProvider> 149 + <GestureHandlerRootView 150 + style={s.h100pct}> 151 + <GlobalGestureEventsProvider> 152 + <IntentDialogProvider> 153 + <TestCtrls /> 154 + <Shell /> 155 + <NuxDialogs /> 156 + <ToastOutlet /> 157 + </IntentDialogProvider> 158 + </GlobalGestureEventsProvider> 159 + </GestureHandlerRootView> 160 + </HideBottomBarBorderProvider> 161 + </EmailVerificationProvider> 162 + </ServiceAccountManager> 163 + </ProgressGuideProvider> 164 + </MutedThreadsProvider> 165 + </BackgroundNotificationPreferencesProvider> 166 + </UnreadNotifsProvider> 167 + </HomeBadgeProvider> 168 + </HiddenRepliesProvider> 169 + </SelectedFeedProvider> 170 + </LoggedOutViewProvider> 171 + </ModerationOptsProvider> 172 + </LabelDefsProvider> 173 + </MessagesProvider> 174 + </ComposerProvider> 175 + </StatsigProvider> 176 + </PolicyUpdateOverlayProvider> 177 + </QueryProvider> 178 + </React.Fragment> 179 + </VideoVolumeProvider> 179 180 </Splash> 180 181 </ContextMenuProvider> 181 182 </ThemeProvider>
+52 -55
src/App.web.tsx
··· 2 2 import './style.css' 3 3 4 4 import React, {useEffect, useState} from 'react' 5 - import {RootSiblingParent} from 'react-native-root-siblings' 6 5 import {SafeAreaProvider} from 'react-native-safe-area-context' 7 6 import {msg} from '@lingui/macro' 8 7 import {useLingui} from '@lingui/react' ··· 98 97 <Alf theme={theme}> 99 98 <ThemeProvider theme={theme}> 100 99 <ContextMenuProvider> 101 - <RootSiblingParent> 102 - <VideoVolumeProvider> 103 - <ActiveVideoProvider> 104 - <React.Fragment 105 - // Resets the entire tree below when it changes: 106 - key={currentAccount?.did}> 107 - <QueryProvider currentDid={currentAccount?.did}> 108 - <PolicyUpdateOverlayProvider> 109 - <StatsigProvider> 110 - <ComposerProvider> 111 - <MessagesProvider> 112 - {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 113 - <LabelDefsProvider> 114 - <ModerationOptsProvider> 115 - <LoggedOutViewProvider> 116 - <SelectedFeedProvider> 117 - <HiddenRepliesProvider> 118 - <HomeBadgeProvider> 119 - <UnreadNotifsProvider> 120 - <BackgroundNotificationPreferencesProvider> 121 - <MutedThreadsProvider> 122 - <SafeAreaProvider> 123 - <ProgressGuideProvider> 124 - <ServiceConfigProvider> 125 - <EmailVerificationProvider> 126 - <HideBottomBarBorderProvider> 127 - <IntentDialogProvider> 128 - <Shell /> 129 - <NuxDialogs /> 130 - <ToastOutlet /> 131 - </IntentDialogProvider> 132 - </HideBottomBarBorderProvider> 133 - </EmailVerificationProvider> 134 - </ServiceConfigProvider> 135 - </ProgressGuideProvider> 136 - </SafeAreaProvider> 137 - </MutedThreadsProvider> 138 - </BackgroundNotificationPreferencesProvider> 139 - </UnreadNotifsProvider> 140 - </HomeBadgeProvider> 141 - </HiddenRepliesProvider> 142 - </SelectedFeedProvider> 143 - </LoggedOutViewProvider> 144 - </ModerationOptsProvider> 145 - </LabelDefsProvider> 146 - </MessagesProvider> 147 - </ComposerProvider> 148 - </StatsigProvider> 149 - </PolicyUpdateOverlayProvider> 150 - </QueryProvider> 151 - </React.Fragment> 152 - </ActiveVideoProvider> 153 - </VideoVolumeProvider> 154 - </RootSiblingParent> 100 + <VideoVolumeProvider> 101 + <ActiveVideoProvider> 102 + <React.Fragment 103 + // Resets the entire tree below when it changes: 104 + key={currentAccount?.did}> 105 + <QueryProvider currentDid={currentAccount?.did}> 106 + <PolicyUpdateOverlayProvider> 107 + <StatsigProvider> 108 + <ComposerProvider> 109 + <MessagesProvider> 110 + {/* LabelDefsProvider MUST come before ModerationOptsProvider */} 111 + <LabelDefsProvider> 112 + <ModerationOptsProvider> 113 + <LoggedOutViewProvider> 114 + <SelectedFeedProvider> 115 + <HiddenRepliesProvider> 116 + <HomeBadgeProvider> 117 + <UnreadNotifsProvider> 118 + <BackgroundNotificationPreferencesProvider> 119 + <MutedThreadsProvider> 120 + <SafeAreaProvider> 121 + <ProgressGuideProvider> 122 + <ServiceConfigProvider> 123 + <EmailVerificationProvider> 124 + <HideBottomBarBorderProvider> 125 + <IntentDialogProvider> 126 + <Shell /> 127 + <NuxDialogs /> 128 + <ToastOutlet /> 129 + </IntentDialogProvider> 130 + </HideBottomBarBorderProvider> 131 + </EmailVerificationProvider> 132 + </ServiceConfigProvider> 133 + </ProgressGuideProvider> 134 + </SafeAreaProvider> 135 + </MutedThreadsProvider> 136 + </BackgroundNotificationPreferencesProvider> 137 + </UnreadNotifsProvider> 138 + </HomeBadgeProvider> 139 + </HiddenRepliesProvider> 140 + </SelectedFeedProvider> 141 + </LoggedOutViewProvider> 142 + </ModerationOptsProvider> 143 + </LabelDefsProvider> 144 + </MessagesProvider> 145 + </ComposerProvider> 146 + </StatsigProvider> 147 + </PolicyUpdateOverlayProvider> 148 + </QueryProvider> 149 + </React.Fragment> 150 + </ActiveVideoProvider> 151 + </VideoVolumeProvider> 155 152 </ContextMenuProvider> 156 153 </ThemeProvider> 157 154 </Alf>
+10 -4
src/alf/util/systemUI.ts
··· 1 1 import * as SystemUI from 'expo-system-ui' 2 2 import {type Theme} from '@bsky.app/alf' 3 3 4 + import {logger} from '#/logger' 4 5 import {isAndroid} from '#/platform/detection' 5 6 6 7 export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) { 7 8 if (isAndroid) { 8 - if (themeType === 'theme') { 9 - SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 10 - } else { 11 - SystemUI.setBackgroundColorAsync('black') 9 + try { 10 + if (themeType === 'theme') { 11 + SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor) 12 + } else { 13 + SystemUI.setBackgroundColorAsync('black') 14 + } 15 + } catch (error) { 16 + // Can reject with 'The current activity is no longer available' - no big deal 17 + logger.debug('Could not set system UI theme', {safeMessage: error}) 12 18 } 13 19 } 14 20 }
+44 -24
src/components/Admonition.tsx
··· 3 3 4 4 import {atoms as a, useBreakpoints, useTheme} from '#/alf' 5 5 import {Button as BaseButton, type ButtonProps} from '#/components/Button' 6 - import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo' 7 - import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye' 8 - import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf' 6 + import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' 7 + import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX' 9 8 import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning' 10 9 import {Text as BaseText, type TextProps} from '#/components/Typography' 11 10 12 11 export const colors = { 13 - warning: { 14 - light: '#DFBC00', 15 - dark: '#BFAF1F', 16 - }, 12 + warning: '#FFC404', 17 13 } 18 14 19 15 type Context = { ··· 29 25 const t = useTheme() 30 26 const {type} = useContext(Context) 31 27 const Icon = { 32 - info: InfoIcon, 33 - tip: TipIcon, 28 + info: CircleInfoIcon, 29 + tip: CircleInfoIcon, 34 30 warning: WarningIcon, 35 - error: ErrorIcon, 31 + error: CircleXIcon, 36 32 }[type] 37 33 const fill = { 38 34 info: t.atoms.text_contrast_medium.color, 39 35 tip: t.palette.primary_500, 40 - warning: colors.warning.light, 36 + warning: colors.warning, 41 37 error: t.palette.negative_500, 42 38 }[type] 43 39 return <Icon fill={fill} size="md" /> 44 40 } 45 41 42 + export function Content({ 43 + children, 44 + style, 45 + ...rest 46 + }: { 47 + children: React.ReactNode 48 + style?: StyleProp<ViewStyle> 49 + }) { 50 + return ( 51 + <View 52 + style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]} 53 + {...rest}> 54 + {children} 55 + </View> 56 + ) 57 + } 58 + 46 59 export function Text({ 47 60 children, 48 61 style, 49 62 ...rest 50 63 }: Pick<TextProps, 'children' | 'style'>) { 51 64 return ( 52 - <BaseText 53 - {...rest} 54 - style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}> 65 + <BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}> 55 66 {children} 56 67 </BaseText> 57 68 ) ··· 60 71 export function Button({ 61 72 children, 62 73 ...props 63 - }: Omit<ButtonProps, 'size' | 'variant' | 'color'>) { 74 + }: Omit<ButtonProps, 'size' | 'variant'>) { 64 75 return ( 65 - <BaseButton size="tiny" variant="outline" color="secondary" {...props}> 76 + <BaseButton size="tiny" {...props}> 66 77 {children} 67 78 </BaseButton> 68 79 ) 69 80 } 70 81 71 - export function Row({children}: {children: React.ReactNode}) { 82 + export function Row({ 83 + children, 84 + style, 85 + }: { 86 + children: React.ReactNode 87 + style?: StyleProp<ViewStyle> 88 + }) { 72 89 return ( 73 - <View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}> 90 + <View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}> 74 91 {children} 75 92 </View> 76 93 ) ··· 88 105 const t = useTheme() 89 106 const {gtMobile} = useBreakpoints() 90 107 const borderColor = { 91 - info: t.atoms.border_contrast_low.borderColor, 92 - tip: t.atoms.border_contrast_low.borderColor, 93 - warning: t.atoms.border_contrast_low.borderColor, 94 - error: t.atoms.border_contrast_low.borderColor, 108 + info: t.atoms.border_contrast_high.borderColor, 109 + tip: t.palette.primary_500, 110 + warning: colors.warning, 111 + error: t.palette.negative_500, 95 112 }[type] 96 113 return ( 97 114 <Context.Provider value={{type}}> 98 115 <View 99 116 style={[ 100 117 gtMobile ? a.p_md : a.p_sm, 118 + a.p_md, 101 119 a.rounded_sm, 102 120 a.border, 103 - t.atoms.bg_contrast_25, 121 + t.atoms.bg, 104 122 {borderColor}, 105 123 style, 106 124 ]}> ··· 123 141 <Outer type={type} style={style}> 124 142 <Row> 125 143 <Icon /> 126 - <Text>{children}</Text> 144 + <Content> 145 + <Text>{children}</Text> 146 + </Content> 127 147 </Row> 128 148 </Outer> 129 149 )
+53 -41
src/components/Button.tsx
··· 274 274 } else if (color === 'primary_subtle') { 275 275 if (!disabled) { 276 276 baseStyles.push({ 277 - backgroundColor: select(t.name, { 278 - light: t.palette.primary_50, 279 - dim: t.palette.primary_100, 280 - dark: t.palette.primary_100, 281 - }), 277 + backgroundColor: t.palette.primary_50, 282 278 }) 283 279 hoverStyles.push({ 284 - backgroundColor: select(t.name, { 285 - light: t.palette.primary_100, 286 - dim: t.palette.primary_200, 287 - dark: t.palette.primary_200, 288 - }), 280 + backgroundColor: t.palette.primary_100, 289 281 }) 290 282 } else { 291 283 baseStyles.push({ ··· 295 287 } else if (color === 'negative_subtle') { 296 288 if (!disabled) { 297 289 baseStyles.push({ 298 - backgroundColor: select(t.name, { 299 - light: t.palette.negative_50, 300 - dim: t.palette.negative_100, 301 - dark: t.palette.negative_100, 302 - }), 290 + backgroundColor: t.palette.negative_50, 303 291 }) 304 292 hoverStyles.push({ 305 - backgroundColor: select(t.name, { 306 - light: t.palette.negative_100, 307 - dim: t.palette.negative_200, 308 - dark: t.palette.negative_200, 309 - }), 293 + backgroundColor: t.palette.negative_100, 310 294 }) 311 295 } else { 312 296 baseStyles.push({ ··· 618 602 } else if (color === 'primary_subtle') { 619 603 if (!disabled) { 620 604 baseStyles.push({ 621 - color: select(t.name, { 622 - light: t.palette.primary_600, 623 - dim: t.palette.primary_800, 624 - dark: t.palette.primary_800, 625 - }), 605 + color: t.palette.primary_600, 626 606 }) 627 607 } else { 628 608 baseStyles.push({ 629 - color: select(t.name, { 630 - light: t.palette.primary_200, 631 - dim: t.palette.primary_200, 632 - dark: t.palette.primary_200, 633 - }), 609 + color: t.palette.primary_200, 634 610 }) 635 611 } 636 612 } else if (color === 'negative_subtle') { 637 613 if (!disabled) { 638 614 baseStyles.push({ 639 - color: select(t.name, { 640 - light: t.palette.negative_600, 641 - dim: t.palette.negative_800, 642 - dark: t.palette.negative_800, 643 - }), 615 + color: t.palette.negative_600, 644 616 }) 645 617 } else { 646 618 baseStyles.push({ 647 - color: select(t.name, { 648 - light: t.palette.negative_200, 649 - dim: t.palette.negative_200, 650 - dark: t.palette.negative_200, 651 - }), 619 + color: t.palette.negative_200, 652 620 }) 653 621 } 654 622 } ··· 755 723 } else if (size === 'small') { 756 724 baseStyles.push(a.text_sm, a.leading_snug, a.font_medium) 757 725 } else if (size === 'tiny') { 758 - baseStyles.push(a.text_xs, a.leading_snug, a.font_medium) 726 + baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold) 759 727 } 760 728 761 729 return StyleSheet.flatten(baseStyles) ··· 869 837 </View> 870 838 ) 871 839 } 840 + 841 + export type StackedButtonProps = Omit< 842 + ButtonProps, 843 + keyof VariantProps | 'children' 844 + > & 845 + Pick<VariantProps, 'color'> & { 846 + children: React.ReactNode 847 + icon: React.ComponentType<SVGIconProps> 848 + } 849 + 850 + export function StackedButton({children, ...props}: StackedButtonProps) { 851 + return ( 852 + <Button 853 + {...props} 854 + size="tiny" 855 + style={[ 856 + a.flex_col, 857 + { 858 + height: 72, 859 + paddingHorizontal: 16, 860 + borderRadius: 20, 861 + gap: 4, 862 + }, 863 + props.style, 864 + ]}> 865 + <StackedButtonInnerText icon={props.icon}> 866 + {children} 867 + </StackedButtonInnerText> 868 + </Button> 869 + ) 870 + } 871 + 872 + function StackedButtonInnerText({ 873 + children, 874 + icon: Icon, 875 + }: Pick<StackedButtonProps, 'icon' | 'children'>) { 876 + const textStyles = useSharedButtonTextStyles() 877 + return ( 878 + <> 879 + <Icon width={24} fill={textStyles.color} /> 880 + <ButtonText>{children}</ButtonText> 881 + </> 882 + ) 883 + }
+2 -2
src/components/Link.tsx
··· 169 169 if (isNative && screen !== 'NotFound') { 170 170 const state = navigation.getState() 171 171 // if screen is not in the current navigator, it means it's 172 - // most likely a tab screen 173 - if (!state.routeNames.includes(screen)) { 172 + // most likely a tab screen. note: state can be undefined 173 + if (!state?.routeNames.includes(screen)) { 174 174 const parent = navigation.getParent() 175 175 if ( 176 176 parent &&
+3 -1
src/components/moderation/LabelsOnMeDialog.tsx
··· 32 32 33 33 export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) { 34 34 return ( 35 - <Dialog.Outer control={props.control}> 35 + <Dialog.Outer 36 + control={props.control} 37 + nativeOptions={{preventExpansion: true}}> 36 38 <Dialog.Handle /> 37 39 <LabelsOnMeDialogInner {...props} /> 38 40 </Dialog.Outer>
+3 -1
src/components/moderation/ModerationDetailsDialog.tsx
··· 24 24 25 25 export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) { 26 26 return ( 27 - <Dialog.Outer control={props.control}> 27 + <Dialog.Outer 28 + control={props.control} 29 + nativeOptions={{preventExpansion: true}}> 28 30 <Dialog.Handle /> 29 31 <ModerationDetailsDialogInner {...props} /> 30 32 </Dialog.Outer>
+2 -1
src/components/moderation/ProfileHeaderAlerts.tsx
··· 6 6 7 7 export function ProfileHeaderAlerts({ 8 8 moderation, 9 + style, 9 10 }: { 10 11 moderation: ModerationDecision 11 12 style?: StyleProp<ViewStyle> ··· 16 17 } 17 18 18 19 return ( 19 - <Pills.Row size="lg"> 20 + <Pills.Row size="lg" style={style}> 20 21 {modui.alerts.filter(unique).map(cause => ( 21 22 <Pills.Label 22 23 size="lg"
+6 -3
src/components/moderation/ReportDialog/index.tsx
··· 219 219 <Admonition.Outer type="error"> 220 220 <Admonition.Row> 221 221 <Admonition.Icon /> 222 - <Admonition.Text> 223 - <Trans>Something went wrong, please try again</Trans> 224 - </Admonition.Text> 222 + <Admonition.Content> 223 + <Admonition.Text> 224 + <Trans>Something went wrong, please try again</Trans> 225 + </Admonition.Text> 226 + </Admonition.Content> 225 227 <Admonition.Button 228 + color="negative_subtle" 226 229 label={_(msg`Retry loading report options`)} 227 230 onPress={() => refetchLabelers()}> 228 231 <ButtonText>
+9 -4
src/lib/hooks/useOTAUpdates.ts
··· 10 10 useUpdates, 11 11 } from 'expo-updates' 12 12 13 + import {isNetworkError} from '#/lib/strings/errors' 13 14 import {logger} from '#/logger' 14 15 import {isIOS} from '#/platform/detection' 15 16 import {IS_TESTFLIGHT} from '#/env' ··· 145 146 } else { 146 147 logger.debug('No update available.') 147 148 } 148 - } catch (e) { 149 - logger.error('OTA Update Error', {error: `${e}`}) 149 + } catch (err) { 150 + if (!isNetworkError(err)) { 151 + logger.error('OTA Update Error', {safeMessage: err}) 152 + } 150 153 } 151 154 }, 10e3) 152 155 }, []) ··· 154 157 const onIsTestFlight = React.useCallback(async () => { 155 158 try { 156 159 await updateTestflight() 157 - } catch (e: any) { 158 - logger.error('Internal OTA Update Error', {error: `${e}`}) 160 + } catch (err: any) { 161 + if (!isNetworkError(err)) { 162 + logger.error('Internal OTA Update Error', {safeMessage: err}) 163 + } 159 164 } 160 165 }, []) 161 166
+1 -1
src/screens/PostThread/components/ThreadItemAnchor.tsx
··· 649 649 650 650 if (!isBackdated) return null 651 651 652 - const orange = t.name === 'light' ? colors.warning.dark : colors.warning.light 652 + const orange = colors.warning 653 653 654 654 return ( 655 655 <>
+13 -12
src/screens/PostThread/index.tsx
··· 148 148 */ 149 149 const shouldHandleScroll = useRef(true) 150 150 /** 151 - * Called any time the content size of the list changes, _just_ before paint. 151 + * Called any time the content size of the list changes. Could be a fresh 152 + * render, items being added to the list, or any resize that changes the 153 + * scrollable size of the content. 152 154 * 153 155 * We want this to fire every time we change params (which will reset 154 156 * `deferParents` via `onLayout` on the anchor post, due to the key change), ··· 193 195 * will give us a _positive_ offset, which will scroll the anchor post 194 196 * back _up_ to the top of the screen. 195 197 */ 196 - list.scrollToOffset({ 197 - offset: anchorOffsetTop - headerHeight, 198 - }) 198 + const offset = anchorOffsetTop - headerHeight 199 + list.scrollToOffset({offset}) 199 200 200 201 /* 201 - * After the second pass, `deferParents` will be `false`, and we need 202 - * to ensure this doesn't run again until scroll handling is requested 203 - * again via `shouldHandleScroll.current === true` and a params 204 - * change via `prepareForParamsUpdate`. 202 + * After we manage to do a positive adjustment, we need to ensure this 203 + * doesn't run again until scroll handling is requested again via 204 + * `shouldHandleScroll.current === true` and a params change via 205 + * `prepareForParamsUpdate`. 205 206 * 206 207 * The `isRoot` here is needed because if we're looking at the anchor 207 208 * post, this handler will not fire after `deferParents` is set to 208 209 * `false`, since there are no parents to render above it. In this case, 209 - * we want to make sure `shouldHandleScroll` is set to `false` so that 210 - * subsequent size changes unrelated to a params change (like pagination) 211 - * do not affect scroll. 210 + * we want to make sure `shouldHandleScroll` is set to `false` right away 211 + * so that subsequent size changes unrelated to a params change (like 212 + * pagination) do not affect scroll. 212 213 */ 213 - if (!deferParents || isRoot) shouldHandleScroll.current = false 214 + if (offset > 0 || isRoot) shouldHandleScroll.current = false 214 215 } 215 216 }) 216 217
+23 -11
src/screens/Profile/Header/Shell.tsx
··· 209 209 210 210 {children} 211 211 212 - {!isPlaceholderProfile && ( 213 - <View 214 - style={[a.px_lg, a.pt_xs, a.pb_sm]} 215 - pointerEvents={isIOS ? 'auto' : 'box-none'}> 216 - {isMe ? ( 217 - <LabelsOnMe type="account" labels={profile.labels} /> 218 - ) : ( 219 - <ProfileHeaderAlerts moderation={moderation} /> 220 - )} 221 - </View> 222 - )} 212 + {!isPlaceholderProfile && 213 + (isMe ? ( 214 + <LabelsOnMe 215 + type="account" 216 + labels={profile.labels} 217 + style={[ 218 + a.px_lg, 219 + a.pt_xs, 220 + a.pb_sm, 221 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 222 + ]} 223 + /> 224 + ) : ( 225 + <ProfileHeaderAlerts 226 + moderation={moderation} 227 + style={[ 228 + a.px_lg, 229 + a.pt_xs, 230 + a.pb_sm, 231 + isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'}, 232 + ]} 233 + /> 234 + ))} 223 235 224 236 <GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}> 225 237 <TouchableWithoutFeedback
+1 -1
src/screens/Settings/AppPasswords.tsx
··· 195 195 </View> 196 196 {appPassword.privileged && ( 197 197 <View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}> 198 - <WarningIcon style={[{color: colors.warning[t.scheme]}]} /> 198 + <WarningIcon style={[{color: colors.warning}]} /> 199 199 <Text style={t.atoms.text_contrast_high}> 200 200 <Trans>Allows access to direct messages</Trans> 201 201 </Text>
+2 -2
src/screens/Settings/NotificationSettings/ActivityNotificationSettings.tsx
··· 134 134 <Admonition.Outer type="tip"> 135 135 <Admonition.Row> 136 136 <Admonition.Icon /> 137 - <View style={[a.flex_1, a.gap_sm]}> 137 + <Admonition.Content> 138 138 <Admonition.Text> 139 139 <Trans> 140 140 Enable notifications for an account by visiting their ··· 166 166 . 167 167 </Trans> 168 168 </Admonition.Text> 169 - </View> 169 + </Admonition.Content> 170 170 </Admonition.Row> 171 171 </Admonition.Outer> 172 172 ) : (
+2 -3
src/screens/Settings/PrivacyAndSecuritySettings.tsx
··· 1 - import {View} from 'react-native' 2 1 import {type AppBskyNotificationDeclaration} from '@atproto/api' 3 2 import {msg, Trans} from '@lingui/macro' 4 3 import {useLingui} from '@lingui/react' ··· 112 111 <Admonition.Outer type="tip" style={[a.flex_1]}> 113 112 <Admonition.Row> 114 113 <Admonition.Icon /> 115 - <View style={[a.flex_1, a.gap_sm]}> 114 + <Admonition.Content> 116 115 <Admonition.Text> 117 116 <Trans> 118 117 Note: Bluesky is an open and public network. This setting ··· 131 130 <Trans>Learn more about what is public on Bluesky.</Trans> 132 131 </InlineLinkText> 133 132 </Admonition.Text> 134 - </View> 133 + </Admonition.Content> 135 134 </Admonition.Row> 136 135 </Admonition.Outer> 137 136 </SettingsList.Item>
+15 -3
src/state/feed-feedback.tsx
··· 28 28 29 29 export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS] 30 30 31 - export const DIRECT_FEEDBACK_INTERACTIONS = new Set< 31 + export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set< 32 32 AppBskyFeedDefs.Interaction['event'] 33 - >(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore']) 33 + >([ 34 + // These are explicit actions and are therefore fine to send. 35 + 'app.bsky.feed.defs#requestLess', 36 + 'app.bsky.feed.defs#requestMore', 37 + // These can be inferred from the firehose and are therefore fine to send. 38 + 'app.bsky.feed.defs#interactionLike', 39 + 'app.bsky.feed.defs#interactionQuote', 40 + 'app.bsky.feed.defs#interactionReply', 41 + 'app.bsky.feed.defs#interactionRepost', 42 + // This can be inferred from pagination requests for everything except the very last page 43 + // so it is fine to send. It is crucial for third party algorithmic feeds to receive these. 44 + 'app.bsky.feed.defs#interactionSeen', 45 + ]) 34 46 35 47 const logger = Logger.create(Logger.Context.FeedFeedback) 36 48 ··· 228 240 return false 229 241 } 230 242 const isDiscover = isDiscoverFeed(feed.feedDescriptor) 231 - return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction) 243 + return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction) 232 244 } 233 245 234 246 function toString(interaction: AppBskyFeedDefs.Interaction): string {
+74 -3
src/view/screens/Storybook/Admonitions.tsx
··· 1 - import {View} from 'react-native' 1 + import {Text as RNText, View} from 'react-native' 2 + import {msg, Trans} from '@lingui/macro' 3 + import {useLingui} from '@lingui/react' 2 4 3 - import {atoms as a} from '#/alf' 4 - import {Admonition} from '#/components/Admonition' 5 + import {atoms as a, useTheme} from '#/alf' 6 + import { 7 + Admonition, 8 + Button as AdmonitionButton, 9 + Content as AdmonitionContent, 10 + Icon as AdmonitionIcon, 11 + Outer as AdmonitionOuter, 12 + Row as AdmonitionRow, 13 + Text as AdmonitionText, 14 + } from '#/components/Admonition' 15 + import {ButtonIcon, ButtonText} from '#/components/Button' 16 + import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise' 17 + import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging' 5 18 import {InlineLinkText} from '#/components/Link' 6 19 import {H1} from '#/components/Typography' 7 20 8 21 export function Admonitions() { 22 + const {_} = useLingui() 23 + const t = useTheme() 24 + 9 25 return ( 10 26 <View style={[a.gap_md]}> 11 27 <H1>Admonitions</H1> ··· 30 46 <Admonition type="error"> 31 47 The quick brown fox jumps over the lazy dog. 32 48 </Admonition> 49 + 50 + <AdmonitionOuter type="error"> 51 + <AdmonitionRow> 52 + <AdmonitionIcon /> 53 + <AdmonitionContent> 54 + <AdmonitionText> 55 + <Trans>Something went wrong, please try again</Trans> 56 + </AdmonitionText> 57 + </AdmonitionContent> 58 + <AdmonitionButton 59 + color="negative_subtle" 60 + label={_(msg`Retry loading report options`)} 61 + onPress={() => {}}> 62 + <ButtonText> 63 + <Trans>Retry</Trans> 64 + </ButtonText> 65 + <ButtonIcon icon={Retry} /> 66 + </AdmonitionButton> 67 + </AdmonitionRow> 68 + </AdmonitionOuter> 69 + 70 + <AdmonitionOuter type="tip"> 71 + <AdmonitionRow> 72 + <AdmonitionIcon /> 73 + <AdmonitionContent> 74 + <AdmonitionText> 75 + <Trans> 76 + Enable notifications for an account by visiting their profile 77 + and pressing the{' '} 78 + <RNText style={[a.font_bold, t.atoms.text_contrast_high]}> 79 + bell icon 80 + </RNText>{' '} 81 + <BellRingingFilledIcon 82 + size="xs" 83 + style={t.atoms.text_contrast_high} 84 + /> 85 + . 86 + </Trans> 87 + </AdmonitionText> 88 + <AdmonitionText> 89 + <Trans> 90 + If you want to restrict who can receive notifications for your 91 + account's activity, you can change this in{' '} 92 + <InlineLinkText 93 + label={_(msg`Privacy and Security settings`)} 94 + to={{screen: 'ActivityPrivacySettings'}} 95 + style={[a.font_bold]}> 96 + Settings &rarr; Privacy and Security 97 + </InlineLinkText> 98 + . 99 + </Trans> 100 + </AdmonitionText> 101 + </AdmonitionContent> 102 + </AdmonitionRow> 103 + </AdmonitionOuter> 33 104 </View> 34 105 ) 35 106 }
+25
src/view/screens/Storybook/Buttons.tsx
··· 8 8 ButtonIcon, 9 9 type ButtonSize, 10 10 ButtonText, 11 + StackedButton, 11 12 } from '#/components/Button' 12 13 import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' 13 14 import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' ··· 17 18 return ( 18 19 <View style={[a.gap_md]}> 19 20 <Text style={[a.font_bold, a.text_5xl]}>Buttons</Text> 21 + 22 + <View style={[a.flex_row, a.gap_md, a.align_start, {maxWidth: 350}]}> 23 + <StackedButton 24 + label="stacked" 25 + icon={Globe} 26 + color="secondary" 27 + style={[a.flex_1]}> 28 + Bop it 29 + </StackedButton> 30 + <StackedButton 31 + label="stacked" 32 + icon={Globe} 33 + color="negative_subtle" 34 + style={[a.flex_1]}> 35 + Twist it 36 + </StackedButton> 37 + <StackedButton 38 + label="stacked" 39 + icon={Globe} 40 + color="primary" 41 + style={[a.flex_1]}> 42 + Pull it 43 + </StackedButton> 44 + </View> 20 45 21 46 {[ 22 47 'primary',
-5
yarn.lock
··· 17105 17105 invariant "^2.2.4" 17106 17106 react-native-is-edge-to-edge "1.1.7" 17107 17107 17108 - react-native-root-siblings@^5.0.1: 17109 - version "5.0.1" 17110 - resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c" 17111 - integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g== 17112 - 17113 17108 react-native-safe-area-context@~5.6.0: 17114 17109 version "5.6.1" 17115 17110 resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"