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/CustomFeed.tsx391
-rw-r--r--src/view/screens/DiscoverFeeds.tsx157
-rw-r--r--src/view/screens/Feeds.tsx320
-rw-r--r--src/view/screens/Home.tsx87
-rw-r--r--src/view/screens/Notifications.tsx57
-rw-r--r--src/view/screens/PreferencesHomeFeed.tsx66
-rw-r--r--src/view/screens/Profile.tsx4
-rw-r--r--src/view/screens/SavedFeeds.tsx2
-rw-r--r--src/view/screens/Settings.tsx11
9 files changed, 569 insertions, 526 deletions
diff --git a/src/view/screens/CustomFeed.tsx b/src/view/screens/CustomFeed.tsx
index af4d01843..eaa21f292 100644
--- a/src/view/screens/CustomFeed.tsx
+++ b/src/view/screens/CustomFeed.tsx
@@ -1,7 +1,7 @@
 import React, {useMemo, useRef} from 'react'
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {useNavigation} from '@react-navigation/native'
+import {useNavigation, useIsFocused} from '@react-navigation/native'
 import {usePalette} from 'lib/hooks/usePalette'
 import {HeartIcon, HeartIconSolid} from 'lib/icons'
 import {CommonNavigatorParams} from 'lib/routes/types'
@@ -14,11 +14,8 @@ import {PostsFeedModel} from 'state/models/feeds/posts'
 import {useCustomFeed} from 'lib/hooks/useCustomFeed'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
 import {Feed} from 'view/com/posts/Feed'
-import {pluralize} from 'lib/strings/helpers'
-import {sanitizeHandle} from 'lib/strings/handles'
 import {TextLink} from 'view/com/util/Link'
-import {UserAvatar} from 'view/com/util/UserAvatar'
-import {ViewHeader} from 'view/com/util/ViewHeader'
+import {SimpleViewHeader} from 'view/com/util/SimpleViewHeader'
 import {Button} from 'view/com/util/forms/Button'
 import {Text} from 'view/com/util/text/Text'
 import * as Toast from 'view/com/util/Toast'
@@ -34,7 +31,6 @@ import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {EmptyState} from 'view/com/util/EmptyState'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {NativeDropdown, DropdownItem} from 'view/com/util/forms/NativeDropdown'
-import {makeProfileLink} from 'lib/routes/links'
 import {resolveName} from 'lib/api'
 import {CenteredView} from 'view/com/util/Views'
 import {NavigationProp} from 'lib/routes/types'
@@ -125,7 +121,10 @@ export const CustomFeedScreenInner = observer(
   }: Props & {feedOwnerDid: string}) {
     const store = useStores()
     const pal = usePalette('default')
-    const {isTabletOrDesktop} = useWebMediaQueries()
+    const palInverted = usePalette('inverted')
+    const navigation = useNavigation<NavigationProp>()
+    const isScreenFocused = useIsFocused()
+    const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
     const {track} = useAnalytics()
     const {rkey, name: handleOrDid} = route.params
     const uri = useMemo(
@@ -186,6 +185,10 @@ export const CustomFeedScreenInner = observer(
       })
     }, [store, currentFeed])
 
+    const onPressViewAuthor = React.useCallback(() => {
+      navigation.navigate('Profile', {name: handleOrDid})
+    }, [handleOrDid, navigation])
+
     const onPressShare = React.useCallback(() => {
       const url = toShareUrl(`/profile/${handleOrDid}/feed/${rkey}`)
       shareUrl(url)
@@ -210,9 +213,40 @@ export const CustomFeedScreenInner = observer(
       store.shell.openComposer({})
     }, [store])
 
+    const onSoftReset = React.useCallback(() => {
+      if (isScreenFocused) {
+        onScrollToTop()
+        algoFeed.refresh()
+      }
+    }, [isScreenFocused, onScrollToTop, algoFeed])
+
+    // fires when page within screen is activated/deactivated
+    React.useEffect(() => {
+      if (!isScreenFocused) {
+        return
+      }
+
+      const softResetSub = store.onScreenSoftReset(onSoftReset)
+      return () => {
+        softResetSub.remove()
+      }
+    }, [store, onSoftReset, isScreenFocused])
+
     const dropdownItems: DropdownItem[] = React.useMemo(() => {
       let items: DropdownItem[] = [
         {
+          testID: 'feedHeaderDropdownViewAuthorBtn',
+          label: 'View author',
+          onPress: onPressViewAuthor,
+          icon: {
+            ios: {
+              name: 'person',
+            },
+            android: '',
+            web: ['far', 'user'],
+          },
+        },
+        {
           testID: 'feedHeaderDropdownToggleSavedBtn',
           label: currentFeed?.isSaved
             ? 'Remove from my feeds'
@@ -260,232 +294,12 @@ export const CustomFeedScreenInner = observer(
         },
       ]
       return items
-    }, [currentFeed?.isSaved, onToggleSaved, onPressReport, onPressShare])
-
-    const renderHeaderBtns = React.useCallback(() => {
-      return (
-        <View style={styles.headerBtns}>
-          <Button
-            type="default-light"
-            testID="toggleLikeBtn"
-            accessibilityLabel="Like this feed"
-            accessibilityHint=""
-            onPress={onToggleLiked}>
-            {currentFeed?.isLiked ? (
-              <HeartIconSolid size={19} style={styles.liked} />
-            ) : (
-              <HeartIcon strokeWidth={3} size={19} style={pal.textLight} />
-            )}
-          </Button>
-          {currentFeed?.isSaved ? (
-            <Button
-              type="default-light"
-              accessibilityLabel={
-                isPinned ? 'Unpin this feed' : 'Pin this feed'
-              }
-              accessibilityHint=""
-              onPress={onTogglePinned}>
-              <FontAwesomeIcon
-                icon="thumb-tack"
-                size={17}
-                color={isPinned ? colors.blue3 : pal.colors.textLight}
-                style={styles.top1}
-              />
-            </Button>
-          ) : undefined}
-          {!currentFeed?.isSaved ? (
-            <Button
-              type="default-light"
-              onPress={onToggleSaved}
-              accessibilityLabel="Add to my feeds"
-              accessibilityHint=""
-              style={styles.headerAddBtn}>
-              <FontAwesomeIcon icon="plus" color={pal.colors.link} size={19} />
-              <Text type="xl-medium" style={pal.link}>
-                Add to My Feeds
-              </Text>
-            </Button>
-          ) : null}
-          <NativeDropdown testID="feedHeaderDropdownBtn" items={dropdownItems}>
-            <View
-              style={{
-                paddingLeft: currentFeed?.isSaved ? 12 : 6,
-                paddingRight: 12,
-                paddingVertical: 8,
-              }}>
-              <FontAwesomeIcon
-                icon="ellipsis"
-                size={20}
-                color={pal.colors.textLight}
-              />
-            </View>
-          </NativeDropdown>
-        </View>
-      )
     }, [
-      pal,
       currentFeed?.isSaved,
-      currentFeed?.isLiked,
-      isPinned,
-      onToggleSaved,
-      onTogglePinned,
-      onToggleLiked,
-      dropdownItems,
-    ])
-
-    const renderListHeaderComponent = React.useCallback(() => {
-      return (
-        <>
-          <View style={[styles.header, pal.border]}>
-            <View style={s.flex1}>
-              <Text
-                testID="feedName"
-                type="title-xl"
-                style={[pal.text, s.bold]}>
-                {currentFeed?.displayName}
-              </Text>
-              {currentFeed && (
-                <Text type="md" style={[pal.textLight]} numberOfLines={1}>
-                  by{' '}
-                  {currentFeed.data.creator.did === store.me.did ? (
-                    'you'
-                  ) : (
-                    <TextLink
-                      text={sanitizeHandle(
-                        currentFeed.data.creator.handle,
-                        '@',
-                      )}
-                      href={makeProfileLink(currentFeed.data.creator)}
-                      style={[pal.textLight]}
-                    />
-                  )}
-                </Text>
-              )}
-              {isTabletOrDesktop && (
-                <View style={[styles.headerBtns, styles.headerBtnsDesktop]}>
-                  <Button
-                    type={currentFeed?.isSaved ? 'default' : 'inverted'}
-                    onPress={onToggleSaved}
-                    accessibilityLabel={
-                      currentFeed?.isSaved
-                        ? 'Unsave this feed'
-                        : 'Save this feed'
-                    }
-                    accessibilityHint=""
-                    label={
-                      currentFeed?.isSaved
-                        ? 'Remove from My Feeds'
-                        : 'Add to My Feeds'
-                    }
-                  />
-                  <Button
-                    type="default"
-                    accessibilityLabel={
-                      isPinned ? 'Unpin this feed' : 'Pin this feed'
-                    }
-                    accessibilityHint=""
-                    onPress={onTogglePinned}>
-                    <FontAwesomeIcon
-                      icon="thumb-tack"
-                      size={15}
-                      color={isPinned ? colors.blue3 : pal.colors.icon}
-                      style={styles.top2}
-                    />
-                  </Button>
-                  <Button
-                    type="default"
-                    accessibilityLabel="Like this feed"
-                    accessibilityHint=""
-                    onPress={onToggleLiked}>
-                    {currentFeed?.isLiked ? (
-                      <HeartIconSolid size={18} style={styles.liked} />
-                    ) : (
-                      <HeartIcon strokeWidth={3} size={18} style={pal.icon} />
-                    )}
-                  </Button>
-                  <Button
-                    type="default"
-                    accessibilityLabel="Share this feed"
-                    accessibilityHint=""
-                    onPress={onPressShare}>
-                    <FontAwesomeIcon
-                      icon="share"
-                      size={18}
-                      color={pal.colors.icon}
-                    />
-                  </Button>
-                  <Button
-                    type="default"
-                    accessibilityLabel="Report this feed"
-                    accessibilityHint=""
-                    onPress={onPressReport}>
-                    <FontAwesomeIcon
-                      icon="circle-exclamation"
-                      size={18}
-                      color={pal.colors.icon}
-                    />
-                  </Button>
-                </View>
-              )}
-            </View>
-            <View>
-              <UserAvatar
-                type="algo"
-                avatar={currentFeed?.data.avatar}
-                size={64}
-              />
-            </View>
-          </View>
-          <View style={styles.headerDetails}>
-            {currentFeed?.data.description ? (
-              <Text style={[pal.text, s.mb10]} numberOfLines={6}>
-                {currentFeed.data.description}
-              </Text>
-            ) : null}
-            <View style={styles.headerDetailsFooter}>
-              {currentFeed ? (
-                <TextLink
-                  type="md-medium"
-                  style={pal.textLight}
-                  href={`/profile/${handleOrDid}/feed/${rkey}/liked-by`}
-                  text={`Liked by ${currentFeed.data.likeCount} ${pluralize(
-                    currentFeed?.data.likeCount || 0,
-                    'user',
-                  )}`}
-                />
-              ) : null}
-            </View>
-          </View>
-          <View
-            style={[
-              styles.fakeSelector,
-              {
-                paddingHorizontal: isTabletOrDesktop ? 16 : 6,
-              },
-              pal.border,
-            ]}>
-            <View
-              style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}>
-              <Text type="md-medium" style={[pal.text]}>
-                Feed
-              </Text>
-            </View>
-          </View>
-        </>
-      )
-    }, [
-      pal,
-      currentFeed,
-      store.me.did,
       onToggleSaved,
-      onToggleLiked,
-      onPressShare,
-      handleOrDid,
       onPressReport,
-      rkey,
-      isPinned,
-      onTogglePinned,
-      isTabletOrDesktop,
+      onPressShare,
+      onPressViewAuthor,
     ])
 
     const renderEmptyState = React.useCallback(() => {
@@ -498,22 +312,100 @@ export const CustomFeedScreenInner = observer(
 
     return (
       <View style={s.hContentRegion}>
-        {!isTabletOrDesktop && (
-          <ViewHeader title="" renderButton={currentFeed && renderHeaderBtns} />
-        )}
+        <SimpleViewHeader
+          showBackButton={isMobile}
+          style={
+            !isMobile && [pal.border, {borderLeftWidth: 1, borderRightWidth: 1}]
+          }>
+          <Text type="title-lg" style={styles.headerText} numberOfLines={1}>
+            {currentFeed ? (
+              <TextLink
+                type="title-lg"
+                href="/"
+                style={[pal.text, {fontWeight: 'bold'}]}
+                text={currentFeed?.displayName || ''}
+                onPress={() => store.emitScreenSoftReset()}
+              />
+            ) : (
+              'Loading...'
+            )}
+          </Text>
+          {currentFeed ? (
+            <>
+              <Button
+                type="default-light"
+                testID="toggleLikeBtn"
+                accessibilityLabel="Like this feed"
+                accessibilityHint=""
+                onPress={onToggleLiked}
+                style={styles.headerBtn}>
+                {currentFeed?.isLiked ? (
+                  <HeartIconSolid size={19} style={styles.liked} />
+                ) : (
+                  <HeartIcon strokeWidth={3} size={19} style={pal.textLight} />
+                )}
+              </Button>
+              {currentFeed?.isSaved ? (
+                <Button
+                  type="default-light"
+                  accessibilityLabel={
+                    isPinned ? 'Unpin this feed' : 'Pin this feed'
+                  }
+                  accessibilityHint=""
+                  onPress={onTogglePinned}
+                  style={styles.headerBtn}>
+                  <FontAwesomeIcon
+                    icon="thumb-tack"
+                    size={17}
+                    color={isPinned ? colors.blue3 : pal.colors.textLight}
+                    style={styles.top1}
+                  />
+                </Button>
+              ) : (
+                <Button
+                  type="inverted"
+                  onPress={onToggleSaved}
+                  accessibilityLabel="Add to my feeds"
+                  accessibilityHint=""
+                  style={styles.headerAddBtn}>
+                  <FontAwesomeIcon
+                    icon="plus"
+                    color={palInverted.colors.text}
+                    size={19}
+                  />
+                  <Text type="button" style={palInverted.text}>
+                    Add{!isMobile && ' to My Feeds'}
+                  </Text>
+                </Button>
+              )}
+            </>
+          ) : null}
+          <NativeDropdown testID="feedHeaderDropdownBtn" items={dropdownItems}>
+            <View
+              style={{
+                paddingLeft: 12,
+                paddingRight: isMobile ? 12 : 0,
+              }}>
+              <FontAwesomeIcon
+                icon="ellipsis"
+                size={20}
+                color={pal.colors.textLight}
+              />
+            </View>
+          </NativeDropdown>
+        </SimpleViewHeader>
         <Feed
           scrollElRef={scrollElRef}
           feed={algoFeed}
           onScroll={onMainScroll}
           scrollEventThrottle={100}
-          ListHeaderComponent={renderListHeaderComponent}
           renderEmptyState={renderEmptyState}
           extraData={[uri, isPinned]}
           style={!isTabletOrDesktop ? {flex: 1} : undefined}
         />
         {isScrolledDown ? (
           <LoadLatestBtn
-            onPress={onScrollToTop}
+            onPress={onSoftReset}
             label="Scroll to top"
             showIndicator={false}
           />
@@ -540,36 +432,19 @@ const styles = StyleSheet.create({
     paddingBottom: 16,
     borderTopWidth: 1,
   },
-  headerBtns: {
-    flexDirection: 'row',
-    alignItems: 'center',
+  headerText: {
+    flex: 1,
+    fontWeight: 'bold',
   },
-  headerBtnsDesktop: {
-    marginTop: 8,
-    gap: 4,
+  headerBtn: {
+    paddingVertical: 0,
   },
   headerAddBtn: {
     flexDirection: 'row',
     alignItems: 'center',
     gap: 4,
-    paddingLeft: 4,
-  },
-  headerDetails: {
-    paddingHorizontal: 16,
-    paddingBottom: 16,
-  },
-  headerDetailsFooter: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    justifyContent: 'space-between',
-  },
-  fakeSelector: {
-    flexDirection: 'row',
-  },
-  fakeSelectorItem: {
-    paddingHorizontal: 12,
-    paddingBottom: 8,
-    borderBottomWidth: 3,
+    paddingVertical: 4,
+    paddingLeft: 10,
   },
   liked: {
     color: colors.red3,
diff --git a/src/view/screens/DiscoverFeeds.tsx b/src/view/screens/DiscoverFeeds.tsx
deleted file mode 100644
index 6aa7a9e31..000000000
--- a/src/view/screens/DiscoverFeeds.tsx
+++ /dev/null
@@ -1,157 +0,0 @@
-import React from 'react'
-import {RefreshControl, StyleSheet, View} from 'react-native'
-import {observer} from 'mobx-react-lite'
-import {useFocusEffect} from '@react-navigation/native'
-import {NativeStackScreenProps, CommonNavigatorParams} from 'lib/routes/types'
-import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {ViewHeader} from '../com/util/ViewHeader'
-import {useStores} from 'state/index'
-import {FeedsDiscoveryModel} from 'state/models/discovery/feeds'
-import {CenteredView, FlatList} from 'view/com/util/Views'
-import {CustomFeed} from 'view/com/feeds/CustomFeed'
-import {Text} from 'view/com/util/text/Text'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {s} from 'lib/styles'
-import {CustomFeedModel} from 'state/models/feeds/custom-feed'
-import {HeaderWithInput} from 'view/com/search/HeaderWithInput'
-import debounce from 'lodash.debounce'
-
-type Props = NativeStackScreenProps<CommonNavigatorParams, 'DiscoverFeeds'>
-export const DiscoverFeedsScreen = withAuthRequired(
-  observer(function DiscoverFeedsScreenImpl({}: Props) {
-    const store = useStores()
-    const pal = usePalette('default')
-    const feeds = React.useMemo(() => new FeedsDiscoveryModel(store), [store])
-    const {isTabletOrDesktop} = useWebMediaQueries()
-
-    // search stuff
-    const [isInputFocused, setIsInputFocused] = React.useState<boolean>(false)
-    const [query, setQuery] = React.useState<string>('')
-    const debouncedSearchFeeds = React.useMemo(
-      () => debounce(q => feeds.search(q), 500), // debounce for 500ms
-      [feeds],
-    )
-    const onChangeQuery = React.useCallback(
-      (text: string) => {
-        setQuery(text)
-        if (text.length > 1) {
-          debouncedSearchFeeds(text)
-        } else {
-          feeds.refresh()
-        }
-      },
-      [debouncedSearchFeeds, feeds],
-    )
-    const onPressClearQuery = React.useCallback(() => {
-      setQuery('')
-      feeds.refresh()
-    }, [feeds])
-    const onPressCancelSearch = React.useCallback(() => {
-      setIsInputFocused(false)
-      setQuery('')
-      feeds.refresh()
-    }, [feeds])
-    const onSubmitQuery = React.useCallback(() => {
-      debouncedSearchFeeds(query)
-      debouncedSearchFeeds.flush()
-    }, [debouncedSearchFeeds, query])
-
-    useFocusEffect(
-      React.useCallback(() => {
-        store.shell.setMinimalShellMode(false)
-        if (!feeds.hasLoaded) {
-          feeds.refresh()
-        }
-      }, [store, feeds]),
-    )
-
-    const onRefresh = React.useCallback(() => {
-      feeds.refresh()
-    }, [feeds])
-
-    const renderListEmptyComponent = () => {
-      return (
-        <View style={styles.empty}>
-          <Text type="lg" style={pal.textLight}>
-            {feeds.isLoading
-              ? isTabletOrDesktop
-                ? 'Loading...'
-                : ''
-              : query
-              ? `No results found for "${query}"`
-              : `We can't find any feeds for some reason. This is probably an error - try refreshing!`}
-          </Text>
-        </View>
-      )
-    }
-
-    const renderItem = React.useCallback(
-      ({item}: {item: CustomFeedModel}) => (
-        <CustomFeed
-          key={item.data.uri}
-          item={item}
-          showSaveBtn
-          showDescription
-          showLikes
-        />
-      ),
-      [],
-    )
-
-    return (
-      <CenteredView style={[styles.container, pal.view]}>
-        <View
-          style={[isTabletOrDesktop && styles.containerDesktop, pal.border]}>
-          <ViewHeader title="Discover Feeds" showOnDesktop />
-        </View>
-        <HeaderWithInput
-          isInputFocused={isInputFocused}
-          query={query}
-          setIsInputFocused={setIsInputFocused}
-          onChangeQuery={onChangeQuery}
-          onPressClearQuery={onPressClearQuery}
-          onPressCancelSearch={onPressCancelSearch}
-          onSubmitQuery={onSubmitQuery}
-          showMenu={false}
-        />
-        <FlatList
-          style={[!isTabletOrDesktop && s.flex1]}
-          data={feeds.feeds}
-          keyExtractor={item => item.data.uri}
-          contentContainerStyle={styles.contentContainer}
-          refreshControl={
-            <RefreshControl
-              refreshing={feeds.isRefreshing}
-              onRefresh={onRefresh}
-              tintColor={pal.colors.text}
-              titleColor={pal.colors.text}
-            />
-          }
-          renderItem={renderItem}
-          initialNumToRender={10}
-          ListEmptyComponent={renderListEmptyComponent}
-          onEndReached={() => feeds.loadMore()}
-          extraData={feeds.isLoading}
-        />
-      </CenteredView>
-    )
-  }),
-)
-
-const styles = StyleSheet.create({
-  container: {
-    flex: 1,
-  },
-  contentContainer: {
-    paddingBottom: 100,
-  },
-  containerDesktop: {
-    borderLeftWidth: 1,
-    borderRightWidth: 1,
-  },
-  empty: {
-    paddingHorizontal: 16,
-    paddingTop: 10,
-  },
-})
diff --git a/src/view/screens/Feeds.tsx b/src/view/screens/Feeds.tsx
index 97c6e8672..d2c4a6d2d 100644
--- a/src/view/screens/Feeds.tsx
+++ b/src/view/screens/Feeds.tsx
@@ -1,90 +1,72 @@
 import React from 'react'
-import {StyleSheet, View} from 'react-native'
-import {useFocusEffect} from '@react-navigation/native'
-import isEqual from 'lodash.isequal'
+import {ActivityIndicator, StyleSheet, RefreshControl, View} from 'react-native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
+import {AtUri} from '@atproto/api'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
-import {FlatList} from 'view/com/util/Views'
 import {ViewHeader} from 'view/com/util/ViewHeader'
-import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
 import {FAB} from 'view/com/util/fab/FAB'
 import {Link} from 'view/com/util/Link'
 import {NativeStackScreenProps, FeedsTabNavigatorParams} from 'lib/routes/types'
 import {observer} from 'mobx-react-lite'
-import {PostsMultiFeedModel} from 'state/models/feeds/multi-feed'
-import {MultiFeed} from 'view/com/posts/MultiFeed'
 import {usePalette} from 'lib/hooks/usePalette'
-import {useTimer} from 'lib/hooks/useTimer'
 import {useStores} from 'state/index'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {ComposeIcon2, CogIcon} from 'lib/icons'
 import {s} from 'lib/styles'
-
-const LOAD_NEW_PROMPT_TIME = 60e3 // 60 seconds
-const MOBILE_HEADER_OFFSET = 40
+import {SearchInput} from 'view/com/util/forms/SearchInput'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {FeedFeedLoadingPlaceholder} from 'view/com/util/LoadingPlaceholder'
+import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
+import debounce from 'lodash.debounce'
+import {Text} from 'view/com/util/text/Text'
+import {MyFeedsUIModel, MyFeedsItem} from 'state/models/ui/my-feeds'
+import {FlatList} from 'view/com/util/Views'
+import {useFocusEffect} from '@react-navigation/native'
+import {CustomFeed} from 'view/com/feeds/CustomFeed'
 
 type Props = NativeStackScreenProps<FeedsTabNavigatorParams, 'Feeds'>
 export const FeedsScreen = withAuthRequired(
   observer<Props>(function FeedsScreenImpl({}: Props) {
     const pal = usePalette('default')
     const store = useStores()
-    const {isMobile} = useWebMediaQueries()
-    const flatListRef = React.useRef<FlatList>(null)
-    const multifeed = React.useMemo<PostsMultiFeedModel>(
-      () => new PostsMultiFeedModel(store),
-      [store],
+    const {isMobile, isTabletOrDesktop} = useWebMediaQueries()
+    const myFeeds = React.useMemo(() => new MyFeedsUIModel(store), [store])
+    const [query, setQuery] = React.useState<string>('')
+    const debouncedSearchFeeds = React.useMemo(
+      () => debounce(q => myFeeds.discovery.search(q), 500), // debounce for 500ms
+      [myFeeds],
     )
-    const [onMainScroll, isScrolledDown, resetMainScroll] =
-      useOnMainScroll(store)
-    const [loadPromptVisible, setLoadPromptVisible] = React.useState(false)
-    const [resetPromptTimer] = useTimer(LOAD_NEW_PROMPT_TIME, () => {
-      setLoadPromptVisible(true)
-    })
-
-    const onSoftReset = React.useCallback(() => {
-      flatListRef.current?.scrollToOffset({offset: 0})
-      multifeed.loadLatest()
-      resetPromptTimer()
-      setLoadPromptVisible(false)
-      resetMainScroll()
-    }, [
-      flatListRef,
-      resetMainScroll,
-      multifeed,
-      resetPromptTimer,
-      setLoadPromptVisible,
-    ])
 
     useFocusEffect(
       React.useCallback(() => {
-        const softResetSub = store.onScreenSoftReset(onSoftReset)
-        const multifeedCleanup = multifeed.registerListeners()
-        const cleanup = () => {
-          softResetSub.remove()
-          multifeedCleanup()
-        }
-
         store.shell.setMinimalShellMode(false)
-        return cleanup
-      }, [store, multifeed, onSoftReset]),
+        myFeeds.setup()
+      }, [store.shell, myFeeds]),
     )
 
-    React.useEffect(() => {
-      if (
-        isEqual(
-          multifeed.feedInfos.map(f => f.uri),
-          store.me.savedFeeds.all.map(f => f.uri),
-        )
-      ) {
-        // no changes
-        return
-      }
-      multifeed.refresh()
-    }, [multifeed, store.me.savedFeeds.all])
-
     const onPressCompose = React.useCallback(() => {
       store.shell.openComposer({})
     }, [store])
+    const onChangeQuery = React.useCallback(
+      (text: string) => {
+        setQuery(text)
+        if (text.length > 1) {
+          debouncedSearchFeeds(text)
+        } else {
+          myFeeds.discovery.refresh()
+        }
+      },
+      [debouncedSearchFeeds, myFeeds.discovery],
+    )
+    const onPressCancelSearch = React.useCallback(() => {
+      setQuery('')
+      myFeeds.discovery.refresh()
+    }, [myFeeds])
+    const onSubmitQuery = React.useCallback(() => {
+      debouncedSearchFeeds(query)
+      debouncedSearchFeeds.flush()
+    }, [debouncedSearchFeeds, query])
 
     const renderHeaderBtn = React.useCallback(() => {
       return (
@@ -99,30 +81,150 @@ export const FeedsScreen = withAuthRequired(
       )
     }, [pal])
 
+    const onRefresh = React.useCallback(() => {
+      myFeeds.refresh()
+    }, [myFeeds])
+
+    const renderItem = React.useCallback(
+      ({item}: {item: MyFeedsItem}) => {
+        if (item.type === 'discover-feeds-loading') {
+          return <FeedFeedLoadingPlaceholder />
+        } else if (item.type === 'spinner') {
+          return (
+            <View style={s.p10}>
+              <ActivityIndicator />
+            </View>
+          )
+        } else if (item.type === 'error') {
+          return <ErrorMessage message={item.error} />
+        } else if (item.type === 'saved-feeds-header') {
+          if (!isMobile) {
+            return (
+              <View
+                style={[
+                  pal.view,
+                  styles.header,
+                  pal.border,
+                  {
+                    borderBottomWidth: 1,
+                  },
+                ]}>
+                <Text type="title-lg" style={[pal.text, s.bold]}>
+                  My Feeds
+                </Text>
+                <Link href="/settings/saved-feeds">
+                  <CogIcon strokeWidth={1.5} style={pal.icon} size={28} />
+                </Link>
+              </View>
+            )
+          }
+          return <View />
+        } else if (item.type === 'saved-feed') {
+          return (
+            <SavedFeed
+              uri={item.feed.uri}
+              avatar={item.feed.data.avatar}
+              displayName={item.feed.displayName}
+            />
+          )
+        } else if (item.type === 'discover-feeds-header') {
+          return (
+            <>
+              <View
+                style={[
+                  pal.view,
+                  styles.header,
+                  {
+                    marginTop: 16,
+                    paddingLeft: isMobile ? 12 : undefined,
+                    paddingRight: 10,
+                    paddingBottom: isMobile ? 6 : undefined,
+                  },
+                ]}>
+                <Text type="title-lg" style={[pal.text, s.bold]}>
+                  Discover new feeds
+                </Text>
+                {!isMobile && (
+                  <SearchInput
+                    query={query}
+                    onChangeQuery={onChangeQuery}
+                    onPressCancelSearch={onPressCancelSearch}
+                    onSubmitQuery={onSubmitQuery}
+                    style={{flex: 1, maxWidth: 250}}
+                  />
+                )}
+              </View>
+              {isMobile && (
+                <View style={{paddingHorizontal: 8, paddingBottom: 10}}>
+                  <SearchInput
+                    query={query}
+                    onChangeQuery={onChangeQuery}
+                    onPressCancelSearch={onPressCancelSearch}
+                    onSubmitQuery={onSubmitQuery}
+                  />
+                </View>
+              )}
+            </>
+          )
+        } else if (item.type === 'discover-feed') {
+          return (
+            <CustomFeed
+              item={item.feed}
+              showSaveBtn
+              showDescription
+              showLikes
+            />
+          )
+        } else if (item.type === 'discover-feeds-no-results') {
+          return (
+            <View
+              style={{
+                paddingHorizontal: 16,
+                paddingTop: 10,
+                paddingBottom: '150%',
+              }}>
+              <Text type="lg" style={pal.textLight}>
+                No results found for "{query}"
+              </Text>
+            </View>
+          )
+        }
+        return null
+      },
+      [isMobile, pal, query, onChangeQuery, onPressCancelSearch, onSubmitQuery],
+    )
+
     return (
       <View style={[pal.view, styles.container]}>
-        <MultiFeed
-          scrollElRef={flatListRef}
-          multifeed={multifeed}
-          onScroll={onMainScroll}
-          scrollEventThrottle={100}
-          headerOffset={isMobile ? MOBILE_HEADER_OFFSET : undefined}
-        />
         {isMobile && (
           <ViewHeader
-            title="My Feeds"
+            title="Feeds"
             canGoBack={false}
-            hideOnScroll
             renderButton={renderHeaderBtn}
+            showBorder
           />
         )}
-        {isScrolledDown || loadPromptVisible ? (
-          <LoadLatestBtn
-            onPress={onSoftReset}
-            label="Load latest posts"
-            showIndicator={loadPromptVisible}
-          />
-        ) : null}
+
+        <FlatList
+          style={[!isTabletOrDesktop && s.flex1, styles.list]}
+          data={myFeeds.items}
+          keyExtractor={item => item._reactKey}
+          contentContainerStyle={styles.contentContainer}
+          refreshControl={
+            <RefreshControl
+              refreshing={myFeeds.isRefreshing}
+              onRefresh={onRefresh}
+              tintColor={pal.colors.text}
+              titleColor={pal.colors.text}
+            />
+          }
+          renderItem={renderItem}
+          initialNumToRender={10}
+          onEndReached={() => myFeeds.loadMore()}
+          extraData={myFeeds.isLoading}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+        />
         <FAB
           testID="composeFAB"
           onPress={onPressCompose}
@@ -136,8 +238,76 @@ export const FeedsScreen = withAuthRequired(
   }),
 )
 
+function SavedFeed({
+  uri,
+  avatar,
+  displayName,
+}: {
+  uri: string
+  avatar: string | undefined
+  displayName: string
+}) {
+  const pal = usePalette('default')
+  const urip = new AtUri(uri)
+  const href = `/profile/${urip.hostname}/feed/${urip.rkey}`
+  const {isMobile} = useWebMediaQueries()
+  return (
+    <Link
+      testID={`saved-feed-${displayName}`}
+      href={href}
+      style={[pal.border, styles.savedFeed, isMobile && styles.savedFeedMobile]}
+      hoverStyle={pal.viewLight}
+      accessibilityLabel={displayName}
+      accessibilityHint=""
+      asAnchor
+      anchorNoUnderline>
+      <UserAvatar type="algo" size={28} avatar={avatar} />
+      <Text
+        type={isMobile ? 'lg' : 'lg-medium'}
+        style={[pal.text, s.flex1]}
+        numberOfLines={1}>
+        {displayName}
+      </Text>
+      {isMobile && (
+        <FontAwesomeIcon
+          icon="chevron-right"
+          size={14}
+          style={pal.textLight as FontAwesomeIconStyle}
+        />
+      )}
+    </Link>
+  )
+}
+
 const styles = StyleSheet.create({
   container: {
     flex: 1,
   },
+  list: {
+    height: '100%',
+  },
+  contentContainer: {
+    paddingBottom: 100,
+  },
+
+  header: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'space-between',
+    gap: 16,
+    paddingHorizontal: 16,
+    paddingVertical: 12,
+  },
+
+  savedFeed: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    paddingHorizontal: 16,
+    paddingVertical: 14,
+    gap: 12,
+    borderBottomWidth: 1,
+  },
+  savedFeedMobile: {
+    paddingVertical: 10,
+  },
 })
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 33cc2e110..60cda31db 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -1,6 +1,8 @@
 import React from 'react'
 import {FlatList, View} from 'react-native'
 import {useFocusEffect, useIsFocused} from '@react-navigation/native'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome'
 import {AppBskyFeedGetFeed as GetCustomFeed} from '@atproto/api'
 import {observer} from 'mobx-react-lite'
 import useAppState from 'react-native-appstate-hook'
@@ -8,6 +10,7 @@ import isEqual from 'lodash.isequal'
 import {NativeStackScreenProps, HomeTabNavigatorParams} from 'lib/routes/types'
 import {PostsFeedModel} from 'state/models/feeds/posts'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
+import {TextLink} from 'view/com/util/Link'
 import {Feed} from '../com/posts/Feed'
 import {FollowingEmptyState} from 'view/com/posts/FollowingEmptyState'
 import {CustomFeedEmptyState} from 'view/com/posts/CustomFeedEmptyState'
@@ -16,14 +19,16 @@ import {FeedsTabBar} from '../com/pager/FeedsTabBar'
 import {Pager, PagerRef, RenderTabBarFnProps} from 'view/com/pager/Pager'
 import {FAB} from '../com/util/fab/FAB'
 import {useStores} from 'state/index'
-import {s} from 'lib/styles'
+import {usePalette} from 'lib/hooks/usePalette'
+import {s, colors} from 'lib/styles'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
 import {ComposeIcon2} from 'lib/icons'
 
 const HEADER_OFFSET_MOBILE = 78
-const HEADER_OFFSET_DESKTOP = 50
+const HEADER_OFFSET_TABLET = 50
+const HEADER_OFFSET_DESKTOP = 0
 const POLL_FREQ = 30e3 // 30sec
 
 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
@@ -154,17 +159,23 @@ const FeedPage = observer(function FeedPageImpl({
   renderEmptyState?: () => JSX.Element
 }) {
   const store = useStores()
-  const {isMobile} = useWebMediaQueries()
+  const pal = usePalette('default')
+  const {isMobile, isTablet, isDesktop} = useWebMediaQueries()
   const [onMainScroll, isScrolledDown, resetMainScroll] = useOnMainScroll(store)
   const {screen, track} = useAnalytics()
   const [headerOffset, setHeaderOffset] = React.useState(
-    isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP,
+    isMobile
+      ? HEADER_OFFSET_MOBILE
+      : isTablet
+      ? HEADER_OFFSET_TABLET
+      : HEADER_OFFSET_DESKTOP,
   )
   const scrollElRef = React.useRef<FlatList>(null)
   const {appState} = useAppState({
     onForeground: () => doPoll(true),
   })
   const isScreenFocused = useIsFocused()
+  const hasNew = feed.hasNewLatest && !feed.isRefreshing
 
   React.useEffect(() => {
     // called on first load
@@ -205,8 +216,14 @@ const FeedPage = observer(function FeedPageImpl({
 
   // listens for resize events
   React.useEffect(() => {
-    setHeaderOffset(isMobile ? HEADER_OFFSET_MOBILE : HEADER_OFFSET_DESKTOP)
-  }, [isMobile])
+    setHeaderOffset(
+      isMobile
+        ? HEADER_OFFSET_MOBILE
+        : isTablet
+        ? HEADER_OFFSET_TABLET
+        : HEADER_OFFSET_DESKTOP,
+    )
+  }, [isMobile, isTablet])
 
   // fires when page within screen is activated/deactivated
   // - check for latest
@@ -222,9 +239,6 @@ const FeedPage = observer(function FeedPageImpl({
     screen('Feed')
     store.log.debug('HomeScreen: Updating feed')
     feed.checkForLatest()
-    if (feed.hasContent) {
-      feed.update()
-    }
 
     return () => {
       clearInterval(pollInterval)
@@ -247,7 +261,59 @@ const FeedPage = observer(function FeedPageImpl({
     feed.refresh()
   }, [feed, scrollToTop])
 
-  const hasNew = feed.hasNewLatest && !feed.isRefreshing
+  const ListHeaderComponent = React.useCallback(() => {
+    if (isDesktop) {
+      return (
+        <View
+          style={[
+            pal.view,
+            {
+              flexDirection: 'row',
+              alignItems: 'center',
+              justifyContent: 'space-between',
+              paddingHorizontal: 18,
+              paddingVertical: 12,
+            },
+          ]}>
+          <TextLink
+            type="title-lg"
+            href="/"
+            style={[pal.text, {fontWeight: 'bold'}]}
+            text={
+              <>
+                {store.session.isSandbox ? 'SANDBOX' : 'Bluesky'}{' '}
+                {hasNew && (
+                  <View
+                    style={{
+                      top: -8,
+                      backgroundColor: colors.blue3,
+                      width: 8,
+                      height: 8,
+                      borderRadius: 4,
+                    }}
+                  />
+                )}
+              </>
+            }
+            onPress={() => store.emitScreenSoftReset()}
+          />
+          <TextLink
+            type="title-lg"
+            href="/settings/home-feed"
+            style={{fontWeight: 'bold'}}
+            text={
+              <FontAwesomeIcon
+                icon="sliders"
+                style={pal.textLight as FontAwesomeIconStyle}
+              />
+            }
+          />
+        </View>
+      )
+    }
+    return <></>
+  }, [isDesktop, pal, store, hasNew])
+
   return (
     <View testID={testID} style={s.h100pct}>
       <Feed
@@ -259,6 +325,7 @@ const FeedPage = observer(function FeedPageImpl({
         onScroll={onMainScroll}
         scrollEventThrottle={100}
         renderEmptyState={renderEmptyState}
+        ListHeaderComponent={ListHeaderComponent}
         headerOffset={headerOffset}
       />
       {(isScrolledDown || hasNew) && (
diff --git a/src/view/screens/Notifications.tsx b/src/view/screens/Notifications.tsx
index 3c257fac8..243cc9596 100644
--- a/src/view/screens/Notifications.tsx
+++ b/src/view/screens/Notifications.tsx
@@ -9,12 +9,15 @@ import {
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
 import {ViewHeader} from '../com/util/ViewHeader'
 import {Feed} from '../com/notifications/Feed'
+import {TextLink} from 'view/com/util/Link'
 import {InvitedUsers} from '../com/notifications/InvitedUsers'
 import {LoadLatestBtn} from 'view/com/util/load-latest/LoadLatestBtn'
 import {useStores} from 'state/index'
 import {useOnMainScroll} from 'lib/hooks/useOnMainScroll'
 import {useTabFocusEffect} from 'lib/hooks/useTabFocusEffect'
-import {s} from 'lib/styles'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {s, colors} from 'lib/styles'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {isWeb} from 'platform/detection'
 
@@ -29,6 +32,12 @@ export const NotificationsScreen = withAuthRequired(
       useOnMainScroll(store)
     const scrollElRef = React.useRef<FlatList>(null)
     const {screen} = useAnalytics()
+    const pal = usePalette('default')
+    const {isDesktop} = useWebMediaQueries()
+
+    const hasNew =
+      store.me.notifications.hasNewLatest &&
+      !store.me.notifications.isRefreshing
 
     // event handlers
     // =
@@ -88,9 +97,48 @@ export const NotificationsScreen = withAuthRequired(
       ),
     )
 
-    const hasNew =
-      store.me.notifications.hasNewLatest &&
-      !store.me.notifications.isRefreshing
+    const ListHeaderComponent = React.useCallback(() => {
+      if (isDesktop) {
+        return (
+          <View
+            style={[
+              pal.view,
+              {
+                flexDirection: 'row',
+                alignItems: 'center',
+                justifyContent: 'space-between',
+                paddingHorizontal: 18,
+                paddingVertical: 12,
+              },
+            ]}>
+            <TextLink
+              type="title-lg"
+              href="/notifications"
+              style={[pal.text, {fontWeight: 'bold'}]}
+              text={
+                <>
+                  Notifications{' '}
+                  {hasNew && (
+                    <View
+                      style={{
+                        top: -8,
+                        backgroundColor: colors.blue3,
+                        width: 8,
+                        height: 8,
+                        borderRadius: 4,
+                      }}
+                    />
+                  )}
+                </>
+              }
+              onPress={() => store.emitScreenSoftReset()}
+            />
+          </View>
+        )
+      }
+      return <></>
+    }, [isDesktop, pal, store, hasNew])
+
     return (
       <View testID="notificationsScreen" style={s.hContentRegion}>
         <ViewHeader title="Notifications" canGoBack={false} />
@@ -100,6 +148,7 @@ export const NotificationsScreen = withAuthRequired(
           onPressTryAgain={onPressTryAgain}
           onScroll={onMainScroll}
           scrollElRef={scrollElRef}
+          ListHeaderComponent={ListHeaderComponent}
         />
         {(isScrolledDown || hasNew) && (
           <LoadLatestBtn
diff --git a/src/view/screens/PreferencesHomeFeed.tsx b/src/view/screens/PreferencesHomeFeed.tsx
index 49c13bfa3..81bdfc95e 100644
--- a/src/view/screens/PreferencesHomeFeed.tsx
+++ b/src/view/screens/PreferencesHomeFeed.tsx
@@ -19,14 +19,7 @@ function RepliesThresholdInput({enabled}: {enabled: boolean}) {
   const [value, setValue] = useState(store.preferences.homeFeedRepliesThreshold)
 
   return (
-    <View style={[s.mt10, !enabled && styles.dimmed]}>
-      <Text type="xs" style={pal.text}>
-        {value === 0
-          ? `Show all replies`
-          : `Show replies with at least ${value} ${
-              value > 1 ? `likes` : `like`
-            }`}
-      </Text>
+    <View style={[!enabled && styles.dimmed]}>
       <Slider
         value={value}
         onValueChange={(v: number | number[]) => {
@@ -40,6 +33,13 @@ function RepliesThresholdInput({enabled}: {enabled: boolean}) {
         disabled={!enabled}
         thumbTintColor={colors.blue3}
       />
+      <Text type="xs" style={pal.text}>
+        {value === 0
+          ? `Show all replies`
+          : `Show replies with at least ${value} ${
+              value > 1 ? `likes` : `like`
+            }`}
+      </Text>
     </View>
   )
 }
@@ -79,8 +79,7 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
               Show Replies
             </Text>
             <Text style={[pal.text, s.pb10]}>
-              Adjust the number of likes a reply must have to be shown in your
-              feed.
+              Set this setting to "No" to hide all replies from your feed.
             </Text>
             <ToggleButton
               type="default-light"
@@ -88,7 +87,36 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
               isSelected={store.preferences.homeFeedRepliesEnabled}
               onPress={store.preferences.toggleHomeFeedRepliesEnabled}
             />
-
+          </View>
+          <View
+            style={[
+              pal.viewLight,
+              styles.card,
+              !store.preferences.homeFeedRepliesEnabled && styles.dimmed,
+            ]}>
+            <Text type="title-sm" style={[pal.text, s.pb5]}>
+              Reply Filters
+            </Text>
+            <Text style={[pal.text, s.pb10]}>
+              Enable this setting to only see replies between people you follow.
+            </Text>
+            <ToggleButton
+              type="default-light"
+              label="Followed users only"
+              isSelected={
+                store.preferences.homeFeedRepliesByFollowedOnlyEnabled
+              }
+              onPress={
+                store.preferences.homeFeedRepliesEnabled
+                  ? store.preferences.toggleHomeFeedRepliesByFollowedOnlyEnabled
+                  : undefined
+              }
+              style={[s.mb10]}
+            />
+            <Text style={[pal.text]}>
+              Adjust the number of likes a reply must have to be shown in your
+              feed.
+            </Text>
             <RepliesThresholdInput
               enabled={store.preferences.homeFeedRepliesEnabled}
             />
@@ -124,6 +152,22 @@ export const PreferencesHomeFeed = observer(function PreferencesHomeFeedImpl({
               onPress={store.preferences.toggleHomeFeedQuotePostsEnabled}
             />
           </View>
+
+          <View style={[pal.viewLight, styles.card]}>
+            <Text type="title-sm" style={[pal.text, s.pb5]}>
+              Show Posts from My Feeds (Experimental)
+            </Text>
+            <Text style={[pal.text, s.pb10]}>
+              Set this setting to "Yes" to show samples of your saved feeds in
+              your following feed.
+            </Text>
+            <ToggleButton
+              type="default-light"
+              label={store.preferences.homeFeedMergeFeedEnabled ? 'Yes' : 'No'}
+              isSelected={store.preferences.homeFeedMergeFeedEnabled}
+              onPress={store.preferences.toggleHomeFeedMergeFeedEnabled}
+            />
+          </View>
         </View>
       </ScrollView>
 
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index 69b5ceee6..241bae1ed 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -69,9 +69,7 @@ export const ProfileScreen = withAuthRequired(
         let aborted = false
         store.shell.setMinimalShellMode(false)
         const feedCleanup = uiState.feed.registerListeners()
-        if (hasSetup) {
-          uiState.update()
-        } else {
+        if (!hasSetup) {
           uiState.setup().then(() => {
             if (aborted) {
               return
diff --git a/src/view/screens/SavedFeeds.tsx b/src/view/screens/SavedFeeds.tsx
index d5c02ba63..5253c5bd6 100644
--- a/src/view/screens/SavedFeeds.tsx
+++ b/src/view/screens/SavedFeeds.tsx
@@ -70,7 +70,7 @@ export const SavedFeeds = withAuthRequired(
       return (
         <>
           <View style={[styles.footerLinks, pal.border]}>
-            <Link style={styles.footerLink} href="/search/feeds">
+            <Link style={styles.footerLink} href="/feeds">
               <FontAwesomeIcon
                 icon="search"
                 size={18}
diff --git a/src/view/screens/Settings.tsx b/src/view/screens/Settings.tsx
index 8a543fa4c..761f50d0a 100644
--- a/src/view/screens/Settings.tsx
+++ b/src/view/screens/Settings.tsx
@@ -40,7 +40,7 @@ import {AccountData} from 'state/models/session'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {NavigationProp} from 'lib/routes/types'
 import {pluralize} from 'lib/strings/helpers'
-import {HandIcon} from 'lib/icons'
+import {HandIcon, HashtagIcon} from 'lib/icons'
 import {formatCount} from 'view/com/util/numeric/format'
 import Clipboard from '@react-native-clipboard/clipboard'
 import {reset as resetNavigation} from '../../Navigation'
@@ -423,17 +423,14 @@ export const SettingsScreen = withAuthRequired(
           <TouchableOpacity
             testID="savedFeedsBtn"
             style={[styles.linkCard, pal.view, isSwitching && styles.dimmed]}
-            accessibilityHint="Saved Feeds"
+            accessibilityHint="My Saved Feeds"
             accessibilityLabel="Opens screen with all saved feeds"
             onPress={onPressSavedFeeds}>
             <View style={[styles.iconContainer, pal.btn]}>
-              <FontAwesomeIcon
-                icon="satellite-dish"
-                style={pal.text as FontAwesomeIconStyle}
-              />
+              <HashtagIcon style={pal.text} size={18} strokeWidth={3} />
             </View>
             <Text type="lg" style={pal.text}>
-              Saved Feeds
+              My Saved Feeds
             </Text>
           </TouchableOpacity>
           <TouchableOpacity