Merge branch 'bluesky-social:main' into patch-3
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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 {
|
||||||
@@ -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
|
||||||
+7
-4
@@ -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"
|
||||||
|
|||||||
+50
-27
@@ -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%)`,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-12
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
+4
-4
@@ -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,
|
||||||
|
|||||||
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+250
-222
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+251
-223
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -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',
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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}} />
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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=""
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export {HomeHeaderLayoutMobile as HomeHeaderLayout} from './HomeHeaderLayoutMobile'
|
||||||
@@ -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,
|
||||||
|
},
|
||||||
|
})
|
||||||
+18
-70
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -122,6 +122,7 @@ function ImageViewing({
|
|||||||
imageSrc={imageSrc}
|
imageSrc={imageSrc}
|
||||||
onRequestClose={onRequestClose}
|
onRequestClose={onRequestClose}
|
||||||
isScrollViewBeingDragged={isDragging}
|
isScrollViewBeingDragged={isDragging}
|
||||||
|
showControls={showControls}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
export * from './FeedsTabBarMobile'
|
|
||||||
@@ -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,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -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,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
+5
-5
@@ -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
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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]}>
|
||||||
|
|||||||
Reference in New Issue
Block a user