about summary refs log tree commit diff
path: root/src/view/screens
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/screens')
-rw-r--r--src/view/screens/Feeds.tsx128
-rw-r--r--src/view/screens/Lists.tsx72
-rw-r--r--src/view/screens/ModerationBlockedAccounts.tsx26
-rw-r--r--src/view/screens/ModerationModlists.tsx64
-rw-r--r--src/view/screens/ModerationMutedAccounts.tsx28
-rw-r--r--src/view/screens/Notifications.tsx178
-rw-r--r--src/view/screens/PostThread.tsx6
-rw-r--r--src/view/screens/Profile.tsx6
-rw-r--r--src/view/screens/ProfileFeed.tsx9
-rw-r--r--src/view/screens/ProfileFollowers.tsx2
-rw-r--r--src/view/screens/ProfileFollows.tsx2
-rw-r--r--src/view/screens/ProfileList.tsx10
-rw-r--r--src/view/screens/SavedFeeds.tsx238
-rw-r--r--src/view/screens/Search/Search.tsx236
14 files changed, 404 insertions, 601 deletions
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 406f11792..0dcf1f016 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -24,14 +24,13 @@ import {useSetMinimalShellMode} from '#/state/shell'
 import {useComposerControls} from '#/state/shell/composer'
 import {ErrorMessage} from '#/view/com/util/error/ErrorMessage'
 import {FAB} from '#/view/com/util/fab/FAB'
-import {TextLink} from '#/view/com/util/Link'
 import {List, ListMethods} from '#/view/com/util/List'
 import {FeedFeedLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder'
 import {Text} from '#/view/com/util/text/Text'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
 import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
 import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
 import {atoms as a, useTheme} from '#/alf'
+import {ButtonIcon} from '#/components/Button'
 import {Divider} from '#/components/Divider'
 import * as FeedCard from '#/components/FeedCard'
 import {SearchInput} from '#/components/forms/SearchInput'
@@ -40,7 +39,9 @@ import {ChevronRight_Stroke2_Corner0_Rounded as ChevronRight} from '#/components
 import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
 import {ListMagnifyingGlass_Stroke2_Corner0_Rounded} from '#/components/icons/ListMagnifyingGlass'
 import {ListSparkle_Stroke2_Corner0_Rounded} from '#/components/icons/ListSparkle'
+import {SettingsGear2_Stroke2_Corner0_Rounded as Gear} from '#/components/icons/SettingsGear2'
 import * as Layout from '#/components/Layout'
+import {Link} from '#/components/Link'
 import * as ListCard from '#/components/ListCard'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Feeds'>
@@ -102,7 +103,7 @@ type FlatlistSlice =
 export function FeedsScreen(_props: Props) {
   const pal = usePalette('default')
   const {openComposer} = useComposerControls()
-  const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
+  const {isMobile} = useWebMediaQueries()
   const [query, setQuery] = React.useState('')
   const [isPTR, setIsPTR] = React.useState(false)
   const {
@@ -374,22 +375,6 @@ export function FeedsScreen(_props: Props) {
     isUserSearching,
   ])
 
-  const renderHeaderBtn = React.useCallback(() => {
-    return (
-      <View style={styles.headerBtnGroup}>
-        <TextLink
-          testID="editFeedsBtn"
-          type="lg-medium"
-          href="/settings/saved-feeds"
-          accessibilityLabel={_(msg`Edit My Feeds`)}
-          accessibilityHint=""
-          text={_(msg`Edit`)}
-          style={[pal.link, a.pr_xs]}
-        />
-      </View>
-    )
-  }, [pal, _])
-
   const searchBarIndex = items.findIndex(
     item => item.type === 'popularFeedsHeader',
   )
@@ -430,36 +415,7 @@ export function FeedsScreen(_props: Props) {
           </View>
         )
       } else if (item.type === 'savedFeedsHeader') {
-        return (
-          <>
-            {!isMobile && (
-              <View
-                style={[
-                  pal.view,
-                  styles.header,
-                  pal.border,
-                  {
-                    borderBottomWidth: 1,
-                  },
-                ]}>
-                <Text type="title-lg" style={[pal.text, s.bold]}>
-                  <Trans>Feeds</Trans>
-                </Text>
-                <View style={styles.headerBtnGroup}>
-                  <TextLink
-                    type="lg"
-                    href="/settings/saved-feeds"
-                    accessibilityLabel={_(msg`Edit My Feeds`)}
-                    accessibilityHint=""
-                    text={_(msg`Edit`)}
-                    style={[pal.link]}
-                  />
-                </View>
-              </View>
-            )}
-            <FeedsSavedHeader />
-          </>
-        )
+        return <FeedsSavedHeader />
       } else if (item.type === 'savedFeedNoResults') {
         return (
           <View
@@ -530,13 +486,8 @@ export function FeedsScreen(_props: Props) {
       return null
     },
     [
-      isMobile,
-      pal.view,
       pal.border,
-      pal.text,
       pal.textLight,
-      pal.link,
-      _,
       query,
       onChangeQuery,
       onPressCancelSearch,
@@ -547,31 +498,45 @@ export function FeedsScreen(_props: Props) {
 
   return (
     <Layout.Screen testID="FeedsScreen">
-      {isMobile && (
-        <ViewHeader
-          title={_(msg`Feeds`)}
-          renderButton={renderHeaderBtn}
-          showBorder
+      <Layout.Center>
+        <Layout.Header.Outer>
+          <Layout.Header.BackButton />
+          <Layout.Header.Content>
+            <Layout.Header.TitleText>
+              <Trans>Feeds</Trans>
+            </Layout.Header.TitleText>
+          </Layout.Header.Content>
+          <Layout.Header.Slot>
+            <Link
+              testID="editFeedsBtn"
+              to="/settings/saved-feeds"
+              label={_(msg`Edit My Feeds`)}
+              size="small"
+              variant="ghost"
+              color="secondary"
+              shape="round"
+              style={[a.justify_center, {right: -3}]}>
+              <ButtonIcon icon={Gear} size="lg" />
+            </Link>
+          </Layout.Header.Slot>
+        </Layout.Header.Outer>
+
+        <List
+          ref={listRef}
+          data={items}
+          keyExtractor={item => item.key}
+          contentContainerStyle={styles.contentContainer}
+          renderItem={renderItem}
+          refreshing={isPTR}
+          onRefresh={isUserSearching ? undefined : onPullToRefresh}
+          initialNumToRender={10}
+          onEndReached={onEndReached}
+          desktopFixedHeight
+          keyboardShouldPersistTaps="handled"
+          keyboardDismissMode="on-drag"
+          sideBorders={false}
         />
-      )}
-
-      <List
-        ref={listRef}
-        style={[!isTabletOrDesktop && s.flex1, styles.list]}
-        data={items}
-        keyExtractor={item => item.key}
-        contentContainerStyle={styles.contentContainer}
-        renderItem={renderItem}
-        refreshing={isPTR}
-        onRefresh={isUserSearching ? undefined : onPullToRefresh}
-        initialNumToRender={10}
-        onEndReached={onEndReached}
-        // @ts-ignore our .web version only -prf
-        desktopFixedHeight
-        scrollIndicatorInsets={{right: 1}}
-        keyboardShouldPersistTaps="handled"
-        keyboardDismissMode="on-drag"
-      />
+      </Layout.Center>
 
       {hasSession && (
         <FAB
@@ -728,7 +693,7 @@ function FeedsSavedHeader() {
       }>
       <IconCircle icon={ListSparkle_Stroke2_Corner0_Rounded} size="lg" />
       <View style={[a.flex_1, a.gap_xs]}>
-        <Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
+        <Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
           <Trans>My Feeds</Trans>
         </Text>
         <Text style={[t.atoms.text_contrast_high]}>
@@ -754,7 +719,7 @@ function FeedsAboutHeader() {
         size="lg"
       />
       <View style={[a.flex_1, a.gap_sm]}>
-        <Text style={[a.flex_1, a.text_2xl, a.font_bold, t.atoms.text]}>
+        <Text style={[a.flex_1, a.text_2xl, a.font_heavy, t.atoms.text]}>
           <Trans>Discover New Feeds</Trans>
         </Text>
         <Text style={[t.atoms.text_contrast_high]}>
@@ -769,9 +734,6 @@ function FeedsAboutHeader() {
 }
 
 const styles = StyleSheet.create({
-  list: {
-    height: '100%',
-  },
   contentContainer: {
     paddingBottom: 100,
   },
diff --git a/src/view/screens/Lists.tsx b/src/view/screens/Lists.tsx
index f654f2bd9..99abf0603 100644
--- a/src/view/screens/Lists.tsx
+++ b/src/view/screens/Lists.tsx
@@ -1,33 +1,26 @@
 import React from 'react'
-import {StyleSheet, View} from 'react-native'
 import {AtUri} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect, useNavigation} from '@react-navigation/native'
 
 import {useEmail} from '#/lib/hooks/useEmail'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
 import {NavigationProp} from '#/lib/routes/types'
-import {s} from '#/lib/styles'
 import {useModalControls} from '#/state/modals'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {MyLists} from '#/view/com/lists/MyLists'
-import {Button} from '#/view/com/util/forms/Button'
-import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
-import {Text} from '#/view/com/util/text/Text'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
 import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
+import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
 import * as Layout from '#/components/Layout'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Lists'>
 export function ListsScreen({}: Props) {
   const {_} = useLingui()
-  const pal = usePalette('default')
   const setMinimalShellMode = useSetMinimalShellMode()
-  const {isMobile} = useWebMediaQueries()
   const navigation = useNavigation<NavigationProp>()
   const {openModal} = useModalControls()
   const {needsEmailVerification} = useEmail()
@@ -62,43 +55,30 @@ export function ListsScreen({}: Props) {
 
   return (
     <Layout.Screen testID="listsScreen">
-      <SimpleViewHeader
-        showBackButton={isMobile}
-        style={[
-          pal.border,
-          isMobile
-            ? {borderBottomWidth: StyleSheet.hairlineWidth}
-            : {
-                borderLeftWidth: StyleSheet.hairlineWidth,
-                borderRightWidth: StyleSheet.hairlineWidth,
-              },
-        ]}>
-        <View style={{flex: 1}}>
-          <Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
-            <Trans>User Lists</Trans>
-          </Text>
-          <Text style={pal.textLight}>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content align="left">
+          <Layout.Header.TitleText>
+            <Trans>Lists</Trans>
+          </Layout.Header.TitleText>
+          <Layout.Header.SubtitleText>
             <Trans>Public, shareable lists which can drive feeds.</Trans>
-          </Text>
-        </View>
-        <View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
-          <Button
-            testID="newUserListBtn"
-            type="default"
-            onPress={onPressNewList}
-            style={{
-              flexDirection: 'row',
-              alignItems: 'center',
-              gap: 8,
-            }}>
-            <FontAwesomeIcon icon="plus" color={pal.colors.text} />
-            <Text type="button" style={pal.text}>
-              <Trans context="action">New</Trans>
-            </Text>
-          </Button>
-        </View>
-      </SimpleViewHeader>
-      <MyLists filter="curate" style={s.flexGrow1} />
+          </Layout.Header.SubtitleText>
+        </Layout.Header.Content>
+        <Button
+          label={_(msg`New list`)}
+          testID="newUserListBtn"
+          color="secondary"
+          variant="solid"
+          size="small"
+          onPress={onPressNewList}>
+          <ButtonIcon icon={PlusIcon} />
+          <ButtonText>
+            <Trans context="action">New</Trans>
+          </ButtonText>
+        </Button>
+      </Layout.Header.Outer>
+      <MyLists filter="curate" style={a.flex_grow} />
       <VerifyEmailDialog
         reasonText={_(
           msg`Before creating a list, you must first verify your email.`,
diff --git a/src/view/screens/ModerationBlockedAccounts.tsx b/src/view/screens/ModerationBlockedAccounts.tsx
index 53e31d1d2..9b0f54984 100644
--- a/src/view/screens/ModerationBlockedAccounts.tsx
+++ b/src/view/screens/ModerationBlockedAccounts.tsx
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
 import {Text} from '#/view/com/util/text/Text'
 import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {CenteredView} from '#/view/com/util/Views'
 import * as Layout from '#/components/Layout'
 
 type Props = NativeStackScreenProps<
@@ -97,14 +96,7 @@ export function ModerationBlockedAccounts({}: Props) {
   )
   return (
     <Layout.Screen testID="blockedAccountsScreen">
-      <CenteredView
-        style={[
-          styles.container,
-          isTabletOrDesktop && styles.containerDesktop,
-          pal.view,
-          pal.border,
-        ]}
-        testID="blockedAccountsScreen">
+      <Layout.Center>
         <ViewHeader title={_(msg`Blocked Accounts`)} showOnDesktop />
         <Text
           type="sm"
@@ -112,6 +104,9 @@ export function ModerationBlockedAccounts({}: Props) {
             styles.description,
             pal.text,
             isTabletOrDesktop && styles.descriptionDesktop,
+            {
+              marginTop: 20,
+            },
           ]}>
           <Trans>
             Blocked accounts cannot reply in your threads, mention you, or
@@ -120,7 +115,7 @@ export function ModerationBlockedAccounts({}: Props) {
           </Trans>
         </Text>
         {isEmpty ? (
-          <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
+          <View style={[pal.border]}>
             {isError ? (
               <ErrorScreen
                 title="Oops!"
@@ -166,21 +161,12 @@ export function ModerationBlockedAccounts({}: Props) {
             desktopFixedHeight
           />
         )}
-      </CenteredView>
+      </Layout.Center>
     </Layout.Screen>
   )
 }
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    paddingBottom: 100,
-  },
-  containerDesktop: {
-    borderLeftWidth: 1,
-    borderRightWidth: 1,
-    paddingBottom: 0,
-  },
   title: {
     textAlign: 'center',
     marginTop: 12,
diff --git a/src/view/screens/ModerationModlists.tsx b/src/view/screens/ModerationModlists.tsx
index c623c5376..0ef4d4389 100644
--- a/src/view/screens/ModerationModlists.tsx
+++ b/src/view/screens/ModerationModlists.tsx
@@ -1,33 +1,26 @@
 import React from 'react'
-import {View} from 'react-native'
 import {AtUri} from '@atproto/api'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 import {useFocusEffect, useNavigation} from '@react-navigation/native'
 
 import {useEmail} from '#/lib/hooks/useEmail'
-import {usePalette} from '#/lib/hooks/usePalette'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
 import {NavigationProp} from '#/lib/routes/types'
-import {s} from '#/lib/styles'
 import {useModalControls} from '#/state/modals'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {MyLists} from '#/view/com/lists/MyLists'
-import {Button} from '#/view/com/util/forms/Button'
-import {SimpleViewHeader} from '#/view/com/util/SimpleViewHeader'
-import {Text} from '#/view/com/util/text/Text'
+import {atoms as a} from '#/alf'
+import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {useDialogControl} from '#/components/Dialog'
 import {VerifyEmailDialog} from '#/components/dialogs/VerifyEmailDialog'
+import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
 import * as Layout from '#/components/Layout'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ModerationModlists'>
 export function ModerationModlistsScreen({}: Props) {
   const {_} = useLingui()
-  const pal = usePalette('default')
   const setMinimalShellMode = useSetMinimalShellMode()
-  const {isMobile} = useWebMediaQueries()
   const navigation = useNavigation<NavigationProp>()
   const {openModal} = useModalControls()
   const {needsEmailVerification} = useEmail()
@@ -62,39 +55,32 @@ export function ModerationModlistsScreen({}: Props) {
 
   return (
     <Layout.Screen testID="moderationModlistsScreen">
-      <SimpleViewHeader
-        showBackButton={isMobile}
-        style={
-          !isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}]
-        }>
-        <View style={{flex: 1}}>
-          <Text type="title-lg" style={[pal.text, {fontWeight: '600'}]}>
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content align="left">
+          <Layout.Header.TitleText>
             <Trans>Moderation Lists</Trans>
-          </Text>
-          <Text style={pal.textLight}>
+          </Layout.Header.TitleText>
+          <Layout.Header.SubtitleText>
             <Trans>
               Public, shareable lists of users to mute or block in bulk.
             </Trans>
-          </Text>
-        </View>
-        <View style={[{marginLeft: 18}, isMobile && {marginLeft: 12}]}>
-          <Button
-            testID="newModListBtn"
-            type="default"
-            onPress={onPressNewList}
-            style={{
-              flexDirection: 'row',
-              alignItems: 'center',
-              gap: 8,
-            }}>
-            <FontAwesomeIcon icon="plus" color={pal.colors.text} />
-            <Text type="button" style={pal.text}>
-              <Trans>New</Trans>
-            </Text>
-          </Button>
-        </View>
-      </SimpleViewHeader>
-      <MyLists filter="mod" style={s.flexGrow1} />
+          </Layout.Header.SubtitleText>
+        </Layout.Header.Content>
+        <Button
+          label={_(msg`New list`)}
+          testID="newModListBtn"
+          color="secondary"
+          variant="solid"
+          size="small"
+          onPress={onPressNewList}>
+          <ButtonIcon icon={PlusIcon} />
+          <ButtonText>
+            <Trans context="action">New</Trans>
+          </ButtonText>
+        </Button>
+      </Layout.Header.Outer>
+      <MyLists filter="mod" style={a.flex_grow} />
       <VerifyEmailDialog
         reasonText={_(
           msg`Before creating a list, you must first verify your email.`,
diff --git a/src/view/screens/ModerationMutedAccounts.tsx b/src/view/screens/ModerationMutedAccounts.tsx
index 6d34c8a5f..6a8c6c3e6 100644
--- a/src/view/screens/ModerationMutedAccounts.tsx
+++ b/src/view/screens/ModerationMutedAccounts.tsx
@@ -23,7 +23,6 @@ import {ProfileCard} from '#/view/com/profile/ProfileCard'
 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
 import {Text} from '#/view/com/util/text/Text'
 import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {CenteredView} from '#/view/com/util/Views'
 import * as Layout from '#/components/Layout'
 
 type Props = NativeStackScreenProps<
@@ -97,21 +96,17 @@ export function ModerationMutedAccounts({}: Props) {
   )
   return (
     <Layout.Screen testID="mutedAccountsScreen">
-      <CenteredView
-        style={[
-          styles.container,
-          isTabletOrDesktop && styles.containerDesktop,
-          pal.view,
-          pal.border,
-        ]}
-        testID="mutedAccountsScreen">
-        <ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
+      <ViewHeader title={_(msg`Muted Accounts`)} showOnDesktop />
+      <Layout.Center>
         <Text
           type="sm"
           style={[
             styles.description,
             pal.text,
             isTabletOrDesktop && styles.descriptionDesktop,
+            {
+              marginTop: 20,
+            },
           ]}>
           <Trans>
             Muted accounts have their posts removed from your feed and from your
@@ -119,7 +114,7 @@ export function ModerationMutedAccounts({}: Props) {
           </Trans>
         </Text>
         {isEmpty ? (
-          <View style={[pal.border, !isTabletOrDesktop && styles.flex1]}>
+          <View style={[pal.border]}>
             {isError ? (
               <ErrorScreen
                 title="Oops!"
@@ -165,21 +160,12 @@ export function ModerationMutedAccounts({}: Props) {
             desktopFixedHeight
           />
         )}
-      </CenteredView>
+      </Layout.Center>
     </Layout.Screen>
   )
 }
 
 const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-    paddingBottom: 100,
-  },
-  containerDesktop: {
-    borderLeftWidth: 1,
-    borderRightWidth: 1,
-    paddingBottom: 0,
-  },
   title: {
     textAlign: 'center',
     marginTop: 12,
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index 531d10a7f..35591f270 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -1,4 +1,4 @@
-import React, {useCallback} from 'react'
+import React from 'react'
 import {View} from 'react-native'
 import {msg, Trans} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
@@ -6,7 +6,6 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'
 import {useQueryClient} from '@tanstack/react-query'
 
 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
-import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
 import {ComposeIcon2} from '#/lib/icons'
 import {
   NativeStackScreenProps,
@@ -14,7 +13,7 @@ import {
 } from '#/lib/routes/types'
 import {s} from '#/lib/styles'
 import {logger} from '#/logger'
-import {isNative} from '#/platform/detection'
+import {isNative, isWeb} from '#/platform/detection'
 import {emitSoftReset, listenSoftReset} from '#/state/events'
 import {RQKEY as NOTIFS_RQKEY} from '#/state/queries/notifications/feed'
 import {
@@ -29,28 +28,25 @@ import {FAB} from '#/view/com/util/fab/FAB'
 import {ListMethods} from '#/view/com/util/List'
 import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
 import {MainScrollProvider} from '#/view/com/util/MainScrollProvider'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {CenteredView} from '#/view/com/util/Views'
-import {atoms as a, useTheme} from '#/alf'
-import {Button} from '#/components/Button'
+import {atoms as a, useBreakpoints, useTheme} from '#/alf'
+import {Button, ButtonIcon} from '#/components/Button'
 import {SettingsGear2_Stroke2_Corner0_Rounded as SettingsIcon} from '#/components/icons/SettingsGear2'
 import * as Layout from '#/components/Layout'
 import {Link} from '#/components/Link'
 import {Loader} from '#/components/Loader'
-import {Text} from '#/components/Typography'
 
 type Props = NativeStackScreenProps<
   NotificationsTabNavigatorParams,
   'Notifications'
 >
 export function NotificationsScreen({route: {params}}: Props) {
+  const t = useTheme()
+  const {gtTablet} = useBreakpoints()
   const {_} = useLingui()
   const setMinimalShellMode = useSetMinimalShellMode()
   const [isScrolledDown, setIsScrolledDown] = React.useState(false)
   const [isLoadingLatest, setIsLoadingLatest] = React.useState(false)
   const scrollElRef = React.useRef<ListMethods>(null)
-  const t = useTheme()
-  const {isDesktop} = useWebMediaQueries()
   const queryClient = useQueryClient()
   const unreadNotifs = useUnreadNotifications()
   const unreadApi = useUnreadNotificationsApi()
@@ -110,121 +106,77 @@ export function NotificationsScreen({route: {params}}: Props) {
     return listenSoftReset(onPressLoadLatest)
   }, [onPressLoadLatest, isScreenFocused])
 
-  const renderButton = useCallback(() => {
-    return (
-      <Link
-        to="/notifications/settings"
-        label={_(msg`Notification settings`)}
-        size="small"
-        variant="ghost"
-        color="secondary"
-        shape="square"
-        style={[a.justify_center]}>
-        <SettingsIcon size="md" style={t.atoms.text_contrast_medium} />
-      </Link>
-    )
-  }, [_, t])
-
-  const ListHeaderComponent = React.useCallback(() => {
-    if (isDesktop) {
-      return (
-        <View
-          style={[
-            t.atoms.bg,
-            a.flex_row,
-            a.align_center,
-            a.justify_between,
-            a.gap_lg,
-            a.px_lg,
-            a.pr_md,
-            a.py_sm,
-          ]}>
+  return (
+    <Layout.Screen testID="notificationsScreen">
+      <Layout.Header.Outer>
+        <Layout.Header.MenuButton />
+        <Layout.Header.Content>
           <Button
             label={_(msg`Notifications`)}
             accessibilityHint={_(msg`Refresh notifications`)}
-            onPress={emitSoftReset}>
-            {({hovered, pressed}) => (
-              <Text
-                style={[
-                  a.text_2xl,
-                  a.font_bold,
-                  (hovered || pressed) && a.underline,
-                ]}>
+            onPress={emitSoftReset}
+            style={[a.justify_start]}>
+            {({hovered}) => (
+              <Layout.Header.TitleText
+                style={[a.w_full, hovered && a.underline]}>
                 <Trans>Notifications</Trans>
-                {hasNew && (
+                {isWeb && gtTablet && hasNew && (
                   <View
-                    style={{
-                      left: 4,
-                      top: -8,
-                      backgroundColor: t.palette.primary_500,
-                      width: 8,
-                      height: 8,
-                      borderRadius: 4,
-                    }}
+                    style={[
+                      a.rounded_full,
+                      {
+                        width: 8,
+                        height: 8,
+                        bottom: 3,
+                        left: 6,
+                        backgroundColor: t.palette.primary_500,
+                      },
+                    ]}
                   />
                 )}
-              </Text>
+              </Layout.Header.TitleText>
             )}
           </Button>
-          <View style={[a.flex_row, a.align_center, a.gap_sm]}>
-            {isLoadingLatest ? <Loader size="md" /> : <></>}
-            {renderButton()}
-          </View>
-        </View>
-      )
-    }
-    return <></>
-  }, [isDesktop, t, hasNew, renderButton, _, isLoadingLatest])
-
-  const renderHeaderSpinner = React.useCallback(() => {
-    return (
-      <View
-        style={[
-          {width: 30, height: 20},
-          a.flex_row,
-          a.align_center,
-          a.justify_end,
-          a.gap_md,
-        ]}>
-        {isLoadingLatest ? <Loader width={20} /> : <></>}
-        {renderButton()}
-      </View>
-    )
-  }, [renderButton, isLoadingLatest])
+        </Layout.Header.Content>
+        <Layout.Header.Slot>
+          <Link
+            to="/notifications/settings"
+            label={_(msg`Notification settings`)}
+            size="small"
+            variant="ghost"
+            color="secondary"
+            shape="round"
+            style={[a.justify_center]}>
+            <ButtonIcon
+              icon={isLoadingLatest ? Loader : SettingsIcon}
+              size="lg"
+            />
+          </Link>
+        </Layout.Header.Slot>
+      </Layout.Header.Outer>
 
-  return (
-    <Layout.Screen testID="notificationsScreen">
-      <CenteredView style={[a.flex_1, {paddingTop: 2}]} sideBorders={true}>
-        <ViewHeader
-          title={_(msg`Notifications`)}
-          canGoBack={false}
-          showBorder={true}
-          renderButton={renderHeaderSpinner}
+      <MainScrollProvider>
+        <Feed
+          onScrolledDownChange={setIsScrolledDown}
+          scrollElRef={scrollElRef}
+          overridePriorityNotifications={params?.show === 'all'}
         />
-        <MainScrollProvider>
-          <Feed
-            onScrolledDownChange={setIsScrolledDown}
-            scrollElRef={scrollElRef}
-            ListHeaderComponent={ListHeaderComponent}
-            overridePriorityNotifications={params?.show === 'all'}
-          />
-        </MainScrollProvider>
-        {(isScrolledDown || hasNew) && (
-          <LoadLatestBtn
-            onPress={onPressLoadLatest}
-            label={_(msg`Load new notifications`)}
-            showIndicator={hasNew}
-          />
-        )}
-        <FAB
-          testID="composeFAB"
-          onPress={() => openComposer({})}
-          icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
-          accessibilityRole="button"
-          accessibilityLabel={_(msg`New post`)}
-          accessibilityHint=""
+      </MainScrollProvider>
+      {(isScrolledDown || hasNew) && (
+        <LoadLatestBtn
+          onPress={onPressLoadLatest}
+          label={_(msg`Load new notifications`)}
+          showIndicator={hasNew}
         />
-      </CenteredView>
+      )}
+      <FAB
+        testID="composeFAB"
+        onPress={() => openComposer({})}
+        icon={<ComposeIcon2 strokeWidth={1.5} size={29} style={s.white} />}
+        accessibilityRole="button"
+        accessibilityLabel={_(msg`New post`)}
+        accessibilityHint=""
+      />
     </Layout.Screen>
   )
 }
diff --git a/src/view/screens/PostThread.tsx b/src/view/screens/PostThread.tsx
index c183569b7..1bad9b6cd 100644
--- a/src/view/screens/PostThread.tsx
+++ b/src/view/screens/PostThread.tsx
@@ -1,12 +1,10 @@
 import React from 'react'
-import {View} from 'react-native'
 import {useFocusEffect} from '@react-navigation/native'
 
 import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types'
 import {makeRecordUri} from '#/lib/strings/url-helpers'
 import {useSetMinimalShellMode} from '#/state/shell'
 import {PostThread as PostThreadComponent} from '#/view/com/post-thread/PostThread'
-import {atoms as a} from '#/alf'
 import * as Layout from '#/components/Layout'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'PostThread'>
@@ -24,9 +22,7 @@ export function PostThreadScreen({route}: Props) {
 
   return (
     <Layout.Screen testID="postThreadScreen">
-      <View style={a.flex_1}>
-        <PostThreadComponent uri={uri} />
-      </View>
+      <PostThreadComponent uri={uri} />
     </Layout.Screen>
   )
 }
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 677fe09f4..6a9b6f7f2 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -40,11 +40,9 @@ import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
 import {ErrorScreen} from '#/view/com/util/error/ErrorScreen'
 import {FAB} from '#/view/com/util/fab/FAB'
 import {ListRef} from '#/view/com/util/List'
-import {CenteredView} from '#/view/com/util/Views'
 import {ProfileHeader, ProfileHeaderLoading} from '#/screens/Profile/Header'
 import {ProfileFeedSection} from '#/screens/Profile/Sections/Feed'
 import {ProfileLabelsSection} from '#/screens/Profile/Sections/Labels'
-import {web} from '#/alf'
 import * as Layout from '#/components/Layout'
 import {ScreenHider} from '#/components/moderation/ScreenHider'
 import {ProfileStarterPacks} from '#/components/StarterPack/ProfileStarterPacks'
@@ -116,9 +114,9 @@ function ProfileScreenInner({route}: Props) {
   // Most pushes will happen here, since we will have only placeholder data
   if (isLoadingDid || isLoadingProfile || starterPacksQuery.isLoading) {
     return (
-      <CenteredView sideBorders style={web({height: '100vh'})}>
+      <Layout.Content>
         <ProfileHeaderLoading />
-      </CenteredView>
+      </Layout.Content>
     )
   }
   if (resolveError || profileError) {
diff --git a/src/view/screens/ProfileFeed.tsx b/src/view/screens/ProfileFeed.tsx
index b34f0f1b0..63469ef4f 100644
--- a/src/view/screens/ProfileFeed.tsx
+++ b/src/view/screens/ProfileFeed.tsx
@@ -49,7 +49,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
 import {LoadingScreen} from '#/view/com/util/LoadingScreen'
 import {Text} from '#/view/com/util/text/Text'
 import * as Toast from '#/view/com/util/Toast'
-import {CenteredView} from '#/view/com/util/Views'
 import {atoms as a, useTheme} from '#/alf'
 import {Button as NewButton, ButtonText} from '#/components/Button'
 import {useRichText} from '#/components/hooks/useRichText'
@@ -98,7 +97,7 @@ export function ProfileFeedScreen(props: Props) {
   if (error) {
     return (
       <Layout.Screen testID="profileFeedScreenError">
-        <CenteredView>
+        <Layout.Content>
           <View style={[pal.view, pal.border, styles.notFoundContainer]}>
             <Text type="title-lg" style={[pal.text, s.mb10]}>
               <Trans>Could not load feed</Trans>
@@ -120,7 +119,7 @@ export function ProfileFeedScreen(props: Props) {
               </Button>
             </View>
           </View>
-        </CenteredView>
+        </Layout.Content>
       </Layout.Screen>
     )
   }
@@ -394,7 +393,7 @@ export function ProfileFeedScreenInner({
   ])
 
   return (
-    <View style={s.hContentRegion}>
+    <>
       <ReportDialog
         control={reportDialogControl}
         params={{
@@ -434,7 +433,7 @@ export function ProfileFeedScreenInner({
           accessibilityHint=""
         />
       )}
-    </View>
+    </>
   )
 }
 
diff --git a/src/view/screens/ProfileFollowers.tsx b/src/view/screens/ProfileFollowers.tsx
index 9fa98cb1a..90c0a57f9 100644
--- a/src/view/screens/ProfileFollowers.tsx
+++ b/src/view/screens/ProfileFollowers.tsx
@@ -10,7 +10,6 @@ import {ProfileFollowers as ProfileFollowersComponent} from '#/view/com/profile/
 import {ViewHeader} from '#/view/com/util/ViewHeader'
 import {CenteredView} from '#/view/com/util/Views'
 import * as Layout from '#/components/Layout'
-import {ListHeaderDesktop} from '#/components/Lists'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollowers'>
 export const ProfileFollowersScreen = ({route}: Props) => {
@@ -27,7 +26,6 @@ export const ProfileFollowersScreen = ({route}: Props) => {
   return (
     <Layout.Screen testID="profileFollowersScreen">
       <CenteredView sideBorders={true}>
-        <ListHeaderDesktop title={_(msg`Followers`)} />
         <ViewHeader title={_(msg`Followers`)} showBorder={!isWeb} />
         <ProfileFollowersComponent name={name} />
       </CenteredView>
diff --git a/src/view/screens/ProfileFollows.tsx b/src/view/screens/ProfileFollows.tsx
index 483ee93ec..134f79993 100644
--- a/src/view/screens/ProfileFollows.tsx
+++ b/src/view/screens/ProfileFollows.tsx
@@ -10,7 +10,6 @@ import {ProfileFollows as ProfileFollowsComponent} from '#/view/com/profile/Prof
 import {ViewHeader} from '#/view/com/util/ViewHeader'
 import {CenteredView} from '#/view/com/util/Views'
 import * as Layout from '#/components/Layout'
-import {ListHeaderDesktop} from '#/components/Lists'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'ProfileFollows'>
 export const ProfileFollowsScreen = ({route}: Props) => {
@@ -27,7 +26,6 @@ export const ProfileFollowsScreen = ({route}: Props) => {
   return (
     <Layout.Screen testID="profileFollowsScreen">
       <CenteredView sideBorders={true}>
-        <ListHeaderDesktop title={_(msg`Following`)} />
         <ViewHeader title={_(msg`Following`)} showBorder={!isWeb} />
         <ProfileFollowsComponent name={name} />
       </CenteredView>
diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx
index cb333befa..a927526ad 100644
--- a/src/view/screens/ProfileList.tsx
+++ b/src/view/screens/ProfileList.tsx
@@ -69,7 +69,6 @@ import {LoadLatestBtn} from '#/view/com/util/load-latest/LoadLatestBtn'
 import {LoadingScreen} from '#/view/com/util/LoadingScreen'
 import {Text} from '#/view/com/util/text/Text'
 import * as Toast from '#/view/com/util/Toast'
-import {CenteredView} from '#/view/com/util/Views'
 import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen'
 import {atoms as a, useTheme} from '#/alf'
 import {useDialogControl} from '#/components/Dialog'
@@ -107,20 +106,20 @@ function ProfileListScreenInner(props: Props) {
 
   if (resolveError) {
     return (
-      <CenteredView>
+      <Layout.Content>
         <ErrorScreen
           error={_(
             msg`We're sorry, but we were unable to resolve this list. If this persists, please contact the list creator, @${handleOrDid}.`,
           )}
         />
-      </CenteredView>
+      </Layout.Content>
     )
   }
   if (listError) {
     return (
-      <CenteredView>
+      <Layout.Content>
         <ErrorScreen error={cleanError(listError)} />
-      </CenteredView>
+      </Layout.Content>
     )
   }
 
@@ -1010,7 +1009,6 @@ function ErrorScreen({error}: {error: string}) {
         pal.view,
         pal.border,
         {
-          marginTop: 10,
           paddingHorizontal: 18,
           paddingVertical: 14,
           borderTopWidth: StyleSheet.hairlineWidth,
diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx
index 3c04ec36f..1b4c84a60 100644
--- a/src/view/screens/SavedFeeds.tsx
+++ b/src/view/screens/SavedFeeds.tsx
@@ -25,13 +25,12 @@ import {FeedSourceCard} from '#/view/com/feeds/FeedSourceCard'
 import {TextLink} from '#/view/com/util/Link'
 import {Text} from '#/view/com/util/text/Text'
 import * as Toast from '#/view/com/util/Toast'
-import {ViewHeader} from '#/view/com/util/ViewHeader'
-import {CenteredView, ScrollView} from '#/view/com/util/Views'
 import {NoFollowingFeed} from '#/screens/Feeds/NoFollowingFeed'
 import {NoSavedFeedsOfAnyType} from '#/screens/Feeds/NoSavedFeedsOfAnyType'
 import {atoms as a, useTheme} from '#/alf'
 import {Button, ButtonIcon, ButtonText} from '#/components/Button'
 import {FilterTimeline_Stroke2_Corner0_Rounded as FilterTimeline} from '#/components/icons/FilterTimeline'
+import {FloppyDisk_Stroke2_Corner0_Rounded as SaveIcon} from '#/components/icons/FloppyDisk'
 import * as Layout from '#/components/Layout'
 import {Loader} from '#/components/Loader'
 
@@ -51,7 +50,7 @@ function SavedFeedsInner({
 }) {
   const pal = usePalette('default')
   const {_} = useLingui()
-  const {isMobile, isTabletOrDesktop, isDesktop} = useWebMediaQueries()
+  const {isMobile, isDesktop} = useWebMediaQueries()
   const setMinimalShellMode = useSetMinimalShellMode()
   const {mutateAsync: overwriteSavedFeeds, isPending: isOverwritePending} =
     useOverwriteSavedFeedsMutation()
@@ -88,136 +87,128 @@ function SavedFeedsInner({
     }
   }, [_, overwriteSavedFeeds, currentFeeds, navigation])
 
-  const renderHeaderBtn = React.useCallback(() => {
-    return (
-      <Button
-        size="small"
-        variant={hasUnsavedChanges ? 'solid' : 'solid'}
-        color={hasUnsavedChanges ? 'primary' : 'secondary'}
-        onPress={onSaveChanges}
-        label={_(msg`Save changes`)}
-        disabled={isOverwritePending || !hasUnsavedChanges}
-        style={[isDesktop && a.mt_sm]}
-        testID="saveChangesBtn">
-        <ButtonText style={[isDesktop && a.text_md]}>
-          {isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
-        </ButtonText>
-        {isOverwritePending && <ButtonIcon icon={Loader} />}
-      </Button>
-    )
-  }, [_, isDesktop, onSaveChanges, hasUnsavedChanges, isOverwritePending])
-
   return (
     <Layout.Screen>
-      <CenteredView
-        style={[a.util_screen_outer]}
-        sideBorders={isTabletOrDesktop}>
-        <ViewHeader
-          title={_(msg`Edit My Feeds`)}
-          showOnDesktop
-          showBorder
-          renderButton={renderHeaderBtn}
-        />
-        <ScrollView style={[a.flex_1]} contentContainerStyle={[a.border_0]}>
-          {noSavedFeedsOfAnyType && (
-            <View style={[pal.border, a.border_b]}>
-              <NoSavedFeedsOfAnyType />
-            </View>
-          )}
+      <Layout.Header.Outer>
+        <Layout.Header.BackButton />
+        <Layout.Header.Content align="left">
+          <Layout.Header.TitleText>
+            <Trans>Feeds</Trans>
+          </Layout.Header.TitleText>
+        </Layout.Header.Content>
+        <Button
+          testID="saveChangesBtn"
+          size="small"
+          variant={hasUnsavedChanges ? 'solid' : 'solid'}
+          color={hasUnsavedChanges ? 'primary' : 'secondary'}
+          onPress={onSaveChanges}
+          label={_(msg`Save changes`)}
+          disabled={isOverwritePending || !hasUnsavedChanges}>
+          <ButtonIcon icon={isOverwritePending ? Loader : SaveIcon} />
+          <ButtonText>
+            {isDesktop ? <Trans>Save changes</Trans> : <Trans>Save</Trans>}
+          </ButtonText>
+        </Button>
+      </Layout.Header.Outer>
 
-          <View style={[pal.text, pal.border, styles.title]}>
-            <Text type="title" style={pal.text}>
-              <Trans>Pinned Feeds</Trans>
-            </Text>
+      <Layout.Content>
+        {noSavedFeedsOfAnyType && (
+          <View style={[pal.border, a.border_b]}>
+            <NoSavedFeedsOfAnyType />
           </View>
+        )}
 
-          {preferences ? (
-            !pinnedFeeds.length ? (
-              <View
-                style={[
-                  pal.border,
-                  isMobile && s.flex1,
-                  pal.viewLight,
-                  styles.empty,
-                ]}>
-                <Text type="lg" style={[pal.text]}>
-                  <Trans>You don't have any pinned feeds.</Trans>
-                </Text>
-              </View>
-            ) : (
-              pinnedFeeds.map(f => (
-                <ListItem
-                  key={f.id}
-                  feed={f}
-                  isPinned
-                  currentFeeds={currentFeeds}
-                  setCurrentFeeds={setCurrentFeeds}
-                  preferences={preferences}
-                />
-              ))
-            )
-          ) : (
-            <ActivityIndicator style={{marginTop: 20}} />
-          )}
+        <View style={[pal.text, pal.border, styles.title]}>
+          <Text type="title" style={pal.text}>
+            <Trans>Pinned Feeds</Trans>
+          </Text>
+        </View>
 
-          {noFollowingFeed && (
-            <View style={[pal.border, a.border_b]}>
-              <NoFollowingFeed />
+        {preferences ? (
+          !pinnedFeeds.length ? (
+            <View
+              style={[
+                pal.border,
+                isMobile && s.flex1,
+                pal.viewLight,
+                styles.empty,
+              ]}>
+              <Text type="lg" style={[pal.text]}>
+                <Trans>You don't have any pinned feeds.</Trans>
+              </Text>
             </View>
-          )}
+          ) : (
+            pinnedFeeds.map(f => (
+              <ListItem
+                key={f.id}
+                feed={f}
+                isPinned
+                currentFeeds={currentFeeds}
+                setCurrentFeeds={setCurrentFeeds}
+                preferences={preferences}
+              />
+            ))
+          )
+        ) : (
+          <ActivityIndicator style={{marginTop: 20}} />
+        )}
 
-          <View style={[pal.text, pal.border, styles.title]}>
-            <Text type="title" style={pal.text}>
-              <Trans>Saved Feeds</Trans>
-            </Text>
+        {noFollowingFeed && (
+          <View style={[pal.border, a.border_b]}>
+            <NoFollowingFeed />
           </View>
-          {preferences ? (
-            !unpinnedFeeds.length ? (
-              <View
-                style={[
-                  pal.border,
-                  isMobile && s.flex1,
-                  pal.viewLight,
-                  styles.empty,
-                ]}>
-                <Text type="lg" style={[pal.text]}>
-                  <Trans>You don't have any saved feeds.</Trans>
-                </Text>
-              </View>
-            ) : (
-              unpinnedFeeds.map(f => (
-                <ListItem
-                  key={f.id}
-                  feed={f}
-                  isPinned={false}
-                  currentFeeds={currentFeeds}
-                  setCurrentFeeds={setCurrentFeeds}
-                  preferences={preferences}
-                />
-              ))
-            )
+        )}
+
+        <View style={[pal.text, pal.border, styles.title]}>
+          <Text type="title" style={pal.text}>
+            <Trans>Saved Feeds</Trans>
+          </Text>
+        </View>
+        {preferences ? (
+          !unpinnedFeeds.length ? (
+            <View
+              style={[
+                pal.border,
+                isMobile && s.flex1,
+                pal.viewLight,
+                styles.empty,
+              ]}>
+              <Text type="lg" style={[pal.text]}>
+                <Trans>You don't have any saved feeds.</Trans>
+              </Text>
+            </View>
           ) : (
-            <ActivityIndicator style={{marginTop: 20}} />
-          )}
+            unpinnedFeeds.map(f => (
+              <ListItem
+                key={f.id}
+                feed={f}
+                isPinned={false}
+                currentFeeds={currentFeeds}
+                setCurrentFeeds={setCurrentFeeds}
+                preferences={preferences}
+              />
+            ))
+          )
+        ) : (
+          <ActivityIndicator style={{marginTop: 20}} />
+        )}
 
-          <View style={styles.footerText}>
-            <Text type="sm" style={pal.textLight}>
-              <Trans>
-                Feeds are custom algorithms that users build with a little
-                coding expertise.{' '}
-                <TextLink
-                  type="sm"
-                  style={pal.link}
-                  href="https://github.com/bluesky-social/feed-generator"
-                  text={_(msg`See this guide`)}
-                />{' '}
-                for more information.
-              </Trans>
-            </Text>
-          </View>
-          <View style={{height: 100}} />
-        </ScrollView>
-      </CenteredView>
+        <View style={styles.footerText}>
+          <Text type="sm" style={pal.textLight}>
+            <Trans>
+              Feeds are custom algorithms that users build with a little coding
+              expertise.{' '}
+              <TextLink
+                type="sm"
+                style={pal.link}
+                href="https://github.com/bluesky-social/feed-generator"
+                text={_(msg`See this guide`)}
+              />{' '}
+              for more information.
+            </Trans>
+          </Text>
+        </View>
+      </Layout.Content>
     </Layout.Screen>
   )
 }
@@ -456,7 +447,6 @@ const styles = StyleSheet.create({
   },
   footerText: {
     paddingHorizontal: 26,
-    paddingTop: 22,
-    paddingBottom: 100,
+    paddingVertical: 22,
   },
 })
diff --git a/src/view/screens/Search/Search.tsx b/src/view/screens/Search/Search.tsx
index 0518bc506..0871458c9 100644
--- a/src/view/screens/Search/Search.tsx
+++ b/src/view/screens/Search/Search.tsx
@@ -55,7 +55,6 @@ import {ProfileCardWithFollowBtn} from '#/view/com/profile/ProfileCard'
 import {Link} from '#/view/com/util/Link'
 import {List} from '#/view/com/util/List'
 import {Text} from '#/view/com/util/text/Text'
-import {CenteredView, ScrollView} from '#/view/com/util/Views'
 import {Explore} from '#/view/screens/Search/Explore'
 import {SearchLinkCard, SearchProfileCard} from '#/view/shell/desktop/Search'
 import {makeSearchQuery, parseSearchQuery} from '#/screens/Search/utils'
@@ -68,63 +67,46 @@ import {Menu_Stroke2_Corner0_Rounded as Menu} from '#/components/icons/Menu'
 import * as Layout from '#/components/Layout'
 
 function Loader() {
-  const pal = usePalette('default')
-  const {isMobile} = useWebMediaQueries()
   return (
-    <CenteredView
-      style={[
-        // @ts-ignore web only -prf
-        {
-          padding: 18,
-          height: isWeb ? '100vh' : undefined,
-        },
-        pal.border,
-      ]}
-      sideBorders={!isMobile}>
-      <ActivityIndicator />
-    </CenteredView>
+    <Layout.Content>
+      <View style={[a.py_xl]}>
+        <ActivityIndicator />
+      </View>
+    </Layout.Content>
   )
 }
 
 function EmptyState({message, error}: {message: string; error?: string}) {
   const pal = usePalette('default')
-  const {isMobile} = useWebMediaQueries()
 
   return (
-    <CenteredView
-      sideBorders={!isMobile}
-      style={[
-        pal.border,
-        // @ts-ignore web only -prf
-        {
-          padding: 18,
-          height: isWeb ? '100vh' : undefined,
-        },
-      ]}>
-      <View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
-        <Text style={[pal.text]}>{message}</Text>
+    <Layout.Content>
+      <View style={[a.p_xl]}>
+        <View style={[pal.viewLight, {padding: 18, borderRadius: 8}]}>
+          <Text style={[pal.text]}>{message}</Text>
 
-        {error && (
-          <>
-            <View
-              style={[
-                {
-                  marginVertical: 12,
-                  height: 1,
-                  width: '100%',
-                  backgroundColor: pal.text.color,
-                  opacity: 0.2,
-                },
-              ]}
-            />
+          {error && (
+            <>
+              <View
+                style={[
+                  {
+                    marginVertical: 12,
+                    height: 1,
+                    width: '100%',
+                    backgroundColor: pal.text.color,
+                    opacity: 0.2,
+                  },
+                ]}
+              />
 
-            <Text style={[pal.textLight]}>
-              <Trans>Error:</Trans> {error}
-            </Text>
-          </>
-        )}
+              <Text style={[pal.textLight]}>
+                <Trans>Error:</Trans> {error}
+              </Text>
+            </>
+          )}
+        </View>
       </View>
-    </CenteredView>
+    </Layout.Content>
   )
 }
 
@@ -224,7 +206,7 @@ let SearchScreenPostResults = ({
                 if (item.type === 'post') {
                   return <Post post={item.post} />
                 } else {
-                  return <Loader />
+                  return null
                 }
               }}
               keyExtractor={item => item.key}
@@ -550,19 +532,13 @@ let SearchScreenInner = ({
     <Pager
       onPageSelected={onPageSelected}
       renderTabBar={props => (
-        <CenteredView
-          sideBorders
+        <Layout.Center
           style={[
-            pal.border,
-            pal.view,
-            web({
-              position: isWeb ? 'sticky' : '',
-              zIndex: 1,
-            }),
+            web([a.sticky, a.z_10]),
             {top: isWeb ? headerHeight : undefined},
           ]}>
           <TabBar items={sections.map(section => section.title)} {...props} />
-        </CenteredView>
+        </Layout.Center>
       )}
       initialPage={0}>
       {sections.map((section, i) => (
@@ -572,7 +548,7 @@ let SearchScreenInner = ({
   ) : hasSession ? (
     <Explore />
   ) : (
-    <CenteredView sideBorders style={pal.border}>
+    <Layout.Center>
       <View
         // @ts-ignore web only -esb
         style={{
@@ -614,7 +590,7 @@ let SearchScreenInner = ({
           </Text>
         </View>
       </View>
-    </CenteredView>
+    </Layout.Center>
   )
 }
 SearchScreenInner = React.memo(SearchScreenInner)
@@ -650,7 +626,7 @@ export function SearchScreen(
    * Arbitrary sizing, so guess and check, used for sticky header alignment and
    * sizing.
    */
-  const headerHeight = 64 + (showFilters ? 40 : 0)
+  const headerHeight = 60 + (showFilters ? 40 : 0)
 
   useFocusEffect(
     useNonReactiveCallback(() => {
@@ -861,73 +837,79 @@ export function SearchScreen(
 
   return (
     <Layout.Screen testID="searchScreen">
-      <CenteredView
+      <View
         style={[
-          a.p_md,
-          a.pb_sm,
-          a.gap_sm,
-          t.atoms.bg,
           web({
             height: headerHeight,
             position: 'sticky',
             top: 0,
             zIndex: 1,
           }),
-        ]}
-        sideBorders={gtMobile}>
-        <View style={[a.flex_row, a.gap_sm]}>
-          {!gtMobile && (
-            <Button
-              testID="viewHeaderBackOrMenuBtn"
-              onPress={onPressMenu}
-              hitSlop={HITSLOP_10}
-              label={_(msg`Menu`)}
-              accessibilityHint={_(msg`Access navigation links and settings`)}
-              size="large"
-              variant="solid"
-              color="secondary"
-              shape="square">
-              <ButtonIcon icon={Menu} size="lg" />
-            </Button>
-          )}
-          <View style={[a.flex_1]}>
-            <SearchInput
-              ref={textInput}
-              value={searchText}
-              onFocus={onSearchInputFocus}
-              onChangeText={onChangeText}
-              onClearText={onPressClearQuery}
-              onSubmitEditing={onSubmit}
-            />
-          </View>
-          {showAutocomplete && (
-            <Button
-              label={_(msg`Cancel search`)}
-              size="large"
-              variant="ghost"
-              color="secondary"
-              style={[a.px_sm]}
-              onPress={onPressCancelSearch}
-              hitSlop={HITSLOP_10}>
-              <ButtonText>
-                <Trans>Cancel</Trans>
-              </ButtonText>
-            </Button>
-          )}
-        </View>
-
-        {showFilters && (
-          <View
-            style={[a.flex_row, a.align_center, a.justify_between, a.gap_sm]}>
-            <View style={[{width: 140}]}>
-              <SearchLanguageDropdown
-                value={params.lang}
-                onChange={params.setLang}
-              />
+        ]}>
+        <Layout.Center>
+          <View style={[a.p_md, a.pb_sm, a.gap_sm, t.atoms.bg]}>
+            <View style={[a.flex_row, a.gap_sm]}>
+              {!gtMobile && (
+                <Button
+                  testID="viewHeaderBackOrMenuBtn"
+                  onPress={onPressMenu}
+                  hitSlop={HITSLOP_10}
+                  label={_(msg`Menu`)}
+                  accessibilityHint={_(
+                    msg`Access navigation links and settings`,
+                  )}
+                  size="large"
+                  variant="solid"
+                  color="secondary"
+                  shape="square">
+                  <ButtonIcon icon={Menu} size="lg" />
+                </Button>
+              )}
+              <View style={[a.flex_1]}>
+                <SearchInput
+                  ref={textInput}
+                  value={searchText}
+                  onFocus={onSearchInputFocus}
+                  onChangeText={onChangeText}
+                  onClearText={onPressClearQuery}
+                  onSubmitEditing={onSubmit}
+                />
+              </View>
+              {showAutocomplete && (
+                <Button
+                  label={_(msg`Cancel search`)}
+                  size="large"
+                  variant="ghost"
+                  color="secondary"
+                  style={[a.px_sm]}
+                  onPress={onPressCancelSearch}
+                  hitSlop={HITSLOP_10}>
+                  <ButtonText>
+                    <Trans>Cancel</Trans>
+                  </ButtonText>
+                </Button>
+              )}
             </View>
+
+            {showFilters && (
+              <View
+                style={[
+                  a.flex_row,
+                  a.align_center,
+                  a.justify_between,
+                  a.gap_sm,
+                ]}>
+                <View style={[{width: 140}]}>
+                  <SearchLanguageDropdown
+                    value={params.lang}
+                    onChange={params.setLang}
+                  />
+                </View>
+              </View>
+            )}
           </View>
-        )}
-      </CenteredView>
+        </Layout.Center>
+      </View>
 
       <View
         style={{
@@ -992,10 +974,7 @@ let AutocompleteResults = ({
       !moderationOpts ? (
         <Loader />
       ) : (
-        <ScrollView
-          style={{height: '100%'}}
-          // @ts-ignore web only -prf
-          dataSet={{stableGutters: '1'}}
+        <Layout.Content
           keyboardShouldPersistTaps="handled"
           keyboardDismissMode="on-drag">
           <SearchLinkCard
@@ -1020,7 +999,7 @@ let AutocompleteResults = ({
             />
           ))}
           <View style={{height: 200}} />
-        </ScrollView>
+        </Layout.Content>
       )}
     </>
   )
@@ -1042,17 +1021,12 @@ function SearchHistory({
   onRemoveItemClick: (item: string) => void
   onRemoveProfileClick: (profile: AppBskyActorDefs.ProfileViewBasic) => void
 }) {
-  const {isTabletOrDesktop, isMobile} = useWebMediaQueries()
+  const {isMobile} = useWebMediaQueries()
   const pal = usePalette('default')
   const {_} = useLingui()
 
   return (
-    <CenteredView
-      sideBorders={isTabletOrDesktop}
-      // @ts-ignore web only -prf
-      style={{
-        height: isWeb ? '100vh' : undefined,
-      }}>
+    <Layout.Content>
       <View style={styles.searchHistoryContainer}>
         {(searchHistory.length > 0 || selectedProfiles.length > 0) && (
           <Text style={[pal.text, styles.searchHistoryTitle]}>
@@ -1152,7 +1126,7 @@ function SearchHistory({
           </View>
         )}
       </View>
-    </CenteredView>
+    </Layout.Content>
   )
 }