about summary refs log tree commit diff
path: root/src/view/screens/ProfileFeed.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens/ProfileFeed.tsx')
-rw-r--r--src/view/screens/ProfileFeed.tsx621
1 files changed, 0 insertions, 621 deletions
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
deleted file mode 100644
index c3f98c067..000000000
--- a/src/view/screens/ProfileFeed.tsx
+++ /dev/null
@@ -1,621 +0,0 @@
-import React, {useCallback, useMemo} from 'react'
-import {Pressable, StyleSheet, View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Plural, Trans} from '@lingui/macro'
-import {useLingui} from '@lingui/react'
-import {useIsFocused, useNavigation} from '@react-navigation/native'
-import {NativeStackScreenProps} from '@react-navigation/native-stack'
-import {useQueryClient} from '@tanstack/react-query'
-
-import {HITSLOP_20} from '#/lib/constants'
-import {useHaptics} from '#/lib/haptics'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useSetTitle} from '#/lib/hooks/useSetTitle'
-import {ComposeIcon2} from '#/lib/icons'
-import {makeCustomFeedLink} from '#/lib/routes/links'
-import {CommonNavigatorParams} from '#/lib/routes/types'
-import {NavigationProp} from '#/lib/routes/types'
-import {shareUrl} from '#/lib/sharing'
-import {makeRecordUri} from '#/lib/strings/url-helpers'
-import {toShareUrl} from '#/lib/strings/url-helpers'
-import {s} from '#/lib/styles'
-import {logger} from '#/logger'
-import {isNative} from '#/platform/detection'
-import {listenSoftReset} from '#/state/events'
-import {FeedFeedbackProvider, useFeedFeedback} from '#/state/feed-feedback'
-import {FeedSourceFeedInfo, useFeedSourceInfoQuery} from '#/state/queries/feed'
-import {useLikeMutation, useUnlikeMutation} from '#/state/queries/like'
-import {FeedDescriptor} from '#/state/queries/post-feed'
-import {RQKEY as FEED_RQKEY} from '#/state/queries/post-feed'
-import {
-  useAddSavedFeedsMutation,
-  usePreferencesQuery,
-  UsePreferencesQueryResponse,
-  useRemoveFeedMutation,
-  useUpdateSavedFeedsMutation,
-} from '#/state/queries/preferences'
-import {useResolveUriQuery} from '#/state/queries/resolve-uri'
-import {truncateAndInvalidate} from '#/state/queries/util'
-import {useSession} from '#/state/session'
-import {useComposerControls} from '#/state/shell/composer'
-import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
-import {PostFeed} from '#/view/com/posts/PostFeed'
-import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
-import {EmptyState} from '#/view/com/util/EmptyState'
-import {FAB} from '#/view/com/util/fab/FAB'
-import {Button} from '#/view/com/util/forms/Button'
-import {ListRef} from '#/view/com/util/List'
-import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
-import {LoadingScreen} from '#/view/com/util/LoadingScreen'
-import {Text} from '#/view/com/util/text/Text'
-import * as Toast from '#/view/com/util/Toast'
-import {atoms as a, useTheme} from '#/alf'
-import {Button as NewButton, ButtonText} from '#/components/Button'
-import {useRichText} from '#/components/hooks/useRichText'
-import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
-import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
-import {
-  Heart2_Filled_Stroke2_Corner0_Rounded as HeartFilled,
-  Heart2_Stroke2_Corner0_Rounded as HeartOutline,
-} from '#/components/icons/Heart2'
-import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus'
-import {Trash_Stroke2_Corner0_Rounded as Trash} from '#/components/icons/Trash'
-import * as Layout from '#/components/Layout'
-import {InlineLinkText} from '#/components/Link'
-import * as Menu from '#/components/Menu'
-import {ReportDialog, useReportDialogControl} from '#/components/ReportDialog'
-import {RichText} from '#/components/RichText'
-
-const SECTION_TITLES = ['Posts']
-
-interface SectionRef {
-  scrollToTop: () => void
-}
-
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFeed'>
-export function ProfileFeedScreen(props: Props) {
-  const {rkey, name: handleOrDid} = props.route.params
-
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const navigation = useNavigation<NavigationProp>()
-
-  const uri = useMemo(
-    () => makeRecordUri(handleOrDid, 'app.bsky.feed.generator', rkey),
-    [rkey, handleOrDid],
-  )
-  const {error, data: resolvedUri} = useResolveUriQuery(uri)
-
-  const onPressBack = React.useCallback(() => {
-    if (navigation.canGoBack()) {
-      navigation.goBack()
-    } else {
-      navigation.navigate('Home')
-    }
-  }, [navigation])
-
-  if (error) {
-    return (
-      <Layout.Screen testID="profileFeedScreenError">
-        <Layout.Content>
-          <View style={[pal.view, pal.border, styles.notFoundContainer]}>
-            <Text type="title-lg" style={[pal.text, s.mb10]}>
-              <Trans>Could not load feed</Trans>
-            </Text>
-            <Text type="md" style={[pal.text, s.mb20]}>
-              {error.toString()}
-            </Text>
-
-            <View style={{flexDirection: 'row'}}>
-              <Button
-                type="default"
-                accessibilityLabel={_(msg`Go back`)}
-                accessibilityHint={_(msg`Returns to previous page`)}
-                onPress={onPressBack}
-                style={{flexShrink: 1}}>
-                <Text type="button" style={pal.text}>
-                  <Trans>Go Back</Trans>
-                </Text>
-              </Button>
-            </View>
-          </View>
-        </Layout.Content>
-      </Layout.Screen>
-    )
-  }
-
-  return resolvedUri ? (
-    <Layout.Screen>
-      <ProfileFeedScreenIntermediate feedUri={resolvedUri.uri} />
-    </Layout.Screen>
-  ) : (
-    <Layout.Screen>
-      <LoadingScreen />
-    </Layout.Screen>
-  )
-}
-
-function ProfileFeedScreenIntermediate({feedUri}: {feedUri: string}) {
-  const {data: preferences} = usePreferencesQuery()
-  const {data: info} = useFeedSourceInfoQuery({uri: feedUri})
-
-  if (!preferences || !info) {
-    return <LoadingScreen />
-  }
-
-  return (
-    <ProfileFeedScreenInner
-      preferences={preferences}
-      feedInfo={info as FeedSourceFeedInfo}
-    />
-  )
-}
-
-export function ProfileFeedScreenInner({
-  preferences,
-  feedInfo,
-}: {
-  preferences: UsePreferencesQueryResponse
-  feedInfo: FeedSourceFeedInfo
-}) {
-  const {_} = useLingui()
-  const t = useTheme()
-  const {hasSession, currentAccount} = useSession()
-  const reportDialogControl = useReportDialogControl()
-  const {openComposer} = useComposerControls()
-  const playHaptic = useHaptics()
-  const feedSectionRef = React.useRef<SectionRef>(null)
-  const isScreenFocused = useIsFocused()
-
-  const {mutateAsync: addSavedFeeds, isPending: isAddSavedFeedPending} =
-    useAddSavedFeedsMutation()
-  const {mutateAsync: removeFeed, isPending: isRemovePending} =
-    useRemoveFeedMutation()
-  const {mutateAsync: updateSavedFeeds, isPending: isUpdateFeedPending} =
-    useUpdateSavedFeedsMutation()
-
-  const isPending =
-    isAddSavedFeedPending || isRemovePending || isUpdateFeedPending
-  const savedFeedConfig = preferences.savedFeeds.find(
-    f => f.value === feedInfo.uri,
-  )
-  const isSaved = Boolean(savedFeedConfig)
-  const isPinned = Boolean(savedFeedConfig?.pinned)
-
-  useSetTitle(feedInfo?.displayName)
-
-  // event handlers
-  //
-
-  const onToggleSaved = React.useCallback(async () => {
-    try {
-      playHaptic()
-
-      if (savedFeedConfig) {
-        await removeFeed(savedFeedConfig)
-        Toast.show(_(msg`Removed from your feeds`))
-      } else {
-        await addSavedFeeds([
-          {
-            type: 'feed',
-            value: feedInfo.uri,
-            pinned: false,
-          },
-        ])
-        Toast.show(_(msg`Saved to your feeds`))
-      }
-    } catch (err) {
-      Toast.show(
-        _(
-          msg`There was an issue updating your feeds, please check your internet connection and try again.`,
-        ),
-        'xmark',
-      )
-      logger.error('Failed to update feeds', {message: err})
-    }
-  }, [_, playHaptic, feedInfo, removeFeed, addSavedFeeds, savedFeedConfig])
-
-  const onTogglePinned = React.useCallback(async () => {
-    try {
-      playHaptic()
-
-      if (savedFeedConfig) {
-        await updateSavedFeeds([
-          {
-            ...savedFeedConfig,
-            pinned: !savedFeedConfig.pinned,
-          },
-        ])
-      } else {
-        await addSavedFeeds([
-          {
-            type: 'feed',
-            value: feedInfo.uri,
-            pinned: true,
-          },
-        ])
-      }
-    } catch (e) {
-      Toast.show(_(msg`There was an issue contacting the server`), 'xmark')
-      logger.error('Failed to toggle pinned feed', {message: e})
-    }
-  }, [
-    playHaptic,
-    feedInfo,
-    _,
-    savedFeedConfig,
-    updateSavedFeeds,
-    addSavedFeeds,
-  ])
-
-  const onPressShare = React.useCallback(() => {
-    const url = toShareUrl(feedInfo.route.href)
-    shareUrl(url)
-  }, [feedInfo])
-
-  const onPressReport = React.useCallback(() => {
-    reportDialogControl.open()
-  }, [reportDialogControl])
-
-  const onCurrentPageSelected = React.useCallback(
-    (index: number) => {
-      if (index === 0) {
-        feedSectionRef.current?.scrollToTop()
-      }
-    },
-    [feedSectionRef],
-  )
-
-  const renderHeader = useCallback(() => {
-    return (
-      <>
-        <ProfileSubpageHeader
-          isLoading={false}
-          href={feedInfo.route.href}
-          title={feedInfo?.displayName}
-          avatar={feedInfo?.avatar}
-          isOwner={feedInfo.creatorDid === currentAccount?.did}
-          creator={
-            feedInfo
-              ? {did: feedInfo.creatorDid, handle: feedInfo.creatorHandle}
-              : undefined
-          }
-          avatarType="algo">
-          <View style={[a.flex_row, a.align_center, a.gap_sm]}>
-            {feedInfo && hasSession && (
-              <NewButton
-                testID={isPinned ? 'unpinBtn' : 'pinBtn'}
-                disabled={isPending}
-                size="small"
-                variant="solid"
-                color={isPinned ? 'secondary' : 'primary'}
-                label={isPinned ? _(msg`Unpin from home`) : _(msg`Pin to home`)}
-                onPress={onTogglePinned}>
-                <ButtonText>
-                  {isPinned ? _(msg`Unpin`) : _(msg`Pin to Home`)}
-                </ButtonText>
-              </NewButton>
-            )}
-            <Menu.Root>
-              <Menu.Trigger label={_(msg`Open feed options menu`)}>
-                {({props, state}) => {
-                  return (
-                    <Pressable
-                      {...props}
-                      hitSlop={HITSLOP_20}
-                      style={[
-                        a.justify_center,
-                        a.align_center,
-                        a.rounded_full,
-                        {height: 36, width: 36},
-                        t.atoms.bg_contrast_25,
-                        (state.hovered || state.pressed) && [
-                          t.atoms.bg_contrast_50,
-                        ],
-                      ]}
-                      testID="headerDropdownBtn">
-                      <FontAwesomeIcon
-                        icon="ellipsis"
-                        size={20}
-                        style={t.atoms.text}
-                      />
-                    </Pressable>
-                  )
-                }}
-              </Menu.Trigger>
-
-              <Menu.Outer>
-                <Menu.Group>
-                  {hasSession && (
-                    <>
-                      <Menu.Item
-                        disabled={isPending}
-                        testID="feedHeaderDropdownToggleSavedBtn"
-                        label={
-                          isSaved
-                            ? _(msg`Remove from my feeds`)
-                            : _(msg`Save to my feeds`)
-                        }
-                        onPress={onToggleSaved}>
-                        <Menu.ItemText>
-                          {isSaved
-                            ? _(msg`Remove from my feeds`)
-                            : _(msg`Save to my feeds`)}
-                        </Menu.ItemText>
-                        <Menu.ItemIcon
-                          icon={isSaved ? Trash : Plus}
-                          position="right"
-                        />
-                      </Menu.Item>
-
-                      <Menu.Item
-                        testID="feedHeaderDropdownReportBtn"
-                        label={_(msg`Report feed`)}
-                        onPress={onPressReport}>
-                        <Menu.ItemText>{_(msg`Report feed`)}</Menu.ItemText>
-                        <Menu.ItemIcon icon={CircleInfo} position="right" />
-                      </Menu.Item>
-                    </>
-                  )}
-
-                  <Menu.Item
-                    testID="feedHeaderDropdownShareBtn"
-                    label={_(msg`Share feed`)}
-                    onPress={onPressShare}>
-                    <Menu.ItemText>{_(msg`Share feed`)}</Menu.ItemText>
-                    <Menu.ItemIcon icon={Share} position="right" />
-                  </Menu.Item>
-                </Menu.Group>
-              </Menu.Outer>
-            </Menu.Root>
-          </View>
-        </ProfileSubpageHeader>
-        <AboutSection
-          feedOwnerDid={feedInfo.creatorDid}
-          feedRkey={feedInfo.route.params.rkey}
-          feedInfo={feedInfo}
-        />
-      </>
-    )
-  }, [
-    _,
-    hasSession,
-    feedInfo,
-    isPinned,
-    onTogglePinned,
-    onToggleSaved,
-    currentAccount?.did,
-    isSaved,
-    onPressReport,
-    onPressShare,
-    t,
-    isPending,
-  ])
-
-  return (
-    <>
-      <ReportDialog
-        control={reportDialogControl}
-        params={{
-          type: 'feedgen',
-          uri: feedInfo.uri,
-          cid: feedInfo.cid,
-        }}
-      />
-      <PagerWithHeader
-        items={SECTION_TITLES}
-        isHeaderReady={true}
-        renderHeader={renderHeader}
-        onCurrentPageSelected={onCurrentPageSelected}>
-        {({headerHeight, scrollElRef, isFocused}) => (
-          <FeedSection
-            ref={feedSectionRef}
-            feed={`feedgen|${feedInfo.uri}`}
-            headerHeight={headerHeight}
-            scrollElRef={scrollElRef as ListRef}
-            isFocused={isScreenFocused && isFocused}
-          />
-        )}
-      </PagerWithHeader>
-      {hasSession && (
-        <FAB
-          testID="composeFAB"
-          onPress={() => openComposer({})}
-          icon={
-            <ComposeIcon2
-              strokeWidth={1.5}
-              size={29}
-              style={{color: 'white'}}
-            />
-          }
-          accessibilityRole="button"
-          accessibilityLabel={_(msg`New post`)}
-          accessibilityHint=""
-        />
-      )}
-    </>
-  )
-}
-
-interface FeedSectionProps {
-  feed: FeedDescriptor
-  headerHeight: number
-  scrollElRef: ListRef
-  isFocused: boolean
-}
-const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>(
-  function FeedSectionImpl({feed, headerHeight, scrollElRef, isFocused}, ref) {
-    const {_} = useLingui()
-    const [hasNew, setHasNew] = React.useState(false)
-    const [isScrolledDown, setIsScrolledDown] = React.useState(false)
-    const queryClient = useQueryClient()
-    const isScreenFocused = useIsFocused()
-    const {hasSession} = useSession()
-    const feedFeedback = useFeedFeedback(feed, hasSession)
-
-    const onScrollToTop = useCallback(() => {
-      scrollElRef.current?.scrollToOffset({
-        animated: isNative,
-        offset: -headerHeight,
-      })
-      truncateAndInvalidate(queryClient, FEED_RQKEY(feed))
-      setHasNew(false)
-    }, [scrollElRef, headerHeight, queryClient, feed, setHasNew])
-
-    React.useImperativeHandle(ref, () => ({
-      scrollToTop: onScrollToTop,
-    }))
-
-    React.useEffect(() => {
-      if (!isScreenFocused) {
-        return
-      }
-      return listenSoftReset(onScrollToTop)
-    }, [onScrollToTop, isScreenFocused])
-
-    const renderPostsEmpty = useCallback(() => {
-      return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} />
-    }, [_])
-
-    return (
-      <View>
-        <FeedFeedbackProvider value={feedFeedback}>
-          <PostFeed
-            enabled={isFocused}
-            feed={feed}
-            pollInterval={60e3}
-            disablePoll={hasNew}
-            scrollElRef={scrollElRef}
-            onHasNew={setHasNew}
-            onScrolledDownChange={setIsScrolledDown}
-            renderEmptyState={renderPostsEmpty}
-            headerOffset={headerHeight}
-          />
-        </FeedFeedbackProvider>
-        {(isScrolledDown || hasNew) && (
-          <LoadLatestBtn
-            onPress={onScrollToTop}
-            label={_(msg`Load new posts`)}
-            showIndicator={hasNew}
-          />
-        )}
-      </View>
-    )
-  },
-)
-
-function AboutSection({
-  feedOwnerDid,
-  feedRkey,
-  feedInfo,
-}: {
-  feedOwnerDid: string
-  feedRkey: string
-  feedInfo: FeedSourceFeedInfo
-}) {
-  const t = useTheme()
-  const pal = usePalette('default')
-  const {_} = useLingui()
-  const [likeUri, setLikeUri] = React.useState(feedInfo.likeUri)
-  const {hasSession} = useSession()
-  const playHaptic = useHaptics()
-  const {mutateAsync: likeFeed, isPending: isLikePending} = useLikeMutation()
-  const {mutateAsync: unlikeFeed, isPending: isUnlikePending} =
-    useUnlikeMutation()
-  const [resolvedRT] = useRichText(feedInfo.description.text || '')
-
-  const isLiked = !!likeUri
-  const likeCount =
-    isLiked && likeUri ? (feedInfo.likeCount || 0) + 1 : feedInfo.likeCount
-
-  const onToggleLiked = React.useCallback(async () => {
-    try {
-      playHaptic()
-
-      if (isLiked && likeUri) {
-        await unlikeFeed({uri: likeUri})
-        setLikeUri('')
-      } else {
-        const res = await likeFeed({uri: feedInfo.uri, cid: feedInfo.cid})
-        setLikeUri(res.uri)
-      }
-    } catch (err) {
-      Toast.show(
-        _(
-          msg`There was an issue contacting the server, please check your internet connection and try again.`,
-        ),
-        'xmark',
-      )
-      logger.error('Failed to toggle like', {message: err})
-    }
-  }, [playHaptic, isLiked, likeUri, unlikeFeed, likeFeed, feedInfo, _])
-
-  return (
-    <View style={[styles.aboutSectionContainer]}>
-      <View style={[a.pt_sm]}>
-        {feedInfo.description ? (
-          <RichText
-            testID="listDescription"
-            style={[a.text_md]}
-            value={resolvedRT ?? feedInfo.description}
-          />
-        ) : (
-          <Text type="lg" style={[{fontStyle: 'italic'}, pal.textLight]}>
-            <Trans>No description</Trans>
-          </Text>
-        )}
-      </View>
-
-      <View style={[a.flex_row, a.gap_sm, a.align_center, a.pb_sm]}>
-        <NewButton
-          size="small"
-          variant="solid"
-          color="secondary"
-          shape="round"
-          label={isLiked ? _(msg`Unlike this feed`) : _(msg`Like this feed`)}
-          testID="toggleLikeBtn"
-          disabled={!hasSession || isLikePending || isUnlikePending}
-          onPress={onToggleLiked}>
-          {isLiked ? (
-            <HeartFilled size="md" fill={s.likeColor.color} />
-          ) : (
-            <HeartOutline size="md" fill={t.atoms.text_contrast_medium.color} />
-          )}
-        </NewButton>
-        {typeof likeCount === 'number' && (
-          <InlineLinkText
-            label={_(msg`View users who like this feed`)}
-            to={makeCustomFeedLink(feedOwnerDid, feedRkey, 'liked-by')}
-            style={[t.atoms.text_contrast_medium, a.font_bold]}>
-            <Trans>
-              Liked by <Plural value={likeCount} one="# user" other="# users" />
-            </Trans>
-          </InlineLinkText>
-        )}
-      </View>
-    </View>
-  )
-}
-
-const styles = StyleSheet.create({
-  btn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 6,
-    paddingVertical: 7,
-    paddingHorizontal: 14,
-    borderRadius: 50,
-    marginLeft: 6,
-  },
-  notFoundContainer: {
-    margin: 10,
-    paddingHorizontal: 18,
-    paddingVertical: 14,
-    borderRadius: 6,
-  },
-  aboutSectionContainer: {
-    paddingVertical: 4,
-    paddingHorizontal: 16,
-    gap: 12,
-  },
-})