Merge branch 'main' into hailey/use-eas-version-code

This commit is contained in:
Hailey
2024-02-17 15:49:40 -08:00
17 changed files with 289 additions and 203 deletions
+1
View File
@@ -215,6 +215,7 @@
<meta name="application-name" content="Bluesky">
<meta name="generator" content="bskyweb">
<meta property="og:site_name" content="Bluesky Social" />
<link type="application/activity+json" href="" />
{% block html_head_extra -%}{%- endblock %}
</head>
+7 -5
View File
@@ -21,7 +21,6 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context'
import Svg, {Path, SvgProps} from 'react-native-svg'
import {isAndroid} from '#/platform/detection'
import {useThemePrefs} from 'state/shell'
import {Logotype} from '#/view/icons/Logotype'
// @ts-ignore
@@ -75,10 +74,8 @@ export function Splash(props: React.PropsWithChildren<Props>) {
isLayoutReady &&
reduceMotion !== undefined
const {colorMode} = useThemePrefs()
const colorScheme = useColorScheme()
const themeName = colorMode === 'system' ? colorScheme : colorMode
const isDarkMode = themeName === 'dark'
const isDarkMode = colorScheme === 'dark'
const logoAnimation = useAnimatedStyle(() => {
return {
@@ -263,7 +260,12 @@ export function Splash(props: React.PropsWithChildren<Props>) {
<View
style={[
StyleSheet.absoluteFillObject,
{backgroundColor: '#fff'},
{
backgroundColor: isDarkMode
? // special off-spec color for dark mode
'#0F1824'
: '#fff',
},
]}
/>
)}
+36
View File
@@ -122,6 +122,9 @@ export const atoms = {
flex_shrink: {
flexShrink: 1,
},
justify_start: {
justifyContent: 'flex-start',
},
justify_center: {
justifyContent: 'center',
},
@@ -140,10 +143,31 @@ export const atoms = {
align_end: {
alignItems: 'flex-end',
},
self_auto: {
alignSelf: 'auto',
},
self_start: {
alignSelf: 'flex-start',
},
self_end: {
alignSelf: 'flex-end',
},
self_center: {
alignSelf: 'center',
},
self_stretch: {
alignSelf: 'stretch',
},
self_baseline: {
alignSelf: 'baseline',
},
/*
* Text
*/
text_left: {
textAlign: 'left',
},
text_center: {
textAlign: 'center',
},
@@ -195,10 +219,16 @@ export const atoms = {
font_bold: {
fontWeight: tokens.fontWeight.semibold,
},
italic: {
fontStyle: 'italic',
},
/*
* Border
*/
border_0: {
borderWidth: 0,
},
border: {
borderWidth: 1,
},
@@ -208,6 +238,12 @@ export const atoms = {
border_b: {
borderBottomWidth: 1,
},
border_l: {
borderLeftWidth: 1,
},
border_r: {
borderRightWidth: 1,
},
/*
* Shadow
+67 -35
View File
@@ -27,7 +27,7 @@ export type ButtonColor =
| 'gradient_sunset'
| 'gradient_nordic'
| 'gradient_bonfire'
export type ButtonSize = 'small' | 'large'
export type ButtonSize = 'tiny' | 'small' | 'large'
export type ButtonShape = 'round' | 'square' | 'default'
export type VariantProps = {
/**
@@ -48,25 +48,32 @@ export type VariantProps = {
shape?: ButtonShape
}
export type ButtonProps = React.PropsWithChildren<
Pick<PressableProps, 'disabled' | 'onPress'> &
AccessibilityProps &
VariantProps & {
testID?: string
label: string
style?: StyleProp<ViewStyle>
}
>
export type ButtonState = {
hovered: boolean
focused: boolean
pressed: boolean
disabled: boolean
}
export type ButtonContext = VariantProps & ButtonState
export type ButtonProps = Pick<
PressableProps,
'disabled' | 'onPress' | 'testID'
> &
AccessibilityProps &
VariantProps & {
testID?: string
label: string
style?: StyleProp<ViewStyle>
children:
| React.ReactNode
| string
| ((context: ButtonContext) => React.ReactNode | string)
}
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
const Context = React.createContext<
VariantProps & {
hovered: boolean
focused: boolean
pressed: boolean
disabled: boolean
}
>({
const Context = React.createContext<VariantProps & ButtonState>({
hovered: false,
focused: false,
pressed: false,
@@ -277,6 +284,8 @@ export function Button({
baseStyles.push({paddingVertical: 15}, a.px_2xl, a.rounded_sm, a.gap_md)
} else if (size === 'small') {
baseStyles.push({paddingVertical: 9}, a.px_lg, a.rounded_sm, a.gap_sm)
} else if (size === 'tiny') {
baseStyles.push({paddingVertical: 4}, a.px_sm, a.rounded_xs, a.gap_xs)
}
} else if (shape === 'round' || shape === 'square') {
if (size === 'large') {
@@ -287,12 +296,18 @@ export function Button({
}
} else if (size === 'small') {
baseStyles.push({height: 40, width: 40})
} else if (size === 'tiny') {
baseStyles.push({height: 20, width: 20})
}
if (shape === 'round') {
baseStyles.push(a.rounded_full)
} else if (shape === 'square') {
baseStyles.push(a.rounded_sm)
if (size === 'tiny') {
baseStyles.push(a.rounded_xs)
} else {
baseStyles.push(a.rounded_sm)
}
}
}
@@ -338,7 +353,7 @@ export function Button({
}
}, [variant, color])
const context = React.useMemo(
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
@@ -349,6 +364,8 @@ export function Button({
[state, variant, color, size, disabled],
)
const flattenedBaseStyles = flatten(baseStyles)
return (
<Pressable
role="button"
@@ -362,15 +379,14 @@ export function Button({
disabled: disabled || false,
}}
style={[
flatten(style),
a.flex_row,
a.align_center,
a.justify_center,
a.overflow_hidden,
a.justify_center,
...baseStyles,
flattenedBaseStyles,
...(state.hovered || state.pressed ? hoverStyles : []),
...(state.focused ? focusStyles : []),
flatten(style),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
@@ -379,21 +395,31 @@ export function Button({
onFocus={onFocus}
onBlur={onBlur}>
{variant === 'gradient' && (
<LinearGradient
colors={
state.hovered || state.pressed || state.focused
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
<View
style={[
a.absolute,
a.inset_0,
a.overflow_hidden,
{borderRadius: flattenedBaseStyles.borderRadius},
]}>
<LinearGradient
colors={
state.hovered || state.pressed || state.focused
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
</View>
)}
<Context.Provider value={context}>
{typeof children === 'string' ? (
<ButtonText>{children}</ButtonText>
) : typeof children === 'function' ? (
children(context)
) : (
children
)}
@@ -493,6 +519,8 @@ export function useSharedButtonTextStyles() {
if (size === 'large') {
baseStyles.push(a.text_md, android({paddingBottom: 1}))
} else if (size === 'tiny') {
baseStyles.push(a.text_xs, android({paddingBottom: 1}))
} else {
baseStyles.push(a.text_sm, android({paddingBottom: 1}))
}
@@ -514,9 +542,11 @@ export function ButtonText({children, style, ...rest}: ButtonTextProps) {
export function ButtonIcon({
icon: Comp,
position,
size: iconSize,
}: {
icon: React.ComponentType<SVGIconProps>
position?: 'left' | 'right'
size?: SVGIconProps['size']
}) {
const {size, disabled} = useButtonContext()
const textStyles = useSharedButtonTextStyles()
@@ -532,7 +562,9 @@ export function ButtonIcon({
},
]}>
<Comp
size={size === 'large' ? 'md' : 'sm'}
size={
iconSize ?? (size === 'large' ? 'md' : size === 'tiny' ? 'xs' : 'sm')
}
style={[{color: textStyles.color, pointerEvents: 'none'}]}
/>
</View>
+53 -32
View File
@@ -13,7 +13,7 @@ import {sanitizeUrl} from '@braintree/sanitize-url'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {isWeb} from '#/platform/detection'
import {useTheme, web, flatten, TextStyleProp} from '#/alf'
import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf'
import {Button, ButtonProps} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {
@@ -35,6 +35,13 @@ type BaseLinkProps = Pick<
Parameters<typeof useLinkProps<AllNavigatorParams>>[0],
'to'
> & {
testID?: string
/**
* Label for a11y. Defaults to the href.
*/
label?: string
/**
* The React Navigation `StackAction` to perform when the link is pressed.
*/
@@ -46,6 +53,18 @@ type BaseLinkProps = Pick<
* Note: atm this only works for `InlineLink`s with a string child.
*/
warnOnMismatchingTextChild?: boolean
/**
* Callback for when the link is pressed.
*
* DO NOT use this for navigation, that's what the `to` prop is for.
*/
onPress?: (e: GestureResponderEvent) => void
/**
* Web-only attribute. Sets `download` attr on web.
*/
download?: string
}
export function useLink({
@@ -53,6 +72,7 @@ export function useLink({
displayText,
action = 'push',
warnOnMismatchingTextChild,
onPress: outerOnPress,
}: BaseLinkProps & {
displayText: string
}) {
@@ -66,6 +86,8 @@ export function useLink({
const onPress = React.useCallback(
(e: GestureResponderEvent) => {
outerOnPress?.(e)
const requiresWarning = Boolean(
warnOnMismatchingTextChild &&
displayText &&
@@ -132,6 +154,7 @@ export function useLink({
displayText,
closeModal,
openModal,
outerOnPress,
],
)
@@ -143,16 +166,7 @@ export function useLink({
}
export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> &
Omit<ButtonProps, 'style' | 'onPress' | 'disabled' | 'label'> & {
/**
* Label for a11y. Defaults to the href.
*/
label?: string
/**
* Web-only attribute. Sets `download` attr on web.
*/
download?: string
}
Omit<ButtonProps, 'onPress' | 'disabled' | 'label'>
/**
* A interactive element that renders as a `<a>` tag on the web. On mobile it
@@ -166,6 +180,7 @@ export function Link({
children,
to,
action = 'push',
onPress: outerOnPress,
download,
...rest
}: LinkProps) {
@@ -173,24 +188,26 @@ export function Link({
to,
displayText: typeof children === 'string' ? children : '',
action,
onPress: outerOnPress,
})
return (
<Button
label={href}
{...rest}
style={[a.justify_start, flatten(rest.style)]}
role="link"
accessibilityRole="link"
href={href}
onPress={onPress}
onPress={download ? undefined : onPress}
{...web({
hrefAttrs: {
target: isExternal ? 'blank' : undefined,
target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
download,
},
dataSet: {
// default to no underline, apply this ourselves
// no underline, only `InlineLink` has underlines
noUnderline: '1',
},
})}>
@@ -200,13 +217,7 @@ export function Link({
}
export type InlineLinkProps = React.PropsWithChildren<
BaseLinkProps &
TextStyleProp & {
/**
* Label for a11y. Defaults to the href.
*/
label?: string
}
BaseLinkProps & TextStyleProp
>
export function InlineLink({
@@ -215,6 +226,8 @@ export function InlineLink({
action = 'push',
warnOnMismatchingTextChild,
style,
onPress: outerOnPress,
download,
...rest
}: InlineLinkProps) {
const t = useTheme()
@@ -224,18 +237,25 @@ export function InlineLink({
displayText: stringChildren ? children : '',
action,
warnOnMismatchingTextChild,
onPress: outerOnPress,
})
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
const flattenedStyle = flatten(style)
return (
<TouchableWithoutFeedback
accessibilityRole="button"
onPress={onPress}
onPress={download ? undefined : onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
@@ -245,27 +265,28 @@ export function InlineLink({
{...rest}
style={[
{color: t.palette.primary_500},
(focused || pressed) && {
(hovered || focused || pressed) && {
outline: 0,
textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500,
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
},
flatten(style),
flattenedStyle,
]}
role="link"
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
accessibilityRole="link"
href={href}
{...web({
hrefAttrs: {
target: isExternal ? 'blank' : undefined,
target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
download,
},
dataSet: {
// default to no underline, apply this ourselves
noUnderline: '1',
},
dataSet: stringChildren
? {}
: {
// default to no underline, apply this ourselves
noUnderline: '1',
},
})}>
{children}
</Text>
+11 -2
View File
@@ -7,11 +7,12 @@ import Animated, {
withTiming,
} from 'react-native-reanimated'
import {atoms as a} from '#/alf'
import {atoms as a, useTheme, flatten} from '#/alf'
import {Props, useCommonSVGProps} from '#/components/icons/common'
import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader'
export function Loader(props: Props) {
const t = useTheme()
const common = useCommonSVGProps(props)
const rotation = useSharedValue(0)
@@ -35,7 +36,15 @@ export function Loader(props: Props) {
{width: common.size, height: common.size},
animatedStyles,
]}>
<Icon {...props} style={[a.absolute, a.inset_0, props.style]} />
<Icon
{...props}
style={[
a.absolute,
a.inset_0,
t.atoms.text_contrast_high,
flatten(props.style),
]}
/>
</Animated.View>
)
}
+19
View File
@@ -344,6 +344,25 @@ export const dimTheme: Theme = {
default: {
...darkTheme.palette.default,
background: dimPalette.black,
backgroundLight: dimPalette.contrast_50,
text: dimPalette.white,
textLight: dimPalette.contrast_700,
textInverted: dimPalette.black,
link: dimPalette.primary_500,
border: dimPalette.contrast_100,
borderDark: dimPalette.contrast_200,
icon: dimPalette.contrast_500,
// non-standard
textVeryLight: dimPalette.contrast_400,
replyLine: dimPalette.contrast_100,
replyLineDot: dimPalette.contrast_200,
unreadNotifBg: dimPalette.primary_975,
unreadNotifBorder: dimPalette.primary_900,
postCtrl: dimPalette.contrast_500,
brandText: dimPalette.primary_500,
emptyStateIcon: dimPalette.contrast_300,
borderLinkHover: dimPalette.contrast_300,
},
},
}
-6
View File
@@ -7,7 +7,6 @@ import {networkRetry} from '#/lib/async/retry'
import {logger} from '#/logger'
import * as persisted from '#/state/persisted'
import {PUBLIC_BSKY_AGENT} from '#/state/queries'
import {IS_PROD} from '#/lib/constants'
import {emitSessionDropped} from '../events'
import {useLoggedOutViewControls} from '#/state/shell/logged-out'
import {useCloseAllActiveElements} from '#/state/util'
@@ -36,7 +35,6 @@ export type SessionState = {
}
export type StateContext = SessionState & {
hasSession: boolean
isSandbox: boolean
}
export type ApiContext = {
createAccount: (props: {
@@ -84,7 +82,6 @@ const StateContext = React.createContext<StateContext>({
accounts: [],
currentAccount: undefined,
hasSession: false,
isSandbox: false,
})
const ApiContext = React.createContext<ApiContext>({
@@ -610,9 +607,6 @@ export function Provider({children}: React.PropsWithChildren<{}>) {
() => ({
...state,
hasSession: !!state.currentAccount,
isSandbox: state.currentAccount
? !IS_PROD(state.currentAccount?.service)
: false,
}),
[state],
)
+3 -12
View File
@@ -46,7 +46,7 @@ export function FeedPage({
renderEmptyState: () => JSX.Element
renderEndOfFeed?: () => JSX.Element
}) {
const {isSandbox, hasSession} = useSession()
const {hasSession} = useSession()
const pal = usePalette('default')
const {_} = useLingui()
const navigation = useNavigation()
@@ -119,7 +119,7 @@ export function FeedPage({
style={[pal.text, {fontWeight: 'bold'}]}
text={
<>
{isSandbox ? 'SANDBOX' : 'Bluesky'}{' '}
Bluesky{' '}
{hasNew && (
<View
style={{
@@ -154,16 +154,7 @@ export function FeedPage({
)
}
return <></>
}, [
isDesktop,
pal.view,
pal.text,
pal.textLight,
hasNew,
_,
isSandbox,
hasSession,
])
}, [isDesktop, pal.view, pal.text, pal.textLight, hasNew, _, hasSession])
return (
<View testID={testID} style={s.h100pct}>
+1 -18
View File
@@ -37,7 +37,6 @@ export function FeedsTabBar(
function FeedsTabBarPublic() {
const pal = usePalette('default')
const {isSandbox} = useSession()
return (
<CenteredView sideBorders>
@@ -56,23 +55,7 @@ function FeedsTabBarPublic() {
type="title-lg"
href="/"
style={[pal.text, {fontWeight: 'bold'}]}
text={
<>
{isSandbox ? 'SANDBOX' : 'Bluesky'}{' '}
{/*hasNew && (
<View
style={{
top: -8,
backgroundColor: colors.blue3,
width: 8,
height: 8,
borderRadius: 4,
}}
/>
)*/}
</>
}
// onPress={emitSoftReset}
text="Bluesky "
/>
</View>
</CenteredView>
+60 -26
View File
@@ -43,10 +43,13 @@ import {
usePreferencesQuery,
} from '#/state/queries/preferences'
import {useSession} from '#/state/session'
import {isAndroid, isNative} from '#/platform/detection'
import {logger} from '#/logger'
import {isAndroid, isNative, isWeb} from '#/platform/detection'
import {moderatePost_wrapped as moderatePost} from '#/lib/moderatePost_wrapped'
// FlatList maintainVisibleContentPosition breaks if too many items
// are prepended. This seems to be an optimal number based on *shrug*.
const PARENTS_CHUNK_SIZE = 15
const MAINTAIN_VISIBLE_CONTENT_POSITION = {
// We don't insert any elements before the root row while loading.
// So the row we want to use as the scroll anchor is the first row.
@@ -165,8 +168,10 @@ function PostThreadLoaded({
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
const ref = useRef<ListMethods>(null)
const highlightedPostRef = useRef<View | null>(null)
const [maxVisible, setMaxVisible] = React.useState(100)
const [isPTRing, setIsPTRing] = React.useState(false)
const [maxParents, setMaxParents] = React.useState(
isWeb ? Infinity : PARENTS_CHUNK_SIZE,
)
const [maxReplies, setMaxReplies] = React.useState(100)
const treeView = React.useMemo(
() => !!threadViewPrefs.lab_treeViewEnabled && hasBranchingReplies(thread),
[threadViewPrefs, thread],
@@ -206,10 +211,18 @@ function PostThreadLoaded({
// maintainVisibleContentPosition and onContentSizeChange
// to "hold onto" the correct row instead of the first one.
} else {
// Everything is loaded.
arr.push(TOP_COMPONENT)
for (const parent of parents) {
arr.push(parent)
// Everything is loaded
let startIndex = Math.max(0, parents.length - maxParents)
if (startIndex === 0) {
arr.push(TOP_COMPONENT)
} else {
// When progressively revealing parents, rendering a placeholder
// here will cause scrolling jumps. Don't add it unless you test it.
// QT'ing this thread is a great way to test all the scrolling hacks:
// https://bsky.app/profile/www.mozzius.dev/post/3kjqhblh6qk2o
}
for (let i = startIndex; i < parents.length; i++) {
arr.push(parents[i])
}
}
}
@@ -220,17 +233,18 @@ function PostThreadLoaded({
if (highlightedPost.ctx.isChildLoading) {
arr.push(CHILD_SPINNER)
} else {
for (const reply of replies) {
arr.push(reply)
for (let i = 0; i < replies.length; i++) {
arr.push(replies[i])
if (i === maxReplies) {
arr.push(LOAD_MORE)
break
}
}
arr.push(BOTTOM_COMPONENT)
}
}
if (arr.length > maxVisible) {
arr = arr.slice(0, maxVisible).concat([LOAD_MORE])
}
return arr
}, [skeleton, maxVisible, deferParents])
}, [skeleton, deferParents, maxParents, maxReplies])
// This is only used on the web to keep the post in view when its parents load.
// On native, we rely on `maintainVisibleContentPosition` instead.
@@ -258,15 +272,28 @@ function PostThreadLoaded({
}
}, [thread])
const onPTR = React.useCallback(async () => {
setIsPTRing(true)
try {
await onRefresh()
} catch (err) {
logger.error('Failed to refresh posts thread', {message: err})
// On native, we reveal parents in chunks. Although they're all already
// loaded and FlatList already has its own virtualization, unfortunately FlatList
// has a bug that causes the content to jump around if too many items are getting
// prepended at once. It also jumps around if items get prepended during scroll.
// To work around this, we prepend rows after scroll bumps against the top and rests.
const needsBumpMaxParents = React.useRef(false)
const onStartReached = React.useCallback(() => {
if (maxParents < skeleton.parents.length) {
needsBumpMaxParents.current = true
}
setIsPTRing(false)
}, [setIsPTRing, onRefresh])
}, [maxParents, skeleton.parents.length])
const bumpMaxParentsIfNeeded = React.useCallback(() => {
if (!isNative) {
return
}
if (needsBumpMaxParents.current) {
needsBumpMaxParents.current = false
setMaxParents(n => n + PARENTS_CHUNK_SIZE)
}
}, [])
const onMomentumScrollEnd = bumpMaxParentsIfNeeded
const onScrollToTop = bumpMaxParentsIfNeeded
const renderItem = React.useCallback(
({item, index}: {item: RowItem; index: number}) => {
@@ -301,7 +328,7 @@ function PostThreadLoaded({
} else if (item === LOAD_MORE) {
return (
<Pressable
onPress={() => setMaxVisible(n => n + 50)}
onPress={() => setMaxReplies(n => n + 50)}
style={[pal.border, pal.view, styles.itemContainer]}
accessibilityLabel={_(msg`Load more posts`)}
accessibilityHint="">
@@ -345,6 +372,8 @@ function PostThreadLoaded({
const next = isThreadPost(posts[index - 1])
? (posts[index - 1] as ThreadPost)
: undefined
const hasUnrevealedParents =
index === 0 && maxParents < skeleton.parents.length
return (
<View
ref={item.ctx.isHighlightedPost ? highlightedPostRef : undefined}
@@ -360,7 +389,9 @@ function PostThreadLoaded({
hasMore={item.ctx.hasMore}
showChildReplyLine={item.ctx.showChildReplyLine}
showParentReplyLine={item.ctx.showParentReplyLine}
hasPrecedingItem={!!prev?.ctx.showChildReplyLine}
hasPrecedingItem={
!!prev?.ctx.showChildReplyLine || hasUnrevealedParents
}
onPostReply={onRefresh}
/>
</View>
@@ -383,6 +414,8 @@ function PostThreadLoaded({
onRefresh,
deferParents,
treeView,
skeleton.parents.length,
maxParents,
_,
],
)
@@ -393,9 +426,10 @@ function PostThreadLoaded({
data={posts}
keyExtractor={item => item._reactKey}
renderItem={renderItem}
refreshing={isPTRing}
onRefresh={onPTR}
onContentSizeChange={isNative ? undefined : onContentSizeChangeWeb}
onStartReached={onStartReached}
onMomentumScrollEnd={onMomentumScrollEnd}
onScrollToTop={onScrollToTop}
maintainVisibleContentPosition={
isNative ? MAINTAIN_VISIBLE_CONTENT_POSITION : undefined
}
+14 -18
View File
@@ -27,7 +27,6 @@ import {PostCtrls} from '../util/post-ctrls/PostCtrls'
import {PostHider} from '../util/moderation/PostHider'
import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts'
import {PostSandboxWarning} from '../util/PostSandboxWarning'
import {ErrorMessage} from '../util/error/ErrorMessage'
import {usePalette} from 'lib/hooks/usePalette'
import {formatCount} from '../util/numeric/format'
@@ -44,6 +43,7 @@ import {Shadow, usePostShadow, POST_TOMBSTONE} from '#/state/cache/post-shadow'
import {ThreadPost} from '#/state/queries/post-thread'
import {useSession} from 'state/session'
import {WhoCanReply} from '../threadgate/WhoCanReply'
import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
export function PostThreadItem({
post,
@@ -164,8 +164,6 @@ let PostThreadItemLoaded = ({
() => countLines(richText?.text) >= MAX_POST_LINES,
)
const {currentAccount} = useSession()
const hasEngagement = post.likeCount || post.repostCount
const rootUri = record.reply?.root?.uri || post.uri
const postHref = React.useMemo(() => {
const urip = new AtUri(post.uri)
@@ -248,7 +246,6 @@ let PostThreadItemLoaded = ({
testID={`postThreadItem-by-${post.author.handle}`}
style={[styles.outer, styles.outerHighlighted, pal.border, pal.view]}
accessible={false}>
<PostSandboxWarning />
<View style={[styles.layout]}>
<View style={[styles.layoutAvi, {paddingBottom: 8}]}>
<PreviewableUserAvatar
@@ -357,9 +354,16 @@ let PostThreadItemLoaded = ({
translatorUrl={translatorUrl}
needsTranslation={needsTranslation}
/>
{hasEngagement ? (
{post.repostCount !== 0 || post.likeCount !== 0 ? (
// Show this section unless we're *sure* it has no engagement.
<View style={[styles.expandedInfo, pal.border]}>
{post.repostCount ? (
{post.repostCount == null && post.likeCount == null && (
// If we're still loading and not sure, assume this post has engagement.
// This lets us avoid a layout shift for the common case (embedded post with likes/reposts).
// TODO: embeds should include metrics to avoid us having to guess.
<LoadingPlaceholder width={50} height={20} />
)}
{post.repostCount != null && post.repostCount !== 0 ? (
<Link
style={styles.expandedInfoItem}
href={repostsHref}
@@ -374,10 +378,8 @@ let PostThreadItemLoaded = ({
{pluralize(post.repostCount, 'repost')}
</Text>
</Link>
) : (
<></>
)}
{post.likeCount ? (
) : null}
{post.likeCount != null && post.likeCount !== 0 ? (
<Link
style={styles.expandedInfoItem}
href={likesHref}
@@ -392,13 +394,9 @@ let PostThreadItemLoaded = ({
{pluralize(post.likeCount, 'like')}
</Text>
</Link>
) : (
<></>
)}
) : null}
</View>
) : (
<></>
)}
) : null}
<View style={[s.pl10, s.pr10, s.pb5]}>
<PostCtrls
big
@@ -438,8 +436,6 @@ let PostThreadItemLoaded = ({
? {marginRight: 4}
: {marginLeft: 2, marginRight: 2}
}>
<PostSandboxWarning />
<View
style={{
flexDirection: 'row',
-3
View File
@@ -21,7 +21,6 @@ import {PostEmbeds} from '../util/post-embeds'
import {ContentHider} from '../util/moderation/ContentHider'
import {PostAlerts} from '../util/moderation/PostAlerts'
import {RichText} from '../util/text/RichText'
import {PostSandboxWarning} from '../util/PostSandboxWarning'
import {PreviewableUserAvatar} from '../util/UserAvatar'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
@@ -160,8 +159,6 @@ let FeedItemInner = ({
href={href}
noFeedback
accessible={false}>
<PostSandboxWarning />
<View style={{flexDirection: 'row', gap: 10, paddingLeft: 8}}>
<View style={{width: 52}}>
{isThreadChild && (
-35
View File
@@ -1,35 +0,0 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {Text} from './text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {useSession} from '#/state/session'
export function PostSandboxWarning() {
const {isSandbox} = useSession()
const pal = usePalette('default')
if (isSandbox) {
return (
<View style={styles.container}>
<Text
type="title-2xl"
style={[pal.text, styles.text]}
accessible={false}>
SANDBOX
</Text>
</View>
)
}
return null
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 6,
right: 10,
},
text: {
fontWeight: 'bold',
opacity: 0.07,
},
})
+9
View File
@@ -6,6 +6,7 @@ import {H1} from '#/components/Typography'
import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe'
import {ArrowTopRight_Stroke2_Corner0_Rounded as ArrowTopRight} from '#/components/icons/ArrowTopRight'
import {CalendarDays_Stroke2_Corner0_Rounded as CalendarDays} from '#/components/icons/CalendarDays'
import {Loader} from '#/components/Loader'
export function Icons() {
const t = useTheme()
@@ -36,6 +37,14 @@ export function Icons() {
<CalendarDays size="lg" fill={t.atoms.text.color} />
<CalendarDays size="xl" fill={t.atoms.text.color} />
</View>
<View style={[a.flex_row, a.gap_xl]}>
<Loader size="xs" fill={t.atoms.text.color} />
<Loader size="sm" fill={t.atoms.text.color} />
<Loader size="md" fill={t.atoms.text.color} />
<Loader size="lg" fill={t.atoms.text.color} />
<Loader size="xl" fill={t.atoms.text.color} />
</View>
</View>
)
}
+6 -1
View File
@@ -19,9 +19,14 @@ export function Links() {
style={[a.text_md]}>
External
</InlineLink>
<InlineLink to="https://bsky.social" style={[a.text_md]}>
<InlineLink to="https://bsky.social" style={[a.text_md, t.atoms.text]}>
<H3>External with custom children</H3>
</InlineLink>
<InlineLink
to="https://bsky.social"
style={[a.text_md, t.atoms.text_contrast_low]}>
External with custom children
</InlineLink>
<InlineLink
to="https://bsky.social"
warnOnMismatchingTextChild
+2 -10
View File
@@ -9,14 +9,13 @@ import {FEEDBACK_FORM_URL, HELP_DESK_URL} from 'lib/constants'
import {s} from 'lib/styles'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {useLingui} from '@lingui/react'
import {Trans, msg} from '@lingui/macro'
import {msg} from '@lingui/macro'
import {useSession} from '#/state/session'
export function DesktopRightNav({routeName}: {routeName: string}) {
const pal = usePalette('default')
const palError = usePalette('error')
const {_} = useLingui()
const {isSandbox, hasSession, currentAccount} = useSession()
const {hasSession, currentAccount} = useSession()
const {isTablet} = useWebMediaQueries()
if (isTablet) {
@@ -49,13 +48,6 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
paddingTop: hasSession ? 0 : 18,
},
]}>
{isSandbox ? (
<View style={[palError.view, styles.messageLine, s.p10]}>
<Text type="md" style={[palError.text, s.bold]}>
<Trans>SANDBOX. Posts and accounts are not permanent.</Trans>
</Text>
</View>
) : undefined}
<View style={[{flexWrap: 'wrap'}, s.flexRow]}>
{hasSession && (
<>