about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/lib/styles.ts4
-rw-r--r--src/lib/themes.ts8
-rw-r--r--src/state/models/me.ts6
-rw-r--r--src/state/models/post-thread-view.ts35
-rw-r--r--src/view/com/composer/Prompt.tsx88
-rw-r--r--src/view/com/notifications/FeedItem.tsx16
-rw-r--r--src/view/com/post-thread/PostThread.tsx2
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx31
-rw-r--r--src/view/com/post/Post.tsx174
-rw-r--r--src/view/com/posts/FeedItem.tsx34
-rw-r--r--src/view/com/util/PostMuted.tsx50
-rw-r--r--src/view/com/util/Toast.tsx84
-rw-r--r--src/view/com/util/UserInfoText.tsx8
-rw-r--r--src/view/screens/Debug.tsx13
-rw-r--r--src/view/screens/PostThread.tsx42
-rw-r--r--src/view/shell/mobile/Menu.tsx21
-rw-r--r--src/view/shell/mobile/index.tsx10
17 files changed, 380 insertions, 246 deletions
diff --git a/src/lib/styles.ts b/src/lib/styles.ts
index a8c387616..dbce39178 100644
--- a/src/lib/styles.ts
+++ b/src/lib/styles.ts
@@ -15,7 +15,7 @@ export const colors = {
   gray5: '#545664',
   gray6: '#373942',
   gray7: '#26272D',
-  gray8: '#101013',
+  gray8: '#141417',
 
   blue0: '#bfe1ff',
   blue1: '#8bc7fd',
@@ -24,6 +24,7 @@ export const colors = {
   blue4: '#0062bd',
   blue5: '#034581',
   blue6: '#012561',
+  blue7: '#001040',
 
   red1: '#ffe6f2',
   red2: '#fba2ce',
@@ -64,6 +65,7 @@ export const s = StyleSheet.create({
   // helpers
   footerSpacer: {height: 100},
   contentContainer: {paddingBottom: 200},
+  contentContainerExtra: {paddingBottom: 300},
   border1: {borderWidth: 1},
   borderTop1: {borderTopWidth: 1},
   borderRight1: {borderRightWidth: 1},
diff --git a/src/lib/themes.ts b/src/lib/themes.ts
index aa166e323..d7043ad2d 100644
--- a/src/lib/themes.ts
+++ b/src/lib/themes.ts
@@ -21,6 +21,7 @@ export const defaultTheme: Theme = {
       replyLine: colors.gray2,
       replyLineDot: colors.gray3,
       unreadNotifBg: '#ebf6ff',
+      unreadNotifBorder: colors.blue1,
       postCtrl: '#71768A',
       brandText: '#0066FF',
       emptyStateIcon: '#B6B6C9',
@@ -296,15 +297,16 @@ export const darkTheme: Theme = {
       textLight: colors.gray3,
       textInverted: colors.black,
       link: colors.blue3,
-      border: colors.gray6,
-      borderDark: colors.gray5,
+      border: colors.black,
+      borderDark: colors.gray6,
       icon: colors.gray4,
 
       // non-standard
       textVeryLight: colors.gray4,
       replyLine: colors.gray5,
       replyLineDot: colors.gray6,
-      unreadNotifBg: colors.blue5,
+      unreadNotifBg: colors.blue7,
+      unreadNotifBorder: colors.blue6,
       postCtrl: '#61657A',
       brandText: '#0085ff',
       emptyStateIcon: colors.gray4,
diff --git a/src/state/models/me.ts b/src/state/models/me.ts
index ea35cd028..077c65595 100644
--- a/src/state/models/me.ts
+++ b/src/state/models/me.ts
@@ -11,6 +11,8 @@ export class MeModel {
   displayName: string = ''
   description: string = ''
   avatar: string = ''
+  followsCount: number | undefined
+  followersCount: number | undefined
   mainFeed: FeedModel
   notifications: NotificationsViewModel
   follows: MyFollowsModel
@@ -90,10 +92,14 @@ export class MeModel {
           this.displayName = profile.data.displayName || ''
           this.description = profile.data.description || ''
           this.avatar = profile.data.avatar || ''
+          this.followsCount = profile.data.followsCount
+          this.followersCount = profile.data.followersCount
         } else {
           this.displayName = ''
           this.description = ''
           this.avatar = ''
+          this.followsCount = profile.data.followsCount
+          this.followersCount = undefined
         }
       })
       this.mainFeed.clear()
diff --git a/src/state/models/post-thread-view.ts b/src/state/models/post-thread-view.ts
index ad989cc53..d58ee691b 100644
--- a/src/state/models/post-thread-view.ts
+++ b/src/state/models/post-thread-view.ts
@@ -21,6 +21,8 @@ export class PostThreadViewPostModel {
   _reactKey: string = ''
   _depth = 0
   _isHighlightedPost = false
+  _showParentReplyLine = false
+  _showChildReplyLine = false
   _hasMore = false
 
   // data
@@ -30,6 +32,14 @@ export class PostThreadViewPostModel {
   replies?: (PostThreadViewPostModel | GetPostThread.NotFoundPost)[]
   richText?: RichText
 
+  get uri() {
+    return this.post.uri
+  }
+
+  get parentUri() {
+    return this.postRecord?.reply?.parent.uri
+  }
+
   constructor(
     public rootStore: RootStoreModel,
     reactKey: string,
@@ -65,6 +75,7 @@ export class PostThreadViewPostModel {
   assignTreeModels(
     keyGen: Generator<string>,
     v: GetPostThread.ThreadViewPost,
+    higlightedPostUri: string,
     includeParent = true,
     includeChildren = true,
   ) {
@@ -77,8 +88,16 @@ export class PostThreadViewPostModel {
           v.parent,
         )
         parentModel._depth = this._depth - 1
+        parentModel._showChildReplyLine = true
         if (v.parent.parent) {
-          parentModel.assignTreeModels(keyGen, v.parent, true, false)
+          parentModel._showParentReplyLine = true //parentModel.uri !== higlightedPostUri
+          parentModel.assignTreeModels(
+            keyGen,
+            v.parent,
+            higlightedPostUri,
+            true,
+            false,
+          )
         }
         this.parent = parentModel
       } else if (GetPostThread.isNotFoundPost(v.parent)) {
@@ -96,8 +115,17 @@ export class PostThreadViewPostModel {
             item,
           )
           itemModel._depth = this._depth + 1
-          if (item.replies) {
-            itemModel.assignTreeModels(keyGen, item, false, true)
+          itemModel._showParentReplyLine =
+            itemModel.parentUri !== higlightedPostUri
+          if (item.replies?.length) {
+            itemModel._showChildReplyLine = true
+            itemModel.assignTreeModels(
+              keyGen,
+              item,
+              higlightedPostUri,
+              false,
+              true,
+            )
           }
           replies.push(itemModel)
         } else if (GetPostThread.isNotFoundPost(item)) {
@@ -333,6 +361,7 @@ export class PostThreadViewModel {
     thread.assignTreeModels(
       keyGen,
       res.data.thread as GetPostThread.ThreadViewPost,
+      thread.uri,
     )
     this.thread = thread
   }
diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx
index 46a0cec62..88d5de2bf 100644
--- a/src/view/com/composer/Prompt.tsx
+++ b/src/view/com/composer/Prompt.tsx
@@ -1,91 +1,45 @@
 import React from 'react'
-import {StyleSheet, TouchableOpacity, View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {StyleSheet, TouchableOpacity} from 'react-native'
+import {UserAvatar} from '../util/UserAvatar'
 import {Text} from '../util/text/Text'
 import {usePalette} from 'lib/hooks/usePalette'
+import {useStores} from 'state/index'
 
 export function ComposePrompt({
-  text = "What's up?",
-  btn = 'Post',
-  isReply = false,
   onPressCompose,
 }: {
-  text?: string
-  btn?: string
-  isReply?: boolean
   onPressCompose: (imagesOpen?: boolean) => void
 }) {
+  const store = useStores()
   const pal = usePalette('default')
   return (
     <TouchableOpacity
-      testID="composePromptButton"
-      style={[
-        pal.view,
-        pal.border,
-        styles.container,
-        isReply ? styles.containerReply : undefined,
-      ]}
+      testID="replyPromptBtn"
+      style={[pal.view, pal.border, styles.prompt]}
       onPress={() => onPressCompose()}>
-      {!isReply && (
-        <FontAwesomeIcon
-          icon={['fas', 'pen-nib']}
-          size={18}
-          style={[pal.textLight, styles.iconLeft]}
-        />
-      )}
-      <View style={styles.textContainer}>
-        <Text type={isReply ? 'lg' : 'lg-medium'} style={pal.textLight}>
-          {text}
-        </Text>
-      </View>
-      {isReply ? (
-        <View
-          style={[styles.btn, {backgroundColor: pal.colors.backgroundLight}]}>
-          <Text type="button" style={pal.textLight}>
-            {btn}
-          </Text>
-        </View>
-      ) : (
-        <TouchableOpacity onPress={() => onPressCompose(true)}>
-          <FontAwesomeIcon
-            icon={['far', 'image']}
-            size={18}
-            style={[pal.textLight, styles.iconRight]}
-          />
-        </TouchableOpacity>
-      )}
+      <UserAvatar
+        handle={store.me.handle}
+        avatar={store.me.avatar}
+        displayName={store.me.displayName}
+        size={38}
+      />
+      <Text type="xl" style={[pal.text, styles.label]}>
+        Write your reply
+      </Text>
     </TouchableOpacity>
   )
 }
 
 const styles = StyleSheet.create({
-  iconLeft: {
-    marginLeft: 22,
-    marginRight: 2,
-  },
-  iconRight: {
-    marginRight: 20,
-  },
-  container: {
-    paddingVertical: 16,
+  prompt: {
+    paddingHorizontal: 20,
+    paddingTop: 10,
+    paddingBottom: 10,
     flexDirection: 'row',
     alignItems: 'center',
     borderTopWidth: 1,
   },
-  containerReply: {
-    paddingVertical: 14,
-    paddingHorizontal: 10,
-  },
-  avatar: {
-    width: 50,
-  },
-  textContainer: {
-    marginLeft: 10,
-    flex: 1,
-  },
-  btn: {
-    paddingVertical: 6,
-    paddingHorizontal: 14,
-    borderRadius: 30,
+  label: {
+    paddingLeft: 12,
   },
 })
diff --git a/src/view/com/notifications/FeedItem.tsx b/src/view/com/notifications/FeedItem.tsx
index 68f12b721..acd00a67d 100644
--- a/src/view/com/notifications/FeedItem.tsx
+++ b/src/view/com/notifications/FeedItem.tsx
@@ -90,10 +90,10 @@ export const FeedItem = observer(function FeedItem({
           style={
             item.isRead
               ? undefined
-              : [
-                  styles.outerUnread,
-                  {backgroundColor: pal.colors.unreadNotifBg},
-                ]
+              : {
+                  backgroundColor: pal.colors.unreadNotifBg,
+                  borderColor: pal.colors.unreadNotifBorder,
+                }
           }
         />
       </Link>
@@ -152,7 +152,10 @@ export const FeedItem = observer(function FeedItem({
         pal.border,
         item.isRead
           ? undefined
-          : [styles.outerUnread, {backgroundColor: pal.colors.unreadNotifBg}],
+          : {
+              backgroundColor: pal.colors.unreadNotifBg,
+              borderColor: pal.colors.unreadNotifBorder,
+            },
       ]}
       href={itemHref}
       title={itemTitle}
@@ -391,9 +394,6 @@ const styles = StyleSheet.create({
     paddingRight: 15,
     borderTopWidth: 1,
   },
-  outerUnread: {
-    borderColor: colors.blue1,
-  },
   layout: {
     flexDirection: 'row',
   },
diff --git a/src/view/com/post-thread/PostThread.tsx b/src/view/com/post-thread/PostThread.tsx
index a417012b0..646d4b276 100644
--- a/src/view/com/post-thread/PostThread.tsx
+++ b/src/view/com/post-thread/PostThread.tsx
@@ -96,7 +96,7 @@ export const PostThread = observer(function PostThread({
       onLayout={onLayout}
       onScrollToIndexFailed={onScrollToIndexFailed}
       style={s.hContentRegion}
-      contentContainerStyle={s.contentContainer}
+      contentContainerStyle={s.contentContainerExtra}
     />
   )
 })
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 8eda0962a..1413148a9 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -21,8 +21,8 @@ import {useStores} from 'state/index'
 import {PostMeta} from '../util/PostMeta'
 import {PostEmbeds} from '../util/PostEmbeds'
 import {PostCtrls} from '../util/PostCtrls'
+import {PostMutedWrapper} from '../util/PostMuted'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {ComposePrompt} from '../composer/Prompt'
 import {usePalette} from 'lib/hooks/usePalette'
 
 const PARENT_REPLY_LINE_LENGTH = 8
@@ -271,23 +271,17 @@ export const PostThreadItem = observer(function PostThreadItem({
             </View>
           </View>
         </View>
-        <ComposePrompt
-          isReply
-          text="Write your reply"
-          btn="Reply"
-          onPressCompose={onPressReply}
-        />
       </>
     )
   } else {
     return (
-      <>
+      <PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
         <Link
           style={[styles.outer, {borderTopColor: pal.colors.border}, pal.view]}
           href={itemHref}
           title={itemTitle}
           noFeedback>
-          {record.reply && (
+          {item._showParentReplyLine && (
             <View
               style={[
                 styles.parentReplyLine,
@@ -295,7 +289,7 @@ export const PostThreadItem = observer(function PostThreadItem({
               ]}
             />
           )}
-          {item.replies?.length !== 0 && (
+          {item._showChildReplyLine && (
             <View
               style={[
                 styles.childReplyLine,
@@ -322,12 +316,7 @@ export const PostThreadItem = observer(function PostThreadItem({
                 did={item.post.author.did}
                 declarationCid={item.post.author.declaration.cid}
               />
-              {item.post.author.viewer?.muted ? (
-                <View style={[styles.mutedWarning, pal.btn]}>
-                  <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} />
-                  <Text type="sm">This post is by a muted account.</Text>
-                </View>
-              ) : item.richText?.text ? (
+              {item.richText?.text ? (
                 <View style={styles.postTextContainer}>
                   <RichText
                     type="post-text"
@@ -384,7 +373,7 @@ export const PostThreadItem = observer(function PostThreadItem({
             />
           </Link>
         ) : undefined}
-      </>
+      </PostMutedWrapper>
     )
   }
 })
@@ -441,14 +430,6 @@ const styles = StyleSheet.create({
     paddingRight: 5,
     maxWidth: 240,
   },
-  mutedWarning: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    padding: 10,
-    marginTop: 2,
-    marginBottom: 6,
-    borderRadius: 2,
-  },
   postTextContainer: {
     flexDirection: 'row',
     alignItems: 'center',
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index bf8dfed05..7b4161afc 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -17,6 +17,7 @@ import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
 import {PostEmbeds} from '../util/PostEmbeds'
 import {PostCtrls} from '../util/PostCtrls'
+import {PostMutedWrapper} from '../util/PostMuted'
 import {Text} from '../util/text/Text'
 import {RichText} from '../util/text/RichText'
 import * as Toast from '../util/Toast'
@@ -140,92 +141,89 @@ export const Post = observer(function Post({
   }
 
   return (
-    <Link
-      style={[styles.outer, pal.view, pal.border, style]}
-      href={itemHref}
-      title={itemTitle}
-      noFeedback>
-      {showReplyLine && <View style={styles.replyLine} />}
-      <View style={styles.layout}>
-        <View style={styles.layoutAvi}>
-          <Link href={authorHref} title={authorTitle}>
-            <UserAvatar
-              size={52}
-              displayName={item.post.author.displayName}
-              handle={item.post.author.handle}
-              avatar={item.post.author.avatar}
-            />
-          </Link>
-        </View>
-        <View style={styles.layoutContent}>
-          <PostMeta
-            authorHandle={item.post.author.handle}
-            authorDisplayName={item.post.author.displayName}
-            timestamp={item.post.indexedAt}
-            did={item.post.author.did}
-            declarationCid={item.post.author.declaration.cid}
-          />
-          {replyAuthorDid !== '' && (
-            <View style={[s.flexRow, s.mb2, s.alignCenter]}>
-              <FontAwesomeIcon
-                icon="reply"
-                size={9}
-                style={[pal.textLight, s.mr5]}
-              />
-              <Text type="sm" style={[pal.textLight, s.mr2]} lineHeight={1.2}>
-                Reply to
-              </Text>
-              <UserInfoText
-                type="sm"
-                did={replyAuthorDid}
-                attr="displayName"
-                style={[pal.textLight]}
+    <PostMutedWrapper isMuted={item.post.author.viewer?.muted === true}>
+      <Link
+        style={[styles.outer, pal.view, pal.border, style]}
+        href={itemHref}
+        title={itemTitle}
+        noFeedback>
+        {showReplyLine && <View style={styles.replyLine} />}
+        <View style={styles.layout}>
+          <View style={styles.layoutAvi}>
+            <Link href={authorHref} title={authorTitle}>
+              <UserAvatar
+                size={52}
+                displayName={item.post.author.displayName}
+                handle={item.post.author.handle}
+                avatar={item.post.author.avatar}
               />
-            </View>
-          )}
-          {item.post.author.viewer?.muted ? (
-            <View style={[styles.mutedWarning, pal.btn]}>
-              <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} />
-              <Text type="sm">This post is by a muted account.</Text>
-            </View>
-          ) : item.richText?.text ? (
-            <View style={styles.postTextContainer}>
-              <RichText
-                type="post-text"
-                richText={item.richText}
-                lineHeight={1.3}
-              />
-            </View>
-          ) : undefined}
-          <PostEmbeds embed={item.post.embed} style={s.mb10} />
-          <PostCtrls
-            itemUri={itemUri}
-            itemCid={itemCid}
-            itemHref={itemHref}
-            itemTitle={itemTitle}
-            author={{
-              avatar: item.post.author.avatar!,
-              handle: item.post.author.handle,
-              displayName: item.post.author.displayName!,
-            }}
-            indexedAt={item.post.indexedAt}
-            text={item.richText?.text || record.text}
-            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}
-            onPressReply={onPressReply}
-            onPressToggleRepost={onPressToggleRepost}
-            onPressToggleUpvote={onPressToggleUpvote}
-            onCopyPostText={onCopyPostText}
-            onOpenTranslate={onOpenTranslate}
-            onDeletePost={onDeletePost}
-          />
+            </Link>
+          </View>
+          <View style={styles.layoutContent}>
+            <PostMeta
+              authorHandle={item.post.author.handle}
+              authorDisplayName={item.post.author.displayName}
+              timestamp={item.post.indexedAt}
+              did={item.post.author.did}
+              declarationCid={item.post.author.declaration.cid}
+            />
+            {replyAuthorDid !== '' && (
+              <View style={[s.flexRow, s.mb2, s.alignCenter]}>
+                <FontAwesomeIcon
+                  icon="reply"
+                  size={9}
+                  style={[pal.textLight, s.mr5]}
+                />
+                <Text type="sm" style={[pal.textLight, s.mr2]} lineHeight={1.2}>
+                  Reply to
+                </Text>
+                <UserInfoText
+                  type="sm"
+                  did={replyAuthorDid}
+                  attr="displayName"
+                  style={[pal.textLight]}
+                />
+              </View>
+            )}
+            {item.richText?.text ? (
+              <View style={styles.postTextContainer}>
+                <RichText
+                  type="post-text"
+                  richText={item.richText}
+                  lineHeight={1.3}
+                />
+              </View>
+            ) : undefined}
+            <PostEmbeds embed={item.post.embed} style={s.mb10} />
+            <PostCtrls
+              itemUri={itemUri}
+              itemCid={itemCid}
+              itemHref={itemHref}
+              itemTitle={itemTitle}
+              author={{
+                avatar: item.post.author.avatar!,
+                handle: item.post.author.handle,
+                displayName: item.post.author.displayName!,
+              }}
+              indexedAt={item.post.indexedAt}
+              text={item.richText?.text || record.text}
+              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}
+              onPressReply={onPressReply}
+              onPressToggleRepost={onPressToggleRepost}
+              onPressToggleUpvote={onPressToggleUpvote}
+              onCopyPostText={onCopyPostText}
+              onOpenTranslate={onOpenTranslate}
+              onDeletePost={onDeletePost}
+            />
+          </View>
         </View>
-      </View>
-    </Link>
+      </Link>
+    </PostMutedWrapper>
   )
 })
 
@@ -245,14 +243,6 @@ const styles = StyleSheet.create({
   layoutContent: {
     flex: 1,
   },
-  mutedWarning: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    padding: 10,
-    marginTop: 2,
-    marginBottom: 6,
-    borderRadius: 2,
-  },
   postTextContainer: {
     flexDirection: 'row',
     alignItems: 'center',
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 1006645a9..8b9a6eb2c 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -15,6 +15,7 @@ import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
 import {PostCtrls} from '../util/PostCtrls'
 import {PostEmbeds} from '../util/PostEmbeds'
+import {PostMutedWrapper} from '../util/PostMuted'
 import {RichText} from '../util/text/RichText'
 import * as Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
@@ -113,6 +114,8 @@ export const FeedItem = observer(function ({
     item._isThreadChild || (!item.reason && !item._hideParent && item.reply)
   const isSmallTop = isChild && item._isThreadChild
   const isNoTop = isChild && !item._isThreadChild
+  const isMuted =
+    item.post.author.viewer?.muted && ignoreMuteFor !== item.post.author.did
   const outerStyles = [
     styles.outer,
     pal.view,
@@ -123,7 +126,7 @@ export const FeedItem = observer(function ({
   ]
 
   return (
-    <>
+    <PostMutedWrapper isMuted={isMuted}>
       {isChild && !item._isThreadChild && item.replyParent ? (
         <FeedItem
           item={item.replyParent}
@@ -160,7 +163,11 @@ export const FeedItem = observer(function ({
                 {color: pal.colors.textLight} as FontAwesomeIconStyle,
               ]}
             />
-            <Text type="sm-bold" style={pal.textLight} lineHeight={1.2}>
+            <Text
+              type="sm-bold"
+              style={pal.textLight}
+              lineHeight={1.2}
+              numberOfLines={1}>
               Reposted by{' '}
               {item.reasonRepost.by.displayName || item.reasonRepost.by.handle}
             </Text>
@@ -207,13 +214,7 @@ export const FeedItem = observer(function ({
                 />
               </View>
             )}
-            {item.post.author.viewer?.muted &&
-            ignoreMuteFor !== item.post.author.did ? (
-              <View style={[styles.mutedWarning, pal.btn]}>
-                <FontAwesomeIcon icon={['far', 'eye-slash']} style={s.mr2} />
-                <Text type="sm">This post is by a muted account.</Text>
-              </View>
-            ) : item.richText?.text ? (
+            {item.richText?.text ? (
               <View style={styles.postTextContainer}>
                 <RichText
                   type="post-text"
@@ -222,9 +223,7 @@ export const FeedItem = observer(function ({
                 />
               </View>
             ) : undefined}
-            {item.post.embed ? (
-              <PostEmbeds embed={item.post.embed} style={styles.embed} />
-            ) : null}
+            <PostEmbeds embed={item.post.embed} style={styles.embed} />
             <PostCtrls
               style={styles.ctrls}
               itemUri={itemUri}
@@ -280,7 +279,7 @@ export const FeedItem = observer(function ({
           </Text>
         </Link>
       ) : undefined}
-    </>
+    </PostMutedWrapper>
   )
 })
 
@@ -319,6 +318,7 @@ const styles = StyleSheet.create({
   includeReason: {
     flexDirection: 'row',
     paddingLeft: 50,
+    paddingRight: 20,
     marginTop: 2,
     marginBottom: 2,
   },
@@ -336,14 +336,6 @@ const styles = StyleSheet.create({
   layoutContent: {
     flex: 1,
   },
-  mutedWarning: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    padding: 10,
-    marginTop: 2,
-    marginBottom: 6,
-    borderRadius: 2,
-  },
   postTextContainer: {
     flexDirection: 'row',
     alignItems: 'center',
diff --git a/src/view/com/util/PostMuted.tsx b/src/view/com/util/PostMuted.tsx
new file mode 100644
index 000000000..d8573bd56
--- /dev/null
+++ b/src/view/com/util/PostMuted.tsx
@@ -0,0 +1,50 @@
+import React from 'react'
+import {StyleSheet, TouchableOpacity, View} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {usePalette} from 'lib/hooks/usePalette'
+import {Text} from './text/Text'
+
+export function PostMutedWrapper({
+  isMuted,
+  children,
+}: React.PropsWithChildren<{isMuted: boolean}>) {
+  const pal = usePalette('default')
+  const [override, setOverride] = React.useState(false)
+  if (!isMuted || override) {
+    return <>{children}</>
+  }
+  return (
+    <View style={[styles.container, pal.view, pal.border]}>
+      <FontAwesomeIcon
+        icon={['far', 'eye-slash']}
+        style={[styles.icon, pal.text]}
+      />
+      <Text type="md" style={pal.textLight}>
+        Post from an account you muted.
+      </Text>
+      <TouchableOpacity
+        style={styles.showBtn}
+        onPress={() => setOverride(true)}>
+        <Text type="md" style={pal.link}>
+          Show post
+        </Text>
+      </TouchableOpacity>
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  container: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingVertical: 14,
+    paddingHorizontal: 18,
+    borderTopWidth: 1,
+  },
+  icon: {
+    marginRight: 10,
+  },
+  showBtn: {
+    marginLeft: 'auto',
+  },
+})
diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx
index 197f47422..34a461f82 100644
--- a/src/view/com/util/Toast.tsx
+++ b/src/view/com/util/Toast.tsx
@@ -1,11 +1,81 @@
-import Toast from 'react-native-root-toast'
+import RootSiblings from 'react-native-root-siblings'
+import React from 'react'
+import {Animated, StyleSheet, View} from 'react-native'
+import {Text} from './text/Text'
+import {colors} from 'lib/styles'
+import {useTheme} from 'lib/ThemeContext'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useAnimatedValue} from 'lib/hooks/useAnimatedValue'
+
+const TIMEOUT = 4e3
 
 export function show(message: string) {
-  Toast.show(message, {
-    duration: Toast.durations.LONG,
-    position: 50,
-    shadow: true,
-    animation: true,
-    hideOnPress: true,
+  const item = new RootSiblings(<Toast message={message} />)
+  setTimeout(() => {
+    item.destroy()
+  }, TIMEOUT)
+}
+
+function Toast({message}: {message: string}) {
+  const theme = useTheme()
+  const pal = usePalette('default')
+  const interp = useAnimatedValue(0)
+
+  React.useEffect(() => {
+    Animated.sequence([
+      Animated.timing(interp, {
+        toValue: 1,
+        duration: 150,
+        useNativeDriver: true,
+      }),
+      Animated.delay(3700),
+      Animated.timing(interp, {
+        toValue: 0,
+        duration: 150,
+        useNativeDriver: true,
+      }),
+    ]).start()
   })
+
+  const opacityStyle = {opacity: interp}
+  return (
+    <View style={styles.container}>
+      <Animated.View
+        style={[
+          pal.view,
+          pal.border,
+          styles.toast,
+          theme.colorScheme === 'dark' && styles.toastDark,
+          opacityStyle,
+        ]}>
+        <Text type="lg-medium" style={pal.text}>
+          {message}
+        </Text>
+      </Animated.View>
+    </View>
+  )
 }
+
+const styles = StyleSheet.create({
+  container: {
+    position: 'absolute',
+    top: 60,
+    left: 0,
+    right: 0,
+    alignItems: 'center',
+  },
+  toast: {
+    paddingHorizontal: 18,
+    paddingVertical: 10,
+    borderRadius: 24,
+    borderWidth: 1,
+    shadowColor: '#000',
+    shadowOpacity: 0.1,
+    shadowOffset: {width: 0, height: 4},
+    marginHorizontal: 6,
+  },
+  toastDark: {
+    backgroundColor: colors.gray6,
+    shadowOpacity: 0.5,
+  },
+})
diff --git a/src/view/com/util/UserInfoText.tsx b/src/view/com/util/UserInfoText.tsx
index 2655232fc..84170b3bf 100644
--- a/src/view/com/util/UserInfoText.tsx
+++ b/src/view/com/util/UserInfoText.tsx
@@ -58,15 +58,15 @@ export function UserInfoText({
   let inner
   if (didFail) {
     inner = (
-      <Text type={type} style={style}>
+      <Text type={type} style={style} numberOfLines={1}>
         {failed}
       </Text>
     )
   } else if (profile) {
     inner = (
-      <Text type={type} style={style} lineHeight={1.2}>{`${prefix || ''}${
-        profile[attr] || profile.handle
-      }`}</Text>
+      <Text type={type} style={style} lineHeight={1.2} numberOfLines={1}>{`${
+        prefix || ''
+      }${profile[attr] || profile.handle}`}</Text>
     )
   } else {
     inner = (
diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx
index f2349195e..eb5ffe20f 100644
--- a/src/view/screens/Debug.tsx
+++ b/src/view/screens/Debug.tsx
@@ -5,6 +5,7 @@ import {ThemeProvider, PaletteColorName} from 'lib/ThemeContext'
 import {usePalette} from 'lib/hooks/usePalette'
 import {s} from 'lib/styles'
 import {displayNotification} from 'lib/notifee'
+import * as Toast from 'view/com/util/Toast'
 
 import {Text} from '../com/util/text/Text'
 import {ViewSelector} from '../com/util/ViewSelector'
@@ -171,16 +172,24 @@ function ErrorView() {
 }
 
 function NotifsView() {
-  const trigger = () => {
+  const triggerPush = () => {
     displayNotification(
       'Paul Frazee liked your post',
       "Hello world! This is a test of the notifications card. The text is long to see how that's handled.",
     )
   }
+  const triggerToast = () => {
+    Toast.show('The task has been completed')
+  }
+  const triggerToast2 = () => {
+    Toast.show('The task has been completed successfully and with no problems')
+  }
   return (
     <View style={s.p10}>
       <View style={s.flexRow}>
-        <Button onPress={trigger} label="Trigger" />
+        <Button onPress={triggerPush} label="Trigger Push" />
+        <Button onPress={triggerToast} label="Trigger Toast" />
+        <Button onPress={triggerToast2} label="Trigger Toast 2" />
       </View>
     </View>
   )
diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx
index e93fcb1ab..0b6829735 100644
--- a/src/view/screens/PostThread.tsx
+++ b/src/view/screens/PostThread.tsx
@@ -1,15 +1,21 @@
 import React, {useEffect, useMemo} from 'react'
-import {View} from 'react-native'
+import {StyleSheet, View} from 'react-native'
 import {makeRecordUri} from 'lib/strings/url-helpers'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {PostThread as PostThreadComponent} from '../com/post-thread/PostThread'
+import {ComposePrompt} from 'view/com/composer/Prompt'
 import {PostThreadViewModel} from 'state/models/post-thread-view'
 import {ScreenParams} from '../routes'
 import {useStores} from 'state/index'
 import {s} from 'lib/styles'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+import {clamp} from 'lodash'
+
+const SHELL_FOOTER_HEIGHT = 44
 
 export const PostThread = ({navIdx, visible, params}: ScreenParams) => {
   const store = useStores()
+  const safeAreaInsets = useSafeAreaInsets()
   const {name, rkey} = params
   const uri = makeRecordUri(name, 'app.bsky.feed.post', rkey)
   const view = useMemo<PostThreadViewModel>(
@@ -48,12 +54,46 @@ export const PostThread = ({navIdx, visible, params}: ScreenParams) => {
     }
   }, [visible, store.nav, store.log, store.shell, name, navIdx, view])
 
+  const onPressReply = React.useCallback(() => {
+    if (!view.thread) {
+      return
+    }
+    store.shell.openComposer({
+      replyTo: {
+        uri: view.thread.post.uri,
+        cid: view.thread.post.cid,
+        text: view.thread.postRecord?.text as string,
+        author: {
+          handle: view.thread.post.author.handle,
+          displayName: view.thread.post.author.displayName,
+          avatar: view.thread.post.author.avatar,
+        },
+      },
+      onPost: () => view.refresh(),
+    })
+  }, [view, store])
+
   return (
     <View style={s.hContentRegion}>
       <ViewHeader title="Post" />
       <View style={s.hContentRegion}>
         <PostThreadComponent uri={uri} view={view} />
       </View>
+      <View
+        style={[
+          styles.prompt,
+          {bottom: SHELL_FOOTER_HEIGHT + clamp(safeAreaInsets.bottom, 15, 30)},
+        ]}>
+        <ComposePrompt onPressCompose={onPressReply} />
+      </View>
     </View>
   )
 }
+
+const styles = StyleSheet.create({
+  prompt: {
+    position: 'absolute',
+    left: 0,
+    right: 0,
+  },
+})
diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx
index bc487aee2..927e712e1 100644
--- a/src/view/shell/mobile/Menu.tsx
+++ b/src/view/shell/mobile/Menu.tsx
@@ -32,6 +32,7 @@ import {Text} from '../../com/util/text/Text'
 import {useTheme} from 'lib/ThemeContext'
 import {usePalette} from 'lib/hooks/usePalette'
 import {useAnalytics} from 'lib/analytics'
+import {pluralize} from 'lib/strings/helpers'
 
 export const Menu = observer(({onClose}: {onClose: () => void}) => {
   const theme = useTheme()
@@ -138,6 +139,16 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
         <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}>
           @{store.me.handle}
         </Text>
+        <Text type="xl" style={[pal.textLight, styles.profileCardFollowers]}>
+          <Text type="xl-medium" style={pal.text}>
+            {store.me.followersCount || 0}
+          </Text>{' '}
+          {pluralize(store.me.followersCount || 0, 'follower')} &middot;{' '}
+          <Text type="xl-medium" style={pal.text}>
+            {store.me.followsCount || 0}
+          </Text>{' '}
+          following
+        </Text>
       </TouchableOpacity>
       <View style={s.flex1} />
       <View>
@@ -267,12 +278,12 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => {
 const styles = StyleSheet.create({
   view: {
     flex: 1,
-    paddingTop: 10,
+    paddingTop: 20,
     paddingBottom: 50,
     paddingLeft: 30,
   },
   viewDarkMode: {
-    backgroundColor: '#202023',
+    backgroundColor: '#1B1919',
   },
 
   profileCardDisplayName: {
@@ -283,6 +294,10 @@ const styles = StyleSheet.create({
     marginTop: 4,
     paddingRight: 20,
   },
+  profileCardFollowers: {
+    marginTop: 16,
+    paddingRight: 20,
+  },
 
   menuItem: {
     flexDirection: 'row',
@@ -316,7 +331,7 @@ const styles = StyleSheet.create({
     flexDirection: 'row',
     justifyContent: 'space-between',
     paddingRight: 30,
-    paddingTop: 20,
+    paddingTop: 80,
   },
   footerBtn: {
     flexDirection: 'row',
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index b836bb76d..01df6c165 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -157,7 +157,7 @@ export const MobileShell: React.FC = observer(() => {
   }
 
   const screenBg = {
-    backgroundColor: theme.colorScheme === 'dark' ? colors.gray7 : colors.gray1,
+    backgroundColor: theme.colorScheme === 'dark' ? colors.black : colors.gray1,
   }
   return (
     <View testID="mobileShellView" style={[styles.outerContainer, pal.view]}>
@@ -202,13 +202,7 @@ export const MobileShell: React.FC = observer(() => {
                       style={[
                         s.h100pct,
                         screenBg,
-                        current
-                          ? [
-                              swipeTransform,
-                              // tabMenuTransform, TODO
-                              // isRunningNewTabAnim ? newTabTransform : undefined, TODO
-                            ]
-                          : undefined,
+                        current ? [swipeTransform] : undefined,
                       ]}>
                       <ErrorBoundary>
                         <Com