about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/view/com/notifications/FeedItem.tsx249
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx5
-rw-r--r--src/view/lib/hooks/usePalette.ts4
-rw-r--r--src/view/lib/themes.ts2
4 files changed, 202 insertions, 58 deletions
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index 0c516a086..75b83d375 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -1,6 +1,12 @@
-import React, {useMemo} from 'react'
+import React from 'react'
 import {observer} from 'mobx-react-lite'
-import {StyleSheet, View} from 'react-native'
+import {
+  Animated,
+  TouchableOpacity,
+  TouchableWithoutFeedback,
+  StyleSheet,
+  View,
+} from 'react-native'
 import {AppBskyEmbedImages} from '@atproto/api'
 import {AtUri} from '../../../third-party/uri'
 import {FontAwesomeIcon, Props} from '@fortawesome/react-native-fontawesome'
@@ -16,16 +22,28 @@ import {ErrorMessage} from '../util/error/ErrorMessage'
 import {Post} from '../post/Post'
 import {Link} from '../util/Link'
 import {usePalette} from '../../lib/hooks/usePalette'
+import {useAnimatedValue} from '../../lib/hooks/useAnimatedValue'
 
 const MAX_AUTHORS = 8
 
+const EXPANDED_AUTHOR_EL_HEIGHT = 35
+const EXPANDED_AUTHORS_CLOSE_EL_HEIGHT = 26
+
+interface Author {
+  href: string
+  handle: string
+  displayName?: string
+  avatar?: string
+}
+
 export const FeedItem = observer(function FeedItem({
   item,
 }: {
   item: NotificationsViewItemModel
 }) {
   const pal = usePalette('default')
-  const itemHref = useMemo(() => {
+  const [isAuthorsExpanded, setAuthorsExpanded] = React.useState<boolean>(false)
+  const itemHref = React.useMemo(() => {
     if (item.isUpvote || item.isRepost) {
       const urip = new AtUri(item.subjectUri)
       return `/profile/${urip.host}/post/${urip.rkey}`
@@ -37,7 +55,7 @@ export const FeedItem = observer(function FeedItem({
     }
     return ''
   }, [item])
-  const itemTitle = useMemo(() => {
+  const itemTitle = React.useMemo(() => {
     if (item.isUpvote || item.isRepost) {
       return 'Post'
     } else if (item.isFollow || item.isAssertion) {
@@ -47,6 +65,10 @@ export const FeedItem = observer(function FeedItem({
     }
   }, [item])
 
+  const onToggleAuthorsExpanded = () => {
+    setAuthorsExpanded(!isAuthorsExpanded)
+  }
+
   if (item.additionalPost?.notFound) {
     // don't render anything if the target post was deleted or unfindable
     return <View />
@@ -93,12 +115,7 @@ export const FeedItem = observer(function FeedItem({
     return <></>
   }
 
-  let authors: {
-    href: string
-    handle: string
-    displayName?: string
-    avatar?: string
-  }[] = [
+  let authors: Author[] = [
     {
       href: `/profile/${item.author.handle}`,
       handle: item.author.handle,
@@ -143,50 +160,45 @@ export const FeedItem = observer(function FeedItem({
           )}
         </View>
         <View style={styles.layoutContent}>
-          <View style={styles.avis}>
-            {authors.slice(0, MAX_AUTHORS).map(author => (
-              <Link
-                style={{marginRight: 5}}
-                key={author.href}
-                href={author.href}
-                title={`@${author.handle}`}>
-                <UserAvatar
-                  size={35}
-                  displayName={author.displayName}
-                  handle={author.handle}
-                  avatar={author.avatar}
-                />
-              </Link>
-            ))}
-            {authors.length > MAX_AUTHORS ? (
-              <Text style={[styles.aviExtraCount, pal.textLight]}>
-                +{authors.length - MAX_AUTHORS}
-              </Text>
-            ) : undefined}
-          </View>
-          <View style={styles.meta}>
-            <Link
-              key={authors[0].href}
-              style={styles.metaItem}
-              href={authors[0].href}
-              title={`@${authors[0].handle}`}>
-              <Text style={[pal.text, s.bold]}>
-                {authors[0].displayName || authors[0].handle}
-              </Text>
-            </Link>
-            {authors.length > 1 ? (
-              <>
-                <Text style={[styles.metaItem, pal.text]}>and</Text>
-                <Text style={[styles.metaItem, pal.text, s.bold]}>
-                  {authors.length - 1} {pluralize(authors.length - 1, 'other')}
+          <TouchableWithoutFeedback
+            onPress={authors.length > 1 ? onToggleAuthorsExpanded : () => {}}>
+            <View>
+              <ExpandedAuthorsList
+                visible={isAuthorsExpanded}
+                authors={authors}
+                onToggleAuthorsExpanded={onToggleAuthorsExpanded}
+              />
+              {isAuthorsExpanded ? (
+                <></>
+              ) : (
+                <CondensedAuthorsList authors={authors} />
+              )}
+              <View style={styles.meta}>
+                <Link
+                  key={authors[0].href}
+                  style={styles.metaItem}
+                  href={authors[0].href}
+                  title={`@${authors[0].handle}`}>
+                  <Text style={[pal.text, s.bold]}>
+                    {authors[0].displayName || authors[0].handle}
+                  </Text>
+                </Link>
+                {authors.length > 1 ? (
+                  <>
+                    <Text style={[styles.metaItem, pal.text]}>and</Text>
+                    <Text style={[styles.metaItem, pal.text, s.bold]}>
+                      {authors.length - 1}{' '}
+                      {pluralize(authors.length - 1, 'other')}
+                    </Text>
+                  </>
+                ) : undefined}
+                <Text style={[styles.metaItem, pal.text]}>{action}</Text>
+                <Text style={[styles.metaItem, pal.textLight]}>
+                  {ago(item.indexedAt)}
                 </Text>
-              </>
-            ) : undefined}
-            <Text style={[styles.metaItem, pal.text]}>{action}</Text>
-            <Text style={[styles.metaItem, pal.textLight]}>
-              {ago(item.indexedAt)}
-            </Text>
-          </View>
+              </View>
+            </View>
+          </TouchableWithoutFeedback>
           {item.isUpvote || item.isRepost ? (
             <AdditionalPostText additionalPost={item.additionalPost} />
           ) : (
@@ -198,6 +210,116 @@ export const FeedItem = observer(function FeedItem({
   )
 })
 
+function CondensedAuthorsList({authors}: {authors: Author[]}) {
+  const pal = usePalette('default')
+  if (authors.length === 1) {
+    return (
+      <View style={styles.avis}>
+        <Link
+          style={s.mr5}
+          href={authors[0].href}
+          title={`@${authors[0].handle}`}>
+          <UserAvatar
+            size={35}
+            displayName={authors[0].displayName}
+            handle={authors[0].handle}
+            avatar={authors[0].avatar}
+          />
+        </Link>
+      </View>
+    )
+  }
+  return (
+    <View style={styles.avis}>
+      {authors.slice(0, MAX_AUTHORS).map(author => (
+        <View key={author.href} style={s.mr5}>
+          <UserAvatar
+            size={35}
+            displayName={author.displayName}
+            handle={author.handle}
+            avatar={author.avatar}
+          />
+        </View>
+      ))}
+      {authors.length > MAX_AUTHORS ? (
+        <Text style={[styles.aviExtraCount, pal.textLight]}>
+          +{authors.length - MAX_AUTHORS}
+        </Text>
+      ) : undefined}
+      <FontAwesomeIcon
+        icon="angle-down"
+        size={18}
+        style={[styles.expandedAuthorsCloseBtnIcon, pal.icon]}
+      />
+    </View>
+  )
+}
+
+function ExpandedAuthorsList({
+  visible,
+  authors,
+  onToggleAuthorsExpanded,
+}: {
+  visible: boolean
+  authors: Author[]
+  onToggleAuthorsExpanded: () => void
+}) {
+  const pal = usePalette('default')
+  const heightInterp = useAnimatedValue(visible ? 1 : 0)
+  const targetHeight =
+    authors.length * (EXPANDED_AUTHOR_EL_HEIGHT + 10) /*10=margin*/ +
+    EXPANDED_AUTHORS_CLOSE_EL_HEIGHT
+  const heightStyle = {
+    height: Animated.multiply(heightInterp, targetHeight),
+    overflow: 'hidden',
+  }
+  React.useEffect(() => {
+    Animated.timing(heightInterp, {
+      toValue: visible ? 1 : 0,
+      duration: 200,
+      useNativeDriver: false,
+    }).start()
+  }, [heightInterp, visible])
+  return (
+    <Animated.View style={(s.mb5, heightStyle)}>
+      <TouchableOpacity
+        style={styles.expandedAuthorsCloseBtn}
+        onPress={onToggleAuthorsExpanded}>
+        <FontAwesomeIcon
+          icon="angle-up"
+          size={18}
+          style={[styles.expandedAuthorsCloseBtnIcon, pal.text]}
+        />
+        <Text type="sm-medium" style={pal.text}>
+          Hide
+        </Text>
+      </TouchableOpacity>
+      {authors.map(author => (
+        <Link
+          href={author.href}
+          title={author.displayName || author.handle}
+          style={styles.expandedAuthor}>
+          <View style={styles.expandedAuthorAvi}>
+            <UserAvatar
+              size={35}
+              displayName={author.displayName}
+              handle={author.handle}
+              avatar={author.avatar}
+            />
+          </View>
+          <View style={s.flex1}>
+            <Text type="lg-bold" numberOfLines={1}>
+              {author.displayName || author.handle}
+              &nbsp;
+              <Text style={[pal.textLight]}>{author.handle}</Text>
+            </Text>
+          </View>
+        </Link>
+      ))}
+    </Animated.View>
+  )
+}
+
 function AdditionalPostText({
   additionalPost,
 }: {
@@ -282,4 +404,25 @@ const styles = StyleSheet.create({
     paddingTop: 4,
     paddingLeft: 36,
   },
+
+  expandedAuthorsCloseBtn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingTop: 8,
+    height: EXPANDED_AUTHORS_CLOSE_EL_HEIGHT,
+    overflow: 'hidden',
+  },
+  expandedAuthorsCloseBtnIcon: {
+    marginLeft: 4,
+    marginRight: 4,
+  },
+  expandedAuthor: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    marginTop: 10,
+    height: EXPANDED_AUTHOR_EL_HEIGHT,
+  },
+  expandedAuthorAvi: {
+    marginRight: 5,
+  },
 })
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 8df6260c2..2c7ab716c 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -103,10 +103,7 @@ export const PostThreadItem = observer(function PostThreadItem({
   if (deleted) {
     return (
       <View style={[styles.outer, pal.border, pal.view, s.p20, s.flexRow]}>
-        <FontAwesomeIcon
-          icon={['far', 'trash-can']}
-          style={{color: pal.colors.icon}}
-        />
+        <FontAwesomeIcon icon={['far', 'trash-can']} style={pal.icon} />
         <Text style={[pal.textLight, s.ml10]}>This post has been deleted.</Text>
       </View>
     )
diff --git a/src/view/lib/hooks/usePalette.ts b/src/view/lib/hooks/usePalette.ts
index 9eb3e41a7..890439f34 100644
--- a/src/view/lib/hooks/usePalette.ts
+++ b/src/view/lib/hooks/usePalette.ts
@@ -10,6 +10,7 @@ export interface UsePaletteValue {
   textLight: TextStyle
   textInverted: TextStyle
   link: TextStyle
+  icon: TextStyle
 }
 export function usePalette(color: PaletteColorName): UsePaletteValue {
   const palette = useTheme().palette[color]
@@ -36,5 +37,8 @@ export function usePalette(color: PaletteColorName): UsePaletteValue {
     link: {
       color: palette.link,
     },
+    icon: {
+      color: palette.icon,
+    },
   }
 }
diff --git a/src/view/lib/themes.ts b/src/view/lib/themes.ts
index 429a76021..b9e2bdacf 100644
--- a/src/view/lib/themes.ts
+++ b/src/view/lib/themes.ts
@@ -13,7 +13,7 @@ export const defaultTheme: Theme = {
       textInverted: colors.white,
       link: colors.blue3,
       border: '#f0e9e9',
-      icon: colors.gray2,
+      icon: colors.gray3,
 
       // non-standard
       textVeryLight: colors.gray4,