diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/feeds/FeedPage.tsx | 21 | ||||
-rw-r--r-- | src/view/com/feeds/FeedSourceCard.tsx | 74 | ||||
-rw-r--r-- | src/view/com/home/HomeHeader.tsx | 19 | ||||
-rw-r--r-- | src/view/com/lightbox/Lightbox.tsx | 25 | ||||
-rw-r--r-- | src/view/com/modals/SelfLabel.tsx | 13 | ||||
-rw-r--r-- | src/view/com/pager/TabBar.tsx | 16 | ||||
-rw-r--r-- | src/view/com/posts/Feed.tsx | 15 | ||||
-rw-r--r-- | src/view/com/posts/FeedErrorMessage.tsx | 53 | ||||
-rw-r--r-- | src/view/com/util/post-ctrls/RepostButton.tsx | 13 |
9 files changed, 138 insertions, 111 deletions
diff --git a/src/view/com/feeds/FeedPage.tsx b/src/view/com/feeds/FeedPage.tsx index bb782809d..6a9fc9346 100644 --- a/src/view/com/feeds/FeedPage.tsx +++ b/src/view/com/feeds/FeedPage.tsx @@ -1,5 +1,6 @@ import React from 'react' -import {useWindowDimensions, View} from 'react-native' +import {View} from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useNavigation} from '@react-navigation/native' @@ -17,9 +18,9 @@ import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import {useComposerControls} from '#/state/shell/composer' import {useAnalytics} from 'lib/analytics/analytics' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {ComposeIcon2} from 'lib/icons' import {s} from 'lib/styles' +import {useHeaderOffset} from '#/components/hooks/useHeaderOffset' import {Feed} from '../posts/Feed' import {FAB} from '../util/fab/FAB' import {ListMethods} from '../util/List' @@ -35,6 +36,7 @@ export function FeedPage({ feedParams, renderEmptyState, renderEndOfFeed, + savedFeedConfig, }: { testID?: string feed: FeedDescriptor @@ -42,6 +44,7 @@ export function FeedPage({ isPageFocused: boolean renderEmptyState: () => JSX.Element renderEndOfFeed?: () => JSX.Element + savedFeedConfig?: AppBskyActorDefs.SavedFeed }) { const {hasSession} = useSession() const {_} = useLingui() @@ -129,6 +132,7 @@ export function FeedPage({ renderEmptyState={renderEmptyState} renderEndOfFeed={renderEndOfFeed} headerOffset={headerOffset} + savedFeedConfig={savedFeedConfig} /> </FeedFeedbackProvider> </MainScrollProvider> @@ -153,16 +157,3 @@ export function FeedPage({ </View> ) } - -function useHeaderOffset() { - const {isDesktop, isTablet} = useWebMediaQueries() - const {fontScale} = useWindowDimensions() - if (isDesktop || isTablet) { - return 0 - } - const navBarHeight = 42 - const tabBarPad = 10 + 10 + 3 // padding + border - const normalLineHeight = 1.2 - const tabBarText = 16 * normalLineHeight * fontScale - return navBarHeight + tabBarPad + tabBarText -} diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 8a21d86ae..bb536bccd 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -1,29 +1,30 @@ import React from 'react' import {Pressable, StyleProp, StyleSheet, View, ViewStyle} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {Text} from '../util/text/Text' -import {RichText} from '#/components/RichText' -import {usePalette} from 'lib/hooks/usePalette' -import {s} from 'lib/styles' -import {UserAvatar} from '../util/UserAvatar' import {AtUri} from '@atproto/api' -import * as Toast from 'view/com/util/Toast' -import {sanitizeHandle} from 'lib/strings/handles' -import {logger} from '#/logger' -import {Trans, msg, Plural} from '@lingui/macro' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' + +import {logger} from '#/logger' +import {FeedSourceInfo, useFeedSourceInfoQuery} from '#/state/queries/feed' import { - usePinFeedMutation, - UsePreferencesQueryResponse, + useAddSavedFeedsMutation, usePreferencesQuery, - useSaveFeedMutation, + UsePreferencesQueryResponse, useRemoveFeedMutation, } from '#/state/queries/preferences' -import {useFeedSourceInfoQuery, FeedSourceInfo} from '#/state/queries/feed' +import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' +import {usePalette} from 'lib/hooks/usePalette' +import {sanitizeHandle} from 'lib/strings/handles' +import {s} from 'lib/styles' import {FeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' +import * as Toast from 'view/com/util/Toast' import {useTheme} from '#/alf' +import {atoms as a} from '#/alf' import * as Prompt from '#/components/Prompt' -import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped' +import {RichText} from '#/components/RichText' +import {Text} from '../util/text/Text' +import {UserAvatar} from '../util/UserAvatar' export function FeedSourceCard({ feedUri, @@ -87,53 +88,54 @@ export function FeedSourceCardLoaded({ const removePromptControl = Prompt.usePromptControl() const navigation = useNavigationDeduped() - const {isPending: isSavePending, mutateAsync: saveFeed} = - useSaveFeedMutation() + const {isPending: isAddSavedFeedPending, mutateAsync: addSavedFeeds} = + useAddSavedFeedsMutation() const {isPending: isRemovePending, mutateAsync: removeFeed} = useRemoveFeedMutation() - const {isPending: isPinPending, mutateAsync: pinFeed} = usePinFeedMutation() - const isSaved = Boolean(preferences?.feeds?.saved?.includes(feed?.uri || '')) + const savedFeedConfig = preferences?.savedFeeds?.find( + f => f.value === feed?.uri, + ) + const isSaved = Boolean(savedFeedConfig) const onSave = React.useCallback(async () => { - if (!feed) return + if (!feed || isSaved) return try { - if (pinOnSave) { - await pinFeed({uri: feed.uri}) - } else { - await saveFeed({uri: feed.uri}) - } + await addSavedFeeds([ + { + type: 'feed', + value: feed.uri, + pinned: pinOnSave, + }, + ]) Toast.show(_(msg`Added to my feeds`)) } catch (e) { Toast.show(_(msg`There was an issue contacting your server`)) logger.error('Failed to save feed', {message: e}) } - }, [_, feed, pinFeed, pinOnSave, saveFeed]) + }, [_, feed, pinOnSave, addSavedFeeds, isSaved]) const onUnsave = React.useCallback(async () => { - if (!feed) return + if (!savedFeedConfig) return try { - await removeFeed({uri: feed.uri}) + await removeFeed(savedFeedConfig) // await item.unsave() Toast.show(_(msg`Removed from my feeds`)) } catch (e) { Toast.show(_(msg`There was an issue contacting your server`)) logger.error('Failed to unsave feed', {message: e}) } - }, [_, feed, removeFeed]) + }, [_, removeFeed, savedFeedConfig]) const onToggleSaved = React.useCallback(async () => { - // Only feeds can be un/saved, lists are handled elsewhere - if (feed?.type !== 'feed') return - if (isSaved) { removePromptControl.open() } else { await onSave() } - }, [feed?.type, isSaved, removePromptControl, onSave]) + }, [isSaved, removePromptControl, onSave]) /* * LOAD STATE @@ -204,7 +206,7 @@ export function FeedSourceCardLoaded({ } }} key={feed.uri}> - <View style={[styles.headerContainer]}> + <View style={[styles.headerContainer, a.align_start]}> <View style={[s.mr10]}> <UserAvatar type="algo" size={36} avatar={feed.avatar} /> </View> @@ -221,11 +223,11 @@ export function FeedSourceCardLoaded({ </Text> </View> - {showSaveBtn && feed.type === 'feed' && ( + {showSaveBtn && ( <View style={[s.justifyCenter]}> <Pressable testID={`feed-${feed.displayName}-toggleSave`} - disabled={isSavePending || isPinPending || isRemovePending} + disabled={isAddSavedFeedPending || isRemovePending} accessibilityRole="button" accessibilityLabel={ isSaved diff --git a/src/view/com/home/HomeHeader.tsx b/src/view/com/home/HomeHeader.tsx index aa3ecb7fc..b068484e8 100644 --- a/src/view/com/home/HomeHeader.tsx +++ b/src/view/com/home/HomeHeader.tsx @@ -1,12 +1,14 @@ import React from 'react' -import {RenderTabBarFnProps} from 'view/com/pager/Pager' -import {HomeHeaderLayout} from './HomeHeaderLayout' -import {FeedSourceInfo} from '#/state/queries/feed' import {useNavigation} from '@react-navigation/native' + +import {usePalette} from '#/lib/hooks/usePalette' +import {FeedSourceInfo} from '#/state/queries/feed' +import {useSession} from '#/state/session' import {NavigationProp} from 'lib/routes/types' import {isWeb} from 'platform/detection' +import {RenderTabBarFnProps} from 'view/com/pager/Pager' import {TabBar} from '../pager/TabBar' -import {usePalette} from '#/lib/hooks/usePalette' +import {HomeHeaderLayout} from './HomeHeaderLayout' export function HomeHeader( props: RenderTabBarFnProps & { @@ -16,12 +18,17 @@ export function HomeHeader( }, ) { const {feeds} = props + const {hasSession} = useSession() const navigation = useNavigation<NavigationProp>() const pal = usePalette('default') const hasPinnedCustom = React.useMemo<boolean>(() => { - return feeds.some(tab => tab.uri !== '') - }, [feeds]) + if (!hasSession) return false + return feeds.some(tab => { + const isFollowing = tab.uri === 'following' + return !isFollowing + }) + }, [feeds, hasSession]) const items = React.useMemo(() => { const pinnedNames = feeds.map(f => f.displayName) diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx index fd4c486af..a95a94835 100644 --- a/src/view/com/lightbox/Lightbox.tsx +++ b/src/view/com/lightbox/Lightbox.tsx @@ -1,22 +1,23 @@ import React from 'react' import {LayoutAnimation, StyleSheet, View} from 'react-native' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import ImageView from './ImageViewing' -import {shareImageModal, saveImageToMediaLibrary} from 'lib/media/manip' -import * as Toast from '../util/Toast' -import {Text} from '../util/text/Text' -import {s, colors} from 'lib/styles' -import {Button} from '../util/forms/Button' -import {isIOS} from 'platform/detection' import * as MediaLibrary from 'expo-media-library' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + import { + ImagesLightbox, + ProfileImageLightbox, useLightbox, useLightboxControls, - ProfileImageLightbox, - ImagesLightbox, } from '#/state/lightbox' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' +import {saveImageToMediaLibrary, shareImageModal} from 'lib/media/manip' +import {colors, s} from 'lib/styles' +import {isIOS} from 'platform/detection' +import {Button} from '../util/forms/Button' +import {Text} from '../util/text/Text' +import * as Toast from '../util/Toast' +import ImageView from './ImageViewing' export function Lightbox() { const {activeLightbox} = useLightbox() diff --git a/src/view/com/modals/SelfLabel.tsx b/src/view/com/modals/SelfLabel.tsx index 2b83c7a9a..ce3fbcef8 100644 --- a/src/view/com/modals/SelfLabel.tsx +++ b/src/view/com/modals/SelfLabel.tsx @@ -1,16 +1,17 @@ import React, {useState} from 'react' import {StyleSheet, TouchableOpacity, View} from 'react-native' -import {Text} from '../util/text/Text' -import {s, colors} from 'lib/styles' +import {msg, Trans} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useModalControls} from '#/state/modals' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {colors, s} from 'lib/styles' import {isWeb} from 'platform/detection' +import {ScrollView} from 'view/com/modals/util' import {Button} from '../util/forms/Button' import {SelectableBtn} from '../util/forms/SelectableBtn' -import {ScrollView} from 'view/com/modals/util' -import {Trans, msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' -import {useModalControls} from '#/state/modals' +import {Text} from '../util/text/Text' const ADULT_CONTENT_LABELS = ['sexual', 'nudity', 'porn'] diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx index ff8acd60c..5791e26a9 100644 --- a/src/view/com/pager/TabBar.tsx +++ b/src/view/com/pager/TabBar.tsx @@ -1,11 +1,12 @@ -import React, {useRef, useMemo, useEffect, useState, useCallback} from 'react' -import {StyleSheet, View, ScrollView, LayoutChangeEvent} from 'react-native' -import {Text} from '../util/text/Text' -import {PressableWithHover} from '../util/PressableWithHover' +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react' +import {LayoutChangeEvent, ScrollView, StyleSheet, View} from 'react-native' + +import {isNative} from '#/platform/detection' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' +import {PressableWithHover} from '../util/PressableWithHover' +import {Text} from '../util/text/Text' import {DraggableScrollView} from './DraggableScrollView' -import {isNative} from '#/platform/detection' export interface TabBarProps { testID?: string @@ -139,7 +140,10 @@ export function TabBar({ <Text type={isDesktop || isTablet ? 'xl-bold' : 'lg-bold'} testID={testID ? `${testID}-${item}` : undefined} - style={selected ? pal.text : pal.textLight}> + style={[ + selected ? pal.text : pal.textLight, + {lineHeight: 20}, + ]}> {item} </Text> </View> diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 8969f7cd2..c51733d1b 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -8,6 +8,7 @@ import { View, ViewStyle, } from 'react-native' +import {AppBskyActorDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useQueryClient} from '@tanstack/react-query' @@ -64,6 +65,7 @@ let Feed = ({ desktopFixedHeightOffset, ListHeaderComponent, extraData, + savedFeedConfig, }: { feed: FeedDescriptor feedParams?: FeedParams @@ -82,6 +84,7 @@ let Feed = ({ desktopFixedHeightOffset?: number ListHeaderComponent?: () => JSX.Element extraData?: any + savedFeedConfig?: AppBskyActorDefs.SavedFeed }): React.ReactNode => { const theme = useTheme() const {track} = useAnalytics() @@ -140,7 +143,6 @@ let Feed = ({ if ( data?.pages.length === 1 && (feed === 'following' || - feed === 'home' || feed === `author|${myDid}|posts_and_author_threads`) ) { queryClient.invalidateQueries({queryKey: RQKEY(feed)}) @@ -280,6 +282,7 @@ let Feed = ({ feedDesc={feed} error={error ?? undefined} onPressTryAgain={onPressTryAgain} + savedFeedConfig={savedFeedConfig} /> ) } else if (item === LOAD_MORE_ERROR_ITEM) { @@ -302,7 +305,15 @@ let Feed = ({ } return <FeedSlice slice={item} /> }, - [feed, error, onPressTryAgain, onPressRetryLoadMore, renderEmptyState, _], + [ + feed, + error, + onPressTryAgain, + onPressRetryLoadMore, + renderEmptyState, + _, + savedFeedConfig, + ], ) const shouldRenderEndOfFeed = diff --git a/src/view/com/posts/FeedErrorMessage.tsx b/src/view/com/posts/FeedErrorMessage.tsx index d4ca38d07..a152bc909 100644 --- a/src/view/com/posts/FeedErrorMessage.tsx +++ b/src/view/com/posts/FeedErrorMessage.tsx @@ -1,21 +1,22 @@ import React from 'react' import {View} from 'react-native' -import {AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api' -import {Text} from '../util/text/Text' -import {Button} from '../util/forms/Button' -import * as Toast from '../util/Toast' -import {ErrorMessage} from '../util/error/ErrorMessage' -import {usePalette} from 'lib/hooks/usePalette' -import {useNavigation} from '@react-navigation/native' -import {NavigationProp} from 'lib/routes/types' -import {logger} from '#/logger' +import {AppBskyActorDefs, AppBskyFeedGetAuthorFeed, AtUri} from '@atproto/api' import {msg as msgLingui, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' -import {FeedDescriptor} from '#/state/queries/post-feed' -import {EmptyState} from '../util/EmptyState' +import {useNavigation} from '@react-navigation/native' + import {cleanError} from '#/lib/strings/errors' +import {logger} from '#/logger' +import {FeedDescriptor} from '#/state/queries/post-feed' import {useRemoveFeedMutation} from '#/state/queries/preferences' +import {usePalette} from 'lib/hooks/usePalette' +import {NavigationProp} from 'lib/routes/types' import * as Prompt from '#/components/Prompt' +import {EmptyState} from '../util/EmptyState' +import {ErrorMessage} from '../util/error/ErrorMessage' +import {Button} from '../util/forms/Button' +import {Text} from '../util/text/Text' +import * as Toast from '../util/Toast' export enum KnownError { Block = 'Block', @@ -33,10 +34,12 @@ export function FeedErrorMessage({ feedDesc, error, onPressTryAgain, + savedFeedConfig, }: { feedDesc: FeedDescriptor error?: Error onPressTryAgain: () => void + savedFeedConfig?: AppBskyActorDefs.SavedFeed }) { const {_: _l} = useLingui() const knownError = React.useMemo( @@ -46,13 +49,15 @@ export function FeedErrorMessage({ if ( typeof knownError !== 'undefined' && knownError !== KnownError.Unknown && - (feedDesc.startsWith('feedgen') || knownError === KnownError.FeedNSFPublic) + (savedFeedConfig?.type === 'feed' || + knownError === KnownError.FeedNSFPublic) ) { return ( <FeedgenErrorMessage feedDesc={feedDesc} knownError={knownError} rawError={error} + savedFeedConfig={savedFeedConfig} /> ) } @@ -79,10 +84,12 @@ function FeedgenErrorMessage({ feedDesc, knownError, rawError, + savedFeedConfig, }: { feedDesc: FeedDescriptor knownError: KnownError rawError?: Error + savedFeedConfig?: AppBskyActorDefs.SavedFeed }) { const pal = usePalette('default') const {_: _l} = useLingui() @@ -131,7 +138,8 @@ function FeedgenErrorMessage({ const onRemoveFeed = React.useCallback(async () => { try { - await removeFeed({uri}) + if (!savedFeedConfig) return + await removeFeed(savedFeedConfig) } catch (err) { Toast.show( _l( @@ -140,7 +148,7 @@ function FeedgenErrorMessage({ ) logger.error('Failed to remove feed', {message: err}) } - }, [uri, removeFeed, _l]) + }, [removeFeed, _l, savedFeedConfig]) const cta = React.useMemo(() => { switch (knownError) { @@ -154,13 +162,14 @@ function FeedgenErrorMessage({ case KnownError.FeedgenUnknown: { return ( <View style={{flexDirection: 'row', alignItems: 'center', gap: 10}}> - {knownError === KnownError.FeedgenDoesNotExist && ( - <Button - type="inverted" - label={_l(msgLingui`Remove feed`)} - onPress={onRemoveFeed} - /> - )} + {knownError === KnownError.FeedgenDoesNotExist && + savedFeedConfig && ( + <Button + type="inverted" + label={_l(msgLingui`Remove feed`)} + onPress={onRemoveFeed} + /> + )} <Button type="default-light" label={_l(msgLingui`View profile`)} @@ -170,7 +179,7 @@ function FeedgenErrorMessage({ ) } } - }, [knownError, onViewProfile, onRemoveFeed, _l]) + }, [knownError, onViewProfile, onRemoveFeed, _l, savedFeedConfig]) return ( <> diff --git a/src/view/com/util/post-ctrls/RepostButton.tsx b/src/view/com/util/post-ctrls/RepostButton.tsx index c1af39a5d..f58417887 100644 --- a/src/view/com/util/post-ctrls/RepostButton.tsx +++ b/src/view/com/util/post-ctrls/RepostButton.tsx @@ -1,14 +1,15 @@ import React, {memo, useCallback} from 'react' import {StyleProp, StyleSheet, TouchableOpacity, ViewStyle} from 'react-native' +import {msg, plural} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {useModalControls} from '#/state/modals' +import {useRequireAuth} from '#/state/session' +import {HITSLOP_10, HITSLOP_20} from 'lib/constants' import {RepostIcon} from 'lib/icons' -import {s, colors} from 'lib/styles' +import {colors, s} from 'lib/styles' import {useTheme} from 'lib/ThemeContext' import {Text} from '../text/Text' -import {HITSLOP_10, HITSLOP_20} from 'lib/constants' -import {useModalControls} from '#/state/modals' -import {useRequireAuth} from '#/state/session' -import {msg, plural} from '@lingui/macro' -import {useLingui} from '@lingui/react' interface Props { isReposted: boolean |