about summary refs log tree commit diff
path: root/src/view/com/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/util')
-rw-r--r--src/view/com/util/PostCtrls.tsx36
-rw-r--r--src/view/com/util/PostEmbeds/QuoteEmbed.tsx58
-rw-r--r--src/view/com/util/PostEmbeds/index.tsx28
-rw-r--r--src/view/com/util/PostMeta.tsx30
4 files changed, 146 insertions, 6 deletions
diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx
index e42c5e63b..cb4dfab26 100644
--- a/src/view/com/util/PostCtrls.tsx
+++ b/src/view/com/util/PostCtrls.tsx
@@ -26,6 +26,7 @@ import {
 } from 'lib/icons'
 import {s, colors} from 'lib/styles'
 import {useTheme} from 'lib/ThemeContext'
+import {useStores} from 'state/index'
 
 interface PostCtrlsOpts {
   itemUri: string
@@ -33,6 +34,13 @@ interface PostCtrlsOpts {
   itemHref: string
   itemTitle: string
   isAuthor: boolean
+  author: {
+    handle: string
+    displayName: string
+    avatar: string
+  }
+  text: string
+  indexedAt: string
   big?: boolean
   style?: StyleProp<ViewStyle>
   replyCount?: number
@@ -86,6 +94,7 @@ function ctrlAnimStyle(interp: Animated.Value) {
 */
 
 export function PostCtrls(opts: PostCtrlsOpts) {
+  const store = useStores()
   const theme = useTheme()
   const defaultCtrlColor = React.useMemo(
     () => ({
@@ -98,7 +107,8 @@ export function PostCtrls(opts: PostCtrlsOpts) {
   // DISABLED see #135
   // const repostRef = React.useRef<TriggerableAnimatedRef | null>(null)
   // const likeRef = React.useRef<TriggerableAnimatedRef | null>(null)
-  const onPressToggleRepostWrapper = () => {
+  const onRepost = () => {
+    store.shell.closeModal()
     if (!opts.isReposted) {
       ReactNativeHapticFeedback.trigger('impactMedium')
       setRepostMod(1)
@@ -122,6 +132,30 @@ export function PostCtrls(opts: PostCtrlsOpts) {
         .then(() => setRepostMod(0))
     }
   }
+
+  const onQuote = () => {
+    store.shell.closeModal()
+    store.shell.openComposer({
+      quote: {
+        uri: opts.itemUri,
+        cid: opts.itemCid,
+        text: opts.text,
+        author: opts.author,
+        indexedAt: opts.indexedAt,
+      },
+    })
+    ReactNativeHapticFeedback.trigger('impactMedium')
+  }
+
+  const onPressToggleRepostWrapper = () => {
+    store.shell.openModal({
+      name: 'repost',
+      onRepost: onRepost,
+      onQuote: onQuote,
+      isReposted: opts.isReposted,
+    })
+  }
+
   const onPressToggleUpvoteWrapper = () => {
     if (!opts.isUpvoted) {
       ReactNativeHapticFeedback.trigger('impactMedium')
diff --git a/src/view/com/util/PostEmbeds/QuoteEmbed.tsx b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx
new file mode 100644
index 000000000..76b71a53d
--- /dev/null
+++ b/src/view/com/util/PostEmbeds/QuoteEmbed.tsx
@@ -0,0 +1,58 @@
+import {StyleSheet} from 'react-native'
+import React from 'react'
+import {AtUri} from '../../../../third-party/uri'
+import {PostMeta} from '../PostMeta'
+import {Link} from '../Link'
+import {Text} from '../text/Text'
+import {usePalette} from 'lib/hooks/usePalette'
+import {ComposerOptsQuote} from 'state/models/shell-ui'
+
+const QuoteEmbed = ({quote}: {quote: ComposerOptsQuote}) => {
+  const pal = usePalette('default')
+  const itemUrip = new AtUri(quote.uri)
+  const itemHref = `/profile/${quote.author.handle}/post/${itemUrip.rkey}`
+  const itemTitle = `Post by ${quote.author.handle}`
+  const isEmpty = React.useMemo(
+    () => quote.text.trim().length === 0,
+    [quote.text],
+  )
+  return (
+    <Link
+      style={[styles.container, pal.border]}
+      href={itemHref}
+      title={itemTitle}>
+      <PostMeta
+        authorAvatar={quote.author.avatar}
+        authorHandle={quote.author.handle}
+        authorDisplayName={quote.author.displayName}
+        timestamp={quote.indexedAt}
+      />
+      <Text type="post-text" style={pal.text} numberOfLines={6}>
+        {isEmpty ? (
+          <Text style={pal.link} lineHeight={1.5}>
+            View post
+          </Text>
+        ) : (
+          quote.text
+        )}
+      </Text>
+    </Link>
+  )
+}
+
+export default QuoteEmbed
+
+const styles = StyleSheet.create({
+  container: {
+    borderRadius: 8,
+    paddingVertical: 8,
+    paddingHorizontal: 12,
+    marginVertical: 8,
+    borderWidth: 1,
+  },
+  quotePost: {
+    flex: 1,
+    paddingLeft: 13,
+    paddingRight: 8,
+  },
+})
diff --git a/src/view/com/util/PostEmbeds/index.tsx b/src/view/com/util/PostEmbeds/index.tsx
index d2186b600..3d3356712 100644
--- a/src/view/com/util/PostEmbeds/index.tsx
+++ b/src/view/com/util/PostEmbeds/index.tsx
@@ -6,7 +6,12 @@ import {
   ViewStyle,
   Image as RNImage,
 } from 'react-native'
-import {AppBskyEmbedImages, AppBskyEmbedExternal} from '@atproto/api'
+import {
+  AppBskyEmbedImages,
+  AppBskyEmbedExternal,
+  AppBskyEmbedRecord,
+  AppBskyFeedPost,
+} from '@atproto/api'
 import {Link} from '../Link'
 import {AutoSizedImage} from '../images/AutoSizedImage'
 import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
@@ -17,8 +22,10 @@ import {saveImageModal} from 'lib/media/manip'
 import YoutubeEmbed from './YoutubeEmbed'
 import ExternalLinkEmbed from './ExternalLinkEmbed'
 import {getYoutubeVideoId} from 'lib/strings/url-helpers'
+import QuoteEmbed from './QuoteEmbed'
 
 type Embed =
+  | AppBskyEmbedRecord.Presented
   | AppBskyEmbedImages.Presented
   | AppBskyEmbedExternal.Presented
   | {$type: string; [k: string]: unknown}
@@ -32,6 +39,25 @@ export function PostEmbeds({
 }) {
   const pal = usePalette('default')
   const store = useStores()
+  if (AppBskyEmbedRecord.isPresented(embed)) {
+    if (
+      AppBskyEmbedRecord.isPresentedRecord(embed.record) &&
+      AppBskyFeedPost.isRecord(embed.record.record) &&
+      AppBskyFeedPost.validateRecord(embed.record.record).success
+    ) {
+      return (
+        <QuoteEmbed
+          quote={{
+            author: embed.record.author,
+            cid: embed.record.cid,
+            uri: embed.record.uri,
+            indexedAt: embed.record.record.createdAt, // TODO
+            text: embed.record.record.text,
+          }}
+        />
+      )
+    }
+  }
   if (AppBskyEmbedImages.isPresented(embed)) {
     if (embed.images.length > 0) {
       const uris = embed.images.map(img => img.fullsize)
diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx
index a07d91899..0c5d41cab 100644
--- a/src/view/com/util/PostMeta.tsx
+++ b/src/view/com/util/PostMeta.tsx
@@ -4,15 +4,17 @@ import {Text} from './text/Text'
 import {ago} from 'lib/strings/time'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useStores} from 'state/index'
+import {UserAvatar} from './UserAvatar'
 import {observer} from 'mobx-react-lite'
 import FollowButton from '../profile/FollowButton'
 
 interface PostMetaOpts {
+  authorAvatar: string | undefined
   authorHandle: string
   authorDisplayName: string | undefined
   timestamp: string
-  did: string
-  declarationCid: string
+  did?: string
+  declarationCid?: string
   showFollowBtn?: boolean
 }
 
@@ -27,11 +29,18 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
   //      don't change this UI immediately, but rather upon future
   //      renders
   const isFollowing = React.useMemo(
-    () => store.me.follows.isFollowing(opts.did),
+    () =>
+      typeof opts.did === 'string' && store.me.follows.isFollowing(opts.did),
     [opts.did, store.me.follows],
   )
 
-  if (opts.showFollowBtn && !isMe && !isFollowing) {
+  if (
+    opts.showFollowBtn &&
+    !isMe &&
+    !isFollowing &&
+    opts.did &&
+    opts.declarationCid
+  ) {
     // two-liner with follow button
     return (
       <View style={[styles.metaTwoLine]}>
@@ -71,6 +80,16 @@ export const PostMeta = observer(function (opts: PostMetaOpts) {
   // one-liner
   return (
     <View style={styles.meta}>
+      {typeof opts.authorAvatar !== 'undefined' && (
+        <View style={[styles.metaItem, styles.avatar]}>
+          <UserAvatar
+            avatar={opts.authorAvatar}
+            handle={opts.authorHandle}
+            displayName={opts.authorDisplayName}
+            size={16}
+          />
+        </View>
+      )}
       <View style={[styles.metaItem, styles.maxWidth]}>
         <Text
           type="lg-bold"
@@ -107,6 +126,9 @@ const styles = StyleSheet.create({
   metaItem: {
     paddingRight: 5,
   },
+  avatar: {
+    alignSelf: 'center',
+  },
   maxWidth: {
     maxWidth: '80%',
   },