diff options
Diffstat (limited to 'src/view/com')
-rw-r--r-- | src/view/com/auth/onboarding/RecommendedFeeds.tsx | 36 | ||||
-rw-r--r-- | src/view/com/auth/onboarding/RecommendedFeedsItem.tsx | 50 | ||||
-rw-r--r-- | src/view/com/feeds/FeedSourceCard.tsx | 125 | ||||
-rw-r--r-- | src/view/com/post-thread/PostThread.tsx | 27 | ||||
-rw-r--r-- | src/view/com/posts/FeedErrorMessage.tsx | 8 | ||||
-rw-r--r-- | src/view/com/util/forms/Button.tsx | 4 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/CustomFeedEmbed.tsx | 38 | ||||
-rw-r--r-- | src/view/com/util/post-embeds/index.tsx | 17 |
8 files changed, 79 insertions, 226 deletions
diff --git a/src/view/com/auth/onboarding/RecommendedFeeds.tsx b/src/view/com/auth/onboarding/RecommendedFeeds.tsx index 400b836d0..d134dae9f 100644 --- a/src/view/com/auth/onboarding/RecommendedFeeds.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeeds.tsx @@ -10,10 +10,8 @@ import {Button} from 'view/com/util/forms/Button' import {RecommendedFeedsItem} from './RecommendedFeedsItem' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {usePalette} from 'lib/hooks/usePalette' -import {useQuery} from '@tanstack/react-query' -import {useStores} from 'state/index' -import {FeedSourceModel} from 'state/models/content/feed-source' import {ErrorMessage} from 'view/com/util/error/ErrorMessage' +import {useSuggestedFeedsQuery} from '#/state/queries/suggested-feeds' type Props = { next: () => void @@ -21,35 +19,11 @@ type Props = { export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ next, }: Props) { - const store = useStores() const pal = usePalette('default') const {isTabletOrMobile} = useWebMediaQueries() - const {isLoading, data: recommendedFeeds} = useQuery({ - staleTime: Infinity, // fixed list rn, never refetch - queryKey: ['onboarding', 'recommended_feeds'], - async queryFn() { - try { - const { - data: {feeds}, - success, - } = await store.agent.app.bsky.feed.getSuggestedFeeds() + const {isLoading, data} = useSuggestedFeedsQuery() - if (!success) { - return [] - } - - return (feeds.length ? feeds : []).map(feed => { - const model = new FeedSourceModel(store, feed.uri) - model.hydrateFeedGenerator(feed) - return model - }) - } catch (e) { - return [] - } - }, - }) - - const hasFeeds = recommendedFeeds && recommendedFeeds.length + const hasFeeds = data && data?.pages?.[0]?.feeds?.length const title = ( <> @@ -118,7 +92,7 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ contentStyle={{paddingHorizontal: 0}}> {hasFeeds ? ( <FlatList - data={recommendedFeeds} + data={data.pages[0].feeds} renderItem={({item}) => <RecommendedFeedsItem item={item} />} keyExtractor={item => item.uri} style={{flex: 1}} @@ -146,7 +120,7 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({ {hasFeeds ? ( <FlatList - data={recommendedFeeds} + data={data.pages[0].feeds} renderItem={({item}) => <RecommendedFeedsItem item={item} />} keyExtractor={item => item.uri} style={{flex: 1}} diff --git a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx index bee23c953..2eaf3cf2d 100644 --- a/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx +++ b/src/view/com/auth/onboarding/RecommendedFeedsItem.tsx @@ -2,6 +2,7 @@ import React from 'react' import {View} from 'react-native' import {observer} from 'mobx-react-lite' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {AppBskyFeedDefs, RichText as BskRichText} from '@atproto/api' import {Text} from 'view/com/util/text/Text' import {RichText} from 'view/com/util/text/RichText' import {Button} from 'view/com/util/forms/Button' @@ -11,33 +12,58 @@ import {HeartIcon} from 'lib/icons' import {usePalette} from 'lib/hooks/usePalette' import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {sanitizeHandle} from 'lib/strings/handles' -import {FeedSourceModel} from 'state/models/content/feed-source' +import { + usePreferencesQuery, + usePinFeedMutation, + useRemoveFeedMutation, +} from '#/state/queries/preferences' +import {logger} from '#/logger' export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ item, }: { - item: FeedSourceModel + item: AppBskyFeedDefs.GeneratorView }) { const {isMobile} = useWebMediaQueries() const pal = usePalette('default') - if (!item) return null + const {data: preferences} = usePreferencesQuery() + const { + mutateAsync: pinFeed, + variables: pinnedFeed, + reset: resetPinFeed, + } = usePinFeedMutation() + const { + mutateAsync: removeFeed, + variables: removedFeed, + reset: resetRemoveFeed, + } = useRemoveFeedMutation() + + if (!item || !preferences) return null + + const isPinned = + !removedFeed?.uri && + (pinnedFeed?.uri || preferences.feeds.saved.includes(item.uri)) + const onToggle = async () => { - if (item.isSaved) { + if (isPinned) { try { - await item.unsave() + await removeFeed({uri: item.uri}) + resetRemoveFeed() } catch (e) { Toast.show('There was an issue contacting your server') - console.error('Failed to unsave feed', {e}) + logger.error('Failed to unsave feed', {error: e}) } } else { try { - await item.pin() + await pinFeed({uri: item.uri}) + resetPinFeed() } catch (e) { Toast.show('There was an issue contacting your server') - console.error('Failed to pin feed', {e}) + logger.error('Failed to pin feed', {error: e}) } } } + return ( <View testID={`feed-${item.displayName}`}> <View @@ -66,10 +92,10 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ </Text> <Text style={[pal.textLight, {marginBottom: 8}]} numberOfLines={1}> - by {sanitizeHandle(item.creatorHandle, '@')} + by {sanitizeHandle(item.creator.handle, '@')} </Text> - {item.descriptionRT ? ( + {item.description ? ( <RichText type="xl" style={[ @@ -80,7 +106,7 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ marginBottom: 18, }, ]} - richText={item.descriptionRT} + richText={new BskRichText({text: item.description || ''})} numberOfLines={6} /> ) : null} @@ -97,7 +123,7 @@ export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({ paddingRight: 2, gap: 6, }}> - {item.isSaved ? ( + {isPinned ? ( <> <FontAwesomeIcon icon="check" diff --git a/src/view/com/feeds/FeedSourceCard.tsx b/src/view/com/feeds/FeedSourceCard.tsx index 6f9687be5..aaafd1959 100644 --- a/src/view/com/feeds/FeedSourceCard.tsx +++ b/src/view/com/feeds/FeedSourceCard.tsx @@ -7,7 +7,6 @@ import {usePalette} from 'lib/hooks/usePalette' import {s} from 'lib/styles' import {UserAvatar} from '../util/UserAvatar' import {observer} from 'mobx-react-lite' -import {FeedSourceModel} from 'state/models/content/feed-source' import {useNavigation} from '@react-navigation/native' import {NavigationProp} from 'lib/routes/types' import {pluralize} from 'lib/strings/helpers' @@ -23,7 +22,7 @@ import { } from '#/state/queries/preferences' import {useFeedSourceInfoQuery} from '#/state/queries/feed' -export const NewFeedSourceCard = observer(function FeedSourceCardImpl({ +export const FeedSourceCard = observer(function FeedSourceCardImpl({ feedUri, style, showSaveBtn = false, @@ -162,128 +161,6 @@ export const NewFeedSourceCard = observer(function FeedSourceCardImpl({ ) }) -export const FeedSourceCard = observer(function FeedSourceCardImpl({ - item, - style, - showSaveBtn = false, - showDescription = false, - showLikes = false, -}: { - item: FeedSourceModel - style?: StyleProp<ViewStyle> - showSaveBtn?: boolean - showDescription?: boolean - showLikes?: boolean -}) { - const pal = usePalette('default') - const navigation = useNavigation<NavigationProp>() - const {openModal} = useModalControls() - - const onToggleSaved = React.useCallback(async () => { - if (item.isSaved) { - openModal({ - name: 'confirm', - title: 'Remove from my feeds', - message: `Remove ${item.displayName} from my feeds?`, - onPressConfirm: async () => { - try { - await item.unsave() - Toast.show('Removed from my feeds') - } catch (e) { - Toast.show('There was an issue contacting your server') - logger.error('Failed to unsave feed', {error: e}) - } - }, - }) - } else { - try { - await item.save() - Toast.show('Added to my feeds') - } catch (e) { - Toast.show('There was an issue contacting your server') - logger.error('Failed to save feed', {error: e}) - } - } - }, [openModal, item]) - - return ( - <Pressable - testID={`feed-${item.displayName}`} - accessibilityRole="button" - style={[styles.container, pal.border, style]} - onPress={() => { - if (item.type === 'feed-generator') { - navigation.push('ProfileFeed', { - name: item.creatorDid, - rkey: new AtUri(item.uri).rkey, - }) - } else if (item.type === 'list') { - navigation.push('ProfileList', { - name: item.creatorDid, - rkey: new AtUri(item.uri).rkey, - }) - } - }} - key={item.uri}> - <View style={[styles.headerContainer]}> - <View style={[s.mr10]}> - <UserAvatar type="algo" size={36} avatar={item.avatar} /> - </View> - <View style={[styles.headerTextContainer]}> - <Text style={[pal.text, s.bold]} numberOfLines={3}> - {item.displayName} - </Text> - <Text style={[pal.textLight]} numberOfLines={3}> - by {sanitizeHandle(item.creatorHandle, '@')} - </Text> - </View> - {showSaveBtn && ( - <View> - <Pressable - accessibilityRole="button" - accessibilityLabel={ - item.isSaved ? 'Remove from my feeds' : 'Add to my feeds' - } - accessibilityHint="" - onPress={onToggleSaved} - hitSlop={15} - style={styles.btn}> - {item.isSaved ? ( - <FontAwesomeIcon - icon={['far', 'trash-can']} - size={19} - color={pal.colors.icon} - /> - ) : ( - <FontAwesomeIcon - icon="plus" - size={18} - color={pal.colors.link} - /> - )} - </Pressable> - </View> - )} - </View> - - {showDescription && item.descriptionRT ? ( - <RichText - style={[pal.textLight, styles.description]} - richText={item.descriptionRT} - numberOfLines={3} - /> - ) : null} - - {showLikes ? ( - <Text type="sm-medium" style={[pal.text, pal.textLight]}> - Liked by {item.likeCount || 0}{' '} - {pluralize(item.likeCount || 0, 'user')} - </Text> - ) : null} - </Pressable> - ) -}) - const styles = StyleSheet.create({ container: { paddingHorizontal: 18, diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx index 1e85b3e31..b0e6f1a31 100644 --- a/src/view/com/post-thread/PostThread.tsx +++ b/src/view/com/post-thread/PostThread.tsx @@ -32,9 +32,12 @@ import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' import {NavigationProp} from 'lib/routes/types' import {sanitizeDisplayName} from 'lib/strings/display-names' import {cleanError} from '#/lib/strings/errors' -import {useStores} from '#/state' import {Trans, msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import { + UsePreferencesQueryResponse, + usePreferencesQuery, +} from '#/state/queries/preferences' // const MAINTAIN_VISIBLE_CONTENT_POSITION = {minIndexForVisible: 2} TODO @@ -59,11 +62,9 @@ type YieldedItem = export function PostThread({ uri, onPressReply, - treeView, }: { uri: string | undefined onPressReply: () => void - treeView: boolean }) { const { isLoading, @@ -74,6 +75,7 @@ export function PostThread({ data: thread, dataUpdatedAt, } = usePostThreadQuery(uri) + const {data: preferences} = usePreferencesQuery() const rootPost = thread?.type === 'post' ? thread.post : undefined const rootPostRecord = thread?.type === 'post' ? thread.record : undefined @@ -96,7 +98,7 @@ export function PostThread({ if (AppBskyFeedDefs.isBlockedPost(thread)) { return <PostThreadBlocked /> } - if (!thread || isLoading) { + if (!thread || isLoading || !preferences) { return ( <CenteredView> <View style={s.p20}> @@ -110,7 +112,7 @@ export function PostThread({ thread={thread} isRefetching={isRefetching} dataUpdatedAt={dataUpdatedAt} - treeView={treeView} + threadViewPrefs={preferences.threadViewPrefs} onRefresh={refetch} onPressReply={onPressReply} /> @@ -121,20 +123,19 @@ function PostThreadLoaded({ thread, isRefetching, dataUpdatedAt, - treeView, + threadViewPrefs, onRefresh, onPressReply, }: { thread: ThreadNode isRefetching: boolean dataUpdatedAt: number - treeView: boolean + threadViewPrefs: UsePreferencesQueryResponse['threadViewPrefs'] onRefresh: () => void onPressReply: () => void }) { const {_} = useLingui() const pal = usePalette('default') - const store = useStores() const {isTablet, isDesktop} = useWebMediaQueries() const ref = useRef<FlatList>(null) // const hasScrolledIntoView = useRef<boolean>(false) TODO @@ -162,16 +163,14 @@ function PostThreadLoaded({ // const highlightedPostIndex = posts.findIndex(post => post._isHighlightedPost) const posts = React.useMemo(() => { let arr = [TOP_COMPONENT].concat( - Array.from( - flattenThreadSkeleton(sortThread(thread, store.preferences.thread)), - ), + Array.from(flattenThreadSkeleton(sortThread(thread, threadViewPrefs))), ) if (arr.length > maxVisible) { arr = arr.slice(0, maxVisible).concat([LOAD_MORE]) } arr.push(BOTTOM_COMPONENT) return arr - }, [thread, maxVisible, store.preferences.thread]) + }, [thread, maxVisible, threadViewPrefs]) // TODO /*const onContentSizeChange = React.useCallback(() => { @@ -297,7 +296,7 @@ function PostThreadLoaded({ post={item.post} record={item.record} dataUpdatedAt={dataUpdatedAt} - treeView={treeView} + treeView={threadViewPrefs.lab_treeViewEnabled} depth={item.ctx.depth} isHighlightedPost={item.ctx.isHighlightedPost} hasMore={item.ctx.hasMore} @@ -322,7 +321,7 @@ function PostThreadLoaded({ pal.colors.border, posts, onRefresh, - treeView, + threadViewPrefs.lab_treeViewEnabled, dataUpdatedAt, _, ], diff --git a/src/view/com/posts/FeedErrorMessage.tsx b/src/view/com/posts/FeedErrorMessage.tsx index e29b35f8a..0ace06e9a 100644 --- a/src/view/com/posts/FeedErrorMessage.tsx +++ b/src/view/com/posts/FeedErrorMessage.tsx @@ -8,12 +8,12 @@ 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 {useStores} from 'state/index' import {logger} from '#/logger' import {useModalControls} from '#/state/modals' import {FeedDescriptor} from '#/state/queries/post-feed' import {EmptyState} from '../util/EmptyState' import {cleanError} from '#/lib/strings/errors' +import {useRemoveFeedMutation} from '#/state/queries/preferences' enum KnownError { Block, @@ -86,12 +86,12 @@ function FeedgenErrorMessage({ knownError: KnownError }) { const pal = usePalette('default') - const store = useStores() const navigation = useNavigation<NavigationProp>() const msg = MESSAGES[knownError] const [_, uri] = feedDesc.split('|') const [ownerDid] = safeParseFeedgenUri(uri) const {openModal, closeModal} = useModalControls() + const {mutateAsync: removeFeed} = useRemoveFeedMutation() const onViewProfile = React.useCallback(() => { navigation.navigate('Profile', {name: ownerDid}) @@ -104,7 +104,7 @@ function FeedgenErrorMessage({ message: 'Remove this feed from your saved feeds?', async onPressConfirm() { try { - await store.preferences.removeSavedFeed(uri) + await removeFeed({uri}) } catch (err) { Toast.show( 'There was an an issue removing this feed. Please check your internet connection and try again.', @@ -116,7 +116,7 @@ function FeedgenErrorMessage({ closeModal() }, }) - }, [store, openModal, closeModal, uri]) + }, [openModal, closeModal, uri, removeFeed]) return ( <View diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx index 270d98317..8f24f8288 100644 --- a/src/view/com/util/forms/Button.tsx +++ b/src/view/com/util/forms/Button.tsx @@ -52,6 +52,7 @@ export function Button({ accessibilityLabelledBy, onAccessibilityEscape, withLoading = false, + disabled = false, }: React.PropsWithChildren<{ type?: ButtonType label?: string @@ -65,6 +66,7 @@ export function Button({ accessibilityLabelledBy?: string onAccessibilityEscape?: () => void withLoading?: boolean + disabled?: boolean }>) { const theme = useTheme() const typeOuterStyle = choose<ViewStyle, Record<ButtonType, ViewStyle>>( @@ -198,7 +200,7 @@ export function Button({ <Pressable style={getStyle} onPress={onPressWrapped} - disabled={isLoading} + disabled={disabled || isLoading} testID={testID} accessibilityRole="button" accessibilityLabel={accessibilityLabel} diff --git a/src/view/com/util/post-embeds/CustomFeedEmbed.tsx b/src/view/com/util/post-embeds/CustomFeedEmbed.tsx deleted file mode 100644 index 624157436..000000000 --- a/src/view/com/util/post-embeds/CustomFeedEmbed.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, {useMemo} from 'react' -import {AppBskyFeedDefs} from '@atproto/api' -import {usePalette} from 'lib/hooks/usePalette' -import {StyleSheet} from 'react-native' -import {useStores} from 'state/index' -import {FeedSourceModel} from 'state/models/content/feed-source' -import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' - -export function CustomFeedEmbed({ - record, -}: { - record: AppBskyFeedDefs.GeneratorView -}) { - const pal = usePalette('default') - const store = useStores() - const item = useMemo(() => { - const model = new FeedSourceModel(store, record.uri) - model.hydrateFeedGenerator(record) - return model - }, [store, record]) - return ( - <FeedSourceCard - item={item} - style={[pal.view, pal.border, styles.customFeedOuter]} - showLikes - /> - ) -} - -const styles = StyleSheet.create({ - customFeedOuter: { - borderWidth: 1, - borderRadius: 8, - marginTop: 4, - paddingHorizontal: 12, - paddingVertical: 12, - }, -}) diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx index 6c13bc2bb..b4c7c45ae 100644 --- a/src/view/com/util/post-embeds/index.tsx +++ b/src/view/com/util/post-embeds/index.tsx @@ -28,9 +28,9 @@ import {ExternalLinkEmbed} from './ExternalLinkEmbed' import {getYoutubeVideoId} from 'lib/strings/url-helpers' import {MaybeQuoteEmbed} from './QuoteEmbed' import {AutoSizedImage} from '../images/AutoSizedImage' -import {CustomFeedEmbed} from './CustomFeedEmbed' import {ListEmbed} from './ListEmbed' import {isCauseALabelOnUri} from 'lib/moderation' +import {FeedSourceCard} from 'view/com/feeds/FeedSourceCard' type Embed = | AppBskyEmbedRecord.View @@ -72,7 +72,13 @@ export function PostEmbeds({ // custom feed embed (i.e. generator view) // = if (AppBskyFeedDefs.isGeneratorView(embed.record)) { - return <CustomFeedEmbed record={embed.record} /> + return ( + <FeedSourceCard + feedUri={embed.record.uri} + style={[pal.view, pal.border, styles.customFeedOuter]} + showLikes + /> + ) } // list embed @@ -206,4 +212,11 @@ const styles = StyleSheet.create({ fontSize: 10, fontWeight: 'bold', }, + customFeedOuter: { + borderWidth: 1, + borderRadius: 8, + marginTop: 4, + paddingHorizontal: 12, + paddingVertical: 12, + }, }) |