Merge branch 'bluesky-social:main' into patch-3

zio/stable
Minseo Lee 2024-02-24 18:23:03 +09:00 committed by GitHub
commit 89c65c856e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
46 changed files with 3732 additions and 3299 deletions

View File

@ -191,7 +191,7 @@ func serve(cctx *cli.Context) error {
e.GET("/settings", server.WebGeneric) e.GET("/settings", server.WebGeneric)
e.GET("/settings/language", server.WebGeneric) e.GET("/settings/language", server.WebGeneric)
e.GET("/settings/app-passwords", server.WebGeneric) e.GET("/settings/app-passwords", server.WebGeneric)
e.GET("/settings/home-feed", server.WebGeneric) e.GET("/settings/following-feed", server.WebGeneric)
e.GET("/settings/saved-feeds", server.WebGeneric) e.GET("/settings/saved-feeds", server.WebGeneric)
e.GET("/settings/threads", server.WebGeneric) e.GET("/settings/threads", server.WebGeneric)
e.GET("/settings/external-embeds", server.WebGeneric) e.GET("/settings/external-embeds", server.WebGeneric)

View File

@ -0,0 +1,56 @@
diff --git a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
index ef4f368..2b0da35 100644
--- a/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
+++ b/node_modules/@react-navigation/native/lib/commonjs/useLinking.js
@@ -273,8 +273,12 @@ function useLinking(ref, _ref) {
});
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
- // An existing entry for this path exists and it's less than current index, go back to that
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {
// We couldn't find an existing entry to go back to, so we'll go back by the delta
diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js
index 62a3b43..11a5a28 100644
--- a/node_modules/@react-navigation/native/lib/module/useLinking.js
+++ b/node_modules/@react-navigation/native/lib/module/useLinking.js
@@ -264,8 +264,12 @@ export default function useLinking(ref, _ref) {
});
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
- // An existing entry for this path exists and it's less than current index, go back to that
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) { // An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {
// We couldn't find an existing entry to go back to, so we'll go back by the delta
diff --git a/node_modules/@react-navigation/native/src/useLinking.tsx b/node_modules/@react-navigation/native/src/useLinking.tsx
index 3db40b7..9ba4ecd 100644
--- a/node_modules/@react-navigation/native/src/useLinking.tsx
+++ b/node_modules/@react-navigation/native/src/useLinking.tsx
@@ -381,7 +381,12 @@ export default function useLinking(
const currentIndex = history.index;
try {
- if (nextIndex !== -1 && nextIndex < currentIndex) {
+ if (
+ nextIndex !== -1 &&
+ nextIndex < currentIndex &&
+ // We should only go back if the entry exists and it's less than current index
+ history.get(nextIndex - currentIndex)
+ ) {
// An existing entry for this path exists and it's less than current index, go back to that
await history.go(nextIndex - currentIndex);
} else {

View File

@ -0,0 +1,5 @@
# React Navigation history bug patch
This patches react-navigation to fix the issues in https://github.com/bluesky-social/social-app/issues/710.
This is based on the PR found at https://github.com/react-navigation/react-navigation/pull/11833

View File

@ -71,7 +71,7 @@ import {AppPasswords} from 'view/screens/AppPasswords'
import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts' import {ModerationMutedAccounts} from 'view/screens/ModerationMutedAccounts'
import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts' import {ModerationBlockedAccounts} from 'view/screens/ModerationBlockedAccounts'
import {SavedFeeds} from 'view/screens/SavedFeeds' import {SavedFeeds} from 'view/screens/SavedFeeds'
import {PreferencesHomeFeed} from 'view/screens/PreferencesHomeFeed' import {PreferencesFollowingFeed} from 'view/screens/PreferencesFollowingFeed'
import {PreferencesThreads} from 'view/screens/PreferencesThreads' import {PreferencesThreads} from 'view/screens/PreferencesThreads'
import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds' import {PreferencesExternalEmbeds} from '#/view/screens/PreferencesExternalEmbeds'
import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth' import {createNativeStackNavigatorWithAuth} from './view/shell/createNativeStackNavigatorWithAuth'
@ -242,9 +242,12 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
options={{title: title(msg`Edit My Feeds`), requireAuth: true}} options={{title: title(msg`Edit My Feeds`), requireAuth: true}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesHomeFeed" name="PreferencesFollowingFeed"
getComponent={() => PreferencesHomeFeed} getComponent={() => PreferencesFollowingFeed}
options={{title: title(msg`Home Feed Preferences`), requireAuth: true}} options={{
title: title(msg`Following Feed Preferences`),
requireAuth: true,
}}
/> />
<Stack.Screen <Stack.Screen
name="PreferencesThreads" name="PreferencesThreads"

View File

@ -73,19 +73,19 @@ export const darkPalette: Palette = {
white: tokens.color.gray_0, white: tokens.color.gray_0,
black: tokens.color.trueBlack, black: tokens.color.trueBlack,
contrast_25: tokens.color.gray_1000, contrast_25: `hsl(211, 28%, 8%)`,
contrast_50: tokens.color.gray_975, contrast_50: `hsl(211, 28%, 11%)`,
contrast_100: tokens.color.gray_950, contrast_100: `hsl(211, 28%, 16%)`,
contrast_200: tokens.color.gray_900, contrast_200: `hsl(211, 28%, 24%)`,
contrast_300: tokens.color.gray_800, contrast_300: `hsl(211, 24%, 31%)`,
contrast_400: tokens.color.gray_700, contrast_400: `hsl(211, 24%, 38%)`,
contrast_500: tokens.color.gray_600, contrast_500: `hsl(211, 20%, 44%)`,
contrast_600: tokens.color.gray_500, contrast_600: `hsl(211, 20%, 55%)`,
contrast_700: tokens.color.gray_400, contrast_700: `hsl(211, 20%, 63%)`,
contrast_800: tokens.color.gray_300, contrast_800: `hsl(211, 20%, 71%)`,
contrast_900: tokens.color.gray_200, contrast_900: `hsl(211, 20%, 79%)`,
contrast_950: tokens.color.gray_100, contrast_950: `hsl(211, 20%, 87%)`,
contrast_975: tokens.color.gray_50, contrast_975: `hsl(211, 20%, 95%)`,
primary_25: tokens.color.blue_25, primary_25: tokens.color.blue_25,
primary_50: tokens.color.blue_50, primary_50: tokens.color.blue_50,
@ -132,21 +132,28 @@ export const darkPalette: Palette = {
export const dimPalette: Palette = { export const dimPalette: Palette = {
...darkPalette, ...darkPalette,
black: tokens.color.gray_1000, black: `hsl(211, 28%, 12%)`,
contrast_25: tokens.color.gray_975, contrast_25: `hsl(211, 28%, 15%)`,
contrast_50: tokens.color.gray_950, contrast_50: `hsl(211, 28%, 18%)`,
contrast_100: tokens.color.gray_900, contrast_100: `hsl(211, 28%, 24%)`,
contrast_200: tokens.color.gray_800, contrast_200: `hsl(211, 28%, 27%)`,
contrast_300: tokens.color.gray_700, contrast_300: `hsl(211, 24%, 34%)`,
contrast_400: tokens.color.gray_600, contrast_400: `hsl(211, 24%, 41%)`,
contrast_500: tokens.color.gray_500, contrast_500: `hsl(211, 20%, 52%)`,
contrast_600: tokens.color.gray_400, contrast_600: `hsl(211, 20%, 55%)`,
contrast_700: tokens.color.gray_300, contrast_700: `hsl(211, 20%, 67%)`,
contrast_800: tokens.color.gray_200, contrast_800: `hsl(211, 20%, 71%)`,
contrast_900: tokens.color.gray_100, contrast_900: `hsl(211, 20%, 79%)`,
contrast_950: tokens.color.gray_50, contrast_950: `hsl(211, 20%, 87%)`,
contrast_975: tokens.color.gray_25, contrast_975: `hsl(211, 20%, 95%)`,
primary_600: `hsl(211, 95%, 39%)`,
primary_700: `hsl(211, 90%, 30%)`,
primary_800: `hsl(211, 90%, 23%)`,
primary_900: `hsl(211, 80%, 16%)`,
primary_950: `hsl(211, 80%, 13%)`,
primary_975: `hsl(211, 80%, 10%)`,
} as const } as const
export const light = { export const light = {
@ -325,6 +332,7 @@ export const dark: Theme = {
export const dim: Theme = { export const dim: Theme = {
...dark, ...dark,
name: 'dim', name: 'dim',
palette: dimPalette,
atoms: { atoms: {
...dark.atoms, ...dark.atoms,
text: { text: {
@ -393,5 +401,20 @@ export const dim: Theme = {
border_contrast_high: { border_contrast_high: {
borderColor: dimPalette.contrast_300, borderColor: dimPalette.contrast_300,
}, },
shadow_sm: {
...atoms.shadow_sm,
shadowOpacity: 0.7,
shadowColor: `hsl(211, 28%, 3%)`,
},
shadow_md: {
...atoms.shadow_md,
shadowOpacity: 0.7,
shadowColor: `hsl(211, 28%, 3%)`,
},
shadow_lg: {
...atoms.shadow_lg,
shadowOpacity: 0.7,
shadowColor: `hsl(211, 28%, 3%)`,
},
}, },
} }

View File

@ -1,5 +1,5 @@
import React from 'react' import React from 'react'
import {GestureResponderEvent, Linking} from 'react-native' import {GestureResponderEvent} from 'react-native'
import { import {
useLinkProps, useLinkProps,
useNavigation, useNavigation,
@ -20,6 +20,7 @@ import {
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {router} from '#/routes' import {router} from '#/routes'
import {Text, TextProps} from '#/components/Typography' import {Text, TextProps} from '#/components/Typography'
import {useOpenLink} from 'state/preferences/in-app-browser'
/** /**
* Only available within a `Link`, since that inherits from `Button`. * Only available within a `Link`, since that inherits from `Button`.
@ -80,6 +81,7 @@ export function useLink({
}) })
const isExternal = isExternalUrl(href) const isExternal = isExternalUrl(href)
const {openModal, closeModal} = useModalControls() const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink()
const onPress = React.useCallback( const onPress = React.useCallback(
(e: GestureResponderEvent) => { (e: GestureResponderEvent) => {
@ -106,7 +108,7 @@ export function useLink({
e.preventDefault() e.preventDefault()
if (isExternal) { if (isExternal) {
Linking.openURL(href) openLink(href)
} else { } else {
/** /**
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch * A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
@ -124,7 +126,7 @@ export function useLink({
href.startsWith('http') || href.startsWith('http') ||
href.startsWith('mailto') href.startsWith('mailto')
) { ) {
Linking.openURL(href) openLink(href)
} else { } else {
closeModal() // close any active modals closeModal() // close any active modals
@ -145,15 +147,16 @@ export function useLink({
} }
}, },
[ [
href,
isExternal,
warnOnMismatchingTextChild,
navigation,
action,
displayText,
closeModal,
openModal,
outerOnPress, outerOnPress,
warnOnMismatchingTextChild,
displayText,
isExternal,
href,
openModal,
openLink,
closeModal,
action,
navigation,
], ],
) )
@ -260,7 +263,7 @@ export function InlineLink({
style={[ style={[
{color: t.palette.primary_500}, {color: t.palette.primary_500},
(hovered || focused || pressed) && { (hovered || focused || pressed) && {
outline: 0, ...web({outline: 0}),
textDecorationLine: 'underline', textDecorationLine: 'underline',
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500, textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
}, },

View File

@ -98,7 +98,7 @@ export function DateField({
timeZoneName={'Etc/UTC'} timeZoneName={'Etc/UTC'}
display="spinner" display="spinner"
// @ts-ignore applies in iOS only -prf // @ts-ignore applies in iOS only -prf
themeVariant={t.name === 'dark' ? 'dark' : 'light'} themeVariant={t.name === 'light' ? 'light' : 'dark'}
value={new Date(value)} value={new Date(value)}
onChange={onChangeInternal} onChange={onChangeInternal}
/> />

View File

@ -47,7 +47,7 @@ export function DateField({
mode="date" mode="date"
timeZoneName={'Etc/UTC'} timeZoneName={'Etc/UTC'}
display="spinner" display="spinner"
themeVariant={t.name === 'dark' ? 'dark' : 'light'} themeVariant={t.name === 'light' ? 'light' : 'dark'}
value={new Date(value)} value={new Date(value)}
onChange={onChangeInternal} onChange={onChangeInternal}
/> />

View File

@ -26,7 +26,7 @@ export interface LinkMeta {
export async function getLinkMeta( export async function getLinkMeta(
agent: BskyAgent, agent: BskyAgent,
url: string, url: string,
timeout = 5e3, timeout = 15e3,
): Promise<LinkMeta> { ): Promise<LinkMeta> {
if (isBskyAppUrl(url)) { if (isBskyAppUrl(url)) {
return extractBskyMeta(agent, url) return extractBskyMeta(agent, url)

View File

@ -30,7 +30,7 @@ export type CommonNavigatorParams = {
CopyrightPolicy: undefined CopyrightPolicy: undefined
AppPasswords: undefined AppPasswords: undefined
SavedFeeds: undefined SavedFeeds: undefined
PreferencesHomeFeed: undefined PreferencesFollowingFeed: undefined
PreferencesThreads: undefined PreferencesThreads: undefined
PreferencesExternalEmbeds: undefined PreferencesExternalEmbeds: undefined
} }

View File

@ -1,3 +1,8 @@
// Regex from the go implementation
// https://github.com/bluesky-social/indigo/blob/main/atproto/syntax/handle.go#L10
const VALIDATE_REGEX =
/^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/
export function makeValidHandle(str: string): string { export function makeValidHandle(str: string): string {
if (str.length > 20) { if (str.length > 20) {
str = str.slice(0, 20) str = str.slice(0, 20)
@ -19,3 +24,27 @@ export function isInvalidHandle(handle: string): boolean {
export function sanitizeHandle(handle: string, prefix = ''): string { export function sanitizeHandle(handle: string, prefix = ''): string {
return isInvalidHandle(handle) ? '⚠Invalid Handle' : `${prefix}${handle}` return isInvalidHandle(handle) ? '⚠Invalid Handle' : `${prefix}${handle}`
} }
export interface IsValidHandle {
handleChars: boolean
frontLength: boolean
totalLength: boolean
overall: boolean
}
// More checks from https://github.com/bluesky-social/atproto/blob/main/packages/pds/src/handle/index.ts#L72
export function validateHandle(str: string, userDomain: string): IsValidHandle {
const fullHandle = createFullHandle(str, userDomain)
const results = {
handleChars:
!str || (VALIDATE_REGEX.test(fullHandle) && !str.includes('.')),
frontLength: str.length >= 3,
totalLength: fullHandle.length <= 253,
}
return {
...results,
overall: !Object.values(results).includes(false),
}
}

View File

@ -23,7 +23,7 @@ export function ago(date: number | string | Date): string {
} else if (diffSeconds < DAY) { } else if (diffSeconds < DAY) {
return `${Math.floor(diffSeconds / HOUR)}h` return `${Math.floor(diffSeconds / HOUR)}h`
} else if (diffSeconds < MONTH) { } else if (diffSeconds < MONTH) {
return `${Math.floor(diffSeconds / DAY)}d` return `${Math.round(diffSeconds / DAY)}d`
} else if (diffSeconds < YEAR) { } else if (diffSeconds < YEAR) {
return `${Math.floor(diffSeconds / MONTH)}mo` return `${Math.floor(diffSeconds / MONTH)}mo`
} else { } else {

View File

@ -306,7 +306,7 @@ export const darkTheme: Theme = {
// non-standard // non-standard
textVeryLight: darkPalette.contrast_400, textVeryLight: darkPalette.contrast_400,
replyLine: darkPalette.contrast_100, replyLine: darkPalette.contrast_200,
replyLineDot: darkPalette.contrast_200, replyLineDot: darkPalette.contrast_200,
unreadNotifBg: darkPalette.primary_975, unreadNotifBg: darkPalette.primary_975,
unreadNotifBorder: darkPalette.primary_900, unreadNotifBorder: darkPalette.primary_900,
@ -355,10 +355,10 @@ export const dimTheme: Theme = {
// non-standard // non-standard
textVeryLight: dimPalette.contrast_400, textVeryLight: dimPalette.contrast_400,
replyLine: dimPalette.contrast_100, replyLine: dimPalette.contrast_200,
replyLineDot: dimPalette.contrast_200, replyLineDot: dimPalette.contrast_200,
unreadNotifBg: dimPalette.primary_975, unreadNotifBg: `hsl(211, 48%, 17%)`,
unreadNotifBorder: dimPalette.primary_900, unreadNotifBorder: `hsl(211, 48%, 30%)`,
postCtrl: dimPalette.contrast_500, postCtrl: dimPalette.contrast_500,
brandText: dimPalette.primary_500, brandText: dimPalette.primary_500,
emptyStateIcon: dimPalette.contrast_300, emptyStateIcon: dimPalette.contrast_300,

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -24,7 +24,7 @@ export const router = new Router({
Debug: '/sys/debug', Debug: '/sys/debug',
Log: '/sys/log', Log: '/sys/log',
AppPasswords: '/settings/app-passwords', AppPasswords: '/settings/app-passwords',
PreferencesHomeFeed: '/settings/home-feed', PreferencesFollowingFeed: '/settings/following-feed',
PreferencesThreads: '/settings/threads', PreferencesThreads: '/settings/threads',
PreferencesExternalEmbeds: '/settings/external-embeds', PreferencesExternalEmbeds: '/settings/external-embeds',
SavedFeeds: '/settings/saved-feeds', SavedFeeds: '/settings/saved-feeds',

View File

@ -23,7 +23,7 @@ import {Step3} from './Step3'
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
import {TextLink} from '../../util/Link' import {TextLink} from '../../util/Link'
import {getAgent} from 'state/session' import {getAgent} from 'state/session'
import {createFullHandle} from 'lib/strings/handles' import {createFullHandle, validateHandle} from 'lib/strings/handles'
export function CreateAccount({onPressBack}: {onPressBack: () => void}) { export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
const {screen} = useAnalytics() const {screen} = useAnalytics()
@ -78,6 +78,10 @@ export function CreateAccount({onPressBack}: {onPressBack: () => void}) {
} }
if (uiState.step === 2) { if (uiState.step === 2) {
if (!validateHandle(uiState.handle, uiState.userDomain).overall) {
return
}
uiDispatch({type: 'set-processing', value: true}) uiDispatch({type: 'set-processing', value: true})
try { try {
const res = await getAgent().resolveHandle({ const res = await getAgent().resolveHandle({

View File

@ -1,15 +1,22 @@
import React from 'react' import React from 'react'
import {StyleSheet, View} from 'react-native' import {View} from 'react-native'
import {CreateAccountState, CreateAccountDispatch} from './state' import {CreateAccountState, CreateAccountDispatch} from './state'
import {Text} from 'view/com/util/text/Text' import {Text} from 'view/com/util/text/Text'
import {StepHeader} from './StepHeader' import {StepHeader} from './StepHeader'
import {s} from 'lib/styles' import {s} from 'lib/styles'
import {TextInput} from '../util/TextInput' import {TextInput} from '../util/TextInput'
import {createFullHandle} from 'lib/strings/handles' import {
createFullHandle,
IsValidHandle,
validateHandle,
} from 'lib/strings/handles'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {atoms as a, useTheme} from '#/alf'
import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times'
import {useFocusEffect} from '@react-navigation/native'
/** STEP 3: Your user handle /** STEP 3: Your user handle
* @field User handle * @field User handle
@ -23,41 +30,111 @@ export function Step2({
}) { }) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const t = useTheme()
const [validCheck, setValidCheck] = React.useState<IsValidHandle>({
handleChars: false,
frontLength: false,
totalLength: true,
overall: false,
})
useFocusEffect(
React.useCallback(() => {
setValidCheck(validateHandle(uiState.handle, uiState.userDomain))
// Disabling this, because we only want to run this when we focus the screen
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []),
)
const onHandleChange = React.useCallback(
(value: string) => {
if (uiState.error) {
uiDispatch({type: 'set-error', value: ''})
}
setValidCheck(validateHandle(value, uiState.userDomain))
uiDispatch({type: 'set-handle', value})
},
[uiDispatch, uiState.error, uiState.userDomain],
)
return ( return (
<View> <View>
<StepHeader uiState={uiState} title={_(msg`Your user handle`)} /> <StepHeader uiState={uiState} title={_(msg`Your user handle`)} />
{uiState.error ? (
<ErrorMessage message={uiState.error} style={styles.error} />
) : undefined}
<View style={s.pb10}> <View style={s.pb10}>
<TextInput <View style={s.mb20}>
testID="handleInput" <TextInput
icon="at" testID="handleInput"
placeholder="e.g. alice" icon="at"
value={uiState.handle} placeholder="e.g. alice"
editable value={uiState.handle}
autoFocus editable
autoComplete="off" autoFocus
autoCorrect={false} autoComplete="off"
onChange={value => uiDispatch({type: 'set-handle', value})} autoCorrect={false}
// TODO: Add explicit text label onChange={onHandleChange}
accessibilityLabel={_(msg`User handle`)} // TODO: Add explicit text label
accessibilityHint={_(msg`Input your user handle`)} accessibilityLabel={_(msg`User handle`)}
/> accessibilityHint={_(msg`Input your user handle`)}
<Text type="lg" style={[pal.text, s.pl5, s.pt10]}> />
<Trans>Your full handle will be</Trans>{' '} <Text type="lg" style={[pal.text, s.pl5, s.pt10]}>
<Text type="lg-bold" style={pal.text}> <Trans>Your full handle will be</Trans>{' '}
@{createFullHandle(uiState.handle, uiState.userDomain)} <Text type="lg-bold" style={pal.text}>
@{createFullHandle(uiState.handle, uiState.userDomain)}
</Text>
</Text> </Text>
</Text> </View>
<View
style={[
a.w_full,
a.rounded_sm,
a.border,
a.p_md,
a.gap_sm,
t.atoms.border_contrast_low,
]}>
{uiState.error ? (
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon valid={false} />
<Text style={[t.atoms.text, a.text_md, a.flex]}>
{uiState.error}
</Text>
</View>
) : undefined}
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon valid={validCheck.handleChars} />
<Text style={[t.atoms.text, a.text_md, a.flex]}>
<Trans>May only contain letters and numbers</Trans>
</Text>
</View>
<View style={[a.w_full, a.flex_row, a.align_center, a.gap_sm]}>
<IsValidIcon
valid={validCheck.frontLength && validCheck.totalLength}
/>
{!validCheck.totalLength ? (
<Text style={[t.atoms.text]}>
<Trans>May not be longer than 253 characters</Trans>
</Text>
) : (
<Text style={[t.atoms.text, a.text_md]}>
<Trans>Must be at least 3 characters</Trans>
</Text>
)}
</View>
</View>
</View> </View>
</View> </View>
) )
} }
const styles = StyleSheet.create({ function IsValidIcon({valid}: {valid: boolean}) {
error: { const t = useTheme()
borderRadius: 6,
marginBottom: 10, if (!valid) {
}, return <Check size="md" style={{color: t.palette.negative_500}} />
}) }
return <Times size="md" style={{color: t.palette.positive_700}} />
}

View File

@ -8,7 +8,7 @@ import {msg} from '@lingui/macro'
import * as EmailValidator from 'email-validator' import * as EmailValidator from 'email-validator'
import {getAge} from 'lib/strings/time' import {getAge} from 'lib/strings/time'
import {logger} from '#/logger' import {logger} from '#/logger'
import {createFullHandle} from '#/lib/strings/handles' import {createFullHandle, validateHandle} from '#/lib/strings/handles'
import {cleanError} from '#/lib/strings/errors' import {cleanError} from '#/lib/strings/errors'
import {useOnboardingDispatch} from '#/state/shell/onboarding' import {useOnboardingDispatch} from '#/state/shell/onboarding'
import {useSessionApi} from '#/state/session' import {useSessionApi} from '#/state/session'
@ -282,7 +282,8 @@ function compute(state: CreateAccountState): CreateAccountState {
!!state.email && !!state.email &&
!!state.password !!state.password
} else if (state.step === 2) { } else if (state.step === 2) {
canNext = !!state.handle canNext =
!!state.handle && validateHandle(state.handle, state.userDomain).overall
} else if (state.step === 3) { } else if (state.step === 3) {
// Step 3 will automatically redirect as soon as the captcha completes // Step 3 will automatically redirect as soon as the captcha completes
canNext = false canNext = false

View File

@ -138,7 +138,7 @@ export function FeedPage({
{hasSession && ( {hasSession && (
<TextLink <TextLink
type="title-lg" type="title-lg"
href="/settings/home-feed" href="/settings/following-feed"
style={{fontWeight: 'bold'}} style={{fontWeight: 'bold'}}
accessibilityLabel={_(msg`Feed Preferences`)} accessibilityLabel={_(msg`Feed Preferences`)}
accessibilityHint="" accessibilityHint=""

View File

@ -0,0 +1,71 @@
import React from 'react'
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {HomeHeaderLayout} from './HomeHeaderLayout'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {isWeb} from 'platform/detection'
import {TabBar} from '../pager/TabBar'
import {usePalette} from '#/lib/hooks/usePalette'
export function HomeHeader(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const {isDesktop} = useWebMediaQueries()
if (isDesktop) {
return null
}
return <HomeHeaderInner {...props} />
}
export function HomeHeaderInner(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const navigation = useNavigation<NavigationProp>()
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
const pal = usePalette('default')
const items = React.useMemo(() => {
const pinnedNames = feeds.map(f => f.displayName)
if (!hasPinnedCustom) {
return pinnedNames.concat('Feeds ✨')
}
return pinnedNames
}, [hasPinnedCustom, feeds])
const onPressFeedsLink = React.useCallback(() => {
if (isWeb) {
navigation.navigate('Feeds')
} else {
navigation.navigate('FeedsTab')
navigation.popToTop()
}
}, [navigation])
const onSelect = React.useCallback(
(index: number) => {
if (!hasPinnedCustom && index === items.length - 1) {
onPressFeedsLink()
} else if (props.onSelect) {
props.onSelect(index)
}
},
[items.length, onPressFeedsLink, props, hasPinnedCustom],
)
return (
<HomeHeaderLayout>
<TabBar
key={items.join(',')}
onPressSelected={props.onPressSelected}
selectedPage={props.selectedPage}
onSelect={onSelect}
testID={props.testID}
items={items}
indicatorColor={pal.colors.link}
/>
</HomeHeaderLayout>
)
}

View File

@ -0,0 +1 @@
export {HomeHeaderLayoutMobile as HomeHeaderLayout} from './HomeHeaderLayoutMobile'

View File

@ -0,0 +1,50 @@
import React from 'react'
import {StyleSheet} from 'react-native'
import Animated from 'react-native-reanimated'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {HomeHeaderLayoutMobile} from './HomeHeaderLayoutMobile'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useShellLayout} from '#/state/shell/shell-layout'
export function HomeHeaderLayout({children}: {children: React.ReactNode}) {
const {isMobile} = useWebMediaQueries()
if (isMobile) {
return <HomeHeaderLayoutMobile>{children}</HomeHeaderLayoutMobile>
} else {
return <HomeHeaderLayoutTablet>{children}</HomeHeaderLayoutTablet>
}
}
function HomeHeaderLayoutTablet({children}: {children: React.ReactNode}) {
const pal = usePalette('default')
const {headerMinimalShellTransform} = useMinimalShellMode()
const {headerHeight} = useShellLayout()
return (
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
<Animated.View
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
onLayout={e => {
headerHeight.value = e.nativeEvent.layout.height
}}>
{children}
</Animated.View>
)
}
const styles = StyleSheet.create({
tabBar: {
// @ts-ignore Web only
position: 'sticky',
zIndex: 1,
// @ts-ignore Web only -prf
left: 'calc(50% - 300px)',
width: 600,
top: 0,
flexDirection: 'row',
alignItems: 'center',
borderLeftWidth: 1,
borderRightWidth: 1,
},
})

View File

@ -1,7 +1,5 @@
import React from 'react' import React from 'react'
import {StyleSheet, TouchableOpacity, View} from 'react-native' import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {Link} from '../util/Link' import {Link} from '../util/Link'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
@ -13,11 +11,7 @@ import {useLingui} from '@lingui/react'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode' import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useSetDrawerOpen} from '#/state/shell/drawer-open' import {useSetDrawerOpen} from '#/state/shell/drawer-open'
import {useShellLayout} from '#/state/shell/shell-layout' import {useShellLayout} from '#/state/shell/shell-layout'
import {useSession} from '#/state/session'
import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {isWeb} from 'platform/detection' import {isWeb} from 'platform/detection'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
import {Logo} from '#/view/icons/Logo' import {Logo} from '#/view/icons/Logo'
import {IS_DEV} from '#/env' import {IS_DEV} from '#/env'
@ -25,49 +19,17 @@ import {atoms} from '#/alf'
import {Link as Link2} from '#/components/Link' import {Link as Link2} from '#/components/Link'
import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette' import {ColorPalette_Stroke2_Corner0_Rounded as ColorPalette} from '#/components/icons/ColorPalette'
export function FeedsTabBar( export function HomeHeaderLayoutMobile({
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void}, children,
) { }: {
children: React.ReactNode
}) {
const pal = usePalette('default') const pal = usePalette('default')
const {hasSession} = useSession()
const {_} = useLingui() const {_} = useLingui()
const setDrawerOpen = useSetDrawerOpen() const setDrawerOpen = useSetDrawerOpen()
const navigation = useNavigation<NavigationProp>()
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
const {headerHeight} = useShellLayout() const {headerHeight} = useShellLayout()
const {headerMinimalShellTransform} = useMinimalShellMode() const {headerMinimalShellTransform} = useMinimalShellMode()
const items = React.useMemo(() => {
if (!hasSession) return []
const pinnedNames = feeds.map(f => f.displayName)
if (!hasPinnedCustom) {
return pinnedNames.concat('Feeds ✨')
}
return pinnedNames
}, [hasSession, hasPinnedCustom, feeds])
const onPressFeedsLink = React.useCallback(() => {
if (isWeb) {
navigation.navigate('Feeds')
} else {
navigation.navigate('FeedsTab')
navigation.popToTop()
}
}, [navigation])
const onSelect = React.useCallback(
(index: number) => {
if (hasSession && !hasPinnedCustom && index === items.length - 1) {
onPressFeedsLink()
} else if (props.onSelect) {
props.onSelect(index)
}
},
[items.length, onPressFeedsLink, props, hasSession, hasPinnedCustom],
)
const onPressAvi = React.useCallback(() => { const onPressAvi = React.useCallback(() => {
setDrawerOpen(true) setDrawerOpen(true)
}, [setDrawerOpen]) }, [setDrawerOpen])
@ -113,35 +75,21 @@ export function FeedsTabBar(
<ColorPalette size="md" /> <ColorPalette size="md" />
</Link2> </Link2>
)} )}
<Link
{hasSession && ( testID="viewHeaderHomeFeedPrefsBtn"
<Link href="/settings/following-feed"
testID="viewHeaderHomeFeedPrefsBtn" hitSlop={HITSLOP_10}
href="/settings/home-feed" accessibilityRole="button"
hitSlop={HITSLOP_10} accessibilityLabel={_(msg`Following Feed Preferences`)}
accessibilityRole="button" accessibilityHint="">
accessibilityLabel={_(msg`Home Feed Preferences`)} <FontAwesomeIcon
accessibilityHint=""> icon="sliders"
<FontAwesomeIcon style={pal.textLight as FontAwesomeIconStyle}
icon="sliders" />
style={pal.textLight as FontAwesomeIconStyle} </Link>
/>
</Link>
)}
</View> </View>
</View> </View>
{children}
{items.length > 0 && (
<TabBar
key={items.join(',')}
onPressSelected={props.onPressSelected}
selectedPage={props.selectedPage}
onSelect={onSelect}
testID={props.testID}
items={items}
indicatorColor={pal.colors.link}
/>
)}
</Animated.View> </Animated.View>
) )
} }

View File

@ -37,6 +37,7 @@ type Props = {
onTap: () => void onTap: () => void
onZoom: (isZoomed: boolean) => void onZoom: (isZoomed: boolean) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean
} }
const ImageItem = ({ const ImageItem = ({
imageSrc, imageSrc,

View File

@ -37,11 +37,18 @@ type Props = {
onTap: () => void onTap: () => void
onZoom: (scaled: boolean) => void onZoom: (scaled: boolean) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean
} }
const AnimatedImage = Animated.createAnimatedComponent(Image) const AnimatedImage = Animated.createAnimatedComponent(Image)
const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => { const ImageItem = ({
imageSrc,
onTap,
onZoom,
onRequestClose,
showControls,
}: Props) => {
const scrollViewRef = useAnimatedRef<Animated.ScrollView>() const scrollViewRef = useAnimatedRef<Animated.ScrollView>()
const translationY = useSharedValue(0) const translationY = useSharedValue(0)
const [loaded, setLoaded] = useState(false) const [loaded, setLoaded] = useState(false)
@ -144,7 +151,7 @@ const ImageItem = ({imageSrc, onTap, onZoom, onRequestClose}: Props) => {
accessibilityLabel={imageSrc.alt} accessibilityLabel={imageSrc.alt}
accessibilityHint="" accessibilityHint=""
onLoad={() => setLoaded(true)} onLoad={() => setLoaded(true)}
enableLiveTextInteraction={!scaled} enableLiveTextInteraction={showControls && !scaled}
/> />
</Animated.ScrollView> </Animated.ScrollView>
</GestureDetector> </GestureDetector>

View File

@ -10,6 +10,7 @@ type Props = {
onTap: () => void onTap: () => void
onZoom: (scaled: boolean) => void onZoom: (scaled: boolean) => void
isScrollViewBeingDragged: boolean isScrollViewBeingDragged: boolean
showControls: boolean
} }
const ImageItem = (_props: Props) => { const ImageItem = (_props: Props) => {

View File

@ -122,6 +122,7 @@ function ImageViewing({
imageSrc={imageSrc} imageSrc={imageSrc}
onRequestClose={onRequestClose} onRequestClose={onRequestClose}
isScrollViewBeingDragged={isDragging} isScrollViewBeingDragged={isDragging}
showControls={showControls}
/> />
</View> </View>
))} ))}

View File

@ -1 +0,0 @@
export * from './FeedsTabBarMobile'

View File

@ -1,138 +0,0 @@
import React from 'react'
import {View, StyleSheet} from 'react-native'
import Animated from 'react-native-reanimated'
import {TabBar} from 'view/com/pager/TabBar'
import {RenderTabBarFnProps} from 'view/com/pager/Pager'
import {usePalette} from 'lib/hooks/usePalette'
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
import {FeedsTabBar as FeedsTabBarMobile} from './FeedsTabBarMobile'
import {useMinimalShellMode} from 'lib/hooks/useMinimalShellMode'
import {useShellLayout} from '#/state/shell/shell-layout'
import {usePinnedFeedsInfos} from '#/state/queries/feed'
import {useSession} from '#/state/session'
import {TextLink} from '#/view/com/util/Link'
import {CenteredView} from '../util/Views'
import {isWeb} from 'platform/detection'
import {useNavigation} from '@react-navigation/native'
import {NavigationProp} from 'lib/routes/types'
export function FeedsTabBar(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const {isMobile, isTablet} = useWebMediaQueries()
const {hasSession} = useSession()
if (isMobile) {
return <FeedsTabBarMobile {...props} />
} else if (isTablet) {
if (hasSession) {
return <FeedsTabBarTablet {...props} />
} else {
return <FeedsTabBarPublic />
}
} else {
return null
}
}
function FeedsTabBarPublic() {
const pal = usePalette('default')
return (
<CenteredView sideBorders>
<View
style={[
pal.view,
{
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 18,
paddingVertical: 12,
},
]}>
<TextLink
type="title-lg"
href="/"
style={[pal.text, {fontWeight: 'bold'}]}
text="Bluesky "
/>
</View>
</CenteredView>
)
}
function FeedsTabBarTablet(
props: RenderTabBarFnProps & {testID?: string; onPressSelected: () => void},
) {
const {feeds, hasPinnedCustom} = usePinnedFeedsInfos()
const pal = usePalette('default')
const {hasSession} = useSession()
const navigation = useNavigation<NavigationProp>()
const {headerMinimalShellTransform} = useMinimalShellMode()
const {headerHeight} = useShellLayout()
const items = React.useMemo(() => {
if (!hasSession) return []
const pinnedNames = feeds.map(f => f.displayName)
if (!hasPinnedCustom) {
return pinnedNames.concat('Feeds ✨')
}
return pinnedNames
}, [hasSession, hasPinnedCustom, feeds])
const onPressDiscoverFeeds = React.useCallback(() => {
if (isWeb) {
navigation.navigate('Feeds')
} else {
navigation.navigate('FeedsTab')
navigation.popToTop()
}
}, [navigation])
const onSelect = React.useCallback(
(index: number) => {
if (hasSession && !hasPinnedCustom && index === items.length - 1) {
onPressDiscoverFeeds()
} else if (props.onSelect) {
props.onSelect(index)
}
},
[items.length, onPressDiscoverFeeds, props, hasSession, hasPinnedCustom],
)
return (
// @ts-ignore the type signature for transform wrong here, translateX and translateY need to be in separate objects -prf
<Animated.View
style={[pal.view, pal.border, styles.tabBar, headerMinimalShellTransform]}
onLayout={e => {
headerHeight.value = e.nativeEvent.layout.height
}}>
<TabBar
key={items.join(',')}
{...props}
onSelect={onSelect}
items={items}
indicatorColor={pal.colors.link}
/>
</Animated.View>
)
}
const styles = StyleSheet.create({
tabBar: {
// @ts-ignore Web only
position: 'sticky',
zIndex: 1,
// @ts-ignore Web only -prf
left: 'calc(50% - 300px)',
width: 600,
top: 0,
flexDirection: 'row',
alignItems: 'center',
borderLeftWidth: 1,
borderRightWidth: 1,
},
})

View File

@ -449,7 +449,7 @@ let PostThreadItemLoaded = ({
styles.replyLine, styles.replyLine,
{ {
flexGrow: 1, flexGrow: 1,
backgroundColor: pal.colors.border, backgroundColor: pal.colors.replyLine,
marginBottom: 4, marginBottom: 4,
}, },
]} ]}
@ -487,7 +487,7 @@ let PostThreadItemLoaded = ({
styles.replyLine, styles.replyLine,
{ {
flexGrow: 1, flexGrow: 1,
backgroundColor: pal.colors.border, backgroundColor: pal.colors.replyLine,
marginTop: 4, marginTop: 4,
}, },
]} ]}

View File

@ -6,7 +6,7 @@ import {FeedDescriptor, FeedParams} from '#/state/queries/post-feed'
import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState' import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed' import {FollowingEndOfFeed} from 'view/com/posts/FollowingEndOfFeed'
import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState' import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
import {FeedsTabBar} from '../com/pager/FeedsTabBar' import {HomeHeader} from '../com/home/HomeHeader'
import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager' import {Pager, RenderTabBarFnProps, PagerRef} from 'view/com/pager/Pager'
import {FeedPage} from 'view/com/feeds/FeedPage' import {FeedPage} from 'view/com/feeds/FeedPage'
import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA' import {HomeLoggedOutCTA} from '../com/auth/HomeLoggedOutCTA'
@ -118,7 +118,7 @@ function HomeScreenReady({
const renderTabBar = React.useCallback( const renderTabBar = React.useCallback(
(props: RenderTabBarFnProps) => { (props: RenderTabBarFnProps) => {
return ( return (
<FeedsTabBar <HomeHeader
key="FEEDS_TAB_BAR" key="FEEDS_TAB_BAR"
selectedPage={props.selectedPage} selectedPage={props.selectedPage}
onSelect={props.onSelect} onSelect={props.onSelect}

View File

@ -78,9 +78,9 @@ function RepliesThresholdInput({
type Props = NativeStackScreenProps< type Props = NativeStackScreenProps<
CommonNavigatorParams, CommonNavigatorParams,
'PreferencesHomeFeed' 'PreferencesFollowingFeed'
> >
export function PreferencesHomeFeed({navigation}: Props) { export function PreferencesFollowingFeed({navigation}: Props) {
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const {isTabletOrDesktop} = useWebMediaQueries() const {isTabletOrDesktop} = useWebMediaQueries()
@ -101,14 +101,14 @@ export function PreferencesHomeFeed({navigation}: Props) {
styles.container, styles.container,
isTabletOrDesktop && styles.desktopContainer, isTabletOrDesktop && styles.desktopContainer,
]}> ]}>
<ViewHeader title={_(msg`Home Feed Preferences`)} showOnDesktop /> <ViewHeader title={_(msg`Following Feed Preferences`)} showOnDesktop />
<View <View
style={[ style={[
styles.titleSection, styles.titleSection,
isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20}, isTabletOrDesktop && {paddingTop: 20, paddingBottom: 20},
]}> ]}>
<Text type="xl" style={[pal.textLight, styles.description]}> <Text type="xl" style={[pal.textLight, styles.description]}>
<Trans>Fine-tune the content you see on your home screen.</Trans> <Trans>Fine-tune the content you see on your Following feed.</Trans>
</Text> </Text>
</View> </View>
@ -260,7 +260,7 @@ export function PreferencesHomeFeed({navigation}: Props) {
<Text style={[pal.text, s.pb10]}> <Text style={[pal.text, s.pb10]}>
<Trans> <Trans>
Set this setting to "Yes" to show samples of your saved feeds in Set this setting to "Yes" to show samples of your saved feeds in
your following feed. This is an experimental feature. your Following feed. This is an experimental feature.
</Trans> </Trans>
</Text> </Text>
<ToggleButton <ToggleButton

View File

@ -241,8 +241,8 @@ export function SettingsScreen({}: Props) {
Toast.show(_(msg`Copied build version to clipboard`)) Toast.show(_(msg`Copied build version to clipboard`))
}, [_]) }, [_])
const openHomeFeedPreferences = React.useCallback(() => { const openFollowingFeedPreferences = React.useCallback(() => {
navigation.navigate('PreferencesHomeFeed') navigation.navigate('PreferencesFollowingFeed')
}, [navigation]) }, [navigation])
const openThreadsPreferences = React.useCallback(() => { const openThreadsPreferences = React.useCallback(() => {
@ -529,7 +529,7 @@ export function SettingsScreen({}: Props) {
pal.view, pal.view,
isSwitchingAccounts && styles.dimmed, isSwitchingAccounts && styles.dimmed,
]} ]}
onPress={openHomeFeedPreferences} onPress={openFollowingFeedPreferences}
accessibilityRole="button" accessibilityRole="button"
accessibilityLabel={_(msg`Home feed preferences`)} accessibilityLabel={_(msg`Home feed preferences`)}
accessibilityHint={_(msg`Opens the home feed preferences`)}> accessibilityHint={_(msg`Opens the home feed preferences`)}>
@ -540,7 +540,7 @@ export function SettingsScreen({}: Props) {
/> />
</View> </View>
<Text type="lg" style={pal.text}> <Text type="lg" style={pal.text}>
<Trans>Home Feed Preferences</Trans> <Trans>Following Feed Preferences</Trans>
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
<TouchableOpacity <TouchableOpacity

View File

@ -2,99 +2,26 @@ import React from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import * as tokens from '#/alf/tokens' import * as tokens from '#/alf/tokens'
import {atoms as a} from '#/alf' import {atoms as a, useTheme} from '#/alf'
export function Palette() { export function Palette() {
const t = useTheme()
return ( return (
<View style={[a.gap_md]}> <View style={[a.gap_md]}>
<View style={[a.flex_row, a.gap_md]}> <View style={[a.flex_row, a.gap_md]}>
<View <View style={[a.flex_1, t.atoms.bg_contrast_25, {height: 60}]} />
style={[a.flex_1, {height: 60, backgroundColor: tokens.color.gray_0}]} <View style={[a.flex_1, t.atoms.bg_contrast_50, {height: 60}]} />
/> <View style={[a.flex_1, t.atoms.bg_contrast_100, {height: 60}]} />
<View <View style={[a.flex_1, t.atoms.bg_contrast_200, {height: 60}]} />
style={[ <View style={[a.flex_1, t.atoms.bg_contrast_300, {height: 60}]} />
a.flex_1, <View style={[a.flex_1, t.atoms.bg_contrast_400, {height: 60}]} />
{height: 60, backgroundColor: tokens.color.gray_25}, <View style={[a.flex_1, t.atoms.bg_contrast_500, {height: 60}]} />
]} <View style={[a.flex_1, t.atoms.bg_contrast_600, {height: 60}]} />
/> <View style={[a.flex_1, t.atoms.bg_contrast_700, {height: 60}]} />
<View <View style={[a.flex_1, t.atoms.bg_contrast_800, {height: 60}]} />
style={[ <View style={[a.flex_1, t.atoms.bg_contrast_900, {height: 60}]} />
a.flex_1, <View style={[a.flex_1, t.atoms.bg_contrast_950, {height: 60}]} />
{height: 60, backgroundColor: tokens.color.gray_50}, <View style={[a.flex_1, t.atoms.bg_contrast_975, {height: 60}]} />
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_100},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_200},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_300},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_400},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_500},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_600},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_700},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_800},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_900},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_950},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_975},
]}
/>
<View
style={[
a.flex_1,
{height: 60, backgroundColor: tokens.color.gray_1000},
]}
/>
</View> </View>
<View style={[a.flex_row, a.gap_md]}> <View style={[a.flex_row, a.gap_md]}>