diff options
Diffstat (limited to 'src/view')
-rw-r--r-- | src/view/com/composer/text-input/TextInput.web.tsx | 6 | ||||
-rw-r--r-- | src/view/com/composer/text-input/web/EmojiPicker.tsx | 37 | ||||
-rw-r--r-- | src/view/com/composer/text-input/web/EmojiPicker.web.tsx | 11 | ||||
-rw-r--r-- | src/view/com/modals/InAppBrowserConsent.tsx | 99 | ||||
-rw-r--r-- | src/view/com/modals/Modal.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/forms/NativeDropdown.web.tsx | 14 | ||||
-rw-r--r-- | src/view/screens/ModerationBlockedAccounts.tsx | 186 | ||||
-rw-r--r-- | src/view/screens/ModerationMutedAccounts.tsx | 182 | ||||
-rw-r--r-- | src/view/shell/Composer.web.tsx | 22 | ||||
-rw-r--r-- | src/view/shell/createNativeStackNavigatorWithAuth.tsx | 25 | ||||
-rw-r--r-- | src/view/shell/desktop/LeftNav.tsx | 2 | ||||
-rw-r--r-- | src/view/shell/index.tsx | 2 |
13 files changed, 253 insertions, 341 deletions
diff --git a/src/view/com/composer/text-input/TextInput.web.tsx b/src/view/com/composer/text-input/TextInput.web.tsx index 8ec4fefa8..06ff9836c 100644 --- a/src/view/com/composer/text-input/TextInput.web.tsx +++ b/src/view/com/composer/text-input/TextInput.web.tsx @@ -12,14 +12,14 @@ import {Placeholder} from '@tiptap/extension-placeholder' import {Text as TiptapText} from '@tiptap/extension-text' import {generateJSON} from '@tiptap/html' import {Fragment, Node, Slice} from '@tiptap/pm/model' -import {EditorContent, JSONContent, useEditor} from '@tiptap/react' +import {EditorContent, type JSONContent, useEditor} from '@tiptap/react' import {useColorSchemeStyle} from '#/lib/hooks/useColorSchemeStyle' import {usePalette} from '#/lib/hooks/usePalette' import {blobToDataUri, isUriImage} from '#/lib/media/util' import {useActorAutocompleteFn} from '#/state/queries/actor-autocomplete' import { - LinkFacetMatch, + type LinkFacetMatch, suggestLinkCardUri, } from '#/view/com/composer/text-input/text-input-util' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' @@ -28,7 +28,7 @@ import {normalizeTextStyles} from '#/alf/typography' import {Portal} from '#/components/Portal' import {Text} from '../../util/text/Text' import {createSuggestion} from './web/Autocomplete' -import {Emoji} from './web/EmojiPicker.web' +import {type Emoji} from './web/EmojiPicker' import {LinkDecorator} from './web/LinkDecorator' import {TagDecorator} from './web/TagDecorator' diff --git a/src/view/com/composer/text-input/web/EmojiPicker.tsx b/src/view/com/composer/text-input/web/EmojiPicker.tsx new file mode 100644 index 000000000..5001753a5 --- /dev/null +++ b/src/view/com/composer/text-input/web/EmojiPicker.tsx @@ -0,0 +1,37 @@ +export type Emoji = { + aliases?: string[] + emoticons: string[] + id: string + keywords: string[] + name: string + native: string + shortcodes?: string + unified: string +} + +export interface EmojiPickerPosition { + top: number + left: number + right: number + bottom: number + nextFocusRef: React.MutableRefObject<HTMLElement> | null +} + +export interface EmojiPickerState { + isOpen: boolean + pos: EmojiPickerPosition +} + +interface IProps { + state: EmojiPickerState + close: () => void + /** + * If `true`, overrides position and ensures picker is pinned to the top of + * the target element. + */ + pinToTop?: boolean +} + +export function EmojiPicker(_opts: IProps) { + return null +} diff --git a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx index b3659f22d..c0cae620f 100644 --- a/src/view/com/composer/text-input/web/EmojiPicker.web.tsx +++ b/src/view/com/composer/text-input/web/EmojiPicker.web.tsx @@ -3,8 +3,7 @@ import {Pressable, useWindowDimensions, View} from 'react-native' import Picker from '@emoji-mart/react' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {DismissableLayer} from '@radix-ui/react-dismissable-layer' -import {FocusScope} from '@radix-ui/react-focus-scope' +import {DismissableLayer, FocusScope} from 'radix-ui/internal' import {textInputWebEmitter} from '#/view/com/composer/text-input/textInputWebEmitter' import {atoms as a, flatten} from '#/alf' @@ -121,7 +120,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) { return ( <Portal> - <FocusScope + <FocusScope.FocusScope loop trapped onUnmountAutoFocus={e => { @@ -154,7 +153,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) { }, ])}> <View style={[{position: 'absolute'}, position]}> - <DismissableLayer + <DismissableLayer.DismissableLayer onFocusOutside={evt => evt.preventDefault()} onDismiss={close}> <Picker @@ -164,7 +163,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) { onEmojiSelect={onInsert} autoFocus={true} /> - </DismissableLayer> + </DismissableLayer.DismissableLayer> </View> </View> @@ -175,7 +174,7 @@ export function EmojiPicker({state, close, pinToTop}: IProps) { onPress={close} style={[a.fixed, a.inset_0]} /> - </FocusScope> + </FocusScope.FocusScope> </Portal> ) } diff --git a/src/view/com/modals/InAppBrowserConsent.tsx b/src/view/com/modals/InAppBrowserConsent.tsx deleted file mode 100644 index 105edfbc6..000000000 --- a/src/view/com/modals/InAppBrowserConsent.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import React from 'react' -import {StyleSheet, View} from 'react-native' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {useOpenLink} from '#/lib/hooks/useOpenLink' -import {usePalette} from '#/lib/hooks/usePalette' -import {s} from '#/lib/styles' -import {useModalControls} from '#/state/modals' -import {useSetInAppBrowser} from '#/state/preferences/in-app-browser' -import {ScrollView} from '#/view/com/modals/util' -import {Button} from '#/view/com/util/forms/Button' -import {Text} from '#/view/com/util/text/Text' - -export const snapPoints = [350] - -export function Component({href}: {href: string}) { - const pal = usePalette('default') - const {closeModal} = useModalControls() - const {_} = useLingui() - const setInAppBrowser = useSetInAppBrowser() - const openLink = useOpenLink() - - const onUseIAB = React.useCallback(() => { - setInAppBrowser(true) - closeModal() - openLink(href, true) - }, [closeModal, setInAppBrowser, href, openLink]) - - const onUseLinking = React.useCallback(() => { - setInAppBrowser(false) - closeModal() - openLink(href, false) - }, [closeModal, setInAppBrowser, href, openLink]) - - return ( - <ScrollView - testID="inAppBrowserConsentModal" - style={[s.flex1, pal.view, {paddingHorizontal: 20, paddingTop: 10}]}> - <Text style={[pal.text, styles.title]}> - <Trans>How should we open this link?</Trans> - </Text> - <Text style={pal.text}> - <Trans> - Your choice will be saved, but can be changed later in settings. - </Trans> - </Text> - <View style={[styles.btnContainer]}> - <Button - testID="confirmBtn" - type="inverted" - onPress={onUseIAB} - accessibilityLabel={_(msg`Use in-app browser`)} - accessibilityHint="" - label={_(msg`Use in-app browser`)} - labelContainerStyle={{justifyContent: 'center', padding: 8}} - labelStyle={[s.f18]} - /> - <Button - testID="confirmBtn" - type="inverted" - onPress={onUseLinking} - accessibilityLabel={_(msg`Use my default browser`)} - accessibilityHint="" - label={_(msg`Use my default browser`)} - labelContainerStyle={{justifyContent: 'center', padding: 8}} - labelStyle={[s.f18]} - /> - <Button - testID="cancelBtn" - type="default" - onPress={() => { - closeModal() - }} - accessibilityLabel={_(msg`Cancel`)} - accessibilityHint="" - label={_(msg`Cancel`)} - labelContainerStyle={{justifyContent: 'center', padding: 8}} - labelStyle={[s.f18]} - /> - </View> - </ScrollView> - ) -} - -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - fontWeight: '600', - fontSize: 24, - marginBottom: 12, - }, - btnContainer: { - marginTop: 20, - flexDirection: 'column', - justifyContent: 'center', - rowGap: 10, - }, -}) diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx index d0b50c857..8fd927f16 100644 --- a/src/view/com/modals/Modal.tsx +++ b/src/view/com/modals/Modal.tsx @@ -11,7 +11,6 @@ import * as ChangePasswordModal from './ChangePassword' import * as CreateOrEditListModal from './CreateOrEditList' import * as DeleteAccountModal from './DeleteAccount' import * as EditProfileModal from './EditProfile' -import * as InAppBrowserConsentModal from './InAppBrowserConsent' import * as InviteCodesModal from './InviteCodes' import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings' import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings' @@ -76,9 +75,6 @@ export function ModalsContainer() { } else if (activeModal?.name === 'link-warning') { snapPoints = LinkWarningModal.snapPoints element = <LinkWarningModal.Component {...activeModal} /> - } else if (activeModal?.name === 'in-app-browser-consent') { - snapPoints = InAppBrowserConsentModal.snapPoints - element = <InAppBrowserConsentModal.Component {...activeModal} /> } else { return null } diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index d5af32236..fd8e3a38b 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -107,11 +107,11 @@ let PostMeta = (opts: PostMetaOpts): React.ReactNode => { a.pl_2xs, a.self_center, { - marginTop: platform({web: -1, ios: -1, android: -2}), + marginTop: platform({web: 0, ios: 0, android: -1}), }, ]}> <VerificationCheck - width={14} + width={platform({android: 13, default: 12})} verifier={verification.role === 'verifier'} /> </View> diff --git a/src/view/com/util/forms/NativeDropdown.web.tsx b/src/view/com/util/forms/NativeDropdown.web.tsx index b3ec319e3..9b4a84e05 100644 --- a/src/view/com/util/forms/NativeDropdown.web.tsx +++ b/src/view/com/util/forms/NativeDropdown.web.tsx @@ -1,9 +1,15 @@ import React from 'react' -import {Pressable, StyleSheet, Text, View, ViewStyle} from 'react-native' -import {IconProp} from '@fortawesome/fontawesome-svg-core' +import { + Pressable, + StyleSheet, + Text, + type View, + type ViewStyle, +} from 'react-native' +import {type IconProp} from '@fortawesome/fontawesome-svg-core' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' -import {MenuItemCommonProps} from 'zeego/lib/typescript/menu' +import {DropdownMenu} from 'radix-ui' +import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu' import {HITSLOP_10} from '#/lib/constants' import {usePalette} from '#/lib/hooks/usePalette' diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx index cefa29f6c..bb94f8083 100644 --- a/src/view/screens/ModerationBlockedAccounts.tsx +++ b/src/view/screens/ModerationBlockedAccounts.tsx @@ -1,19 +1,10 @@ -import React from 'react' -import { - ActivityIndicator, - FlatList, - RefreshControl, - StyleSheet, - View, -} from 'react-native' +import {useCallback, useMemo, useState} from 'react' +import {type StyleProp, View, type ViewStyle} from 'react-native' import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {Trans} from '@lingui/macro' import {useFocusEffect} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {type CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -21,11 +12,12 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useMyBlockedAccountsQuery} from '#/state/queries/my-blocked-accounts' import {useSetMinimalShellMode} from '#/state/shell' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' -import {Text} from '#/view/com/util/text/Text' -import {ViewHeader} from '#/view/com/util/ViewHeader' +import {List} from '#/view/com/util/List' import {atoms as a, useTheme} from '#/alf' import * as Layout from '#/components/Layout' +import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' +import {Text} from '#/components/Typography' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -33,13 +25,10 @@ type Props = NativeStackScreenProps< > export function ModerationBlockedAccounts({}: Props) { const t = useTheme() - const pal = usePalette('default') - const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop} = useWebMediaQueries() const moderationOpts = useModerationOpts() - const [isPTRing, setIsPTRing] = React.useState(false) + const [isPTRing, setIsPTRing] = useState(false) const { data, isFetching, @@ -51,7 +40,7 @@ export function ModerationBlockedAccounts({}: Props) { isFetchingNextPage, } = useMyBlockedAccountsQuery() const isEmpty = !isFetching && !data?.pages[0]?.blocks.length - const profiles = React.useMemo(() => { + const profiles = useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.blocks) } @@ -59,12 +48,12 @@ export function ModerationBlockedAccounts({}: Props) { }, [data]) useFocusEffect( - React.useCallback(() => { + useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]), ) - const onRefresh = React.useCallback(async () => { + const onRefresh = useCallback(async () => { setIsPTRing(true) try { await refetch() @@ -74,7 +63,7 @@ export function ModerationBlockedAccounts({}: Props) { setIsPTRing(false) }, [refetch, setIsPTRing]) - const onEndReached = React.useCallback(async () => { + const onEndReached = useCallback(async () => { if (isFetching || !hasNextPage || isError) return try { @@ -104,28 +93,22 @@ export function ModerationBlockedAccounts({}: Props) { </View> ) } + return ( <Layout.Screen testID="blockedAccountsScreen"> - <Layout.Center style={[a.flex_1, {paddingBottom: 100}]}> - <ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop /> - <Text - type="sm" - style={[ - styles.description, - pal.text, - isTabletOrDesktop && styles.descriptionDesktop, - { - marginTop: 20, - }, - ]}> - <Trans> - Blocked accounts cannot reply in your threads, mention you, or - otherwise interact with you. You will not see their content and they - will be prevented from seeing yours. - </Trans> - </Text> + <Layout.Center> + <Layout.Header.Outer> + <Layout.Header.BackButton /> + <Layout.Header.Content> + <Layout.Header.TitleText> + <Trans>Blocked Accounts</Trans> + </Layout.Header.TitleText> + </Layout.Header.Content> + <Layout.Header.Slot /> + </Layout.Header.Outer> {isEmpty ? ( - <View style={[pal.border]}> + <View> + <Info style={[a.border_b]} /> {isError ? ( <ErrorScreen title="Oops!" @@ -133,42 +116,29 @@ export function ModerationBlockedAccounts({}: Props) { onPressTryAgain={refetch} /> ) : ( - <View style={[styles.empty, pal.viewLight]}> - <Text type="lg" style={[pal.text, styles.emptyText]}> - <Trans> - You have not blocked any accounts yet. To block an account, - go to their profile and select "Block account" from the menu - on their account. - </Trans> - </Text> - </View> + <Empty /> )} </View> ) : ( - <FlatList - style={[!isTabletOrDesktop && styles.flex1]} + <List data={profiles} keyExtractor={(item: ActorDefs.ProfileView) => item.did} - refreshControl={ - <RefreshControl - refreshing={isPTRing} - onRefresh={onRefresh} - tintColor={pal.colors.text} - titleColor={pal.colors.text} - /> - } + refreshing={isPTRing} + onRefresh={onRefresh} onEndReached={onEndReached} renderItem={renderItem} initialNumToRender={15} // FIXME(dan) - ListFooterComponent={() => ( - <View style={styles.footer}> - {(isFetching || isFetchingNextPage) && <ActivityIndicator />} - </View> - )} - // @ts-ignore our .web version only -prf - desktopFixedHeight + ListHeaderComponent={Info} + ListFooterComponent={ + <ListFooter + isFetchingNextPage={isFetchingNextPage} + hasNextPage={hasNextPage} + error={cleanError(error)} + onRetry={fetchNextPage} + /> + } /> )} </Layout.Center> @@ -176,37 +146,53 @@ export function ModerationBlockedAccounts({}: Props) { ) } -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - marginTop: 12, - marginBottom: 12, - }, - description: { - textAlign: 'center', - paddingHorizontal: 30, - marginBottom: 14, - }, - descriptionDesktop: { - marginTop: 14, - }, - - flex1: { - flex: 1, - }, - empty: { - paddingHorizontal: 20, - paddingVertical: 20, - borderRadius: 16, - marginHorizontal: 24, - marginTop: 10, - }, - emptyText: { - textAlign: 'center', - }, +function Empty() { + const t = useTheme() + return ( + <View style={[a.pt_2xl, a.px_xl, a.align_center]}> + <View + style={[ + a.py_md, + a.px_lg, + a.rounded_sm, + t.atoms.bg_contrast_25, + a.border, + t.atoms.border_contrast_low, + {maxWidth: 400}, + ]}> + <Text style={[a.text_sm, a.text_center, t.atoms.text_contrast_high]}> + <Trans> + You have not blocked any accounts yet. To block an account, go to + their profile and select "Block account" from the menu on their + account. + </Trans> + </Text> + </View> + </View> + ) +} - footer: { - height: 200, - paddingTop: 20, - }, -}) +function Info({style}: {style?: StyleProp<ViewStyle>}) { + const t = useTheme() + return ( + <View + style={[ + a.w_full, + t.atoms.bg_contrast_25, + a.py_md, + a.px_xl, + a.border_t, + {marginTop: a.border.borderWidth * -1}, + t.atoms.border_contrast_low, + style, + ]}> + <Text style={[a.text_center, a.text_sm, t.atoms.text_contrast_high]}> + <Trans> + Blocked accounts cannot reply in your threads, mention you, or + otherwise interact with you. You will not see their content and they + will be prevented from seeing yours. + </Trans> + </Text> + </View> + ) +} diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx index f49337b7c..11d787ca1 100644 --- a/src/view/screens/ModerationMutedAccounts.tsx +++ b/src/view/screens/ModerationMutedAccounts.tsx @@ -1,19 +1,11 @@ -import React from 'react' -import { - ActivityIndicator, - FlatList, - RefreshControl, - StyleSheet, - View, -} from 'react-native' +import {useCallback, useMemo, useState} from 'react' +import {type StyleProp, View, type ViewStyle} from 'react-native' import {type AppBskyActorDefs as ActorDefs} from '@atproto/api' -import {msg, Trans} from '@lingui/macro' +import {Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect} from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' -import {usePalette} from '#/lib/hooks/usePalette' -import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {type CommonNavigatorParams} from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' @@ -21,11 +13,12 @@ import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useMyMutedAccountsQuery} from '#/state/queries/my-muted-accounts' import {useSetMinimalShellMode} from '#/state/shell' import {ErrorScreen} from '#/view/com/util/error/ErrorScreen' -import {Text} from '#/view/com/util/text/Text' -import {ViewHeader} from '#/view/com/util/ViewHeader' +import {List} from '#/view/com/util/List' import {atoms as a, useTheme} from '#/alf' import * as Layout from '#/components/Layout' +import {ListFooter} from '#/components/Lists' import * as ProfileCard from '#/components/ProfileCard' +import {Text} from '#/components/Typography' type Props = NativeStackScreenProps< CommonNavigatorParams, @@ -33,13 +26,11 @@ type Props = NativeStackScreenProps< > export function ModerationMutedAccounts({}: Props) { const t = useTheme() - const pal = usePalette('default') + const moderationOpts = useModerationOpts() const {_} = useLingui() const setMinimalShellMode = useSetMinimalShellMode() - const {isTabletOrDesktop} = useWebMediaQueries() - const moderationOpts = useModerationOpts() - const [isPTRing, setIsPTRing] = React.useState(false) + const [isPTRing, setIsPTRing] = useState(false) const { data, isFetching, @@ -51,7 +42,7 @@ export function ModerationMutedAccounts({}: Props) { isFetchingNextPage, } = useMyMutedAccountsQuery() const isEmpty = !isFetching && !data?.pages[0]?.mutes.length - const profiles = React.useMemo(() => { + const profiles = useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.mutes) } @@ -59,12 +50,12 @@ export function ModerationMutedAccounts({}: Props) { }, [data]) useFocusEffect( - React.useCallback(() => { + useCallback(() => { setMinimalShellMode(false) }, [setMinimalShellMode]), ) - const onRefresh = React.useCallback(async () => { + const onRefresh = useCallback(async () => { setIsPTRing(true) try { await refetch() @@ -74,7 +65,7 @@ export function ModerationMutedAccounts({}: Props) { setIsPTRing(false) }, [refetch, setIsPTRing]) - const onEndReached = React.useCallback(async () => { + const onEndReached = useCallback(async () => { if (isFetching || !hasNextPage || isError) return try { @@ -120,25 +111,19 @@ export function ModerationMutedAccounts({}: Props) { } return ( <Layout.Screen testID="mutedAccountsScreen"> - <ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop /> - <Layout.Center style={[a.flex_1, {paddingBottom: 100}]}> - <Text - type="sm" - style={[ - styles.description, - pal.text, - isTabletOrDesktop && styles.descriptionDesktop, - { - marginTop: 20, - }, - ]}> - <Trans> - Muted accounts have their posts removed from your feed and from your - notifications. Mutes are completely private. - </Trans> - </Text> + <Layout.Header.Outer> + <Layout.Header.BackButton /> + <Layout.Header.Content> + <Layout.Header.TitleText> + <Trans>Muted Accounts</Trans> + </Layout.Header.TitleText> + </Layout.Header.Content> + <Layout.Header.Slot /> + </Layout.Header.Outer> + <Layout.Center> {isEmpty ? ( - <View style={[pal.border]}> + <View> + <Info style={[a.border_b]} /> {isError ? ( <ErrorScreen title="Oops!" @@ -146,42 +131,29 @@ export function ModerationMutedAccounts({}: Props) { onPressTryAgain={refetch} /> ) : ( - <View style={[styles.empty, pal.viewLight]}> - <Text type="lg" style={[pal.text, styles.emptyText]}> - <Trans> - You have not muted any accounts yet. To mute an account, go - to their profile and select "Mute account" from the menu on - their account. - </Trans> - </Text> - </View> + <Empty /> )} </View> ) : ( - <FlatList - style={[!isTabletOrDesktop && styles.flex1]} + <List data={profiles} keyExtractor={item => item.did} - refreshControl={ - <RefreshControl - refreshing={isPTRing} - onRefresh={onRefresh} - tintColor={pal.colors.text} - titleColor={pal.colors.text} - /> - } + refreshing={isPTRing} + onRefresh={onRefresh} onEndReached={onEndReached} renderItem={renderItem} initialNumToRender={15} // FIXME(dan) - ListFooterComponent={() => ( - <View style={styles.footer}> - {(isFetching || isFetchingNextPage) && <ActivityIndicator />} - </View> - )} - // @ts-ignore our .web version only -prf - desktopFixedHeight + ListHeaderComponent={Info} + ListFooterComponent={ + <ListFooter + isFetchingNextPage={isFetchingNextPage} + hasNextPage={hasNextPage} + error={cleanError(error)} + onRetry={fetchNextPage} + /> + } /> )} </Layout.Center> @@ -189,37 +161,51 @@ export function ModerationMutedAccounts({}: Props) { ) } -const styles = StyleSheet.create({ - title: { - textAlign: 'center', - marginTop: 12, - marginBottom: 12, - }, - description: { - textAlign: 'center', - paddingHorizontal: 30, - marginBottom: 14, - }, - descriptionDesktop: { - marginTop: 14, - }, - - flex1: { - flex: 1, - }, - empty: { - paddingHorizontal: 20, - paddingVertical: 20, - borderRadius: 16, - marginHorizontal: 24, - marginTop: 10, - }, - emptyText: { - textAlign: 'center', - }, +function Empty() { + const t = useTheme() + return ( + <View style={[a.pt_2xl, a.px_xl, a.align_center]}> + <View + style={[ + a.py_md, + a.px_lg, + a.rounded_sm, + t.atoms.bg_contrast_25, + a.border, + t.atoms.border_contrast_low, + {maxWidth: 400}, + ]}> + <Text style={[a.text_sm, a.text_center, t.atoms.text_contrast_high]}> + <Trans> + You have not muted any accounts yet. To mute an account, go to their + profile and select "Mute account" from the menu on their account. + </Trans> + </Text> + </View> + </View> + ) +} - footer: { - height: 200, - paddingTop: 20, - }, -}) +function Info({style}: {style?: StyleProp<ViewStyle>}) { + const t = useTheme() + return ( + <View + style={[ + a.w_full, + t.atoms.bg_contrast_25, + a.py_md, + a.px_xl, + a.border_t, + {marginTop: a.border.borderWidth * -1}, + t.atoms.border_contrast_low, + style, + ]}> + <Text style={[a.text_center, a.text_sm, t.atoms.text_contrast_high]}> + <Trans> + Muted accounts have their posts removed from your feed and from your + notifications. Mutes are completely private. + </Trans> + </Text> + </View> + ) +} diff --git a/src/view/shell/Composer.web.tsx b/src/view/shell/Composer.web.tsx index b76e88372..ce3695212 100644 --- a/src/view/shell/Composer.web.tsx +++ b/src/view/shell/Composer.web.tsx @@ -1,18 +1,16 @@ import React from 'react' import {StyleSheet, View} from 'react-native' -import {DismissableLayer} from '@radix-ui/react-dismissable-layer' -import {useFocusGuards} from '@radix-ui/react-focus-guards' -import {FocusScope} from '@radix-ui/react-focus-scope' +import {DismissableLayer, FocusGuards, FocusScope} from 'radix-ui/internal' import {RemoveScrollBar} from 'react-remove-scroll-bar' import {useA11y} from '#/state/a11y' import {useModals} from '#/state/modals' -import {ComposerOpts, useComposerState} from '#/state/shell/composer' +import {type ComposerOpts, useComposerState} from '#/state/shell/composer' import { EmojiPicker, - EmojiPickerPosition, - EmojiPickerState, -} from '#/view/com/composer/text-input/web/EmojiPicker.web' + type EmojiPickerPosition, + type EmojiPickerState, +} from '#/view/com/composer/text-input/web/EmojiPicker' import {atoms as a, flatten, useBreakpoints, useTheme} from '#/alf' import {ComposePost, useComposerCancelRef} from '../com/composer/Composer' @@ -66,11 +64,11 @@ function Inner({state}: {state: ComposerOpts}) { })) }, []) - useFocusGuards() + FocusGuards.useFocusGuards() return ( - <FocusScope loop trapped asChild> - <DismissableLayer + <FocusScope.FocusScope loop trapped asChild> + <DismissableLayer.DismissableLayer role="dialog" aria-modal style={flatten([ @@ -114,8 +112,8 @@ function Inner({state}: {state: ComposerOpts}) { /> </View> <EmojiPicker state={pickerState} close={onClosePicker} /> - </DismissableLayer> - </FocusScope> + </DismissableLayer.DismissableLayer> + </FocusScope.FocusScope> ) } diff --git a/src/view/shell/createNativeStackNavigatorWithAuth.tsx b/src/view/shell/createNativeStackNavigatorWithAuth.tsx index 11beaa2e9..868bba5b0 100644 --- a/src/view/shell/createNativeStackNavigatorWithAuth.tsx +++ b/src/view/shell/createNativeStackNavigatorWithAuth.tsx @@ -5,21 +5,21 @@ import {View} from 'react-native' // Copyright (c) 2017 React Navigation Contributors import { createNavigatorFactory, - EventArg, - ParamListBase, - StackActionHelpers, + type EventArg, + type ParamListBase, + type StackActionHelpers, StackActions, - StackNavigationState, + type StackNavigationState, StackRouter, - StackRouterOptions, + type StackRouterOptions, useNavigationBuilder, } from '@react-navigation/native' -import type { - NativeStackNavigationEventMap, - NativeStackNavigationOptions, +import { + type NativeStackNavigationEventMap, + type NativeStackNavigationOptions, } from '@react-navigation/native-stack' import {NativeStackView} from '@react-navigation/native-stack' -import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types' +import {type NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types' import {PWI_ENABLED} from '#/lib/build-flags' import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' @@ -35,7 +35,7 @@ import {Deactivated} from '#/screens/Deactivated' import {Onboarding} from '#/screens/Onboarding' import {SignupQueued} from '#/screens/SignupQueued' import {Takendown} from '#/screens/Takendown' -import {atoms as a} from '#/alf' +import {atoms as a, useLayoutBreakpoints} from '#/alf' import {BottomBarWeb} from './bottom-bar/BottomBarWeb' import {DesktopLeftNav} from './desktop/LeftNav' import {DesktopRightNav} from './desktop/RightNav' @@ -101,7 +101,8 @@ function NativeStackNavigator({ const onboardingState = useOnboardingState() const {showLoggedOut} = useLoggedOutView() const {setShowLoggedOut} = useLoggedOutViewControls() - const {isMobile, isTabletOrMobile} = useWebMediaQueries() + const {isMobile} = useWebMediaQueries() + const {leftNavMinimal} = useLayoutBreakpoints() if (!hasSession && (!PWI_ENABLED || activeRouteRequiresAuth || isNative)) { return <LoggedOut /> } @@ -138,7 +139,7 @@ function NativeStackNavigator({ // Show the bottom bar if we have a session only on mobile web. If we don't have a session, we want to show it // on both tablet and mobile web so that we see the create account CTA. - const showBottomBar = hasSession ? isMobile : isTabletOrMobile + const showBottomBar = hasSession ? isMobile : leftNavMinimal return ( <NavigationContent> diff --git a/src/view/shell/desktop/LeftNav.tsx b/src/view/shell/desktop/LeftNav.tsx index 5cef18ebf..7d7c0ac8d 100644 --- a/src/view/shell/desktop/LeftNav.tsx +++ b/src/view/shell/desktop/LeftNav.tsx @@ -571,7 +571,7 @@ export function DesktopLeftNav() { ]}> {hasSession ? ( <ProfileCard /> - ) : isDesktop ? ( + ) : !leftNavMinimal ? ( <View style={[a.pt_xl]}> <NavSignupCard /> </View> diff --git a/src/view/shell/index.tsx b/src/view/shell/index.tsx index 3d3a5520c..1e34f6da5 100644 --- a/src/view/shell/index.tsx +++ b/src/view/shell/index.tsx @@ -25,6 +25,7 @@ import {ModalsContainer} from '#/view/com/modals/Modal' import {ErrorBoundary} from '#/view/com/util/ErrorBoundary' import {atoms as a, select, useTheme} from '#/alf' import {setSystemUITheme} from '#/alf/util/systemUI' +import {InAppBrowserConsentDialog} from '#/components/dialogs/InAppBrowserConsent' import {MutedWordsDialog} from '#/components/dialogs/MutedWords' import {SigninDialog} from '#/components/dialogs/Signin' import {Outlet as PortalOutlet} from '#/components/Portal' @@ -151,6 +152,7 @@ function ShellInner() { <ModalsContainer /> <MutedWordsDialog /> <SigninDialog /> + <InAppBrowserConsentDialog /> <Lightbox /> <PortalOutlet /> <BottomSheetOutlet /> |