Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9abf028c85 | |||
| 38445f31f4 | |||
| 4e418c0d05 | |||
| 3fe5cc1d5e |
@@ -37,7 +37,6 @@ module.exports = {
|
||||
'Toast.Action',
|
||||
'AgeAssuranceAdmonition',
|
||||
'Span',
|
||||
'StackedButton',
|
||||
],
|
||||
impliedTextProps: [],
|
||||
suggestedTextWrappers: {
|
||||
|
||||
+13
-9
@@ -1,18 +1,22 @@
|
||||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import React from 'react'
|
||||
import {render} from '@testing-library/react-native'
|
||||
|
||||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {RootStoreProvider, RootStoreModel} from '../src/state'
|
||||
import {ThemeProvider} from '../src/lib/ThemeContext'
|
||||
import {type RootStoreModel, RootStoreProvider} from '../src/state'
|
||||
|
||||
const customRender = (ui: any, rootStore: RootStoreModel) =>
|
||||
render(
|
||||
// eslint-disable-next-line react-native/no-inline-styles
|
||||
<GestureHandlerRootView style={{flex: 1}}>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<ThemeProvider theme="light">
|
||||
<SafeAreaProvider>{ui}</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
<RootSiblingParent>
|
||||
<RootStoreProvider value={rootStore}>
|
||||
<ThemeProvider theme="light">
|
||||
<SafeAreaProvider>{ui}</SafeAreaProvider>
|
||||
</ThemeProvider>
|
||||
</RootStoreProvider>
|
||||
</RootSiblingParent>
|
||||
</GestureHandlerRootView>,
|
||||
)
|
||||
|
||||
|
||||
+2
-1
@@ -71,7 +71,7 @@
|
||||
"icons:optimize": "svgo -f ./assets/icons"
|
||||
},
|
||||
"dependencies": {
|
||||
"@atproto/api": "^0.16.7",
|
||||
"@atproto/api": "^0.17.0",
|
||||
"@bitdrift/react-native": "^0.6.8",
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@bsky.app/alf": "^0.1.2",
|
||||
@@ -197,6 +197,7 @@
|
||||
"react-native-progress": "bluesky-social/react-native-progress",
|
||||
"react-native-qrcode-styled": "^0.3.3",
|
||||
"react-native-reanimated": "^3.19.1",
|
||||
"react-native-root-siblings": "^5.0.1",
|
||||
"react-native-safe-area-context": "~5.6.0",
|
||||
"react-native-screens": "~4.16.0",
|
||||
"react-native-svg": "15.12.1",
|
||||
|
||||
+60
-61
@@ -4,6 +4,7 @@ import '#/view/icons'
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {GestureHandlerRootView} from 'react-native-gesture-handler'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
import {
|
||||
initialWindowMetrics,
|
||||
SafeAreaProvider,
|
||||
@@ -83,11 +84,7 @@ if (isIOS) {
|
||||
}
|
||||
if (isAndroid) {
|
||||
// iOS is handled by the config plugin -sfn
|
||||
ScreenOrientation.lockAsync(
|
||||
ScreenOrientation.OrientationLock.PORTRAIT_UP,
|
||||
).catch(error =>
|
||||
logger.debug('Could not lock orientation', {safeMessage: error}),
|
||||
)
|
||||
ScreenOrientation.lockAsync(ScreenOrientation.OrientationLock.PORTRAIT_UP)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -136,62 +133,64 @@ function InnerApp() {
|
||||
<ThemeProvider theme={theme}>
|
||||
<ContextMenuProvider>
|
||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||
<VideoVolumeProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceAccountManager>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<GlobalGestureEventsProvider>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</GlobalGestureEventsProvider>
|
||||
</GestureHandlerRootView>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</VideoVolumeProvider>
|
||||
<RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceAccountManager>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<GestureHandlerRootView
|
||||
style={s.h100pct}>
|
||||
<GlobalGestureEventsProvider>
|
||||
<IntentDialogProvider>
|
||||
<TestCtrls />
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</GlobalGestureEventsProvider>
|
||||
</GestureHandlerRootView>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceAccountManager>
|
||||
</ProgressGuideProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</VideoVolumeProvider>
|
||||
</RootSiblingParent>
|
||||
</Splash>
|
||||
</ContextMenuProvider>
|
||||
</ThemeProvider>
|
||||
|
||||
+57
-54
@@ -3,6 +3,7 @@ import '#/view/icons'
|
||||
import './style.css'
|
||||
|
||||
import React, {useEffect, useState} from 'react'
|
||||
import {RootSiblingParent} from 'react-native-root-siblings'
|
||||
import {SafeAreaProvider} from 'react-native-safe-area-context'
|
||||
import {msg} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -110,60 +111,62 @@ function InnerApp() {
|
||||
<Alf theme={theme}>
|
||||
<ThemeProvider theme={theme}>
|
||||
<ContextMenuProvider>
|
||||
<VideoVolumeProvider>
|
||||
<ActiveVideoProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceConfigProvider>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</ActiveVideoProvider>
|
||||
</VideoVolumeProvider>
|
||||
<RootSiblingParent>
|
||||
<VideoVolumeProvider>
|
||||
<ActiveVideoProvider>
|
||||
<React.Fragment
|
||||
// Resets the entire tree below when it changes:
|
||||
key={currentAccount?.did}>
|
||||
<QueryProvider currentDid={currentAccount?.did}>
|
||||
<PolicyUpdateOverlayProvider>
|
||||
<StatsigProvider>
|
||||
<AgeAssuranceProvider>
|
||||
<ComposerProvider>
|
||||
<MessagesProvider>
|
||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||
<LabelDefsProvider>
|
||||
<ModerationOptsProvider>
|
||||
<LoggedOutViewProvider>
|
||||
<SelectedFeedProvider>
|
||||
<HiddenRepliesProvider>
|
||||
<HomeBadgeProvider>
|
||||
<UnreadNotifsProvider>
|
||||
<BackgroundNotificationPreferencesProvider>
|
||||
<MutedThreadsProvider>
|
||||
<SafeAreaProvider>
|
||||
<ProgressGuideProvider>
|
||||
<ServiceConfigProvider>
|
||||
<EmailVerificationProvider>
|
||||
<HideBottomBarBorderProvider>
|
||||
<IntentDialogProvider>
|
||||
<Shell />
|
||||
<NuxDialogs />
|
||||
<ToastOutlet />
|
||||
</IntentDialogProvider>
|
||||
</HideBottomBarBorderProvider>
|
||||
</EmailVerificationProvider>
|
||||
</ServiceConfigProvider>
|
||||
</ProgressGuideProvider>
|
||||
</SafeAreaProvider>
|
||||
</MutedThreadsProvider>
|
||||
</BackgroundNotificationPreferencesProvider>
|
||||
</UnreadNotifsProvider>
|
||||
</HomeBadgeProvider>
|
||||
</HiddenRepliesProvider>
|
||||
</SelectedFeedProvider>
|
||||
</LoggedOutViewProvider>
|
||||
</ModerationOptsProvider>
|
||||
</LabelDefsProvider>
|
||||
</MessagesProvider>
|
||||
</ComposerProvider>
|
||||
</AgeAssuranceProvider>
|
||||
</StatsigProvider>
|
||||
</PolicyUpdateOverlayProvider>
|
||||
</QueryProvider>
|
||||
</React.Fragment>
|
||||
</ActiveVideoProvider>
|
||||
</VideoVolumeProvider>
|
||||
</RootSiblingParent>
|
||||
</ContextMenuProvider>
|
||||
</ThemeProvider>
|
||||
</Alf>
|
||||
|
||||
@@ -1,20 +1,14 @@
|
||||
import * as SystemUI from 'expo-system-ui'
|
||||
import {type Theme} from '@bsky.app/alf'
|
||||
|
||||
import {logger} from '#/logger'
|
||||
import {isAndroid} from '#/platform/detection'
|
||||
|
||||
export function setSystemUITheme(themeType: 'theme' | 'lightbox', t: Theme) {
|
||||
if (isAndroid) {
|
||||
try {
|
||||
if (themeType === 'theme') {
|
||||
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
|
||||
} else {
|
||||
SystemUI.setBackgroundColorAsync('black')
|
||||
}
|
||||
} catch (error) {
|
||||
// Can reject with 'The current activity is no longer available' - no big deal
|
||||
logger.debug('Could not set system UI theme', {safeMessage: error})
|
||||
if (themeType === 'theme') {
|
||||
SystemUI.setBackgroundColorAsync(t.atoms.bg.backgroundColor)
|
||||
} else {
|
||||
SystemUI.setBackgroundColorAsync('black')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,17 @@ import {type StyleProp, View, type ViewStyle} from 'react-native'
|
||||
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button as BaseButton, type ButtonProps} from '#/components/Button'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo'
|
||||
import {CircleX_Stroke2_Corner0_Rounded as CircleXIcon} from '#/components/icons/CircleX'
|
||||
import {CircleInfo_Stroke2_Corner0_Rounded as ErrorIcon} from '#/components/icons/CircleInfo'
|
||||
import {Eye_Stroke2_Corner0_Rounded as InfoIcon} from '#/components/icons/Eye'
|
||||
import {Leaf_Stroke2_Corner0_Rounded as TipIcon} from '#/components/icons/Leaf'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import {Text as BaseText, type TextProps} from '#/components/Typography'
|
||||
|
||||
export const colors = {
|
||||
warning: '#FFC404',
|
||||
warning: {
|
||||
light: '#DFBC00',
|
||||
dark: '#BFAF1F',
|
||||
},
|
||||
}
|
||||
|
||||
type Context = {
|
||||
@@ -25,44 +29,29 @@ export function Icon() {
|
||||
const t = useTheme()
|
||||
const {type} = useContext(Context)
|
||||
const Icon = {
|
||||
info: CircleInfoIcon,
|
||||
tip: CircleInfoIcon,
|
||||
info: InfoIcon,
|
||||
tip: TipIcon,
|
||||
warning: WarningIcon,
|
||||
error: CircleXIcon,
|
||||
error: ErrorIcon,
|
||||
}[type]
|
||||
const fill = {
|
||||
info: t.atoms.text_contrast_medium.color,
|
||||
tip: t.palette.primary_500,
|
||||
warning: colors.warning,
|
||||
warning: colors.warning.light,
|
||||
error: t.palette.negative_500,
|
||||
}[type]
|
||||
return <Icon fill={fill} size="md" />
|
||||
}
|
||||
|
||||
export function Content({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
return (
|
||||
<View
|
||||
style={[a.gap_sm, a.flex_1, {minHeight: 20}, a.justify_center, style]}
|
||||
{...rest}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export function Text({
|
||||
children,
|
||||
style,
|
||||
...rest
|
||||
}: Pick<TextProps, 'children' | 'style'>) {
|
||||
return (
|
||||
<BaseText {...rest} style={[a.text_sm, a.leading_snug, a.pr_md, style]}>
|
||||
<BaseText
|
||||
{...rest}
|
||||
style={[a.flex_1, a.text_sm, a.leading_snug, a.pr_md, style]}>
|
||||
{children}
|
||||
</BaseText>
|
||||
)
|
||||
@@ -71,23 +60,17 @@ export function Text({
|
||||
export function Button({
|
||||
children,
|
||||
...props
|
||||
}: Omit<ButtonProps, 'size' | 'variant'>) {
|
||||
}: Omit<ButtonProps, 'size' | 'variant' | 'color'>) {
|
||||
return (
|
||||
<BaseButton size="tiny" {...props}>
|
||||
<BaseButton size="tiny" variant="outline" color="secondary" {...props}>
|
||||
{children}
|
||||
</BaseButton>
|
||||
)
|
||||
}
|
||||
|
||||
export function Row({
|
||||
children,
|
||||
style,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
style?: StyleProp<ViewStyle>
|
||||
}) {
|
||||
export function Row({children}: {children: React.ReactNode}) {
|
||||
return (
|
||||
<View style={[a.flex_1, a.flex_row, a.align_start, a.gap_sm, style]}>
|
||||
<View style={[a.flex_1, a.flex_row, a.align_center, a.gap_sm]}>
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
@@ -105,20 +88,19 @@ export function Outer({
|
||||
const t = useTheme()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const borderColor = {
|
||||
info: t.atoms.border_contrast_high.borderColor,
|
||||
tip: t.palette.primary_500,
|
||||
warning: colors.warning,
|
||||
error: t.palette.negative_500,
|
||||
info: t.atoms.border_contrast_low.borderColor,
|
||||
tip: t.atoms.border_contrast_low.borderColor,
|
||||
warning: t.atoms.border_contrast_low.borderColor,
|
||||
error: t.atoms.border_contrast_low.borderColor,
|
||||
}[type]
|
||||
return (
|
||||
<Context.Provider value={{type}}>
|
||||
<View
|
||||
style={[
|
||||
gtMobile ? a.p_md : a.p_sm,
|
||||
a.p_md,
|
||||
a.rounded_sm,
|
||||
a.border,
|
||||
t.atoms.bg,
|
||||
t.atoms.bg_contrast_25,
|
||||
{borderColor},
|
||||
style,
|
||||
]}>
|
||||
@@ -141,9 +123,7 @@ export function Admonition({
|
||||
<Outer type={type} style={style}>
|
||||
<Row>
|
||||
<Icon />
|
||||
<Content>
|
||||
<Text>{children}</Text>
|
||||
</Content>
|
||||
<Text>{children}</Text>
|
||||
</Row>
|
||||
</Outer>
|
||||
)
|
||||
|
||||
+41
-53
@@ -274,10 +274,18 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
} else if (color === 'primary_subtle') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
backgroundColor: t.palette.primary_50,
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.primary_50,
|
||||
dim: t.palette.primary_100,
|
||||
dark: t.palette.primary_100,
|
||||
}),
|
||||
})
|
||||
hoverStyles.push({
|
||||
backgroundColor: t.palette.primary_100,
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.primary_100,
|
||||
dim: t.palette.primary_200,
|
||||
dark: t.palette.primary_200,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
@@ -287,10 +295,18 @@ export const Button = React.forwardRef<View, ButtonProps>(
|
||||
} else if (color === 'negative_subtle') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
backgroundColor: t.palette.negative_50,
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.negative_50,
|
||||
dim: t.palette.negative_100,
|
||||
dark: t.palette.negative_100,
|
||||
}),
|
||||
})
|
||||
hoverStyles.push({
|
||||
backgroundColor: t.palette.negative_100,
|
||||
backgroundColor: select(t.name, {
|
||||
light: t.palette.negative_100,
|
||||
dim: t.palette.negative_200,
|
||||
dark: t.palette.negative_200,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
@@ -602,21 +618,37 @@ export function useSharedButtonTextStyles() {
|
||||
} else if (color === 'primary_subtle') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
color: t.palette.primary_600,
|
||||
color: select(t.name, {
|
||||
light: t.palette.primary_600,
|
||||
dim: t.palette.primary_800,
|
||||
dark: t.palette.primary_800,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
color: t.palette.primary_200,
|
||||
color: select(t.name, {
|
||||
light: t.palette.primary_200,
|
||||
dim: t.palette.primary_200,
|
||||
dark: t.palette.primary_200,
|
||||
}),
|
||||
})
|
||||
}
|
||||
} else if (color === 'negative_subtle') {
|
||||
if (!disabled) {
|
||||
baseStyles.push({
|
||||
color: t.palette.negative_600,
|
||||
color: select(t.name, {
|
||||
light: t.palette.negative_600,
|
||||
dim: t.palette.negative_800,
|
||||
dark: t.palette.negative_800,
|
||||
}),
|
||||
})
|
||||
} else {
|
||||
baseStyles.push({
|
||||
color: t.palette.negative_200,
|
||||
color: select(t.name, {
|
||||
light: t.palette.negative_200,
|
||||
dim: t.palette.negative_200,
|
||||
dark: t.palette.negative_200,
|
||||
}),
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -723,7 +755,7 @@ export function useSharedButtonTextStyles() {
|
||||
} else if (size === 'small') {
|
||||
baseStyles.push(a.text_sm, a.leading_snug, a.font_medium)
|
||||
} else if (size === 'tiny') {
|
||||
baseStyles.push(a.text_xs, a.leading_snug, a.font_semi_bold)
|
||||
baseStyles.push(a.text_xs, a.leading_snug, a.font_medium)
|
||||
}
|
||||
|
||||
return StyleSheet.flatten(baseStyles)
|
||||
@@ -837,47 +869,3 @@ export function ButtonIcon({
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export type StackedButtonProps = Omit<
|
||||
ButtonProps,
|
||||
keyof VariantProps | 'children'
|
||||
> &
|
||||
Pick<VariantProps, 'color'> & {
|
||||
children: React.ReactNode
|
||||
icon: React.ComponentType<SVGIconProps>
|
||||
}
|
||||
|
||||
export function StackedButton({children, ...props}: StackedButtonProps) {
|
||||
return (
|
||||
<Button
|
||||
{...props}
|
||||
size="tiny"
|
||||
style={[
|
||||
a.flex_col,
|
||||
{
|
||||
height: 72,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 20,
|
||||
gap: 4,
|
||||
},
|
||||
props.style,
|
||||
]}>
|
||||
<StackedButtonInnerText icon={props.icon}>
|
||||
{children}
|
||||
</StackedButtonInnerText>
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
|
||||
function StackedButtonInnerText({
|
||||
children,
|
||||
icon: Icon,
|
||||
}: Pick<StackedButtonProps, 'icon' | 'children'>) {
|
||||
const textStyles = useSharedButtonTextStyles()
|
||||
return (
|
||||
<>
|
||||
<Icon width={24} fill={textStyles.color} />
|
||||
<ButtonText>{children}</ButtonText>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -165,8 +165,8 @@ export function useLink({
|
||||
if (isNative && screen !== 'NotFound') {
|
||||
const state = navigation.getState()
|
||||
// if screen is not in the current navigator, it means it's
|
||||
// most likely a tab screen. note: state can be undefined
|
||||
if (!state?.routeNames.includes(screen)) {
|
||||
// most likely a tab screen
|
||||
if (!state.routeNames.includes(screen)) {
|
||||
const parent = navigation.getParent()
|
||||
if (
|
||||
parent &&
|
||||
|
||||
@@ -32,9 +32,7 @@ export interface LabelsOnMeDialogProps {
|
||||
|
||||
export function LabelsOnMeDialog(props: LabelsOnMeDialogProps) {
|
||||
return (
|
||||
<Dialog.Outer
|
||||
control={props.control}
|
||||
nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Outer control={props.control}>
|
||||
<Dialog.Handle />
|
||||
<LabelsOnMeDialogInner {...props} />
|
||||
</Dialog.Outer>
|
||||
|
||||
@@ -24,9 +24,7 @@ export interface ModerationDetailsDialogProps {
|
||||
|
||||
export function ModerationDetailsDialog(props: ModerationDetailsDialogProps) {
|
||||
return (
|
||||
<Dialog.Outer
|
||||
control={props.control}
|
||||
nativeOptions={{preventExpansion: true}}>
|
||||
<Dialog.Outer control={props.control}>
|
||||
<Dialog.Handle />
|
||||
<ModerationDetailsDialogInner {...props} />
|
||||
</Dialog.Outer>
|
||||
|
||||
@@ -6,7 +6,6 @@ import * as Pills from '#/components/Pills'
|
||||
|
||||
export function ProfileHeaderAlerts({
|
||||
moderation,
|
||||
style,
|
||||
}: {
|
||||
moderation: ModerationDecision
|
||||
style?: StyleProp<ViewStyle>
|
||||
@@ -17,7 +16,7 @@ export function ProfileHeaderAlerts({
|
||||
}
|
||||
|
||||
return (
|
||||
<Pills.Row size="lg" style={style}>
|
||||
<Pills.Row size="lg">
|
||||
{modui.alerts.filter(unique).map(cause => (
|
||||
<Pills.Label
|
||||
size="lg"
|
||||
|
||||
@@ -219,13 +219,10 @@ function Inner(props: ReportDialogProps) {
|
||||
<Admonition.Outer type="error">
|
||||
<Admonition.Row>
|
||||
<Admonition.Icon />
|
||||
<Admonition.Content>
|
||||
<Admonition.Text>
|
||||
<Trans>Something went wrong, please try again</Trans>
|
||||
</Admonition.Text>
|
||||
</Admonition.Content>
|
||||
<Admonition.Text>
|
||||
<Trans>Something went wrong, please try again</Trans>
|
||||
</Admonition.Text>
|
||||
<Admonition.Button
|
||||
color="negative_subtle"
|
||||
label={_(msg`Retry loading report options`)}
|
||||
onPress={() => refetchLabelers()}>
|
||||
<ButtonText>
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
useUpdates,
|
||||
} from 'expo-updates'
|
||||
|
||||
import {isNetworkError} from '#/lib/strings/errors'
|
||||
import {logger} from '#/logger'
|
||||
import {isIOS} from '#/platform/detection'
|
||||
import {IS_TESTFLIGHT} from '#/env'
|
||||
@@ -146,10 +145,8 @@ export function useOTAUpdates() {
|
||||
} else {
|
||||
logger.debug('No update available.')
|
||||
}
|
||||
} catch (err) {
|
||||
if (!isNetworkError(err)) {
|
||||
logger.error('OTA Update Error', {safeMessage: err})
|
||||
}
|
||||
} catch (e) {
|
||||
logger.error('OTA Update Error', {error: `${e}`})
|
||||
}
|
||||
}, 10e3)
|
||||
}, [])
|
||||
@@ -157,10 +154,8 @@ export function useOTAUpdates() {
|
||||
const onIsTestFlight = React.useCallback(async () => {
|
||||
try {
|
||||
await updateTestflight()
|
||||
} catch (err: any) {
|
||||
if (!isNetworkError(err)) {
|
||||
logger.error('Internal OTA Update Error', {safeMessage: err})
|
||||
}
|
||||
} catch (e: any) {
|
||||
logger.error('Internal OTA Update Error', {error: `${e}`})
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
||||
@@ -8298,8 +8298,8 @@ msgstr ""
|
||||
msgid "Something wrong? Let us know."
|
||||
msgstr ""
|
||||
|
||||
#: src/App.native.tsx:128
|
||||
#: src/App.web.tsx:100
|
||||
#: src/App.native.tsx:125
|
||||
#: src/App.web.tsx:101
|
||||
msgid "Sorry! Your session expired. Please sign in again."
|
||||
msgstr ""
|
||||
|
||||
|
||||
@@ -621,7 +621,7 @@ function BackdatedPostIndicator({post}: {post: AppBskyFeedDefs.PostView}) {
|
||||
|
||||
if (!isBackdated) return null
|
||||
|
||||
const orange = colors.warning
|
||||
const orange = t.name === 'light' ? colors.warning.dark : colors.warning.light
|
||||
|
||||
return (
|
||||
<>
|
||||
|
||||
@@ -148,9 +148,7 @@ export function PostThread({uri}: {uri: string}) {
|
||||
*/
|
||||
const shouldHandleScroll = useRef(true)
|
||||
/**
|
||||
* Called any time the content size of the list changes. Could be a fresh
|
||||
* render, items being added to the list, or any resize that changes the
|
||||
* scrollable size of the content.
|
||||
* Called any time the content size of the list changes, _just_ before paint.
|
||||
*
|
||||
* We want this to fire every time we change params (which will reset
|
||||
* `deferParents` via `onLayout` on the anchor post, due to the key change),
|
||||
@@ -195,23 +193,24 @@ export function PostThread({uri}: {uri: string}) {
|
||||
* will give us a _positive_ offset, which will scroll the anchor post
|
||||
* back _up_ to the top of the screen.
|
||||
*/
|
||||
const offset = anchorOffsetTop - headerHeight
|
||||
list.scrollToOffset({offset})
|
||||
list.scrollToOffset({
|
||||
offset: anchorOffsetTop - headerHeight,
|
||||
})
|
||||
|
||||
/*
|
||||
* After we manage to do a positive adjustment, we need to ensure this
|
||||
* doesn't run again until scroll handling is requested again via
|
||||
* `shouldHandleScroll.current === true` and a params change via
|
||||
* `prepareForParamsUpdate`.
|
||||
* After the second pass, `deferParents` will be `false`, and we need
|
||||
* to ensure this doesn't run again until scroll handling is requested
|
||||
* again via `shouldHandleScroll.current === true` and a params
|
||||
* change via `prepareForParamsUpdate`.
|
||||
*
|
||||
* The `isRoot` here is needed because if we're looking at the anchor
|
||||
* post, this handler will not fire after `deferParents` is set to
|
||||
* `false`, since there are no parents to render above it. In this case,
|
||||
* we want to make sure `shouldHandleScroll` is set to `false` right away
|
||||
* so that subsequent size changes unrelated to a params change (like
|
||||
* pagination) do not affect scroll.
|
||||
* we want to make sure `shouldHandleScroll` is set to `false` so that
|
||||
* subsequent size changes unrelated to a params change (like pagination)
|
||||
* do not affect scroll.
|
||||
*/
|
||||
if (offset > 0 || isRoot) shouldHandleScroll.current = false
|
||||
if (!deferParents || isRoot) shouldHandleScroll.current = false
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -209,29 +209,17 @@ let ProfileHeaderShell = ({
|
||||
|
||||
{children}
|
||||
|
||||
{!isPlaceholderProfile &&
|
||||
(isMe ? (
|
||||
<LabelsOnMe
|
||||
type="account"
|
||||
labels={profile.labels}
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.pt_xs,
|
||||
a.pb_sm,
|
||||
isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'},
|
||||
]}
|
||||
/>
|
||||
) : (
|
||||
<ProfileHeaderAlerts
|
||||
moderation={moderation}
|
||||
style={[
|
||||
a.px_lg,
|
||||
a.pt_xs,
|
||||
a.pb_sm,
|
||||
isIOS ? a.pointer_events_auto : {pointerEvents: 'box-none'},
|
||||
]}
|
||||
/>
|
||||
))}
|
||||
{!isPlaceholderProfile && (
|
||||
<View
|
||||
style={[a.px_lg, a.pt_xs, a.pb_sm]}
|
||||
pointerEvents={isIOS ? 'auto' : 'box-none'}>
|
||||
{isMe ? (
|
||||
<LabelsOnMe type="account" labels={profile.labels} />
|
||||
) : (
|
||||
<ProfileHeaderAlerts moderation={moderation} />
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
|
||||
<GrowableAvatar style={[a.absolute, {top: 104, left: 10}]}>
|
||||
<TouchableWithoutFeedback
|
||||
|
||||
@@ -195,7 +195,7 @@ function AppPasswordCard({
|
||||
</View>
|
||||
{appPassword.privileged && (
|
||||
<View style={[a.flex_row, a.gap_sm, a.align_center, a.mt_md]}>
|
||||
<WarningIcon style={[{color: colors.warning}]} />
|
||||
<WarningIcon style={[{color: colors.warning[t.scheme]}]} />
|
||||
<Text style={t.atoms.text_contrast_high}>
|
||||
<Trans>Allows access to direct messages</Trans>
|
||||
</Text>
|
||||
|
||||
@@ -134,7 +134,7 @@ export function ActivityNotificationSettingsScreen({}: Props) {
|
||||
<Admonition.Outer type="tip">
|
||||
<Admonition.Row>
|
||||
<Admonition.Icon />
|
||||
<Admonition.Content>
|
||||
<View style={[a.flex_1, a.gap_sm]}>
|
||||
<Admonition.Text>
|
||||
<Trans>
|
||||
Enable notifications for an account by visiting their
|
||||
@@ -166,7 +166,7 @@ export function ActivityNotificationSettingsScreen({}: Props) {
|
||||
.
|
||||
</Trans>
|
||||
</Admonition.Text>
|
||||
</Admonition.Content>
|
||||
</View>
|
||||
</Admonition.Row>
|
||||
</Admonition.Outer>
|
||||
) : (
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import {View} from 'react-native'
|
||||
import {type AppBskyNotificationDeclaration} from '@atproto/api'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -111,7 +112,7 @@ export function PrivacyAndSecuritySettingsScreen({}: Props) {
|
||||
<Admonition.Outer type="tip" style={[a.flex_1]}>
|
||||
<Admonition.Row>
|
||||
<Admonition.Icon />
|
||||
<Admonition.Content>
|
||||
<View style={[a.flex_1, a.gap_sm]}>
|
||||
<Admonition.Text>
|
||||
<Trans>
|
||||
Note: Bluesky is an open and public network. This setting
|
||||
@@ -130,7 +131,7 @@ export function PrivacyAndSecuritySettingsScreen({}: Props) {
|
||||
<Trans>Learn more about what is public on Bluesky.</Trans>
|
||||
</InlineLinkText>
|
||||
</Admonition.Text>
|
||||
</Admonition.Content>
|
||||
</View>
|
||||
</Admonition.Row>
|
||||
</Admonition.Outer>
|
||||
</SettingsList.Item>
|
||||
|
||||
@@ -28,21 +28,9 @@ import {useAgent} from './session'
|
||||
|
||||
export const FEEDBACK_FEEDS = [...PROD_FEEDS, ...STAGING_FEEDS]
|
||||
|
||||
export const THIRD_PARTY_ALLOWED_INTERACTIONS = new Set<
|
||||
export const DIRECT_FEEDBACK_INTERACTIONS = new Set<
|
||||
AppBskyFeedDefs.Interaction['event']
|
||||
>([
|
||||
// These are explicit actions and are therefore fine to send.
|
||||
'app.bsky.feed.defs#requestLess',
|
||||
'app.bsky.feed.defs#requestMore',
|
||||
// These can be inferred from the firehose and are therefore fine to send.
|
||||
'app.bsky.feed.defs#interactionLike',
|
||||
'app.bsky.feed.defs#interactionQuote',
|
||||
'app.bsky.feed.defs#interactionReply',
|
||||
'app.bsky.feed.defs#interactionRepost',
|
||||
// This can be inferred from pagination requests for everything except the very last page
|
||||
// so it is fine to send. It is crucial for third party algorithmic feeds to receive these.
|
||||
'app.bsky.feed.defs#interactionSeen',
|
||||
])
|
||||
>(['app.bsky.feed.defs#requestLess', 'app.bsky.feed.defs#requestMore'])
|
||||
|
||||
const logger = Logger.create(Logger.Context.FeedFeedback)
|
||||
|
||||
@@ -240,7 +228,7 @@ function isInteractionAllowed(
|
||||
return false
|
||||
}
|
||||
const isDiscover = isDiscoverFeed(feed.feedDescriptor)
|
||||
return isDiscover ? true : THIRD_PARTY_ALLOWED_INTERACTIONS.has(interaction)
|
||||
return isDiscover ? true : DIRECT_FEEDBACK_INTERACTIONS.has(interaction)
|
||||
}
|
||||
|
||||
function toString(interaction: AppBskyFeedDefs.Interaction): string {
|
||||
|
||||
@@ -1,45 +1,10 @@
|
||||
import {useEffect, useRef} from 'react'
|
||||
import * as Location from 'expo-location'
|
||||
import {createPermissionHook} from 'expo-modules-core'
|
||||
|
||||
import {logger} from '#/state/geolocation/logger'
|
||||
import {getDeviceGeolocation} from '#/state/geolocation/util'
|
||||
import {device, useStorage} from '#/storage'
|
||||
|
||||
/**
|
||||
* Location.useForegroundPermissions on web just errors if the navigator.permissions API is not available.
|
||||
* We need to catch and ignore it, since it's effectively denied.
|
||||
* @see https://github.com/expo/expo/blob/72f1562ed9cce5ff6dfe04aa415b71632a3d4b87/packages/expo-location/src/Location.ts#L290-L293
|
||||
*/
|
||||
const useForegroundPermissions = createPermissionHook({
|
||||
getMethod: () =>
|
||||
Location.getForegroundPermissionsAsync().catch(error => {
|
||||
logger.debug(
|
||||
'useForegroundPermission: error getting location permissions',
|
||||
{safeMessage: error},
|
||||
)
|
||||
return {
|
||||
status: Location.PermissionStatus.DENIED,
|
||||
granted: false,
|
||||
canAskAgain: false,
|
||||
expires: 0,
|
||||
}
|
||||
}),
|
||||
requestMethod: () =>
|
||||
Location.requestForegroundPermissionsAsync().catch(error => {
|
||||
logger.debug(
|
||||
'useForegroundPermission: error requesting location permissions',
|
||||
{safeMessage: error},
|
||||
)
|
||||
return {
|
||||
status: Location.PermissionStatus.DENIED,
|
||||
granted: false,
|
||||
canAskAgain: false,
|
||||
expires: 0,
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
||||
/**
|
||||
* Hook to get and sync the device geolocation from the device GPS and store it
|
||||
* using device storage. If permissions are not granted, it will clear any cached
|
||||
@@ -47,7 +12,7 @@ const useForegroundPermissions = createPermissionHook({
|
||||
*/
|
||||
export function useSyncedDeviceGeolocation() {
|
||||
const synced = useRef(false)
|
||||
const [status] = useForegroundPermissions()
|
||||
const [status] = Location.useForegroundPermissions()
|
||||
const [deviceGeolocation, setDeviceGeolocation] = useStorage(device, [
|
||||
'deviceGeolocation',
|
||||
])
|
||||
|
||||
@@ -321,15 +321,7 @@ class BskyAppAgent extends BskyAgent {
|
||||
|
||||
// Now the agent is ready.
|
||||
const account = agentToSessionAccountOrThrow(this)
|
||||
let lastSession = this.sessionManager.session
|
||||
this.persistSessionHandler = event => {
|
||||
if (this.sessionManager.session) {
|
||||
lastSession = this.sessionManager.session
|
||||
} else if (event === 'network-error') {
|
||||
// Put it back, we'll try again later.
|
||||
this.sessionManager.session = lastSession
|
||||
}
|
||||
|
||||
onSessionChange(this, account.did, event)
|
||||
if (event !== 'create' && event !== 'update') {
|
||||
addSessionErrorLog(account.did, event)
|
||||
|
||||
@@ -1,27 +1,11 @@
|
||||
import {Text as RNText, View} from 'react-native'
|
||||
import {msg, Trans} from '@lingui/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
import {View} from 'react-native'
|
||||
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {
|
||||
Admonition,
|
||||
Button as AdmonitionButton,
|
||||
Content as AdmonitionContent,
|
||||
Icon as AdmonitionIcon,
|
||||
Outer as AdmonitionOuter,
|
||||
Row as AdmonitionRow,
|
||||
Text as AdmonitionText,
|
||||
} from '#/components/Admonition'
|
||||
import {ButtonIcon, ButtonText} from '#/components/Button'
|
||||
import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise'
|
||||
import {BellRinging_Filled_Corner0_Rounded as BellRingingFilledIcon} from '#/components/icons/BellRinging'
|
||||
import {atoms as a} from '#/alf'
|
||||
import {Admonition} from '#/components/Admonition'
|
||||
import {InlineLinkText} from '#/components/Link'
|
||||
import {H1} from '#/components/Typography'
|
||||
|
||||
export function Admonitions() {
|
||||
const {_} = useLingui()
|
||||
const t = useTheme()
|
||||
|
||||
return (
|
||||
<View style={[a.gap_md]}>
|
||||
<H1>Admonitions</H1>
|
||||
@@ -46,61 +30,6 @@ export function Admonitions() {
|
||||
<Admonition type="error">
|
||||
The quick brown fox jumps over the lazy dog.
|
||||
</Admonition>
|
||||
|
||||
<AdmonitionOuter type="error">
|
||||
<AdmonitionRow>
|
||||
<AdmonitionIcon />
|
||||
<AdmonitionContent>
|
||||
<AdmonitionText>
|
||||
<Trans>Something went wrong, please try again</Trans>
|
||||
</AdmonitionText>
|
||||
</AdmonitionContent>
|
||||
<AdmonitionButton
|
||||
color="negative_subtle"
|
||||
label={_(msg`Retry loading report options`)}
|
||||
onPress={() => {}}>
|
||||
<ButtonText>
|
||||
<Trans>Retry</Trans>
|
||||
</ButtonText>
|
||||
<ButtonIcon icon={Retry} />
|
||||
</AdmonitionButton>
|
||||
</AdmonitionRow>
|
||||
</AdmonitionOuter>
|
||||
|
||||
<AdmonitionOuter type="tip">
|
||||
<AdmonitionRow>
|
||||
<AdmonitionIcon />
|
||||
<AdmonitionContent>
|
||||
<AdmonitionText>
|
||||
<Trans>
|
||||
Enable notifications for an account by visiting their profile
|
||||
and pressing the{' '}
|
||||
<RNText style={[a.font_bold, t.atoms.text_contrast_high]}>
|
||||
bell icon
|
||||
</RNText>{' '}
|
||||
<BellRingingFilledIcon
|
||||
size="xs"
|
||||
style={t.atoms.text_contrast_high}
|
||||
/>
|
||||
.
|
||||
</Trans>
|
||||
</AdmonitionText>
|
||||
<AdmonitionText>
|
||||
<Trans>
|
||||
If you want to restrict who can receive notifications for your
|
||||
account's activity, you can change this in{' '}
|
||||
<InlineLinkText
|
||||
label={_(msg`Privacy and Security settings`)}
|
||||
to={{screen: 'ActivityPrivacySettings'}}
|
||||
style={[a.font_bold]}>
|
||||
Settings → Privacy and Security
|
||||
</InlineLinkText>
|
||||
.
|
||||
</Trans>
|
||||
</AdmonitionText>
|
||||
</AdmonitionContent>
|
||||
</AdmonitionRow>
|
||||
</AdmonitionOuter>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
ButtonIcon,
|
||||
type ButtonSize,
|
||||
ButtonText,
|
||||
StackedButton,
|
||||
} from '#/components/Button'
|
||||
import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron'
|
||||
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
|
||||
@@ -19,30 +18,6 @@ export function Buttons() {
|
||||
<View style={[a.gap_md]}>
|
||||
<Text style={[a.font_bold, a.text_5xl]}>Buttons</Text>
|
||||
|
||||
<View style={[a.flex_row, a.gap_md, a.align_start, {maxWidth: 350}]}>
|
||||
<StackedButton
|
||||
label="stacked"
|
||||
icon={Globe}
|
||||
color="secondary"
|
||||
style={[a.flex_1]}>
|
||||
Bop it
|
||||
</StackedButton>
|
||||
<StackedButton
|
||||
label="stacked"
|
||||
icon={Globe}
|
||||
color="negative_subtle"
|
||||
style={[a.flex_1]}>
|
||||
Twist it
|
||||
</StackedButton>
|
||||
<StackedButton
|
||||
label="stacked"
|
||||
icon={Globe}
|
||||
color="primary"
|
||||
style={[a.flex_1]}>
|
||||
Pull it
|
||||
</StackedButton>
|
||||
</View>
|
||||
|
||||
{[
|
||||
'primary',
|
||||
'secondary',
|
||||
|
||||
@@ -77,6 +77,20 @@
|
||||
tlds "^1.234.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/api@^0.17.0":
|
||||
version "0.17.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/api/-/api-0.17.0.tgz#1fe87ef703f8020dbe00bb5e5cc18622b8b91f4a"
|
||||
integrity sha512-FNS9SW7/3kslAnJH7F4fO9/jPjXzC0NMD6u9NjJ/h4EnaIEpWHZQPkmD9Q2hvAwD6+Uo2boYZEPKkOa55Lr5Dg==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.3"
|
||||
"@atproto/lexicon" "^0.5.1"
|
||||
"@atproto/syntax" "^0.4.1"
|
||||
"@atproto/xrpc" "^0.7.5"
|
||||
await-lock "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
tlds "^1.234.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/aws@^0.2.28":
|
||||
version "0.2.28"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/aws/-/aws-0.2.28.tgz#17bd88a6276e323ebb094a3f01bd94b1173a29a4"
|
||||
@@ -170,6 +184,16 @@
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common-web@^0.4.3":
|
||||
version "0.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common-web/-/common-web-0.4.3.tgz#b4480220b5682db09da45f4ef906eb7619c838b5"
|
||||
integrity sha512-nRDINmSe4VycJzPo6fP/hEltBcULFxt9Kw7fQk6405FyAWZiTluYHlXOnU7GkQfeUK44OENG1qFTBcmCJ7e8pg==
|
||||
dependencies:
|
||||
graphemer "^1.4.0"
|
||||
multiformats "^9.9.0"
|
||||
uint8arrays "3.0.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/common@0.1.0":
|
||||
version "0.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/common/-/common-0.1.0.tgz#4216a8fef5b985ab62ac21252a0f8ca0f4a0f210"
|
||||
@@ -303,6 +327,17 @@
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/lexicon@^0.5.1":
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/lexicon/-/lexicon-0.5.1.tgz#e9b7d5c70dc5a38518a8069cd80fea77ab526947"
|
||||
integrity sha512-y8AEtYmfgVl4fqFxqXAeGvhesiGkxiy3CWoJIfsFDDdTlZUC8DFnZrYhcqkIop3OlCkkljvpSJi1hbeC1tbi8A==
|
||||
dependencies:
|
||||
"@atproto/common-web" "^0.4.3"
|
||||
"@atproto/syntax" "^0.4.1"
|
||||
iso-datestring-validator "^2.2.2"
|
||||
multiformats "^9.9.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/oauth-provider-api@0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/oauth-provider-api/-/oauth-provider-api-0.3.0.tgz#c53a6f2584e6e53746b6cdf233be591fdf7d4355"
|
||||
@@ -516,6 +551,14 @@
|
||||
"@atproto/lexicon" "^0.5.0"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@atproto/xrpc@^0.7.5":
|
||||
version "0.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@atproto/xrpc/-/xrpc-0.7.5.tgz#40cef1a657b5f28af8ebec9e3dac5872e58e88ea"
|
||||
integrity sha512-MUYNn5d2hv8yVegRL0ccHvTHAVj5JSnW07bkbiaz96UH45lvYNRVwt44z+yYVnb0/mvBzyD3/ZQ55TRGt7fHkA==
|
||||
dependencies:
|
||||
"@atproto/lexicon" "^0.5.1"
|
||||
zod "^3.23.8"
|
||||
|
||||
"@aws-crypto/crc32@3.0.0":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@aws-crypto/crc32/-/crc32-3.0.0.tgz#07300eca214409c33e3ff769cd5697b57fdd38fa"
|
||||
@@ -17071,6 +17114,11 @@ react-native-reanimated@^3.19.1:
|
||||
invariant "^2.2.4"
|
||||
react-native-is-edge-to-edge "1.1.7"
|
||||
|
||||
react-native-root-siblings@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-root-siblings/-/react-native-root-siblings-5.0.1.tgz#97e050e5155228f65810fb1c466ff8e769c5272c"
|
||||
integrity sha512-Ay3k/fBj6ReUkWX5WNS+oEAcgPLEGOK8n7K/L7D85mf3xvd8rm/b4spsv26E4HlFzluVx5HKbxEt9cl0wQ1u3g==
|
||||
|
||||
react-native-safe-area-context@~5.6.0:
|
||||
version "5.6.1"
|
||||
resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-5.6.1.tgz#cb4d249ef1a6f7e8fd0cfdfa9764838dffda26b6"
|
||||
|
||||
Reference in New Issue
Block a user