about summary refs log tree commit diff
path: root/src/view/com/post-thread
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-03-31 13:17:26 -0500
committerGitHub <noreply@github.com>2023-03-31 13:17:26 -0500
commita3334a01a221877d3e06e02f960fda441f3460bd (patch)
tree64cdbb1232d1a3c00750c346b6e3ae529b51d1b0 /src/view/com/post-thread
parent19f3a2fa92a61ddb785fc4e42d73792c1d0e772c (diff)
downloadvoidsky-a3334a01a221877d3e06e02f960fda441f3460bd.tar.zst
Lex refactor (#362)
* Remove the hackcheck for upgrades

* Rename the PostEmbeds folder to match the codebase style

* Updates to latest lex refactor

* Update to use new bsky agent

* Update to use api package's richtext library

* Switch to upsertProfile

* Add TextEncoder/TextDecoder polyfill

* Add Intl.Segmenter polyfill

* Update composer to calculate lengths by grapheme

* Fix detox

* Fix login in e2e

* Create account e2e passing

* Implement an e2e mocking framework

* Don't use private methods on mobx models as mobx can't track them

* Add tooling for e2e-specific builds and add e2e media-picker mock

* Add some tests and fix some bugs around profile editing

* Add shell tests

* Add home screen tests

* Add thread screen tests

* Add tests for other user profile screens

* Add search screen tests

* Implement profile imagery change tools and tests

* Update to new embed behaviors

* Add post tests

* Fix to profile-screen test

* Fix session resumption

* Update web composer to new api

* 1.11.0

* Fix pagination cursor parameters

* Add quote posts to notifications

* Fix embed layouts

* Remove youtube inline player and improve tap handling on link cards

* Reset minimal shell mode on all screen loads and feed swipes (close #299)

* Update podfile.lock

* Improve post notfound UI (close #366)

* Bump atproto packages
Diffstat (limited to 'src/view/com/post-thread')
-rw-r--r--src/view/com/post-thread/PostLikedBy.tsx (renamed from src/view/com/post-thread/PostVotedBy.tsx)19
-rw-r--r--src/view/com/post-thread/PostRepostedBy.tsx1
-rw-r--r--src/view/com/post-thread/PostThread.tsx60
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx271
4 files changed, 198 insertions, 153 deletions
diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostLikedBy.tsx
index f86798097..9fb46702e 100644
--- a/src/view/com/post-thread/PostVotedBy.tsx
+++ b/src/view/com/post-thread/PostLikedBy.tsx
@@ -2,24 +2,18 @@ import React, {useEffect} from 'react'
 import {observer} from 'mobx-react-lite'
 import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
 import {CenteredView, FlatList} from '../util/Views'
-import {VotesViewModel, VoteItem} from 'state/models/votes-view'
+import {LikesViewModel, LikeItem} from 'state/models/likes-view'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
 
-export const PostVotedBy = observer(function PostVotedBy({
-  uri,
-  direction,
-}: {
-  uri: string
-  direction: 'up' | 'down'
-}) {
+export const PostLikedBy = observer(function PostVotedBy({uri}: {uri: string}) {
   const pal = usePalette('default')
   const store = useStores()
   const view = React.useMemo(
-    () => new VotesViewModel(store, {uri, direction}),
-    [store, uri, direction],
+    () => new LikesViewModel(store, {uri}),
+    [store, uri],
   )
 
   useEffect(() => {
@@ -55,11 +49,10 @@ export const PostVotedBy = observer(function PostVotedBy({
 
   // loaded
   // =
-  const renderItem = ({item}: {item: VoteItem}) => (
+  const renderItem = ({item}: {item: LikeItem}) => (
     <ProfileCardWithFollowBtn
       key={item.actor.did}
       did={item.actor.did}
-      declarationCid={item.actor.declaration.cid}
       handle={item.actor.handle}
       displayName={item.actor.displayName}
       avatar={item.actor.avatar}
@@ -68,7 +61,7 @@ export const PostVotedBy = observer(function PostVotedBy({
   )
   return (
     <FlatList
-      data={view.votes}
+      data={view.likes}
       keyExtractor={item => item.actor.did}
       refreshControl={
         <RefreshControl
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index fda54469c..147d0271f 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -64,7 +64,6 @@ export const PostRepostedBy = observer(function PostRepostedBy({
     <ProfileCardWithFollowBtn
       key={item.did}
       did={item.did}
-      declarationCid={item.declaration.cid}
       handle={item.handle}
       displayName={item.displayName}
       avatar={item.avatar}
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index d0452331b..569c6e392 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -1,17 +1,30 @@
 import React, {useRef} from 'react'
 import {observer} from 'mobx-react-lite'
-import {ActivityIndicator, RefreshControl, StyleSheet, View} from 'react-native'
+import {
+  ActivityIndicator,
+  RefreshControl,
+  StyleSheet,
+  TouchableOpacity,
+  View,
+} from 'react-native'
 import {CenteredView, FlatList} from '../util/Views'
 import {
   PostThreadViewModel,
   PostThreadViewPostModel,
 } from 'state/models/post-thread-view'
+import {
+  FontAwesomeIcon,
+  FontAwesomeIconStyle,
+} from '@fortawesome/react-native-fontawesome'
 import {PostThreadItem} from './PostThreadItem'
 import {ComposePrompt} from '../composer/Prompt'
 import {ErrorMessage} from '../util/error/ErrorMessage'
+import {Text} from '../util/text/Text'
 import {s} from 'lib/styles'
 import {isDesktopWeb} from 'platform/detection'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useNavigation} from '@react-navigation/native'
+import {NavigationProp} from 'lib/routes/types'
 
 const REPLY_PROMPT = {_reactKey: '__reply__', _isHighlightedPost: false}
 const BOTTOM_BORDER = {
@@ -32,6 +45,7 @@ export const PostThread = observer(function PostThread({
   const pal = usePalette('default')
   const ref = useRef<FlatList>(null)
   const [isRefreshing, setIsRefreshing] = React.useState(false)
+  const navigation = useNavigation<NavigationProp>()
   const posts = React.useMemo(() => {
     if (view.thread) {
       return Array.from(flattenThread(view.thread)).concat([BOTTOM_BORDER])
@@ -41,6 +55,7 @@ export const PostThread = observer(function PostThread({
 
   // events
   // =
+
   const onRefresh = React.useCallback(async () => {
     setIsRefreshing(true)
     try {
@@ -50,6 +65,7 @@ export const PostThread = observer(function PostThread({
     }
     setIsRefreshing(false)
   }, [view, setIsRefreshing])
+
   const onLayout = React.useCallback(() => {
     const index = posts.findIndex(post => post._isHighlightedPost)
     if (index !== -1) {
@@ -60,6 +76,7 @@ export const PostThread = observer(function PostThread({
       })
     }
   }, [posts, ref])
+
   const onScrollToIndexFailed = React.useCallback(
     (info: {
       index: number
@@ -73,6 +90,15 @@ export const PostThread = observer(function PostThread({
     },
     [ref],
   )
+
+  const onPressBack = React.useCallback(() => {
+    if (navigation.canGoBack()) {
+      navigation.goBack()
+    } else {
+      navigation.navigate('Home')
+    }
+  }, [navigation])
+
   const renderItem = React.useCallback(
     ({item}: {item: YieldedItem}) => {
       if (item === REPLY_PROMPT) {
@@ -104,6 +130,30 @@ export const PostThread = observer(function PostThread({
   // error
   // =
   if (view.hasError) {
+    if (view.notFound) {
+      return (
+        <CenteredView>
+          <View style={[pal.view, pal.border, styles.notFoundContainer]}>
+            <Text type="title-lg" style={[pal.text, s.mb5]}>
+              Post not found
+            </Text>
+            <Text type="md" style={[pal.text, s.mb10]}>
+              The post may have been deleted.
+            </Text>
+            <TouchableOpacity onPress={onPressBack}>
+              <Text type="2xl" style={pal.link}>
+                <FontAwesomeIcon
+                  icon="angle-left"
+                  style={[pal.link as FontAwesomeIconStyle, s.mr5]}
+                  size={14}
+                />
+                Back
+              </Text>
+            </TouchableOpacity>
+          </View>
+        </CenteredView>
+      )
+    }
     return (
       <CenteredView>
         <ErrorMessage message={view.error} onPressTryAgain={onRefresh} />
@@ -159,12 +209,18 @@ function* flattenThread(
         yield* flattenThread(reply as PostThreadViewPostModel)
       }
     }
-  } else if (!isAscending && !post.parent && post.post.replyCount > 0) {
+  } else if (!isAscending && !post.parent && post.post.replyCount) {
     post._hasMore = true
   }
 }
 
 const styles = StyleSheet.create({
+  notFoundContainer: {
+    margin: 10,
+    paddingHorizontal: 18,
+    paddingVertical: 14,
+    borderRadius: 6,
+  },
   bottomBorder: {
     borderBottomWidth: 1,
   },
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 17c7943d9..cf2148060 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -19,7 +19,7 @@ import {ago} from 'lib/strings/time'
 import {pluralize} from 'lib/strings/helpers'
 import {useStores} from 'state/index'
 import {PostMeta} from '../util/PostMeta'
-import {PostEmbeds} from '../util/PostEmbeds'
+import {PostEmbeds} from '../util/post-embeds'
 import {PostCtrls} from '../util/PostCtrls'
 import {PostMutedWrapper} from '../util/PostMuted'
 import {ErrorMessage} from '../util/error/ErrorMessage'
@@ -38,7 +38,7 @@ export const PostThreadItem = observer(function PostThreadItem({
   const store = useStores()
   const [deleted, setDeleted] = React.useState(false)
   const record = item.postRecord
-  const hasEngagement = item.post.upvoteCount || item.post.repostCount
+  const hasEngagement = item.post.likeCount || item.post.repostCount
 
   const itemUri = item.post.uri
   const itemCid = item.post.cid
@@ -49,11 +49,11 @@ export const PostThreadItem = observer(function PostThreadItem({
   const itemTitle = `Post by ${item.post.author.handle}`
   const authorHref = `/profile/${item.post.author.handle}`
   const authorTitle = item.post.author.handle
-  const upvotesHref = React.useMemo(() => {
+  const likesHref = React.useMemo(() => {
     const urip = new AtUri(item.post.uri)
-    return `/profile/${item.post.author.handle}/post/${urip.rkey}/upvoted-by`
+    return `/profile/${item.post.author.handle}/post/${urip.rkey}/liked-by`
   }, [item.post.uri, item.post.author.handle])
-  const upvotesTitle = 'Likes on this post'
+  const likesTitle = 'Likes on this post'
   const repostsHref = React.useMemo(() => {
     const urip = new AtUri(item.post.uri)
     return `/profile/${item.post.author.handle}/post/${urip.rkey}/reposted-by`
@@ -80,10 +80,10 @@ export const PostThreadItem = observer(function PostThreadItem({
       .toggleRepost()
       .catch(e => store.log.error('Failed to toggle repost', e))
   }, [item, store])
-  const onPressToggleUpvote = React.useCallback(() => {
+  const onPressToggleLike = React.useCallback(() => {
     return item
-      .toggleUpvote()
-      .catch(e => store.log.error('Failed to toggle upvote', e))
+      .toggleLike()
+      .catch(e => store.log.error('Failed to toggle like', e))
   }, [item, store])
   const onCopyPostText = React.useCallback(() => {
     Clipboard.setString(record?.text || '')
@@ -125,153 +125,151 @@ export const PostThreadItem = observer(function PostThreadItem({
 
   if (item._isHighlightedPost) {
     return (
-      <>
-        <View
-          style={[
-            styles.outer,
-            styles.outerHighlighted,
-            {borderTopColor: pal.colors.border},
-            pal.view,
-          ]}>
-          <View style={styles.layout}>
-            <View style={styles.layoutAvi}>
-              <Link href={authorHref} title={authorTitle} asAnchor>
-                <UserAvatar size={52} avatar={item.post.author.avatar} />
-              </Link>
-            </View>
-            <View style={styles.layoutContent}>
-              <View style={[styles.meta, styles.metaExpandedLine1]}>
-                <View style={[s.flexRow, s.alignBaseline]}>
-                  <Link
-                    style={styles.metaItem}
-                    href={authorHref}
-                    title={authorTitle}>
-                    <Text
-                      type="xl-bold"
-                      style={[pal.text]}
-                      numberOfLines={1}
-                      lineHeight={1.2}>
-                      {item.post.author.displayName || item.post.author.handle}
-                    </Text>
-                  </Link>
-                  <Text type="md" style={[styles.metaItem, pal.textLight]}>
-                    &middot; {ago(item.post.indexedAt)}
-                  </Text>
-                </View>
-                <View style={s.flex1} />
-                <PostDropdownBtn
-                  style={styles.metaItem}
-                  itemUri={itemUri}
-                  itemCid={itemCid}
-                  itemHref={itemHref}
-                  itemTitle={itemTitle}
-                  isAuthor={item.post.author.did === store.me.did}
-                  onCopyPostText={onCopyPostText}
-                  onOpenTranslate={onOpenTranslate}
-                  onDeletePost={onDeletePost}>
-                  <FontAwesomeIcon
-                    icon="ellipsis-h"
-                    size={14}
-                    style={[s.mt2, s.mr5, pal.textLight]}
-                  />
-                </PostDropdownBtn>
-              </View>
-              <View style={styles.meta}>
+      <View
+        testID={`postThreadItem-by-${item.post.author.handle}`}
+        style={[
+          styles.outer,
+          styles.outerHighlighted,
+          {borderTopColor: pal.colors.border},
+          pal.view,
+        ]}>
+        <View style={styles.layout}>
+          <View style={styles.layoutAvi}>
+            <Link href={authorHref} title={authorTitle} asAnchor>
+              <UserAvatar size={52} avatar={item.post.author.avatar} />
+            </Link>
+          </View>
+          <View style={styles.layoutContent}>
+            <View style={[styles.meta, styles.metaExpandedLine1]}>
+              <View style={[s.flexRow, s.alignBaseline]}>
                 <Link
                   style={styles.metaItem}
                   href={authorHref}
                   title={authorTitle}>
-                  <Text type="md" style={[pal.textLight]} numberOfLines={1}>
-                    @{item.post.author.handle}
+                  <Text
+                    type="xl-bold"
+                    style={[pal.text]}
+                    numberOfLines={1}
+                    lineHeight={1.2}>
+                    {item.post.author.displayName || item.post.author.handle}
                   </Text>
                 </Link>
+                <Text type="md" style={[styles.metaItem, pal.textLight]}>
+                  &middot; {ago(item.post.indexedAt)}
+                </Text>
               </View>
-            </View>
-          </View>
-          <View style={[s.pl10, s.pr10, s.pb10]}>
-            {item.richText?.text ? (
-              <View
-                style={[
-                  styles.postTextContainer,
-                  styles.postTextLargeContainer,
-                ]}>
-                <RichText
-                  type="post-text-lg"
-                  richText={item.richText}
-                  lineHeight={1.3}
-                />
-              </View>
-            ) : undefined}
-            <PostEmbeds embed={item.post.embed} style={s.mb10} />
-            {item._isHighlightedPost && hasEngagement ? (
-              <View style={[styles.expandedInfo, pal.border]}>
-                {item.post.repostCount ? (
-                  <Link
-                    style={styles.expandedInfoItem}
-                    href={repostsHref}
-                    title={repostsTitle}>
-                    <Text type="lg" style={pal.textLight}>
-                      <Text type="xl-bold" style={pal.text}>
-                        {item.post.repostCount}
-                      </Text>{' '}
-                      {pluralize(item.post.repostCount, 'repost')}
-                    </Text>
-                  </Link>
-                ) : (
-                  <></>
-                )}
-                {item.post.upvoteCount ? (
-                  <Link
-                    style={styles.expandedInfoItem}
-                    href={upvotesHref}
-                    title={upvotesTitle}>
-                    <Text type="lg" style={pal.textLight}>
-                      <Text type="xl-bold" style={pal.text}>
-                        {item.post.upvoteCount}
-                      </Text>{' '}
-                      {pluralize(item.post.upvoteCount, 'like')}
-                    </Text>
-                  </Link>
-                ) : (
-                  <></>
-                )}
-              </View>
-            ) : (
-              <></>
-            )}
-            <View style={[s.pl10, s.pb5]}>
-              <PostCtrls
-                big
+              <View style={s.flex1} />
+              <PostDropdownBtn
+                testID="postDropdownBtn"
+                style={styles.metaItem}
                 itemUri={itemUri}
                 itemCid={itemCid}
                 itemHref={itemHref}
                 itemTitle={itemTitle}
-                author={{
-                  avatar: item.post.author.avatar!,
-                  handle: item.post.author.handle,
-                  displayName: item.post.author.displayName!,
-                }}
-                text={item.richText?.text || record.text}
-                indexedAt={item.post.indexedAt}
                 isAuthor={item.post.author.did === store.me.did}
-                isReposted={!!item.post.viewer.repost}
-                isUpvoted={!!item.post.viewer.upvote}
-                onPressReply={onPressReply}
-                onPressToggleRepost={onPressToggleRepost}
-                onPressToggleUpvote={onPressToggleUpvote}
                 onCopyPostText={onCopyPostText}
                 onOpenTranslate={onOpenTranslate}
-                onDeletePost={onDeletePost}
+                onDeletePost={onDeletePost}>
+                <FontAwesomeIcon
+                  icon="ellipsis-h"
+                  size={14}
+                  style={[s.mt2, s.mr5, pal.textLight]}
+                />
+              </PostDropdownBtn>
+            </View>
+            <View style={styles.meta}>
+              <Link
+                style={styles.metaItem}
+                href={authorHref}
+                title={authorTitle}>
+                <Text type="md" style={[pal.textLight]} numberOfLines={1}>
+                  @{item.post.author.handle}
+                </Text>
+              </Link>
+            </View>
+          </View>
+        </View>
+        <View style={[s.pl10, s.pr10, s.pb10]}>
+          {item.richText?.text ? (
+            <View
+              style={[styles.postTextContainer, styles.postTextLargeContainer]}>
+              <RichText
+                type="post-text-lg"
+                richText={item.richText}
+                lineHeight={1.3}
               />
             </View>
+          ) : undefined}
+          <PostEmbeds embed={item.post.embed} style={s.mb10} />
+          {item._isHighlightedPost && hasEngagement ? (
+            <View style={[styles.expandedInfo, pal.border]}>
+              {item.post.repostCount ? (
+                <Link
+                  style={styles.expandedInfoItem}
+                  href={repostsHref}
+                  title={repostsTitle}>
+                  <Text testID="repostCount" type="lg" style={pal.textLight}>
+                    <Text type="xl-bold" style={pal.text}>
+                      {item.post.repostCount}
+                    </Text>{' '}
+                    {pluralize(item.post.repostCount, 'repost')}
+                  </Text>
+                </Link>
+              ) : (
+                <></>
+              )}
+              {item.post.likeCount ? (
+                <Link
+                  style={styles.expandedInfoItem}
+                  href={likesHref}
+                  title={likesTitle}>
+                  <Text testID="likeCount" type="lg" style={pal.textLight}>
+                    <Text type="xl-bold" style={pal.text}>
+                      {item.post.likeCount}
+                    </Text>{' '}
+                    {pluralize(item.post.likeCount, 'like')}
+                  </Text>
+                </Link>
+              ) : (
+                <></>
+              )}
+            </View>
+          ) : (
+            <></>
+          )}
+          <View style={[s.pl10, s.pb5]}>
+            <PostCtrls
+              big
+              itemUri={itemUri}
+              itemCid={itemCid}
+              itemHref={itemHref}
+              itemTitle={itemTitle}
+              author={{
+                avatar: item.post.author.avatar!,
+                handle: item.post.author.handle,
+                displayName: item.post.author.displayName!,
+              }}
+              text={item.richText?.text || record.text}
+              indexedAt={item.post.indexedAt}
+              isAuthor={item.post.author.did === store.me.did}
+              isReposted={!!item.post.viewer?.repost}
+              isLiked={!!item.post.viewer?.like}
+              onPressReply={onPressReply}
+              onPressToggleRepost={onPressToggleRepost}
+              onPressToggleLike={onPressToggleLike}
+              onCopyPostText={onCopyPostText}
+              onOpenTranslate={onOpenTranslate}
+              onDeletePost={onDeletePost}
+            />
           </View>
         </View>
-      </>
+      </View>
     )
   } else {
     return (
       <PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
         <Link
+          testID={`postThreadItem-by-${item.post.author.handle}`}
           style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
           href={itemHref}
           title={itemTitle}
@@ -305,7 +303,6 @@ export const PostThreadItem = observer(function PostThreadItem({
                 timestamp={item.post.indexedAt}
                 postHref={itemHref}
                 did={item.post.author.did}
-                declarationCid={item.post.author.declaration.cid}
               />
               {item.richText?.text ? (
                 <View style={styles.postTextContainer}>
@@ -333,12 +330,12 @@ export const PostThreadItem = observer(function PostThreadItem({
                 isAuthor={item.post.author.did === store.me.did}
                 replyCount={item.post.replyCount}
                 repostCount={item.post.repostCount}
-                upvoteCount={item.post.upvoteCount}
-                isReposted={!!item.post.viewer.repost}
-                isUpvoted={!!item.post.viewer.upvote}
+                likeCount={item.post.likeCount}
+                isReposted={!!item.post.viewer?.repost}
+                isLiked={!!item.post.viewer?.like}
                 onPressReply={onPressReply}
                 onPressToggleRepost={onPressToggleRepost}
-                onPressToggleUpvote={onPressToggleUpvote}
+                onPressToggleLike={onPressToggleLike}
                 onCopyPostText={onCopyPostText}
                 onOpenTranslate={onOpenTranslate}
                 onDeletePost={onDeletePost}