diff options
Diffstat (limited to 'src/view/screens/CustomFeed.tsx')
-rw-r--r-- | src/view/screens/CustomFeed.tsx | 495 |
1 files changed, 0 insertions, 495 deletions
diff --git a/src/view/screens/CustomFeed.tsx b/src/view/screens/CustomFeed.tsx deleted file mode 100644 index f9383639c..000000000 --- a/src/view/screens/CustomFeed.tsx +++ /dev/null @@ -1,495 +0,0 @@ -import React, {useMemo, useRef} from 'react' -import {NativeStackScreenProps} from '@react-navigation/native-stack' -import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' -import {useNavigation, useIsFocused} from '@react-navigation/native' -import {usePalette} from 'lib/hooks/usePalette' -import {HeartIcon, HeartIconSolid} from 'lib/icons' -import {CommonNavigatorParams} from 'lib/routes/types' -import {makeRecordUri} from 'lib/strings/url-helpers' -import {colors, s} from 'lib/styles' -import {observer} from 'mobx-react-lite' -import {FlatList, StyleSheet, View, ActivityIndicator} from 'react-native' -import {useStores} from 'state/index' -import {PostsFeedModel} from 'state/models/feeds/posts' -import {useCustomFeed} from 'lib/hooks/useCustomFeed' -import {withAuthRequired} from 'view/com/auth/withAuthRequired' -import {Feed} from 'view/com/posts/Feed' -import {TextLink} from 'view/com/util/Link' -import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader' -import {Button} from 'view/com/util/forms/Button' -import {Text} from 'view/com/util/text/Text' -import * as Toast from 'view/com/util/Toast' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {useSetTitle} from 'lib/hooks/useSetTitle' -import {shareUrl} from 'lib/sharing' -import {toShareUrl} from 'lib/strings/url-helpers' -import {Haptics} from 'lib/haptics' -import {ComposeIcon2} from 'lib/icons' -import {FAB} from '../com/util/fab/FAB' -import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn' -import {useOnMainScroll} from 'lib/hooks/useOnMainScroll' -import {EmptyState} from 'view/com/util/EmptyState' -import {useAnalytics} from 'lib/analytics/analytics' -import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown' -import {resolveName} from 'lib/api' -import {CenteredView} from 'view/com/util/Views' -import {NavigationProp} from 'lib/routes/types' - -type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'> - -export const CustomFeedScreen = withAuthRequired( - observer(function CustomFeedScreenImpl(props: Props) { - const pal = usePalette('default') - const store = useStores() - const navigation = useNavigation<NavigationProp>() - - const {name: handleOrDid} = props.route.params - - const [feedOwnerDid, setFeedOwnerDid] = React.useState<string | undefined>() - const [error, setError] = React.useState<string | undefined>() - - const onPressBack = React.useCallback(() => { - if (navigation.canGoBack()) { - navigation.goBack() - } else { - navigation.navigate('Home') - } - }, [navigation]) - - React.useEffect(() => { - /* - * We must resolve the DID of the feed owner before we can fetch the feed. - */ - async function fetchDid() { - try { - const did = await resolveName(store, handleOrDid) - setFeedOwnerDid(did) - } catch (e) { - setError( - `We're sorry, but we were unable to resolve this feed. If this persists, please contact the feed creator, @${handleOrDid}.`, - ) - } - } - - fetchDid() - }, [store, handleOrDid, setFeedOwnerDid]) - - if (error) { - return ( - <CenteredView> - <View style={[pal.view, pal.border, styles.notFoundContainer]}> - <Text type="title-lg" style={[pal.text, s.mb10]}> - Could not load feed - </Text> - <Text type="md" style={[pal.text, s.mb20]}> - {error} - </Text> - - <View style={{flexDirection: 'row'}}> - <Button - type="default" - accessibilityLabel="Go Back" - accessibilityHint="Return to previous page" - onPress={onPressBack} - style={{flexShrink: 1}}> - <Text type="button" style={pal.text}> - Go Back - </Text> - </Button> - </View> - </View> - </CenteredView> - ) - } - - return feedOwnerDid ? ( - <CustomFeedScreenInner {...props} feedOwnerDid={feedOwnerDid} /> - ) : ( - <CenteredView> - <View style={s.p20}> - <ActivityIndicator size="large" /> - </View> - </CenteredView> - ) - }), -) - -export const CustomFeedScreenInner = observer( - function CustomFeedScreenInnerImpl({ - route, - feedOwnerDid, - }: Props & {feedOwnerDid: string}) { - const store = useStores() - const pal = usePalette('default') - const palInverted = usePalette('inverted') - const navigation = useNavigation<NavigationProp>() - const isScreenFocused = useIsFocused() - const {isMobile, isTabletOrDesktop} = useWebMediaQueries() - const {track} = useAnalytics() - const {rkey, name: handleOrDid} = route.params - const uri = useMemo( - () => makeRecordUri(feedOwnerDid, 'app.bsky.feed.generator', rkey), - [rkey, feedOwnerDid], - ) - const scrollElRef = useRef<FlatList>(null) - const currentFeed = useCustomFeed(uri) - const algoFeed: PostsFeedModel = useMemo(() => { - const feed = new PostsFeedModel(store, 'custom', { - feed: uri, - }) - feed.setup() - return feed - }, [store, uri]) - const isPinned = store.me.savedFeeds.isPinned(uri) - const [onMainScroll, isScrolledDown, resetMainScroll] = - useOnMainScroll(store) - useSetTitle(currentFeed?.displayName) - - const onToggleSaved = React.useCallback(async () => { - try { - Haptics.default() - if (currentFeed?.isSaved) { - await currentFeed?.unsave() - } else { - await currentFeed?.save() - } - } catch (err) { - Toast.show( - 'There was an an issue updating your feeds, please check your internet connection and try again.', - ) - store.log.error('Failed up update feeds', {err}) - } - }, [store, currentFeed]) - - const onToggleLiked = React.useCallback(async () => { - Haptics.default() - try { - if (currentFeed?.isLiked) { - await currentFeed?.unlike() - } else { - await currentFeed?.like() - } - } catch (err) { - Toast.show( - 'There was an an issue contacting the server, please check your internet connection and try again.', - ) - store.log.error('Failed up toggle like', {err}) - } - }, [store, currentFeed]) - - const onTogglePinned = React.useCallback(async () => { - Haptics.default() - store.me.savedFeeds.togglePinnedFeed(currentFeed!).catch(e => { - Toast.show('There was an issue contacting the server') - store.log.error('Failed to toggle pinned feed', {e}) - }) - }, [store, currentFeed]) - - const onPressAbout = React.useCallback(() => { - store.shell.openModal({ - name: 'confirm', - title: currentFeed?.displayName || '', - message: - currentFeed?.data.description || 'This feed has no description.', - confirmBtnText: 'Close', - onPressConfirm() {}, - }) - }, [store, currentFeed]) - - const onPressViewAuthor = React.useCallback(() => { - navigation.navigate('Profile', {name: handleOrDid}) - }, [handleOrDid, navigation]) - - const onPressShare = React.useCallback(() => { - const url = toShareUrl(`/profile/${handleOrDid}/feed/${rkey}`) - shareUrl(url) - track('CustomFeed:Share') - }, [handleOrDid, rkey, track]) - - const onPressReport = React.useCallback(() => { - if (!currentFeed) return - store.shell.openModal({ - name: 'report', - uri: currentFeed.uri, - cid: currentFeed.data.cid, - }) - }, [store, currentFeed]) - - const onScrollToTop = React.useCallback(() => { - scrollElRef.current?.scrollToOffset({offset: 0, animated: true}) - resetMainScroll() - }, [scrollElRef, resetMainScroll]) - - const onPressCompose = React.useCallback(() => { - store.shell.openComposer({}) - }, [store]) - - const onSoftReset = React.useCallback(() => { - if (isScreenFocused) { - onScrollToTop() - algoFeed.refresh() - } - }, [isScreenFocused, onScrollToTop, algoFeed]) - - // fires when page within screen is activated/deactivated - React.useEffect(() => { - if (!isScreenFocused) { - return - } - - const softResetSub = store.onScreenSoftReset(onSoftReset) - return () => { - softResetSub.remove() - } - }, [store, onSoftReset, isScreenFocused]) - - const dropdownItems: DropdownItem[] = React.useMemo(() => { - return [ - currentFeed - ? { - testID: 'feedHeaderDropdownAboutBtn', - label: 'About this feed', - onPress: onPressAbout, - icon: { - ios: { - name: 'info.circle', - }, - android: '', - web: 'info', - }, - } - : undefined, - { - testID: 'feedHeaderDropdownViewAuthorBtn', - label: 'View author', - onPress: onPressViewAuthor, - icon: { - ios: { - name: 'person', - }, - android: '', - web: ['far', 'user'], - }, - }, - { - testID: 'feedHeaderDropdownToggleSavedBtn', - label: currentFeed?.isSaved - ? 'Remove from my feeds' - : 'Add to my feeds', - onPress: onToggleSaved, - icon: currentFeed?.isSaved - ? { - ios: { - name: 'trash', - }, - android: 'ic_delete', - web: 'trash', - } - : { - ios: { - name: 'plus', - }, - android: '', - web: 'plus', - }, - }, - { - testID: 'feedHeaderDropdownReportBtn', - label: 'Report feed', - onPress: onPressReport, - icon: { - ios: { - name: 'exclamationmark.triangle', - }, - android: 'ic_menu_report_image', - web: 'circle-exclamation', - }, - }, - { - testID: 'feedHeaderDropdownShareBtn', - label: 'Share link', - onPress: onPressShare, - icon: { - ios: { - name: 'square.and.arrow.up', - }, - android: 'ic_menu_share', - web: 'share', - }, - }, - ].filter(Boolean) as DropdownItem[] - }, [ - currentFeed, - onPressAbout, - onToggleSaved, - onPressReport, - onPressShare, - onPressViewAuthor, - ]) - - const renderEmptyState = React.useCallback(() => { - return ( - <View style={[pal.border, {borderTopWidth: 1, paddingTop: 20}]}> - <EmptyState icon="feed" message="This feed is empty!" /> - </View> - ) - }, [pal.border]) - - return ( - <View style={s.hContentRegion}> - <SimpleViewHeader - showBackButton={isMobile} - style={ - !isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}] - }> - <Text type="title-lg" style={styles.headerText} numberOfLines={1}> - {currentFeed ? ( - <TextLink - type="title-lg" - href="/" - style={[pal.text, {fontWeight: 'bold'}]} - text={currentFeed?.displayName || ''} - onPress={() => store.emitScreenSoftReset()} - /> - ) : ( - 'Loading...' - )} - </Text> - {currentFeed ? ( - <> - <Button - type="default-light" - testID="toggleLikeBtn" - accessibilityLabel="Like this feed" - accessibilityHint="" - onPress={onToggleLiked} - style={styles.headerBtn}> - {currentFeed?.isLiked ? ( - <HeartIconSolid size={19} style={styles.liked} /> - ) : ( - <HeartIcon strokeWidth={3} size={19} style={pal.textLight} /> - )} - </Button> - {currentFeed?.isSaved ? ( - <Button - type="default-light" - accessibilityLabel={ - isPinned ? 'Unpin this feed' : 'Pin this feed' - } - accessibilityHint="" - onPress={onTogglePinned} - style={styles.headerBtn}> - <FontAwesomeIcon - icon="thumb-tack" - size={17} - color={isPinned ? colors.blue3 : pal.colors.textLight} - style={styles.top1} - /> - </Button> - ) : ( - <Button - type="inverted" - onPress={onToggleSaved} - accessibilityLabel="Add to my feeds" - accessibilityHint="" - style={styles.headerAddBtn}> - <FontAwesomeIcon - icon="plus" - color={palInverted.colors.text} - size={19} - /> - <Text type="button" style={palInverted.text}> - Add{!isMobile && ' to My Feeds'} - </Text> - </Button> - )} - </> - ) : null} - <NativeDropdown - testID="feedHeaderDropdownBtn" - items={dropdownItems} - accessibilityLabel="More options" - accessibilityHint=""> - <View - style={{ - paddingLeft: 12, - paddingRight: isMobile ? 12 : 0, - }}> - <FontAwesomeIcon - icon="ellipsis" - size={20} - color={pal.colors.textLight} - /> - </View> - </NativeDropdown> - </SimpleViewHeader> - <Feed - scrollElRef={scrollElRef} - feed={algoFeed} - onScroll={onMainScroll} - scrollEventThrottle={100} - renderEmptyState={renderEmptyState} - extraData={[uri, isPinned]} - style={!isTabletOrDesktop ? {flex: 1} : undefined} - /> - {isScrolledDown ? ( - <LoadLatestBtn - onPress={onSoftReset} - label="Scroll to top" - showIndicator={false} - /> - ) : null} - <FAB - testID="composeFAB" - onPress={onPressCompose} - icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />} - accessibilityRole="button" - accessibilityLabel="New post" - accessibilityHint="" - /> - </View> - ) - }, -) - -const styles = StyleSheet.create({ - header: { - flexDirection: 'row', - gap: 12, - paddingHorizontal: 16, - paddingTop: 12, - paddingBottom: 16, - borderTopWidth: 1, - }, - headerText: { - flex: 1, - fontWeight: 'bold', - }, - headerBtn: { - paddingVertical: 0, - }, - headerAddBtn: { - flexDirection: 'row', - alignItems: 'center', - gap: 4, - paddingVertical: 4, - paddingLeft: 10, - }, - liked: { - color: colors.red3, - }, - top1: { - position: 'relative', - top: 1, - }, - top2: { - position: 'relative', - top: 2, - }, - notFoundContainer: { - margin: 10, - paddingHorizontal: 18, - paddingVertical: 14, - borderRadius: 6, - }, -}) |