about summary refs log tree commit diff
path: root/src/view/com/post-thread
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/post-thread')
-rw-r--r--src/view/com/post-thread/PostRepostedBy.tsx69
-rw-r--r--src/view/com/post-thread/PostThread.tsx61
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx76
-rw-r--r--src/view/com/post-thread/PostVotedBy.tsx77
4 files changed, 109 insertions, 174 deletions
diff --git a/src/view/com/post-thread/PostRepostedBy.tsx b/src/view/com/post-thread/PostRepostedBy.tsx
index dacdfa50f..a9fabac3d 100644
--- a/src/view/com/post-thread/PostRepostedBy.tsx
+++ b/src/view/com/post-thread/PostRepostedBy.tsx
@@ -5,13 +5,10 @@ import {CenteredView, FlatList} from '../util/Views'
 import {
   RepostedByViewModel,
   RepostedByItem,
-} from '../../../state/models/reposted-by-view'
-import {UserAvatar} from '../util/UserAvatar'
+} from 'state/models/reposted-by-view'
+import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {Link} from '../util/Link'
-import {Text} from '../util/text/Text'
-import {useStores} from '../../../state'
-import {s, colors} from '../../lib/styles'
+import {useStores} from 'state/index'
 
 export const PostRepostedBy = observer(function PostRepostedBy({
   uri,
@@ -62,7 +59,15 @@ export const PostRepostedBy = observer(function PostRepostedBy({
   // loaded
   // =
   const renderItem = ({item}: {item: RepostedByItem}) => (
-    <RepostedByItemCom item={item} />
+    <ProfileCardWithFollowBtn
+      key={item.did}
+      did={item.did}
+      declarationCid={item.declaration.cid}
+      handle={item.handle}
+      displayName={item.displayName}
+      avatar={item.avatar}
+      isFollowedBy={!!item.viewer?.followedBy}
+    />
   )
   return (
     <FlatList
@@ -83,57 +88,7 @@ export const PostRepostedBy = observer(function PostRepostedBy({
   )
 })
 
-const RepostedByItemCom = ({item}: {item: RepostedByItem}) => {
-  return (
-    <Link
-      style={styles.outer}
-      href={`/profile/${item.handle}`}
-      title={item.handle}
-      noFeedback>
-      <View style={styles.layout}>
-        <View style={styles.layoutAvi}>
-          <UserAvatar
-            size={40}
-            displayName={item.displayName}
-            handle={item.handle}
-            avatar={item.avatar}
-          />
-        </View>
-        <View style={styles.layoutContent}>
-          <Text style={[s.f15, s.bold]}>{item.displayName || item.handle}</Text>
-          <Text style={[s.f14, s.gray5]}>@{item.handle}</Text>
-        </View>
-      </View>
-    </Link>
-  )
-}
-
 const styles = StyleSheet.create({
-  outer: {
-    marginTop: 1,
-    backgroundColor: colors.white,
-  },
-  layout: {
-    flexDirection: 'row',
-  },
-  layoutAvi: {
-    width: 60,
-    paddingLeft: 10,
-    paddingTop: 10,
-    paddingBottom: 10,
-  },
-  avi: {
-    width: 40,
-    height: 40,
-    borderRadius: 20,
-    resizeMode: 'cover',
-  },
-  layoutContent: {
-    flex: 1,
-    paddingRight: 10,
-    paddingTop: 10,
-    paddingBottom: 10,
-  },
   footer: {
     height: 200,
     paddingTop: 20,
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index 59dbf1e16..0a092c46b 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -1,14 +1,14 @@
 import React, {useRef} from 'react'
 import {observer} from 'mobx-react-lite'
-import {ActivityIndicator, View} from 'react-native'
+import {ActivityIndicator} from 'react-native'
 import {CenteredView, FlatList} from '../util/Views'
 import {
   PostThreadViewModel,
   PostThreadViewPostModel,
-} from '../../../state/models/post-thread-view'
+} from 'state/models/post-thread-view'
 import {PostThreadItem} from './PostThreadItem'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {s} from '../../lib/styles'
+import {s} from 'lib/styles'
 
 export const PostThread = observer(function PostThread({
   uri,
@@ -18,15 +18,24 @@ export const PostThread = observer(function PostThread({
   view: PostThreadViewModel
 }) {
   const ref = useRef<FlatList>(null)
-  const posts = view.thread ? Array.from(flattenThread(view.thread)) : []
-  const onRefresh = () => {
-    view
-      ?.refresh()
-      .catch(err =>
-        view.rootStore.log.error('Failed to refresh posts thread', err),
-      )
-  }
-  const onLayout = () => {
+  const [isRefreshing, setIsRefreshing] = React.useState(false)
+  const posts = React.useMemo(
+    () => (view.thread ? Array.from(flattenThread(view.thread)) : []),
+    [view.thread],
+  )
+
+  // events
+  // =
+  const onRefresh = React.useCallback(async () => {
+    setIsRefreshing(true)
+    try {
+      view?.refresh()
+    } catch (err) {
+      view.rootStore.log.error('Failed to refresh posts thread', err)
+    }
+    setIsRefreshing(false)
+  }, [view, setIsRefreshing])
+  const onLayout = React.useCallback(() => {
     const index = posts.findIndex(post => post._isHighlightedPost)
     if (index !== -1) {
       ref.current?.scrollToIndex({
@@ -35,17 +44,20 @@ export const PostThread = observer(function PostThread({
         viewOffset: 40,
       })
     }
-  }
-  const onScrollToIndexFailed = (info: {
-    index: number
-    highestMeasuredFrameIndex: number
-    averageItemLength: number
-  }) => {
-    ref.current?.scrollToOffset({
-      animated: false,
-      offset: info.averageItemLength * info.index,
-    })
-  }
+  }, [posts, ref])
+  const onScrollToIndexFailed = React.useCallback(
+    (info: {
+      index: number
+      highestMeasuredFrameIndex: number
+      averageItemLength: number
+    }) => {
+      ref.current?.scrollToOffset({
+        animated: false,
+        offset: info.averageItemLength * info.index,
+      })
+    },
+    [ref],
+  )
 
   // loading
   // =
@@ -76,9 +88,10 @@ export const PostThread = observer(function PostThread({
     <FlatList
       ref={ref}
       data={posts}
+      initialNumToRender={posts.length}
       keyExtractor={item => item._reactKey}
       renderItem={renderItem}
-      refreshing={view.isRefreshing}
+      refreshing={isRefreshing}
       onRefresh={onRefresh}
       onLayout={onLayout}
       onScrollToIndexFailed={onScrollToIndexFailed}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index d39296285..cd3a49d64 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -1,4 +1,4 @@
-import React, {useMemo, useState} from 'react'
+import React from 'react'
 import {observer} from 'mobx-react-lite'
 import {StyleSheet, View} from 'react-native'
 import Clipboard from '@react-native-clipboard/clipboard'
@@ -7,22 +7,23 @@ import {
   FontAwesomeIcon,
   FontAwesomeIconStyle,
 } from '@fortawesome/react-native-fontawesome'
-import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
+import {PostThreadViewPostModel} from 'state/models/post-thread-view'
 import {Link} from '../util/Link'
 import {RichText} from '../util/text/RichText'
 import {Text} from '../util/text/Text'
 import {PostDropdownBtn} from '../util/forms/DropdownButton'
 import * as Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
-import {s} from '../../lib/styles'
-import {ago, pluralize} from '../../../lib/strings'
-import {useStores} from '../../../state'
+import {s} from 'lib/styles'
+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 {PostCtrls} from '../util/PostCtrls'
 import {ErrorMessage} from '../util/error/ErrorMessage'
 import {ComposePrompt} from '../composer/Prompt'
-import {usePalette} from '../../lib/hooks/usePalette'
+import {usePalette} from 'lib/hooks/usePalette'
 
 const PARENT_REPLY_LINE_LENGTH = 8
 
@@ -35,29 +36,31 @@ export const PostThreadItem = observer(function PostThreadItem({
 }) {
   const pal = usePalette('default')
   const store = useStores()
-  const [deleted, setDeleted] = useState(false)
+  const [deleted, setDeleted] = React.useState(false)
   const record = item.postRecord
   const hasEngagement = item.post.upvoteCount || item.post.repostCount
 
-  const itemHref = useMemo(() => {
+  const itemUri = item.post.uri
+  const itemCid = item.post.cid
+  const itemHref = React.useMemo(() => {
     const urip = new AtUri(item.post.uri)
     return `/profile/${item.post.author.handle}/post/${urip.rkey}`
   }, [item.post.uri, item.post.author.handle])
   const itemTitle = `Post by ${item.post.author.handle}`
   const authorHref = `/profile/${item.post.author.handle}`
   const authorTitle = item.post.author.handle
-  const upvotesHref = useMemo(() => {
+  const upvotesHref = React.useMemo(() => {
     const urip = new AtUri(item.post.uri)
     return `/profile/${item.post.author.handle}/post/${urip.rkey}/upvoted-by`
   }, [item.post.uri, item.post.author.handle])
   const upvotesTitle = 'Likes on this post'
-  const repostsHref = useMemo(() => {
+  const repostsHref = React.useMemo(() => {
     const urip = new AtUri(item.post.uri)
     return `/profile/${item.post.author.handle}/post/${urip.rkey}/reposted-by`
   }, [item.post.uri, item.post.author.handle])
   const repostsTitle = 'Reposts of this post'
 
-  const onPressReply = () => {
+  const onPressReply = React.useCallback(() => {
     store.shell.openComposer({
       replyTo: {
         uri: item.post.uri,
@@ -71,22 +74,22 @@ export const PostThreadItem = observer(function PostThreadItem({
       },
       onPost: onPostReply,
     })
-  }
-  const onPressToggleRepost = () => {
-    item
+  }, [store, item, record, onPostReply])
+  const onPressToggleRepost = React.useCallback(() => {
+    return item
       .toggleRepost()
       .catch(e => store.log.error('Failed to toggle repost', e))
-  }
-  const onPressToggleUpvote = () => {
-    item
+  }, [item, store])
+  const onPressToggleUpvote = React.useCallback(() => {
+    return item
       .toggleUpvote()
       .catch(e => store.log.error('Failed to toggle upvote', e))
-  }
-  const onCopyPostText = () => {
+  }, [item, store])
+  const onCopyPostText = React.useCallback(() => {
     Clipboard.setString(record?.text || '')
     Toast.show('Copied to clipboard')
-  }
-  const onDeletePost = () => {
+  }, [record])
+  const onDeletePost = React.useCallback(() => {
     item.delete().then(
       () => {
         setDeleted(true)
@@ -97,7 +100,7 @@ export const PostThreadItem = observer(function PostThreadItem({
         Toast.show('Failed to delete post, please try again')
       },
     )
-  }
+  }, [item, store])
 
   if (!record) {
     return <ErrorMessage message="Invalid or unsupported post record" />
@@ -154,6 +157,8 @@ export const PostThreadItem = observer(function PostThreadItem({
                 <View style={s.flex1} />
                 <PostDropdownBtn
                   style={styles.metaItem}
+                  itemUri={itemUri}
+                  itemCid={itemCid}
                   itemHref={itemHref}
                   itemTitle={itemTitle}
                   isAuthor={item.post.author.did === store.me.did}
@@ -179,7 +184,7 @@ export const PostThreadItem = observer(function PostThreadItem({
             </View>
           </View>
           <View style={[s.pl10, s.pr10, s.pb10]}>
-            {record.text ? (
+            {item.richText?.text ? (
               <View
                 style={[
                   styles.postTextContainer,
@@ -187,8 +192,7 @@ export const PostThreadItem = observer(function PostThreadItem({
                 ]}>
                 <RichText
                   type="post-text-lg"
-                  text={record.text}
-                  entities={record.entities}
+                  richText={item.richText}
                   lineHeight={1.3}
                 />
               </View>
@@ -233,6 +237,8 @@ export const PostThreadItem = observer(function PostThreadItem({
             <View style={[s.pl10, s.pb5]}>
               <PostCtrls
                 big
+                itemUri={itemUri}
+                itemCid={itemCid}
                 itemHref={itemHref}
                 itemTitle={itemTitle}
                 isAuthor={item.post.author.did === store.me.did}
@@ -301,12 +307,11 @@ export const PostThreadItem = observer(function PostThreadItem({
                   <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} />
                   <Text type="sm">This post is by a muted account.</Text>
                 </View>
-              ) : record.text ? (
+              ) : item.richText?.text ? (
                 <View style={styles.postTextContainer}>
                   <RichText
                     type="post-text"
-                    text={record.text}
-                    entities={record.entities}
+                    richText={item.richText}
                     style={pal.text}
                     lineHeight={1.3}
                   />
@@ -314,6 +319,8 @@ export const PostThreadItem = observer(function PostThreadItem({
               ) : undefined}
               <PostEmbeds embed={item.post.embed} style={s.mb10} />
               <PostCtrls
+                itemUri={itemUri}
+                itemCid={itemCid}
                 itemHref={itemHref}
                 itemTitle={itemTitle}
                 isAuthor={item.post.author.did === store.me.did}
@@ -341,7 +348,12 @@ export const PostThreadItem = observer(function PostThreadItem({
             href={itemHref}
             title={itemTitle}
             noFeedback>
-            <Text style={pal.link}>Load more</Text>
+            <Text style={pal.link}>Continue thread...</Text>
+            <FontAwesomeIcon
+              icon="angle-right"
+              style={pal.link as FontAwesomeIconStyle}
+              size={18}
+            />
           </Link>
         ) : undefined}
       </>
@@ -433,8 +445,12 @@ const styles = StyleSheet.create({
     marginRight: 10,
   },
   loadMore: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
     borderTopWidth: 1,
-    paddingLeft: 28,
+    paddingLeft: 80,
+    paddingRight: 20,
     paddingVertical: 10,
+    marginBottom: 8,
   },
 })
diff --git a/src/view/com/post-thread/PostVotedBy.tsx b/src/view/com/post-thread/PostVotedBy.tsx
index 680bbadf4..2734aaea9 100644
--- a/src/view/com/post-thread/PostVotedBy.tsx
+++ b/src/view/com/post-thread/PostVotedBy.tsx
@@ -2,14 +2,10 @@ import React, {useEffect} from 'react'
 import {observer} from 'mobx-react-lite'
 import {ActivityIndicator, StyleSheet, View} from 'react-native'
 import {CenteredView, FlatList} from '../util/Views'
-import {VotesViewModel, VoteItem} from '../../../state/models/votes-view'
-import {Link} from '../util/Link'
-import {Text} from '../util/text/Text'
+import {VotesViewModel, VoteItem} from 'state/models/votes-view'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {UserAvatar} from '../util/UserAvatar'
-import {useStores} from '../../../state'
-import {s} from '../../lib/styles'
-import {usePalette} from '../../lib/hooks/usePalette'
+import {ProfileCardWithFollowBtn} from '../profile/ProfileCard'
+import {useStores} from 'state/index'
 
 export const PostVotedBy = observer(function PostVotedBy({
   uri,
@@ -57,7 +53,17 @@ export const PostVotedBy = observer(function PostVotedBy({
 
   // loaded
   // =
-  const renderItem = ({item}: {item: VoteItem}) => <LikedByItem item={item} />
+  const renderItem = ({item}: {item: VoteItem}) => (
+    <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}
+      isFollowedBy={!!item.actor.viewer?.followedBy}
+    />
+  )
   return (
     <FlatList
       data={view.votes}
@@ -77,62 +83,7 @@ export const PostVotedBy = observer(function PostVotedBy({
   )
 })
 
-const LikedByItem = ({item}: {item: VoteItem}) => {
-  const pal = usePalette('default')
-
-  return (
-    <Link
-      style={[styles.outer, pal.view]}
-      href={`/profile/${item.actor.handle}`}
-      title={item.actor.handle}
-      noFeedback>
-      <View style={styles.layout}>
-        <View style={styles.layoutAvi}>
-          <UserAvatar
-            size={40}
-            displayName={item.actor.displayName}
-            handle={item.actor.handle}
-            avatar={item.actor.avatar}
-          />
-        </View>
-        <View style={styles.layoutContent}>
-          <Text style={[s.f15, s.bold, pal.text]}>
-            {item.actor.displayName || item.actor.handle}
-          </Text>
-          <Text style={[s.f14, s.gray5, pal.textLight]}>
-            @{item.actor.handle}
-          </Text>
-        </View>
-      </View>
-    </Link>
-  )
-}
-
 const styles = StyleSheet.create({
-  outer: {
-    marginTop: 1,
-  },
-  layout: {
-    flexDirection: 'row',
-  },
-  layoutAvi: {
-    width: 60,
-    paddingLeft: 10,
-    paddingTop: 10,
-    paddingBottom: 10,
-  },
-  avi: {
-    width: 40,
-    height: 40,
-    borderRadius: 20,
-    resizeMode: 'cover',
-  },
-  layoutContent: {
-    flex: 1,
-    paddingRight: 10,
-    paddingTop: 10,
-    paddingBottom: 10,
-  },
   footer: {
     height: 200,
     paddingTop: 20,