about summary refs log tree commit diff
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2022-07-20 19:30:07 -0500
committerPaul Frazee <pfrazee@gmail.com>2022-07-20 19:30:07 -0500
commit39483d92db90b69c8e1b47d82829d79651a69d25 (patch)
tree1236a1ac65b1a0b27d95c5e6466ac06d194e4c0f
parentc712cbbfe27cca5db5d87abd8d7fd3b749492fcc (diff)
downloadvoidsky-39483d92db90b69c8e1b47d82829d79651a69d25.tar.zst
Factor out common styles; fixes and improvements to post-thread-view
-rw-r--r--src/state/models/post-thread-view.ts26
-rw-r--r--src/view/com/feed/FeedItem.tsx60
-rw-r--r--src/view/com/post-thread/PostThread.tsx1
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx125
-rw-r--r--src/view/lib/strings.ts9
-rw-r--r--src/view/lib/styles.ts37
6 files changed, 159 insertions, 99 deletions
diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts
index e2e0f7462..27a10cb8e 100644
--- a/src/state/models/post-thread-view.ts
+++ b/src/state/models/post-thread-view.ts
@@ -3,8 +3,20 @@ import {bsky, AdxUri} from '@adxp/mock-api'
 import _omit from 'lodash.omit'
 import {RootStoreModel} from './root-store'
 
+function* reactKeyGenerator(): Generator<string> {
+  let counter = 0
+  while (true) {
+    yield `item-${counter++}`
+  }
+}
+
 export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
+  // ui state
   _reactKey: string = ''
+  _depth = 0
+  _isHighlightedPost = false
+
+  // data
   uri: string = ''
   author: bsky.PostThreadView.User = {did: '', name: '', displayName: ''}
   record: Record<string, unknown> = {}
@@ -26,15 +38,15 @@ export class PostThreadViewPostModel implements bsky.PostThreadView.Post {
     }
   }
 
-  setReplies(v: bsky.PostThreadView.Post) {
+  setReplies(keyGen: Generator<string>, v: bsky.PostThreadView.Post) {
     if (v.replies) {
       const replies = []
-      let counter = 0
       for (const item of v.replies) {
         // TODO: validate .record
-        const itemModel = new PostThreadViewPostModel(`item-${counter++}`, item)
+        const itemModel = new PostThreadViewPostModel(keyGen.next().value, item)
+        itemModel._depth = this._depth + 1
         if (item.replies) {
-          itemModel.setReplies(item)
+          itemModel.setReplies(keyGen, item)
         }
         replies.push(itemModel)
       }
@@ -146,8 +158,10 @@ export class PostThreadViewModel implements bsky.PostThreadView.Response {
 
   private _replaceAll(res: bsky.PostThreadView.Response) {
     // TODO: validate .record
-    const thread = new PostThreadViewPostModel('item-0', res.thread)
-    thread.setReplies(res.thread)
+    const keyGen = reactKeyGenerator()
+    const thread = new PostThreadViewPostModel(keyGen.next().value, res.thread)
+    thread._isHighlightedPost = true
+    thread.setReplies(keyGen, res.thread)
     this.thread = thread
   }
 }
diff --git a/src/view/com/feed/FeedItem.tsx b/src/view/com/feed/FeedItem.tsx
index 7a57326f6..18af53dde 100644
--- a/src/view/com/feed/FeedItem.tsx
+++ b/src/view/com/feed/FeedItem.tsx
@@ -13,6 +13,7 @@ import moment from 'moment'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {OnNavigateContent} from '../../routes/types'
 import {FeedViewItemModel} from '../../../state/models/feed-view'
+import {s} from '../../lib/styles'
 
 const IMAGES: Record<string, ImageSourcePropType> = {
   'alice.com': require('../../assets/alice.jpg'),
@@ -39,8 +40,11 @@ export const FeedItem = observer(function FeedItem({
     <TouchableOpacity style={styles.outer} onPress={onPressOuter}>
       {item.repostedBy && (
         <View style={styles.repostedBy}>
-          <FontAwesomeIcon icon="retweet" style={styles.repostedByIcon} />
-          <Text style={styles.repostedByText}>
+          <FontAwesomeIcon
+            icon="retweet"
+            style={[styles.repostedByIcon, s.gray]}
+          />
+          <Text style={[s.gray, s.bold, s.f13]}>
             Reposted by {item.repostedBy.displayName}
           </Text>
         </View>
@@ -54,28 +58,30 @@ export const FeedItem = observer(function FeedItem({
         </View>
         <View style={styles.layoutContent}>
           <View style={styles.meta}>
-            <Text style={[styles.metaItem, styles.metaDisplayName]}>
+            <Text style={[styles.metaItem, s.f15, s.bold]}>
               {item.author.displayName}
             </Text>
-            <Text style={[styles.metaItem, styles.metaName]}>
+            <Text style={[styles.metaItem, s.f14, s.gray]}>
               @{item.author.name}
             </Text>
-            <Text style={[styles.metaItem, styles.metaDate]}>
+            <Text style={[styles.metaItem, s.f14, s.gray]}>
               &middot; {moment(item.indexedAt).fromNow(true)}
             </Text>
           </View>
-          <Text style={styles.postText}>{record.text}</Text>
+          <Text style={[styles.postText, s.f15, s['lh15-1.3']]}>
+            {record.text}
+          </Text>
           <View style={styles.ctrls}>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlReplyIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon={['far', 'comment']}
               />
               <Text>{item.replyCount}</Text>
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlRepostIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon="retweet"
                 size={22}
               />
@@ -83,14 +89,14 @@ export const FeedItem = observer(function FeedItem({
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlLikeIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon={['far', 'heart']}
               />
               <Text>{item.likeCount}</Text>
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlShareIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon="share-from-square"
               />
             </View>
@@ -114,12 +120,6 @@ const styles = StyleSheet.create({
   },
   repostedByIcon: {
     marginRight: 2,
-    color: 'gray',
-  },
-  repostedByText: {
-    color: 'gray',
-    fontWeight: 'bold',
-    fontSize: 13,
   },
   layout: {
     flexDirection: 'row',
@@ -144,20 +144,7 @@ const styles = StyleSheet.create({
   metaItem: {
     paddingRight: 5,
   },
-  metaDisplayName: {
-    fontSize: 15,
-    fontWeight: 'bold',
-  },
-  metaName: {
-    fontSize: 14,
-    color: 'gray',
-  },
-  metaDate: {
-    fontSize: 14,
-    color: 'gray',
-  },
   postText: {
-    fontSize: 15,
     paddingBottom: 5,
   },
   ctrls: {
@@ -170,20 +157,7 @@ const styles = StyleSheet.create({
     paddingLeft: 4,
     paddingRight: 4,
   },
-  ctrlReplyIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlRepostIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlLikeIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlShareIcon: {
+  ctrlIcon: {
     marginRight: 5,
-    color: 'gray',
   },
 })
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index bc6642e07..7bbad36be 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -62,7 +62,6 @@ export const PostThread = observer(function PostThread({
   }
   return (
     <View>
-      {view.isRefreshing && <ActivityIndicator />}
       {view.hasContent && (
         <FlatList
           data={posts}
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 33857f48a..985d11dfa 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -13,6 +13,8 @@ import moment from 'moment'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {OnNavigateContent} from '../../routes/types'
 import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
+import {s} from '../../lib/styles'
+import {pluralize} from '../../lib/strings'
 
 const IMAGES: Record<string, ImageSourcePropType> = {
   'alice.com': require('../../assets/alice.jpg'),
@@ -20,6 +22,14 @@ const IMAGES: Record<string, ImageSourcePropType> = {
   'carla.com': require('../../assets/carla.jpg'),
 }
 
+function iter<T>(n: number, fn: (i: number) => T): Array<T> {
+  const arr: T[] = []
+  for (let i = 0; i < n; i++) {
+    arr.push(fn(i))
+  }
+  return arr
+}
+
 export const PostThreadItem = observer(function PostThreadItem({
   item, // onNavigateContent,
 }: {
@@ -27,12 +37,16 @@ export const PostThreadItem = observer(function PostThreadItem({
   onNavigateContent: OnNavigateContent
 }) {
   const record = item.record as unknown as bsky.Post.Record
+  const hasEngagement = item.likeCount || item.repostCount
   const onPressOuter = () => {
     // TODO onNavigateContent
   }
   return (
     <TouchableOpacity style={styles.outer} onPress={onPressOuter}>
       <View style={styles.layout}>
+        {iter(item._depth, () => (
+          <View style={styles.replyBar} />
+        ))}
         <View style={styles.layoutAvi}>
           <Image
             style={styles.avi}
@@ -41,28 +55,58 @@ export const PostThreadItem = observer(function PostThreadItem({
         </View>
         <View style={styles.layoutContent}>
           <View style={styles.meta}>
-            <Text style={[styles.metaItem, styles.metaDisplayName]}>
+            <Text style={[styles.metaItem, s.f15, s.bold]}>
               {item.author.displayName}
             </Text>
-            <Text style={[styles.metaItem, styles.metaName]}>
+            <Text style={[styles.metaItem, s.f14, s.gray]}>
               @{item.author.name}
             </Text>
-            <Text style={[styles.metaItem, styles.metaDate]}>
+            <Text style={[styles.metaItem, s.f14, s.gray]}>
               &middot; {moment(item.indexedAt).fromNow(true)}
             </Text>
           </View>
-          <Text style={styles.postText}>{record.text}</Text>
+          <Text
+            style={[
+              styles.postText,
+              ...(item._isHighlightedPost
+                ? [s.f16, s['lh16-1.3']]
+                : [s.f15, s['lh15-1.3']]),
+            ]}>
+            {record.text}
+          </Text>
+          {item._isHighlightedPost && hasEngagement ? (
+            <View style={styles.expandedInfo}>
+              {item.repostCount ? (
+                <Text style={[styles.expandedInfoItem, s.gray, s.semiBold]}>
+                  <Text style={[s.bold, s.black]}>{item.repostCount}</Text>{' '}
+                  {pluralize(item.repostCount, 'repost')}
+                </Text>
+              ) : (
+                <></>
+              )}
+              {item.likeCount ? (
+                <Text style={[styles.expandedInfoItem, s.gray, s.semiBold]}>
+                  <Text style={[s.bold, s.black]}>{item.likeCount}</Text>{' '}
+                  {pluralize(item.likeCount, 'like')}
+                </Text>
+              ) : (
+                <></>
+              )}
+            </View>
+          ) : (
+            <></>
+          )}
           <View style={styles.ctrls}>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlReplyIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon={['far', 'comment']}
               />
               <Text>{item.replyCount}</Text>
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlRepostIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon="retweet"
                 size={22}
               />
@@ -70,14 +114,14 @@ export const PostThreadItem = observer(function PostThreadItem({
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlLikeIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon={['far', 'heart']}
               />
               <Text>{item.likeCount}</Text>
             </View>
             <View style={styles.ctrl}>
               <FontAwesomeIcon
-                style={styles.ctrlShareIcon}
+                style={[styles.ctrlIcon, s.gray]}
                 icon="share-from-square"
               />
             </View>
@@ -93,26 +137,20 @@ const styles = StyleSheet.create({
     borderTopWidth: 1,
     borderTopColor: '#e8e8e8',
     backgroundColor: '#fff',
-    padding: 10,
   },
-  repostedBy: {
+  layout: {
     flexDirection: 'row',
-    paddingLeft: 70,
   },
-  repostedByIcon: {
+  replyBar: {
+    width: 5,
+    backgroundColor: '#d4f0ff',
     marginRight: 2,
-    color: 'gray',
-  },
-  repostedByText: {
-    color: 'gray',
-    fontWeight: 'bold',
-    fontSize: 13,
-  },
-  layout: {
-    flexDirection: 'row',
   },
   layoutAvi: {
-    width: 70,
+    width: 80,
+    paddingLeft: 10,
+    paddingTop: 10,
+    paddingBottom: 10,
   },
   avi: {
     width: 60,
@@ -122,6 +160,9 @@ const styles = StyleSheet.create({
   },
   layoutContent: {
     flex: 1,
+    paddingRight: 10,
+    paddingTop: 10,
+    paddingBottom: 10,
   },
   meta: {
     flexDirection: 'row',
@@ -131,22 +172,21 @@ const styles = StyleSheet.create({
   metaItem: {
     paddingRight: 5,
   },
-  metaDisplayName: {
-    fontSize: 15,
-    fontWeight: 'bold',
-  },
-  metaName: {
-    fontSize: 14,
-    color: 'gray',
-  },
-  metaDate: {
-    fontSize: 14,
-    color: 'gray',
-  },
   postText: {
-    fontSize: 15,
     paddingBottom: 5,
   },
+  expandedInfo: {
+    flexDirection: 'row',
+    padding: 10,
+    borderColor: '#e8e8e8',
+    borderTopWidth: 1,
+    borderBottomWidth: 1,
+    marginTop: 5,
+    marginBottom: 10,
+  },
+  expandedInfoItem: {
+    marginRight: 10,
+  },
   ctrls: {
     flexDirection: 'row',
   },
@@ -157,20 +197,7 @@ const styles = StyleSheet.create({
     paddingLeft: 4,
     paddingRight: 4,
   },
-  ctrlReplyIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlRepostIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlLikeIcon: {
-    marginRight: 5,
-    color: 'gray',
-  },
-  ctrlShareIcon: {
+  ctrlIcon: {
     marginRight: 5,
-    color: 'gray',
   },
 })
diff --git a/src/view/lib/strings.ts b/src/view/lib/strings.ts
new file mode 100644
index 000000000..1be1112b1
--- /dev/null
+++ b/src/view/lib/strings.ts
@@ -0,0 +1,9 @@
+export function pluralize(n: number, base: string, plural?: string): string {
+  if (n === 1) {
+    return base
+  }
+  if (plural) {
+    return plural
+  }
+  return base + 's'
+}
diff --git a/src/view/lib/styles.ts b/src/view/lib/styles.ts
new file mode 100644
index 000000000..44ee2dba7
--- /dev/null
+++ b/src/view/lib/styles.ts
@@ -0,0 +1,37 @@
+import {StyleSheet} from 'react-native'
+
+export const s = StyleSheet.create({
+  // font weights
+  fw600: {fontWeight: '600'},
+  bold: {fontWeight: '600'},
+  fw500: {fontWeight: '500'},
+  semiBold: {fontWeight: '500'},
+  fw400: {fontWeight: '400'},
+  normal: {fontWeight: '400'},
+  fw300: {fontWeight: '300'},
+  light: {fontWeight: '300'},
+  fw200: {fontWeight: '200'},
+
+  // font sizes
+  f13: {fontSize: 13},
+  f14: {fontSize: 14},
+  f15: {fontSize: 15},
+  f16: {fontSize: 16},
+  f18: {fontSize: 18},
+
+  // line heights
+  ['lh13-1']: {lineHeight: 13},
+  ['lh13-1.3']: {lineHeight: 16.9}, // 1.3 of 13px
+  ['lh14-1']: {lineHeight: 14},
+  ['lh14-1.3']: {lineHeight: 18.2}, // 1.3 of 14px
+  ['lh15-1']: {lineHeight: 15},
+  ['lh15-1.3']: {lineHeight: 19.5}, // 1.3 of 15px
+  ['lh16-1']: {lineHeight: 16},
+  ['lh16-1.3']: {lineHeight: 20.8}, // 1.3 of 16px
+  ['lh18-1']: {lineHeight: 18},
+  ['lh18-1.3']: {lineHeight: 23.4}, // 1.3 of 18px
+
+  // colors
+  black: {color: 'black'},
+  gray: {color: 'gray'},
+})