diff --git a/.prettierignore b/.prettierignore index 8ccbae2148..e107e129a4 100644 --- a/.prettierignore +++ b/.prettierignore @@ -15,3 +15,6 @@ android ios src/locale/locales lib/react-compiler-runtime +bskyweb/static +coverage +web-build diff --git a/bskyweb/cmd/bskyweb/server.go b/bskyweb/cmd/bskyweb/server.go index 0f49ce5c3a..54c5f075ac 100644 --- a/bskyweb/cmd/bskyweb/server.go +++ b/bskyweb/cmd/bskyweb/server.go @@ -255,6 +255,7 @@ func serve(cctx *cli.Context) error { e.GET("/support/community-guidelines", server.WebGeneric) e.GET("/support/copyright", server.WebGeneric) e.GET("/intent/compose", server.WebGeneric) + e.GET("/intent/verify-email", server.WebGeneric) e.GET("/messages", server.WebGeneric) e.GET("/messages/:conversation", server.WebGeneric) diff --git a/package.json b/package.json index 273d8ddb35..e40634685f 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "@fortawesome/free-regular-svg-icons": "^6.1.1", "@fortawesome/free-solid-svg-icons": "^6.1.1", "@fortawesome/react-native-fontawesome": "^0.3.2", - "@haileyok/bluesky-bottom-sheet": "^0.1.1-alpha.9", + "@haileyok/bluesky-bottom-sheet": "^0.1.1-alpha.11", "@haileyok/bluesky-video": "0.1.10", "@lingui/react": "^4.5.0", "@mattermost/react-native-paste-input": "^0.7.1", diff --git a/src/Navigation.tsx b/src/Navigation.tsx index 53e8274d54..323f668b79 100644 --- a/src/Navigation.tsx +++ b/src/Navigation.tsx @@ -78,8 +78,8 @@ import {BottomBar} from '#/view/shell/bottom-bar/BottomBar' import {createNativeStackNavigatorWithAuth} from '#/view/shell/createNativeStackNavigatorWithAuth' import {SharedPreferencesTesterScreen} from '#/screens/E2E/SharedPreferencesTesterScreen' import HashtagScreen from '#/screens/Hashtag' +import {MessagesScreen} from '#/screens/Messages/ChatList' import {MessagesConversationScreen} from '#/screens/Messages/Conversation' -import {MessagesScreen} from '#/screens/Messages/List' import {MessagesSettingsScreen} from '#/screens/Messages/Settings' import {ModerationScreen} from '#/screens/Moderation' import {PostLikedByScreen} from '#/screens/Post/PostLikedBy' diff --git a/src/alf/atoms.ts b/src/alf/atoms.ts index 0c8eb330d7..2625beda27 100644 --- a/src/alf/atoms.ts +++ b/src/alf/atoms.ts @@ -901,4 +901,32 @@ export const atoms = { hidden: { display: 'none', }, + + /* + * Transition + */ + transition_none: web({ + transitionProperty: 'none', + }), + transition_all: web({ + transitionProperty: 'all', + transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)', + transitionDuration: '100ms', + }), + transition_color: web({ + transitionProperty: + 'color, background-color, border-color, text-decoration-color, fill, stroke', + transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)', + transitionDuration: '100ms', + }), + transition_opacity: web({ + transitionProperty: 'opacity', + transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)', + transitionDuration: '100ms', + }), + transition_transform: web({ + transitionProperty: 'transform', + transitionTimingFunction: 'cubic-bezier(0.17, 0.73, 0.14, 1)', + transitionDuration: '100ms', + }), } as const diff --git a/src/alf/fonts.ts b/src/alf/fonts.ts index ae2aaa97a0..54fe7a34e0 100644 --- a/src/alf/fonts.ts +++ b/src/alf/fonts.ts @@ -1,3 +1,5 @@ +import {useFonts} from 'expo-font' + import {isWeb} from '#/platform/detection' import {Device, device} from '#/storage' @@ -63,3 +65,15 @@ export function applyFonts( */ style.fontVariant = ['no-contextual'] } + +/* + * IMPORTANT: This is unused. Expo statically extracts these fonts. + * + * All used fonts MUST be configured here. Unused fonts can be commented out. + */ +export function DO_NOT_USE() { + return useFonts({ + InterVariable: require('../../assets/fonts/inter/InterVariable.ttf'), + 'InterVariable-Italic': require('../../assets/fonts/inter/InterVariable-Italic.ttf'), + }) +} diff --git a/src/components/BottomSheetButton.tsx b/src/components/BottomSheetButton.tsx deleted file mode 100644 index 9eaee55deb..0000000000 --- a/src/components/BottomSheetButton.tsx +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react' - -import {Button, ButtonProps} from '#/components/Button' -import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' - -export function BottomSheetButton({children, ...rest}: ButtonProps) { - return ( - - ) -} diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 98ad1709c4..0c7c1ac46d 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -89,7 +89,7 @@ export type ButtonProps = Pick< style?: StyleProp hoverStyle?: StyleProp children: NonTextElements | ((context: ButtonContext) => NonTextElements) - Component?: React.ComponentType + PressableComponent?: React.ComponentType } export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean} @@ -117,7 +117,7 @@ export const Button = React.forwardRef( disabled = false, style, hoverStyle: hoverStyleProp, - Component, + PressableComponent, ...rest }, ref, @@ -125,11 +125,11 @@ export const Button = React.forwardRef( // This will pick the correct default pressable to use. If we are inside a dialog, we need to use the RNGH // pressable so that it is usable inside the dialog. const {insideDialog} = useDialogContext() - if (!Component) { + if (!PressableComponent) { if (insideDialog) { - Component = NormalizedRNGHPressable + PressableComponent = NormalizedRNGHPressable } else { - Component = Pressable + PressableComponent = Pressable } } @@ -464,7 +464,7 @@ export const Button = React.forwardRef( const flattenedBaseStyles = flatten([baseStyles, style]) return ( - ( {typeof children === 'function' ? children(context) : children} - + ) }, ) diff --git a/src/components/Menu/index.tsx b/src/components/Menu/index.tsx index 809df96908..bfdcf4099d 100644 --- a/src/components/Menu/index.tsx +++ b/src/components/Menu/index.tsx @@ -105,11 +105,7 @@ export function Outer({ export function Item({children, label, style, onPress, ...rest}: ItemProps) { const t = useTheme() const {control} = React.useContext(Context) - const { - state: focused, - onIn: onHoverIn, - onOut: onHoverOut, - } = useInteractionState() + const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState() const { state: pressed, onIn: onPressIn, @@ -121,9 +117,15 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) { {...rest} accessibilityHint="" accessibilityLabel={label} + onFocus={onFocus} + onBlur={onBlur} onPress={e => { control?.close() onPress(e) + + if (!e.defaultPrevented) { + control?.close() + } }} onPressIn={e => { onPressIn() @@ -133,8 +135,6 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) { onPressOut() rest.onPressOut?.(e) }} - onHoverIn={onHoverIn} - onHoverOut={onHoverOut} style={[ a.flex_row, a.align_center, diff --git a/src/components/ReportDialog/SubmitView.tsx b/src/components/ReportDialog/SubmitView.tsx index e323d15042..682e918c2d 100644 --- a/src/components/ReportDialog/SubmitView.tsx +++ b/src/components/ReportDialog/SubmitView.tsx @@ -16,6 +16,7 @@ import * as Toggle from '#/components/forms/Toggle' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {ChevronLeft_Stroke2_Corner0_Rounded as ChevronLeft} from '#/components/icons/Chevron' import {Loader} from '#/components/Loader' +import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' import {Text} from '#/components/Typography' import {ReportDialogProps} from './types' @@ -153,7 +154,8 @@ export function SubmitView({ + label={title} + PressableComponent={NormalizedRNGHPressable}> ) diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx index daa18f94e7..55fd98d711 100644 --- a/src/components/dialogs/MutedWords.tsx +++ b/src/components/dialogs/MutedWords.tsx @@ -30,6 +30,7 @@ import {PageText_Stroke2_Corner0_Rounded as PageText} from '#/components/icons/P import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times' import {Loader} from '#/components/Loader' +import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' import * as Prompt from '#/components/Prompt' import {Text} from '#/components/Typography' @@ -168,7 +169,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -183,7 +185,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -207,7 +210,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -222,7 +226,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -256,7 +261,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -272,7 +278,8 @@ function MutedWordsInner() { + style={[a.flex_1]} + PressableComponent={NormalizedRNGHPressable}> @@ -302,7 +309,8 @@ function MutedWordsInner() { name="exclude_following" style={[a.flex_row, a.justify_between]} value={excludeFollowing} - onChange={setExcludeFollowing}> + onChange={setExcludeFollowing} + PressableComponent={NormalizedRNGHPressable}> diff --git a/src/components/dialogs/PostInteractionSettingsDialog.tsx b/src/components/dialogs/PostInteractionSettingsDialog.tsx index 93be838542..cc8b4fc9bf 100644 --- a/src/components/dialogs/PostInteractionSettingsDialog.tsx +++ b/src/components/dialogs/PostInteractionSettingsDialog.tsx @@ -37,6 +37,7 @@ import * as Toggle from '#/components/forms/Toggle' import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' import {Loader} from '#/components/Loader' +import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' import {Text} from '#/components/Typography' export type PostInteractionSettingsFormProps = { @@ -303,7 +304,8 @@ export function PostInteractionSettingsForm({ } value={quotesEnabled} onChange={onChangeQuotesEnabled} - style={[, a.justify_between, a.pt_xs]}> + style={[, a.justify_between, a.pt_xs]} + PressableComponent={NormalizedRNGHPressable}> {quotesEnabled ? ( Quote posts enabled diff --git a/src/components/dms/MessagesNUX.tsx b/src/components/dms/MessagesNUX.tsx deleted file mode 100644 index 723696a04d..0000000000 --- a/src/components/dms/MessagesNUX.tsx +++ /dev/null @@ -1,174 +0,0 @@ -import React, {useCallback, useEffect} from 'react' -import {View} from 'react-native' -import {ChatBskyActorDeclaration} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {useUpdateActorDeclaration} from '#/state/queries/messages/actor-declaration' -import {useProfileQuery} from '#/state/queries/profile' -import {useSession} from '#/state/session' -import * as Toast from '#/view/com/util/Toast' -import {atoms as a, useTheme, web} from '#/alf' -import {Button, ButtonText} from '#/components/Button' -import * as Dialog from '#/components/Dialog' -import * as Toggle from '#/components/forms/Toggle' -import {Message_Stroke2_Corner0_Rounded} from '#/components/icons/Message' -import {Text} from '#/components/Typography' - -export function MessagesNUX() { - const control = Dialog.useDialogControl() - - const {currentAccount} = useSession() - const {data: profile} = useProfileQuery({ - did: currentAccount!.did, - }) - - useEffect(() => { - if (profile && typeof profile.associated?.chat === 'undefined') { - const timeout = setTimeout(() => { - control.open() - }, 1000) - - return () => { - clearTimeout(timeout) - } - } - }, [profile, control]) - - if (!profile) return null - - return ( - - - - ) -} - -function DialogInner({ - chatDeclation, -}: { - chatDeclation?: ChatBskyActorDeclaration.Record -}) { - const control = Dialog.useDialogContext() - const {_} = useLingui() - const t = useTheme() - - const [initialized, setInitialzed] = React.useState(false) - const {mutate: updateDeclaration} = useUpdateActorDeclaration({ - onError: () => { - Toast.show(_(msg`Failed to update settings`), 'xmark') - }, - }) - - const onSelectItem = useCallback( - (keys: string[]) => { - const key = keys[0] - if (!key) return - updateDeclaration(key as 'all' | 'none' | 'following') - }, - [updateDeclaration], - ) - - useEffect(() => { - if (!chatDeclation && !initialized) { - updateDeclaration('following') - setInitialzed(true) - } - }, [chatDeclation, updateDeclaration, initialized]) - - return ( - - - - - - Direct messages are here! - - - Privately chat with other users. - - - - - - Who can message you? - - - You can change this at any time. - - - - - - - - Everyone - - - - - - Users I follow - - - - - - No one - - - - - - - - - - - - ) -} diff --git a/src/components/forms/Toggle.tsx b/src/components/forms/Toggle.tsx index 6dc387b239..bb5f154df0 100644 --- a/src/components/forms/Toggle.tsx +++ b/src/components/forms/Toggle.tsx @@ -1,9 +1,9 @@ import React from 'react' -import {Pressable, View, ViewStyle} from 'react-native' +import {Pressable, PressableProps, View, ViewStyle} from 'react-native' import Animated, {LinearTransition} from 'react-native-reanimated' +import {HITSLOP_10} from '#/lib/constants' import {isNative} from '#/platform/detection' -import {HITSLOP_10} from 'lib/constants' import { atoms as a, flatten, @@ -68,6 +68,7 @@ export type ItemProps = ViewStyleProp & { onChange?: (selected: boolean) => void isInvalid?: boolean children: ((props: ItemState) => React.ReactNode) | React.ReactNode + PressableComponent?: React.ComponentType } export function useItemContext() { @@ -159,6 +160,7 @@ export function Item({ style, type = 'checkbox', label, + PressableComponent = Pressable, ...rest }: ItemProps) { const { @@ -206,7 +208,7 @@ export function Item({ return ( - {typeof children === 'function' ? children(state) : children} - + ) } diff --git a/src/components/intents/VerifyEmailIntentDialog.tsx b/src/components/intents/VerifyEmailIntentDialog.tsx index 9f3808020a..070ba7c40e 100644 --- a/src/components/intents/VerifyEmailIntentDialog.tsx +++ b/src/components/intents/VerifyEmailIntentDialog.tsx @@ -3,11 +3,14 @@ import {View} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isNative} from '#/platform/detection' import {useAgent, useSession} from '#/state/session' -import {atoms as a} from '#/alf' -import {Button, ButtonText} from '#/components/Button' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {Button, ButtonIcon, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' import {DialogControlProps} from '#/components/Dialog' +import {Divider} from '#/components/Divider' +import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Resend} from '#/components/icons/ArrowRotateCounterClockwise' import {useIntentDialogs} from '#/components/intents/IntentDialogs' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' @@ -22,7 +25,9 @@ export function VerifyEmailIntentDialog() { ) } -function Inner({control}: {control: DialogControlProps}) { +function Inner({}: {control: DialogControlProps}) { + const t = useTheme() + const {gtMobile} = useBreakpoints() const {_} = useLingui() const {verifyEmailState: state} = useIntentDialogs() const [status, setStatus] = React.useState< @@ -57,43 +62,47 @@ function Inner({control}: {control: DialogControlProps}) { } return ( - - + {status === 'loading' ? ( - + ) : status === 'success' ? ( - <> - + + Email Verified - + - Thanks, you have successfully verified your email address. + Thanks, you have successfully verified your email address. You + can close this dialog. - + ) : status === 'failure' ? ( - <> - + + Invalid Verification Code - + The verification code you have provided is invalid. Please make sure that you have used the correct verification link or request a new one. - + ) : ( - <> - + + Email Resent - + We have sent another verification email to{' '} @@ -102,38 +111,29 @@ function Inner({control}: {control: DialogControlProps}) { . - + )} - {status !== 'loading' ? ( - + + {status === 'failure' && ( + <> + - {status === 'failure' ? ( - - ) : null} - - ) : null} + + )} + + ) } diff --git a/src/screens/Messages/List/index.tsx b/src/screens/Messages/ChatList.tsx similarity index 98% rename from src/screens/Messages/List/index.tsx rename to src/screens/Messages/ChatList.tsx index efd717f0b4..9912456e13 100644 --- a/src/screens/Messages/List/index.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -22,7 +22,6 @@ import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {DialogControlProps, useDialogControl} from '#/components/Dialog' import {NewChat} from '#/components/dms/dialogs/NewChatDialog' -import {MessagesNUX} from '#/components/dms/MessagesNUX' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as Retry} from '#/components/icons/ArrowRotateCounterClockwise' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' @@ -33,7 +32,7 @@ import {Link} from '#/components/Link' import {ListFooter} from '#/components/Lists' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' -import {ChatListItem} from './ChatListItem' +import {ChatListItem} from './components/ChatListItem' type Props = NativeStackScreenProps @@ -151,8 +150,6 @@ export function MessagesScreen({navigation, route}: Props) { if (conversations.length < 1) { return ( - - {gtMobile ? ( - {!gtMobile && ( ( return ( - - - - - + + + + + + + ) }, diff --git a/src/view/com/auth/server-input/index.tsx b/src/view/com/auth/server-input/index.tsx index de7ed6d15b..659ff7f733 100644 --- a/src/view/com/auth/server-input/index.tsx +++ b/src/view/com/auth/server-input/index.tsx @@ -12,6 +12,7 @@ import * as TextField from '#/components/forms/TextField' import * as ToggleButton from '#/components/forms/ToggleButton' import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' import {InlineLinkText} from '#/components/Link' +import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable' import {P, Text} from '#/components/Typography' export function ServerInputDialog({ @@ -82,7 +83,10 @@ export function ServerInputDialog({ label="Preferences" values={fixedOption} onChange={setFixedOption}> - + {_(msg`Bluesky`)} @@ -90,7 +94,8 @@ export function ServerInputDialog({ + label={_(msg`Custom`)} + PressableComponent={NormalizedRNGHPressable}> {_(msg`Custom`)} diff --git a/src/view/com/post-thread/PostThreadComposePrompt.tsx b/src/view/com/post-thread/PostThreadComposePrompt.tsx index 67981618e1..5ad4c256dd 100644 --- a/src/view/com/post-thread/PostThreadComposePrompt.tsx +++ b/src/view/com/post-thread/PostThreadComposePrompt.tsx @@ -5,12 +5,12 @@ import {useLingui} from '@lingui/react' import {PressableScale} from '#/lib/custom-animations/PressableScale' import {useHaptics} from '#/lib/haptics' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useHapticsDisabled} from '#/state/preferences' import {useProfileQuery} from '#/state/queries/profile' import {useSession} from '#/state/session' import {UserAvatar} from '#/view/com/util/UserAvatar' -import {atoms as a, useTheme} from '#/alf' +import {atoms as a, useBreakpoints, useTheme} from '#/alf' +import {useInteractionState} from '#/components/hooks/useInteractionState' import {Text} from '#/components/Typography' export function PostThreadComposePrompt({ @@ -21,10 +21,15 @@ export function PostThreadComposePrompt({ const {currentAccount} = useSession() const {data: profile} = useProfileQuery({did: currentAccount?.did}) const {_} = useLingui() - const {isTabletOrDesktop} = useWebMediaQueries() + const {gtMobile} = useBreakpoints() const t = useTheme() const playHaptics = useHaptics() const isHapticsDisabled = useHapticsDisabled() + const { + state: hovered, + onIn: onHoverIn, + onOut: onHoverOut, + } = useInteractionState() const onPress = () => { playHaptics('Light') @@ -42,13 +47,15 @@ export function PostThreadComposePrompt({ accessibilityLabel={_(msg`Compose reply`)} accessibilityHint={_(msg`Opens composer`)} style={[ - {paddingTop: 8, paddingBottom: isTabletOrDesktop ? 8 : 11}, - a.px_sm, + gtMobile ? a.py_xs : {paddingTop: 8, paddingBottom: 11}, + gtMobile ? {paddingLeft: 6, paddingRight: 6} : a.px_sm, a.border_t, t.atoms.border_contrast_low, t.atoms.bg, ]} - onPress={onPress}> + onPress={onPress} + onHoverIn={onHoverIn} + onHoverOut={onHoverOut}> diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index 14c869df8d..896e197b65 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -156,7 +156,6 @@ let RepostButton = ({