about summary refs log tree commit diff
path: root/src/view
diff options
context:
space:
mode:
Diffstat (limited to 'src/view')
-rw-r--r--src/view/com/composer/ComposePost.tsx136
-rw-r--r--src/view/com/composer/Prompt.tsx40
-rw-r--r--src/view/com/discover/SuggestedFollows.tsx12
-rw-r--r--src/view/com/modals/CreateScene.tsx6
-rw-r--r--src/view/com/modals/EditProfile.tsx6
-rw-r--r--src/view/com/modals/InviteToScene.tsx12
-rw-r--r--src/view/com/notifications/InviteAccepter.tsx7
-rw-r--r--src/view/com/post-thread/PostThreadItem.tsx219
-rw-r--r--src/view/com/post/Post.tsx14
-rw-r--r--src/view/com/posts/FeedItem.tsx14
-rw-r--r--src/view/com/profile/ProfileHeader.tsx68
-rw-r--r--src/view/com/util/PostCtrls.tsx70
-rw-r--r--src/view/com/util/Toast.native.tsx2
-rw-r--r--src/view/com/util/Toast.tsx69
-rw-r--r--src/view/com/util/ViewHeader.tsx42
-rw-r--r--src/view/lib/icons.tsx4
-rw-r--r--src/view/screens/Home.tsx12
-rw-r--r--src/view/screens/Profile.tsx11
-rw-r--r--src/view/shell/mobile/MainMenu.tsx17
-rw-r--r--src/view/shell/mobile/index.tsx15
20 files changed, 360 insertions, 416 deletions
diff --git a/src/view/com/composer/ComposePost.tsx b/src/view/com/composer/ComposePost.tsx
index 10305adb6..ce42ee17e 100644
--- a/src/view/com/composer/ComposePost.tsx
+++ b/src/view/com/composer/ComposePost.tsx
@@ -1,4 +1,4 @@
-import React, {useEffect, useMemo, useState} from 'react'
+import React, {useEffect, useMemo, useRef, useState} from 'react'
 import {observer} from 'mobx-react-lite'
 import {
   ActivityIndicator,
@@ -17,9 +17,10 @@ import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {UserAutocompleteViewModel} from '../../../state/models/user-autocomplete-view'
 import {UserLocalPhotosModel} from '../../../state/models/user-local-photos'
 import {Autocomplete} from './Autocomplete'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import ProgressCircle from '../util/ProgressCircle'
 import {TextLink} from '../util/Link'
+import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from '../../../state'
 import * as apilib from '../../../state/lib/api'
 import {ComposerOpts} from '../../../state/models/shell-ui'
@@ -28,7 +29,6 @@ import {detectLinkables} from '../../../lib/strings'
 import {openPicker, openCamera} from 'react-native-image-crop-picker'
 
 const MAX_TEXT_LENGTH = 256
-const WARNING_TEXT_LENGTH = 200
 const DANGER_TEXT_LENGTH = MAX_TEXT_LENGTH
 
 export const ComposePost = observer(function ComposePost({
@@ -41,6 +41,7 @@ export const ComposePost = observer(function ComposePost({
   onClose: () => void
 }) {
   const store = useStores()
+  const textInput = useRef<TextInput>(null)
   const [isProcessing, setIsProcessing] = useState(false)
   const [error, setError] = useState('')
   const [text, setText] = useState('')
@@ -57,6 +58,22 @@ export const ComposePost = observer(function ComposePost({
   useEffect(() => {
     autocompleteView.setup()
   })
+  useEffect(() => {
+    // HACK
+    // wait a moment before focusing the input to resolve some layout bugs with the keyboard-avoiding-view
+    // -prf
+    let to: NodeJS.Timeout | undefined
+    if (textInput.current) {
+      to = setTimeout(() => {
+        textInput.current?.focus()
+      }, 250)
+    }
+    return () => {
+      if (to) {
+        clearTimeout(to)
+      }
+    }
+  }, [textInput.current])
 
   useEffect(() => {
     localPhotos.setup()
@@ -90,7 +107,10 @@ export const ComposePost = observer(function ComposePost({
     }
     setIsProcessing(true)
     try {
-      await apilib.post(store, text, replyTo, autocompleteView.knownHandles)
+      const replyRef = replyTo
+        ? {uri: replyTo.uri, cid: replyTo.cid}
+        : undefined
+      await apilib.post(store, text, replyRef, autocompleteView.knownHandles)
     } catch (e: any) {
       console.error(`Failed to create post: ${e.toString()}`)
       setError(
@@ -101,13 +121,7 @@ export const ComposePost = observer(function ComposePost({
     }
     onPost?.()
     onClose()
-    Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`, {
-      duration: Toast.durations.LONG,
-      position: Toast.positions.TOP,
-      shadow: true,
-      animation: true,
-      hideOnPress: true,
-    })
+    Toast.show(`Your ${replyTo ? 'reply' : 'post'} has been published`)
   }
   const onSelectAutocompleteItem = (item: string) => {
     setText(replaceTextAutocompletePrefix(text, item))
@@ -115,12 +129,7 @@ export const ComposePost = observer(function ComposePost({
   }
 
   const canPost = text.length <= MAX_TEXT_LENGTH
-  const progressColor =
-    text.length > DANGER_TEXT_LENGTH
-      ? '#e60000'
-      : text.length > WARNING_TEXT_LENGTH
-      ? '#f7c600'
-      : undefined
+  const progressColor = text.length > DANGER_TEXT_LENGTH ? '#e60000' : undefined
 
   const textDecorated = useMemo(() => {
     let i = 0
@@ -142,7 +151,7 @@ export const ComposePost = observer(function ComposePost({
       <SafeAreaView style={s.flex1}>
         <View style={styles.topbar}>
           <TouchableOpacity onPress={onPressCancel}>
-            <Text style={[s.blue3, s.f16]}>Cancel</Text>
+            <Text style={[s.blue3, s.f18]}>Cancel</Text>
           </TouchableOpacity>
           <View style={s.flex1} />
           {isProcessing ? (
@@ -156,7 +165,9 @@ export const ComposePost = observer(function ComposePost({
                 start={{x: 0, y: 0}}
                 end={{x: 1, y: 1}}
                 style={styles.postBtn}>
-                <Text style={[s.white, s.f16, s.bold]}>Post</Text>
+                <Text style={[s.white, s.f16, s.bold]}>
+                  {replyTo ? 'Reply' : 'Post'}
+                </Text>
               </LinearGradient>
             </TouchableOpacity>
           ) : (
@@ -178,39 +189,46 @@ export const ComposePost = observer(function ComposePost({
           </View>
         )}
         {replyTo ? (
-          <View>
-            <Text style={s.gray4}>
-              Replying to{' '}
+          <View style={styles.replyToLayout}>
+            <UserAvatar
+              handle={replyTo.author.handle}
+              displayName={replyTo.author.displayName}
+              size={50}
+            />
+            <View style={styles.replyToPost}>
               <TextLink
                 href={`/profile/${replyTo.author.handle}`}
-                text={'@' + replyTo.author.handle}
-                style={[s.bold, s.gray5]}
+                text={replyTo.author.displayName || replyTo.author.handle}
+                style={[s.f16, s.bold]}
               />
-            </Text>
-            <View style={styles.replyToPost}>
-              <Text style={s.gray5}>{replyTo.text}</Text>
+              <Text style={[s.f16, s['lh16-1.3']]} numberOfLines={6}>
+                {replyTo.text}
+              </Text>
             </View>
           </View>
         ) : undefined}
-        <TextInput
-          multiline
-          scrollEnabled
-          onChangeText={(text: string) => onChangeText(text)}
-          placeholder={
-            replyTo
-              ? 'Write your reply'
-              : photoUris.length === 0
-              ? "What's up?"
-              : 'Add a comment...'
-          }
-          style={styles.textInput}>
-          {textDecorated}
-        </TextInput>
+        <View style={styles.textInputLayout}>
+          <UserAvatar
+            handle={store.me.handle || ''}
+            displayName={store.me.displayName}
+            size={50}
+          />
+          <TextInput
+            ref={textInput}
+            multiline
+            scrollEnabled
+            onChangeText={(text: string) => onChangeText(text)}
+            placeholder={replyTo ? 'Write your reply' : "What's up?"}
+            style={styles.textInput}>
+            {textDecorated}
+          </TextInput>
+        </View>
         {photoUris.length !== 0 && (
           <View style={styles.selectedImageContainer}>
             {photoUris.length !== 0 &&
-              photoUris.map(item => (
+              photoUris.map((item, index) => (
                 <View
+                  key={`selected-image-${index}`}
                   style={[
                     styles.selectedImage,
                     photoUris.length === 1
@@ -264,8 +282,9 @@ export const ComposePost = observer(function ComposePost({
                 style={{color: colors.blue3}}
               />
             </TouchableOpacity>
-            {localPhotos.photos.map(item => (
+            {localPhotos.photos.map((item, index) => (
               <TouchableOpacity
+                key={`local-image-${index}`}
                 style={styles.photoButton}
                 onPress={() => {
                   setPhotoUris([item.node.image.uri, ...photoUris])
@@ -343,9 +362,9 @@ const styles = StyleSheet.create({
     flexDirection: 'row',
     alignItems: 'center',
     paddingTop: 10,
-    paddingBottom: 5,
+    paddingBottom: 10,
     paddingHorizontal: 5,
-    height: 50,
+    height: 55,
   },
   postBtn: {
     borderRadius: 20,
@@ -371,19 +390,30 @@ const styles = StyleSheet.create({
     justifyContent: 'center',
     marginRight: 5,
   },
+  textInputLayout: {
+    flexDirection: 'row',
+    flex: 1,
+    borderTopWidth: 1,
+    borderTopColor: colors.gray2,
+    paddingTop: 16,
+  },
   textInput: {
     flex: 1,
     padding: 5,
-    fontSize: 21,
+    fontSize: 18,
+    marginLeft: 8,
+  },
+  replyToLayout: {
+    flexDirection: 'row',
+    borderTopWidth: 1,
+    borderTopColor: colors.gray2,
+    paddingTop: 16,
+    paddingBottom: 16,
   },
   replyToPost: {
-    paddingHorizontal: 8,
-    paddingVertical: 6,
-    borderWidth: 1,
-    borderColor: colors.gray2,
-    borderRadius: 6,
-    marginTop: 5,
-    marginBottom: 10,
+    flex: 1,
+    paddingLeft: 13,
+    paddingRight: 8,
   },
   contentCenter: {alignItems: 'center'},
   selectedImageContainer: {
diff --git a/src/view/com/composer/Prompt.tsx b/src/view/com/composer/Prompt.tsx
index f9fd7e7d3..7805e00dd 100644
--- a/src/view/com/composer/Prompt.tsx
+++ b/src/view/com/composer/Prompt.tsx
@@ -1,29 +1,42 @@
 import React from 'react'
 import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {colors} from '../../lib/styles'
 import {useStores} from '../../../state'
 import {UserAvatar} from '../util/UserAvatar'
 
-export function ComposePrompt({onPressCompose}: {onPressCompose: () => void}) {
+export function ComposePrompt({
+  noAvi = false,
+  text = "What's up?",
+  btn = 'Post',
+  onPressCompose,
+}: {
+  noAvi?: boolean
+  text?: string
+  btn?: string
+  onPressCompose: () => void
+}) {
   const store = useStores()
   const onPressAvatar = () => {
     store.nav.navigate(`/profile/${store.me.handle}`)
   }
   return (
-    <TouchableOpacity style={styles.container} onPress={onPressCompose}>
-      <TouchableOpacity style={styles.avatar} onPress={onPressAvatar}>
-        <UserAvatar
-          size={50}
-          handle={store.me.handle || ''}
-          displayName={store.me.displayName}
-        />
-      </TouchableOpacity>
+    <TouchableOpacity
+      style={[styles.container, noAvi ? styles.noAviContainer : undefined]}
+      onPress={onPressCompose}>
+      {!noAvi ? (
+        <TouchableOpacity style={styles.avatar} onPress={onPressAvatar}>
+          <UserAvatar
+            size={50}
+            handle={store.me.handle || ''}
+            displayName={store.me.displayName}
+          />
+        </TouchableOpacity>
+      ) : undefined}
       <View style={styles.textContainer}>
-        <Text style={styles.text}>What's up?</Text>
+        <Text style={styles.text}>{text}</Text>
       </View>
       <View style={styles.btn}>
-        <Text style={styles.btnText}>Post</Text>
+        <Text style={styles.btnText}>{btn}</Text>
       </View>
     </TouchableOpacity>
   )
@@ -40,6 +53,9 @@ const styles = StyleSheet.create({
     alignItems: 'center',
     backgroundColor: colors.white,
   },
+  noAviContainer: {
+    paddingVertical: 14,
+  },
   avatar: {
     width: 50,
   },
diff --git a/src/view/com/discover/SuggestedFollows.tsx b/src/view/com/discover/SuggestedFollows.tsx
index d8cb0c4db..d5875f0f7 100644
--- a/src/view/com/discover/SuggestedFollows.tsx
+++ b/src/view/com/discover/SuggestedFollows.tsx
@@ -14,7 +14,7 @@ import _omit from 'lodash.omit'
 import {ErrorScreen} from '../util/ErrorScreen'
 import {Link} from '../util/Link'
 import {UserAvatar} from '../util/UserAvatar'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {useStores} from '../../../state'
 import * as apilib from '../../../state/lib/api'
 import {
@@ -63,10 +63,7 @@ export const SuggestedFollows = observer(
         setFollows({[item.did]: res.uri, ...follows})
       } catch (e) {
         console.log(e)
-        Toast.show('An issue occurred, please try again.', {
-          duration: Toast.durations.LONG,
-          position: Toast.positions.TOP,
-        })
+        Toast.show('An issue occurred, please try again.')
       }
     }
     const onPressUnfollow = async (item: SuggestedActor) => {
@@ -75,10 +72,7 @@ export const SuggestedFollows = observer(
         setFollows(_omit(follows, [item.did]))
       } catch (e) {
         console.log(e)
-        Toast.show('An issue occurred, please try again.', {
-          duration: Toast.durations.LONG,
-          position: Toast.positions.TOP,
-        })
+        Toast.show('An issue occurred, please try again.')
       }
     }
 
diff --git a/src/view/com/modals/CreateScene.tsx b/src/view/com/modals/CreateScene.tsx
index 445374623..646d5b242 100644
--- a/src/view/com/modals/CreateScene.tsx
+++ b/src/view/com/modals/CreateScene.tsx
@@ -1,5 +1,5 @@
 import React, {useState} from 'react'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {
   ActivityIndicator,
   StyleSheet,
@@ -71,9 +71,7 @@ export function Component({}: {}) {
           },
         )
         .catch(e => console.error(e)) // an error here is not critical
-      Toast.show('Scene created', {
-        position: Toast.positions.TOP,
-      })
+      Toast.show('Scene created')
       store.shell.closeModal()
       store.nav.navigate(`/profile/${fullHandle}`)
     } catch (e: any) {
diff --git a/src/view/com/modals/EditProfile.tsx b/src/view/com/modals/EditProfile.tsx
index f739b0843..50acccf67 100644
--- a/src/view/com/modals/EditProfile.tsx
+++ b/src/view/com/modals/EditProfile.tsx
@@ -1,5 +1,5 @@
 import React, {useState} from 'react'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {BottomSheetScrollView, BottomSheetTextInput} from '@gorhom/bottom-sheet'
@@ -52,9 +52,7 @@ export function Component({
           }
         },
       )
-      Toast.show('Profile updated', {
-        position: Toast.positions.TOP,
-      })
+      Toast.show('Profile updated')
       onUpdate?.()
       store.shell.closeModal()
     } catch (e: any) {
diff --git a/src/view/com/modals/InviteToScene.tsx b/src/view/com/modals/InviteToScene.tsx
index 2d4e372c1..8df38daf0 100644
--- a/src/view/com/modals/InviteToScene.tsx
+++ b/src/view/com/modals/InviteToScene.tsx
@@ -1,6 +1,6 @@
 import React, {useState, useEffect, useMemo} from 'react'
 import {observer} from 'mobx-react-lite'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {
   ActivityIndicator,
   FlatList,
@@ -83,10 +83,7 @@ export const Component = observer(function Component({
         follow.declaration.cid,
       )
       setCreatedInvites({[follow.did]: assertionUri, ...createdInvites})
-      Toast.show('Invite sent', {
-        duration: Toast.durations.LONG,
-        position: Toast.positions.TOP,
-      })
+      Toast.show('Invite sent')
     } catch (e) {
       setError('There was an issue with the invite. Please try again.')
       console.error(e)
@@ -119,10 +116,7 @@ export const Component = observer(function Component({
         [assertion.uri]: true,
         ...deletedPendingInvites,
       })
-      Toast.show('Invite removed', {
-        duration: Toast.durations.LONG,
-        position: Toast.positions.TOP,
-      })
+      Toast.show('Invite removed')
     } catch (e) {
       setError('There was an issue with the invite. Please try again.')
       console.error(e)
diff --git a/src/view/com/notifications/InviteAccepter.tsx b/src/view/com/notifications/InviteAccepter.tsx
index 7d735a66b..72bc06764 100644
--- a/src/view/com/notifications/InviteAccepter.tsx
+++ b/src/view/com/notifications/InviteAccepter.tsx
@@ -7,7 +7,7 @@ import {NotificationsViewItemModel} from '../../../state/models/notifications-vi
 import {ConfirmModel} from '../../../state/models/shell-ui'
 import {useStores} from '../../../state'
 import {ProfileCard} from '../profile/ProfileCard'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {s, colors, gradients} from '../../lib/styles'
 
 export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
@@ -46,10 +46,7 @@ export function InviteAccepter({item}: {item: NotificationsViewItemModel}) {
       },
     })
     store.me.refreshMemberships()
-    Toast.show('Invite accepted', {
-      duration: Toast.durations.LONG,
-      position: Toast.positions.TOP,
-    })
+    Toast.show('Invite accepted')
     setConfirmationUri(uri)
   }
   return (
diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx
index 95b02837d..85c241ce4 100644
--- a/src/view/com/post-thread/PostThreadItem.tsx
+++ b/src/view/com/post-thread/PostThreadItem.tsx
@@ -8,7 +8,7 @@ import {PostThreadViewPostModel} from '../../../state/models/post-thread-view'
 import {Link} from '../util/Link'
 import {RichText} from '../util/RichText'
 import {PostDropdownBtn} from '../util/DropdownBtn'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {s, colors} from '../../lib/styles'
 import {ago, pluralize} from '../../../lib/strings'
@@ -16,6 +16,7 @@ import {useStores} from '../../../state'
 import {PostMeta} from '../util/PostMeta'
 import {PostEmbeds} from '../util/PostEmbeds'
 import {PostCtrls} from '../util/PostCtrls'
+import {ComposePrompt} from '../composer/Prompt'
 
 const PARENT_REPLY_LINE_LENGTH = 8
 const REPLYING_TO_LINE_LENGTH = 6
@@ -78,131 +79,133 @@ export const PostThreadItem = observer(function PostThreadItem({
     item.delete().then(
       () => {
         setDeleted(true)
-        Toast.show('Post deleted', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Post deleted')
       },
       e => {
         console.error(e)
-        Toast.show('Failed to delete post, please try again', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Failed to delete post, please try again')
       },
     )
   }
 
   if (item._isHighlightedPost) {
     return (
-      <View style={styles.outer}>
-        <View style={styles.layout}>
-          <View style={styles.layoutAvi}>
-            <Link href={authorHref} title={authorTitle}>
-              <UserAvatar
-                size={50}
-                displayName={item.author.displayName}
-                handle={item.author.handle}
-              />
-            </Link>
-          </View>
-          <View style={styles.layoutContent}>
-            <View style={[styles.meta, {paddingTop: 5, paddingBottom: 0}]}>
-              <Link
-                style={styles.metaItem}
-                href={authorHref}
-                title={authorTitle}>
-                <Text style={[s.f16, s.bold]} numberOfLines={1}>
-                  {item.author.displayName || item.author.handle}
-                </Text>
-              </Link>
-              <Text style={[styles.metaItem, s.f15, s.gray5]}>
-                &middot; {ago(item.indexedAt)}
-              </Text>
-              <View style={s.flex1} />
-              <PostDropdownBtn
-                style={styles.metaItem}
-                itemHref={itemHref}
-                itemTitle={itemTitle}
-                isAuthor={item.author.did === store.me.did}
-                onDeletePost={onDeletePost}>
-                <FontAwesomeIcon
-                  icon="ellipsis-h"
-                  size={14}
-                  style={[s.mt2, s.mr5]}
+      <>
+        <View style={styles.outer}>
+          <View style={styles.layout}>
+            <View style={styles.layoutAvi}>
+              <Link href={authorHref} title={authorTitle}>
+                <UserAvatar
+                  size={50}
+                  displayName={item.author.displayName}
+                  handle={item.author.handle}
                 />
-              </PostDropdownBtn>
-            </View>
-            <View style={styles.meta}>
-              <Link
-                style={styles.metaItem}
-                href={authorHref}
-                title={authorTitle}>
-                <Text style={[s.f15, s.gray5]} numberOfLines={1}>
-                  @{item.author.handle}
-                </Text>
               </Link>
             </View>
-          </View>
-        </View>
-        <View style={[s.pl10, s.pr10, s.pb10]}>
-          <View
-            style={[styles.postTextContainer, styles.postTextLargeContainer]}>
-            <RichText
-              text={record.text}
-              entities={record.entities}
-              style={[styles.postText, styles.postTextLarge]}
-            />
-          </View>
-          <PostEmbeds entities={record.entities} />
-          {item._isHighlightedPost && hasEngagement ? (
-            <View style={styles.expandedInfo}>
-              {item.repostCount ? (
+            <View style={styles.layoutContent}>
+              <View style={[styles.meta, {paddingTop: 5, paddingBottom: 0}]}>
                 <Link
-                  style={styles.expandedInfoItem}
-                  href={repostsHref}
-                  title={repostsTitle}>
-                  <Text style={[s.gray5, s.semiBold, s.f18]}>
-                    <Text style={[s.bold, s.black, s.f18]}>
-                      {item.repostCount}
-                    </Text>{' '}
-                    {pluralize(item.repostCount, 'repost')}
+                  style={styles.metaItem}
+                  href={authorHref}
+                  title={authorTitle}>
+                  <Text style={[s.f16, s.bold]} numberOfLines={1}>
+                    {item.author.displayName || item.author.handle}
                   </Text>
                 </Link>
-              ) : (
-                <></>
-              )}
-              {item.upvoteCount ? (
+                <Text style={[styles.metaItem, s.f15, s.gray5]}>
+                  &middot; {ago(item.indexedAt)}
+                </Text>
+                <View style={s.flex1} />
+                <PostDropdownBtn
+                  style={styles.metaItem}
+                  itemHref={itemHref}
+                  itemTitle={itemTitle}
+                  isAuthor={item.author.did === store.me.did}
+                  onDeletePost={onDeletePost}>
+                  <FontAwesomeIcon
+                    icon="ellipsis-h"
+                    size={14}
+                    style={[s.mt2, s.mr5]}
+                  />
+                </PostDropdownBtn>
+              </View>
+              <View style={styles.meta}>
                 <Link
-                  style={styles.expandedInfoItem}
-                  href={upvotesHref}
-                  title={upvotesTitle}>
-                  <Text style={[s.gray5, s.semiBold, s.f18]}>
-                    <Text style={[s.bold, s.black, s.f18]}>
-                      {item.upvoteCount}
-                    </Text>{' '}
-                    {pluralize(item.upvoteCount, 'upvote')}
+                  style={styles.metaItem}
+                  href={authorHref}
+                  title={authorTitle}>
+                  <Text style={[s.f15, s.gray5]} numberOfLines={1}>
+                    @{item.author.handle}
                   </Text>
                 </Link>
-              ) : (
-                <></>
-              )}
+              </View>
+            </View>
+          </View>
+          <View style={[s.pl10, s.pr10, s.pb10]}>
+            <View
+              style={[styles.postTextContainer, styles.postTextLargeContainer]}>
+              <RichText
+                text={record.text}
+                entities={record.entities}
+                style={[styles.postText, styles.postTextLarge]}
+              />
+            </View>
+            <PostEmbeds entities={record.entities} style={s.mb10} />
+            {item._isHighlightedPost && hasEngagement ? (
+              <View style={styles.expandedInfo}>
+                {item.repostCount ? (
+                  <Link
+                    style={styles.expandedInfoItem}
+                    href={repostsHref}
+                    title={repostsTitle}>
+                    <Text style={[s.gray5, s.semiBold, s.f17]}>
+                      <Text style={[s.bold, s.black, s.f17]}>
+                        {item.repostCount}
+                      </Text>{' '}
+                      {pluralize(item.repostCount, 'repost')}
+                    </Text>
+                  </Link>
+                ) : (
+                  <></>
+                )}
+                {item.upvoteCount ? (
+                  <Link
+                    style={styles.expandedInfoItem}
+                    href={upvotesHref}
+                    title={upvotesTitle}>
+                    <Text style={[s.gray5, s.semiBold, s.f17]}>
+                      <Text style={[s.bold, s.black, s.f17]}>
+                        {item.upvoteCount}
+                      </Text>{' '}
+                      {pluralize(item.upvoteCount, 'upvote')}
+                    </Text>
+                  </Link>
+                ) : (
+                  <></>
+                )}
+              </View>
+            ) : (
+              <></>
+            )}
+            <View style={[s.pl10, s.pb5]}>
+              <PostCtrls
+                big
+                isReposted={!!item.myState.repost}
+                isUpvoted={!!item.myState.upvote}
+                onPressReply={onPressReply}
+                onPressToggleRepost={onPressToggleRepost}
+                onPressToggleUpvote={onPressToggleUpvote}
+              />
             </View>
-          ) : (
-            <></>
-          )}
-          <View style={[s.pl10]}>
-            <PostCtrls
-              replyCount={item.replyCount}
-              repostCount={item.repostCount}
-              upvoteCount={item.upvoteCount}
-              isReposted={!!item.myState.repost}
-              isUpvoted={!!item.myState.upvote}
-              onPressReply={onPressReply}
-              onPressToggleRepost={onPressToggleRepost}
-              onPressToggleUpvote={onPressToggleUpvote}
-            />
           </View>
         </View>
-      </View>
+        <ComposePrompt
+          noAvi
+          text="Write your reply"
+          btn="Reply"
+          onPressCompose={onPressReply}
+        />
+      </>
     )
   } else {
     return (
@@ -345,8 +348,8 @@ const styles = StyleSheet.create({
   },
   postText: {
     fontFamily: 'Helvetica Neue',
-    fontSize: 17,
-    lineHeight: 22.1, // 1.3 of 17px
+    fontSize: 16,
+    lineHeight: 20.8, // 1.3 of 16px
   },
   postTextContainer: {
     flexDirection: 'row',
@@ -371,7 +374,7 @@ const styles = StyleSheet.create({
     borderTopWidth: 1,
     borderBottomWidth: 1,
     marginTop: 5,
-    marginBottom: 10,
+    marginBottom: 15,
   },
   expandedInfoItem: {
     marginRight: 10,
diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx
index d0df1b295..4d668cac3 100644
--- a/src/view/com/post/Post.tsx
+++ b/src/view/com/post/Post.tsx
@@ -10,7 +10,7 @@ import {UserInfoText} from '../util/UserInfoText'
 import {PostMeta} from '../util/PostMeta'
 import {PostCtrls} from '../util/PostCtrls'
 import {RichText} from '../util/RichText'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {useStores} from '../../../state'
 import {s, colors} from '../../lib/styles'
@@ -99,15 +99,11 @@ export const Post = observer(function Post({uri}: {uri: string}) {
     item.delete().then(
       () => {
         setDeleted(true)
-        Toast.show('Post deleted', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Post deleted')
       },
       e => {
         console.error(e)
-        Toast.show('Failed to delete post, please try again', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Failed to delete post, please try again')
       },
     )
   }
@@ -196,7 +192,7 @@ const styles = StyleSheet.create({
   },
   postText: {
     fontFamily: 'Helvetica Neue',
-    fontSize: 17,
-    lineHeight: 22.1, // 1.3 of 17px
+    fontSize: 16,
+    lineHeight: 20.8, // 1.3 of 16px
   },
 })
diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx
index 4063b2008..4d50531bd 100644
--- a/src/view/com/posts/FeedItem.tsx
+++ b/src/view/com/posts/FeedItem.tsx
@@ -11,7 +11,7 @@ import {PostMeta} from '../util/PostMeta'
 import {PostCtrls} from '../util/PostCtrls'
 import {PostEmbeds} from '../util/PostEmbeds'
 import {RichText} from '../util/RichText'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {UserAvatar} from '../util/UserAvatar'
 import {s, colors} from '../../lib/styles'
 import {useStores} from '../../../state'
@@ -70,15 +70,11 @@ export const FeedItem = observer(function FeedItem({
     item.delete().then(
       () => {
         setDeleted(true)
-        Toast.show('Post deleted', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Post deleted')
       },
       e => {
         console.error(e)
-        Toast.show('Failed to delete post, please try again', {
-          position: Toast.positions.TOP,
-        })
+        Toast.show('Failed to delete post, please try again')
       },
     )
   }
@@ -254,7 +250,7 @@ const styles = StyleSheet.create({
   },
   postText: {
     fontFamily: 'Helvetica Neue',
-    fontSize: 17,
-    lineHeight: 22.1, // 1.3 of 17px
+    fontSize: 16,
+    lineHeight: 20.8, // 1.3 of 16px
   },
 })
diff --git a/src/view/com/profile/ProfileHeader.tsx b/src/view/com/profile/ProfileHeader.tsx
index 9325a88a3..1b25c7c13 100644
--- a/src/view/com/profile/ProfileHeader.tsx
+++ b/src/view/com/profile/ProfileHeader.tsx
@@ -1,12 +1,6 @@
 import React, {useMemo} from 'react'
 import {observer} from 'mobx-react-lite'
-import {
-  ActivityIndicator,
-  StyleSheet,
-  Text,
-  TouchableOpacity,
-  View,
-} from 'react-native'
+import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
 import LinearGradient from 'react-native-linear-gradient'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {AtUri} from '../../../third-party/uri'
@@ -20,9 +14,8 @@ import {
 import {pluralize} from '../../../lib/strings'
 import {s, colors} from '../../lib/styles'
 import {getGradient} from '../../lib/asset-gen'
-import {MagnifyingGlassIcon} from '../../lib/icons'
 import {DropdownBtn, DropdownItem} from '../util/DropdownBtn'
-import Toast from '../util/Toast'
+import * as Toast from '../util/Toast'
 import {LoadingPlaceholder} from '../util/LoadingPlaceholder'
 import {RichText} from '../util/RichText'
 import {UserAvatar} from '../util/UserAvatar'
@@ -55,10 +48,6 @@ export const ProfileHeader = observer(function ProfileHeader({
           `${view.myState.follow ? 'Following' : 'No longer following'} ${
             view.displayName || view.handle
           }`,
-          {
-            duration: Toast.durations.LONG,
-            position: Toast.positions.TOP,
-          },
         )
       },
       err => console.error('Failed to toggle follow', err),
@@ -94,10 +83,7 @@ export const ProfileHeader = observer(function ProfileHeader({
         did: store.me.did || '',
         rkey: new AtUri(view.myState.member).rkey,
       })
-      Toast.show(`Scene left`, {
-        duration: Toast.durations.LONG,
-        position: Toast.positions.TOP,
-      })
+      Toast.show(`Scene left`)
     }
     onRefreshAll()
   }
@@ -108,18 +94,6 @@ export const ProfileHeader = observer(function ProfileHeader({
     return (
       <View style={styles.outer}>
         <LoadingPlaceholder width="100%" height={120} />
-        {store.nav.tab.canGoBack ? (
-          <TouchableOpacity style={styles.backButton} onPress={onPressBack}>
-            <FontAwesomeIcon
-              size={18}
-              icon="angle-left"
-              style={styles.backIcon}
-            />
-          </TouchableOpacity>
-        ) : undefined}
-        <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}>
-          <MagnifyingGlassIcon size={19} style={styles.searchIcon} />
-        </TouchableOpacity>
         <View style={styles.avi}>
           <LoadingPlaceholder
             width={80}
@@ -179,18 +153,6 @@ export const ProfileHeader = observer(function ProfileHeader({
   return (
     <View style={styles.outer}>
       <UserBanner handle={view.handle} />
-      {store.nav.tab.canGoBack ? (
-        <TouchableOpacity style={styles.backButton} onPress={onPressBack}>
-          <FontAwesomeIcon
-            size={18}
-            icon="angle-left"
-            style={styles.backIcon}
-          />
-        </TouchableOpacity>
-      ) : undefined}
-      <TouchableOpacity style={styles.searchBtn} onPress={onPressSearch}>
-        <MagnifyingGlassIcon size={19} style={styles.searchIcon} />
-      </TouchableOpacity>
       <View style={styles.avi}>
         <UserAvatar
           size={80}
@@ -353,30 +315,6 @@ const styles = StyleSheet.create({
     width: '100%',
     height: 120,
   },
-  backButton: {
-    position: 'absolute',
-    top: 10,
-    left: 12,
-    backgroundColor: '#ffff',
-    padding: 6,
-    borderRadius: 30,
-  },
-  backIcon: {
-    width: 14,
-    height: 14,
-    color: colors.black,
-  },
-  searchBtn: {
-    position: 'absolute',
-    top: 10,
-    right: 12,
-    backgroundColor: '#ffff',
-    padding: 5,
-    borderRadius: 30,
-  },
-  searchIcon: {
-    color: colors.black,
-  },
   avi: {
     position: 'absolute',
     top: 80,
diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx
index a316d8959..10b54be3f 100644
--- a/src/view/com/util/PostCtrls.tsx
+++ b/src/view/com/util/PostCtrls.tsx
@@ -12,9 +12,10 @@ import {UpIcon, UpIconSolid} from '../../lib/icons'
 import {s, colors} from '../../lib/styles'
 
 interface PostCtrlsOpts {
-  replyCount: number
-  repostCount: number
-  upvoteCount: number
+  big?: boolean
+  replyCount?: number
+  repostCount?: number
+  upvoteCount?: number
   isReposted: boolean
   isUpvoted: boolean
   onPressReply: () => void
@@ -30,17 +31,17 @@ export function PostCtrls(opts: PostCtrlsOpts) {
   const interp2 = useSharedValue<number>(0)
 
   const anim1Style = useAnimatedStyle(() => ({
-    transform: [{scale: interpolate(interp1.value, [0, 1.0], [1.0, 3.0])}],
+    transform: [{scale: interpolate(interp1.value, [0, 1.0], [1.0, 4.0])}],
     opacity: interpolate(interp1.value, [0, 1.0], [1.0, 0.0]),
   }))
   const anim2Style = useAnimatedStyle(() => ({
-    transform: [{scale: interpolate(interp2.value, [0, 1.0], [1.0, 3.0])}],
+    transform: [{scale: interpolate(interp2.value, [0, 1.0], [1.0, 4.0])}],
     opacity: interpolate(interp2.value, [0, 1.0], [1.0, 0.0]),
   }))
 
   const onPressToggleRepostWrapper = () => {
     if (!opts.isReposted) {
-      interp1.value = withTiming(1, {duration: 300}, () => {
+      interp1.value = withTiming(1, {duration: 400}, () => {
         interp1.value = withDelay(100, withTiming(0, {duration: 20}))
       })
     }
@@ -48,7 +49,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
   }
   const onPressToggleUpvoteWrapper = () => {
     if (!opts.isUpvoted) {
-      interp2.value = withTiming(1, {duration: 300}, () => {
+      interp2.value = withTiming(1, {duration: 400}, () => {
         interp2.value = withDelay(100, withTiming(0, {duration: 20}))
       })
     }
@@ -62,9 +63,11 @@ export function PostCtrls(opts: PostCtrlsOpts) {
           <FontAwesomeIcon
             style={styles.ctrlIcon}
             icon={['far', 'comment']}
-            size={14}
+            size={opts.big ? 20 : 14}
           />
-          <Text style={[sRedgray, s.ml5, s.f16]}>{opts.replyCount}</Text>
+          {typeof opts.replyCount !== 'undefined' ? (
+            <Text style={[sRedgray, s.ml5, s.f16]}>{opts.replyCount}</Text>
+          ) : undefined}
         </TouchableOpacity>
       </View>
       <View style={s.flex1}>
@@ -77,17 +80,19 @@ export function PostCtrls(opts: PostCtrlsOpts) {
                 opts.isReposted ? styles.ctrlIconReposted : styles.ctrlIcon
               }
               icon="retweet"
-              size={18}
+              size={opts.big ? 22 : 18}
             />
           </Animated.View>
-          <Text
-            style={
-              opts.isReposted
-                ? [s.bold, s.green3, s.f16, s.ml5]
-                : [sRedgray, s.f16, s.ml5]
-            }>
-            {opts.repostCount}
-          </Text>
+          {typeof opts.repostCount !== 'undefined' ? (
+            <Text
+              style={
+                opts.isReposted
+                  ? [s.bold, s.green3, s.f16, s.ml5]
+                  : [sRedgray, s.f16, s.ml5]
+              }>
+              {opts.repostCount}
+            </Text>
+          ) : undefined}
         </TouchableOpacity>
       </View>
       <View style={s.flex1}>
@@ -96,19 +101,28 @@ export function PostCtrls(opts: PostCtrlsOpts) {
           onPress={onPressToggleUpvoteWrapper}>
           <Animated.View style={anim2Style}>
             {opts.isUpvoted ? (
-              <UpIconSolid style={[styles.ctrlIconUpvoted]} size={18} />
+              <UpIconSolid
+                style={[styles.ctrlIconUpvoted]}
+                size={opts.big ? 22 : 18}
+              />
             ) : (
-              <UpIcon style={[styles.ctrlIcon]} size={18} strokeWidth={1.5} />
+              <UpIcon
+                style={[styles.ctrlIcon]}
+                size={opts.big ? 22 : 18}
+                strokeWidth={1.5}
+              />
             )}
           </Animated.View>
-          <Text
-            style={
-              opts.isUpvoted
-                ? [s.bold, s.red3, s.f16, s.ml5]
-                : [sRedgray, s.f16, s.ml5]
-            }>
-            {opts.upvoteCount}
-          </Text>
+          {typeof opts.upvoteCount !== 'undefined' ? (
+            <Text
+              style={
+                opts.isUpvoted
+                  ? [s.bold, s.red3, s.f16, s.ml5]
+                  : [sRedgray, s.f16, s.ml5]
+              }>
+              {opts.upvoteCount}
+            </Text>
+          ) : undefined}
         </TouchableOpacity>
       </View>
       <View style={s.flex1}></View>
diff --git a/src/view/com/util/Toast.native.tsx b/src/view/com/util/Toast.native.tsx
deleted file mode 100644
index 4b9fd7f80..000000000
--- a/src/view/com/util/Toast.native.tsx
+++ /dev/null
@@ -1,2 +0,0 @@
-import Toast from 'react-native-root-toast'
-export default Toast
diff --git a/src/view/com/util/Toast.tsx b/src/view/com/util/Toast.tsx
index 1726b71b3..197f47422 100644
--- a/src/view/com/util/Toast.tsx
+++ b/src/view/com/util/Toast.tsx
@@ -1,62 +1,11 @@
-/*
- * Note: the dataSet properties are used to leverage custom CSS in public/index.html
- */
-
-import React, {useState, useEffect} from 'react'
-// @ts-ignore no declarations available -prf
-import {Text, View} from 'react-native-web'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-
-interface ActiveToast {
-  text: string
-}
-type GlobalSetActiveToast = (_activeToast: ActiveToast | undefined) => void
-
-// globals
-// =
-let globalSetActiveToast: GlobalSetActiveToast | undefined
-let toastTimeout: NodeJS.Timeout | undefined
-
-// components
-// =
-type ToastContainerProps = {}
-const ToastContainer: React.FC<ToastContainerProps> = ({}) => {
-  const [activeToast, setActiveToast] = useState<ActiveToast | undefined>()
-  useEffect(() => {
-    globalSetActiveToast = (t: ActiveToast | undefined) => {
-      setActiveToast(t)
-    }
+import Toast from 'react-native-root-toast'
+
+export function show(message: string) {
+  Toast.show(message, {
+    duration: Toast.durations.LONG,
+    position: 50,
+    shadow: true,
+    animation: true,
+    hideOnPress: true,
   })
-  return (
-    <>
-      {activeToast && (
-        <View dataSet={{'toast-container': 1}}>
-          <FontAwesomeIcon icon="check" size={24} />
-          <Text>{activeToast.text}</Text>
-        </View>
-      )}
-    </>
-  )
-}
-
-// exports
-// =
-export default {
-  show(text: string, _opts: any) {
-    console.log('TODO: toast', text)
-    if (toastTimeout) {
-      clearTimeout(toastTimeout)
-    }
-    globalSetActiveToast?.({text})
-    toastTimeout = setTimeout(() => {
-      globalSetActiveToast?.(undefined)
-    }, 2e3)
-  },
-  positions: {
-    TOP: 0,
-  },
-  durations: {
-    LONG: 0,
-  },
-  ToastContainer,
 }
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 55a71ea26..50b7e6532 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -1,7 +1,6 @@
 import React from 'react'
 import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {UserAvatar} from './UserAvatar'
 import {colors} from '../../lib/styles'
 import {MagnifyingGlassIcon} from '../../lib/icons'
 import {useStores} from '../../../state'
@@ -9,14 +8,19 @@ import {useStores} from '../../../state'
 export function ViewHeader({
   title,
   subtitle,
+  onPost,
 }: {
   title: string
   subtitle?: string
+  onPost?: () => void
 }) {
   const store = useStores()
   const onPressBack = () => {
     store.nav.tab.goBack()
   }
+  const onPressCompose = () => {
+    store.shell.openComposer({onPost})
+  }
   const onPressSearch = () => {
     store.nav.navigate(`/search`)
   }
@@ -26,9 +30,7 @@ export function ViewHeader({
         <TouchableOpacity onPress={onPressBack} style={styles.backIcon}>
           <FontAwesomeIcon size={18} icon="angle-left" style={{marginTop: 6}} />
         </TouchableOpacity>
-      ) : (
-        <View style={styles.cornerPlaceholder} />
-      )}
+      ) : undefined}
       <View style={styles.titleContainer}>
         <Text style={styles.title}>{title}</Text>
         {subtitle ? (
@@ -37,8 +39,17 @@ export function ViewHeader({
           </Text>
         ) : undefined}
       </View>
-      <TouchableOpacity onPress={onPressSearch} style={styles.searchBtn}>
-        <MagnifyingGlassIcon size={17} style={styles.searchBtnIcon} />
+      <TouchableOpacity onPress={onPressCompose} style={styles.btn}>
+        <FontAwesomeIcon size={18} icon="plus" />
+      </TouchableOpacity>
+      <TouchableOpacity
+        onPress={onPressSearch}
+        style={[styles.btn, {marginLeft: 8}]}>
+        <MagnifyingGlassIcon
+          size={18}
+          strokeWidth={3}
+          style={styles.searchBtnIcon}
+        />
       </TouchableOpacity>
     </View>
   )
@@ -59,33 +70,28 @@ const styles = StyleSheet.create({
   titleContainer: {
     flexDirection: 'row',
     alignItems: 'baseline',
-    marginLeft: 'auto',
     marginRight: 'auto',
   },
   title: {
-    fontSize: 16,
+    fontSize: 21,
     fontWeight: '600',
   },
   subtitle: {
-    fontSize: 15,
-    marginLeft: 3,
+    fontSize: 18,
+    marginLeft: 6,
     color: colors.gray4,
     maxWidth: 200,
   },
 
-  cornerPlaceholder: {
-    width: 30,
-    height: 30,
-  },
   backIcon: {width: 30, height: 30},
-  searchBtn: {
+  btn: {
     flexDirection: 'row',
     alignItems: 'center',
     justifyContent: 'center',
     backgroundColor: colors.gray1,
-    width: 30,
-    height: 30,
-    borderRadius: 15,
+    width: 36,
+    height: 36,
+    borderRadius: 20,
   },
   searchBtnIcon: {
     color: colors.black,
diff --git a/src/view/lib/icons.tsx b/src/view/lib/icons.tsx
index 05b1ec601..7e3313597 100644
--- a/src/view/lib/icons.tsx
+++ b/src/view/lib/icons.tsx
@@ -94,15 +94,17 @@ export function HomeIconSolid({
 export function MagnifyingGlassIcon({
   style,
   size,
+  strokeWidth = 2,
 }: {
   style?: StyleProp<ViewStyle>
   size?: string | number
+  strokeWidth?: number
 }) {
   return (
     <Svg
       fill="none"
       viewBox="0 0 24 24"
-      strokeWidth={2}
+      strokeWidth={strokeWidth}
       stroke="currentColor"
       width={size || 24}
       height={size || 24}
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 8dd7ca411..5925b6f80 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -47,6 +47,7 @@ export const Home = observer(function Home({
     if (!visible) {
       return
     }
+
     if (hasSetup) {
       console.log('Updating home feed')
       defaultFeedView.update()
@@ -80,7 +81,11 @@ export const Home = observer(function Home({
 
   return (
     <View style={s.flex1}>
-      <ViewHeader title="Bluesky" subtitle="Private Beta" />
+      <ViewHeader
+        title="Bluesky"
+        subtitle="Private Beta"
+        onPost={onCreatePost}
+      />
       <Feed
         key="default"
         feed={defaultFeedView}
@@ -106,8 +111,8 @@ const styles = StyleSheet.create({
     left: 10,
     bottom: 15,
     backgroundColor: colors.pink3,
-    paddingHorizontal: 10,
-    paddingVertical: 8,
+    paddingHorizontal: 12,
+    paddingVertical: 10,
     borderRadius: 30,
     shadowColor: '#000',
     shadowOpacity: 0.3,
@@ -117,5 +122,6 @@ const styles = StyleSheet.create({
     color: colors.white,
     fontWeight: 'bold',
     marginLeft: 5,
+    fontSize: 16,
   },
 })
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index d1abcd731..2cfcf975c 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -15,7 +15,8 @@ import {PostFeedLoadingPlaceholder} from '../com/util/LoadingPlaceholder'
 import {ErrorScreen} from '../com/util/ErrorScreen'
 import {ErrorMessage} from '../com/util/ErrorMessage'
 import {EmptyState} from '../com/util/EmptyState'
-import Toast from '../com/util/Toast'
+import {ViewHeader} from '../com/util/ViewHeader'
+import * as Toast from '../com/util/Toast'
 import {s, colors} from '../lib/styles'
 
 const LOADING_ITEM = {_reactKey: '__loading__'}
@@ -77,10 +78,7 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
         `You'll be able to invite them again if you change your mind.`,
         async () => {
           await uiState.members.removeMember(membership.did)
-          Toast.show(`User removed`, {
-            duration: Toast.durations.LONG,
-            position: Toast.positions.TOP,
-          })
+          Toast.show(`User removed`)
         },
       ),
     )
@@ -219,8 +217,11 @@ export const Profile = observer(({navIdx, visible, params}: ScreenParams) => {
     renderItem = () => <View />
   }
 
+  const title =
+    uiState.profile.displayName || uiState.profile.handle || params.name
   return (
     <View style={styles.container}>
+      <ViewHeader title={title} />
       {uiState.profile.hasError ? (
         <ErrorScreen
           title="Failed to load profile"
diff --git a/src/view/shell/mobile/MainMenu.tsx b/src/view/shell/mobile/MainMenu.tsx
index d05e70a81..8a7264612 100644
--- a/src/view/shell/mobile/MainMenu.tsx
+++ b/src/view/shell/mobile/MainMenu.tsx
@@ -8,7 +8,6 @@ import {
   TouchableWithoutFeedback,
   View,
 } from 'react-native'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import Animated, {
   useSharedValue,
   useAnimatedStyle,
@@ -25,10 +24,17 @@ import {CreateSceneModel} from '../../../state/models/shell-ui'
 import {s, colors} from '../../lib/styles'
 
 export const MainMenu = observer(
-  ({active, onClose}: {active: boolean; onClose: () => void}) => {
+  ({
+    active,
+    insetBottom,
+    onClose,
+  }: {
+    active: boolean
+    insetBottom: number
+    onClose: () => void
+  }) => {
     const store = useStores()
     const initInterp = useSharedValue<number>(0)
-    const insets = useSafeAreaInsets()
 
     useEffect(() => {
       if (active) {
@@ -172,7 +178,7 @@ export const MainMenu = observer(
         <Animated.View
           style={[
             styles.wrapper,
-            {bottom: insets.bottom + 55},
+            {bottom: insetBottom + 45},
             wrapperAnimStyle,
           ]}>
           <SafeAreaView>
@@ -267,7 +273,8 @@ const styles = StyleSheet.create({
     alignItems: 'center',
     height: 40,
     paddingHorizontal: 10,
-    marginBottom: 16,
+    marginTop: 12,
+    marginBottom: 20,
   },
   section: {
     paddingHorizontal: 10,
diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx
index e7c695ca9..ccde52a2c 100644
--- a/src/view/shell/mobile/index.tsx
+++ b/src/view/shell/mobile/index.tsx
@@ -70,7 +70,7 @@ const Btn = ({
   onPress?: (event: GestureResponderEvent) => void
   onLongPress?: (event: GestureResponderEvent) => void
 }) => {
-  let size = 21
+  let size = 24
   let addedStyles
   let IconEl
   if (icon === 'menu') {
@@ -79,17 +79,17 @@ const Btn = ({
     IconEl = GridIconSolid
   } else if (icon === 'home') {
     IconEl = HomeIcon
-    size = 24
+    size = 27
   } else if (icon === 'home-solid') {
     IconEl = HomeIconSolid
-    size = 24
+    size = 27
   } else if (icon === 'bell') {
     IconEl = BellIcon
-    size = 24
+    size = 27
     addedStyles = {position: 'relative', top: -1} as ViewStyle
   } else if (icon === 'bell-solid') {
     IconEl = BellIconSolid
-    size = 24
+    size = 27
     addedStyles = {position: 'relative', top: -1} as ViewStyle
   } else {
     IconEl = FontAwesomeIcon
@@ -316,7 +316,7 @@ export const MobileShell: React.FC = observer(() => {
       <View
         style={[
           styles.bottomBar,
-          {paddingBottom: clamp(safeAreaInsets.bottom, 15, 30)},
+          {paddingBottom: clamp(safeAreaInsets.bottom, 15, 40)},
         ]}>
         <Btn
           icon={isAtHome ? 'home-solid' : 'home'}
@@ -343,6 +343,7 @@ export const MobileShell: React.FC = observer(() => {
       </View>
       <MainMenu
         active={isMainMenuActive}
+        insetBottom={clamp(safeAreaInsets.bottom, 15, 40)}
         onClose={() => setMainMenuActive(false)}
       />
       <Modal />
@@ -491,7 +492,7 @@ const styles = StyleSheet.create({
   },
   ctrl: {
     flex: 1,
-    paddingTop: 15,
+    paddingTop: 12,
     paddingBottom: 5,
   },
   notificationCount: {