From bec94ed82c47f9a5275cbc5fc740dd97dda10183 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Wed, 17 May 2023 22:58:53 -0500
Subject: Desktop styling fixes

---
 src/view/com/util/ViewHeader.tsx | 30 +++++++++++++++++++++++++++---
 1 file changed, 27 insertions(+), 3 deletions(-)

(limited to 'src/view/com/util/ViewHeader.tsx')

diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 97802394e..7f13f1838 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -20,12 +20,14 @@ export const ViewHeader = observer(function ({
   canGoBack,
   hideOnScroll,
   showOnDesktop,
+  showBorder,
   renderButton,
 }: {
   title: string
   canGoBack?: boolean
   hideOnScroll?: boolean
   showOnDesktop?: boolean
+  showBorder?: boolean
   renderButton?: () => JSX.Element
 }) {
   const pal = usePalette('default')
@@ -57,7 +59,7 @@ export const ViewHeader = observer(function ({
     }
 
     return (
-      <Container hideOnScroll={hideOnScroll || false}>
+      <Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
         <TouchableOpacity
           testID="viewHeaderDrawerBtn"
           onPress={canGoBack ? onPressBack : onPressMenu}
@@ -115,9 +117,11 @@ const Container = observer(
   ({
     children,
     hideOnScroll,
+    showBorder,
   }: {
     children: React.ReactNode
     hideOnScroll: boolean
+    showBorder: boolean
   }) => {
     const store = useStores()
     const pal = usePalette('default')
@@ -145,11 +149,28 @@ const Container = observer(
     }
 
     if (!hideOnScroll) {
-      return <View style={[styles.header, pal.view]}>{children}</View>
+      return (
+        <View
+          style={[
+            styles.header,
+            pal.view,
+            pal.border,
+            showBorder && styles.border,
+          ]}>
+          {children}
+        </View>
+      )
     }
     return (
       <Animated.View
-        style={[styles.header, pal.view, styles.headerFloating, transform]}>
+        style={[
+          styles.header,
+          pal.view,
+          pal.border,
+          styles.headerFloating,
+          transform,
+          showBorder && styles.border,
+        ]}>
         {children}
       </Animated.View>
     )
@@ -172,6 +193,9 @@ const styles = StyleSheet.create({
     borderBottomWidth: 1,
     paddingVertical: 12,
   },
+  border: {
+    borderBottomWidth: 1,
+  },
 
   titleContainer: {
     marginLeft: 'auto',
-- 
cgit 1.4.1


From 5537d19e555c39f5f9a0ec16735ea4c3860357c4 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Thu, 18 May 2023 14:39:04 -0500
Subject: Update saved feeds to use preferences

---
 src/state/models/feeds/custom-feed.ts       | 20 ++--------
 src/state/models/media/image.ts             |  2 +-
 src/state/models/ui/preferences.ts          | 57 ++++++++++++++++++++++-------
 src/state/models/ui/saved-feeds.ts          | 25 +++++++------
 src/state/models/ui/shell.ts                |  2 +-
 src/view/com/feeds/CustomFeed.tsx           |  2 +-
 src/view/com/util/ViewHeader.tsx            |  2 +-
 src/view/com/util/moderation/ImageHider.tsx |  8 ++--
 8 files changed, 68 insertions(+), 50 deletions(-)

(limited to 'src/view/com/util/ViewHeader.tsx')

diff --git a/src/state/models/feeds/custom-feed.ts b/src/state/models/feeds/custom-feed.ts
index 5e550ec69..e457d2d1e 100644
--- a/src/state/models/feeds/custom-feed.ts
+++ b/src/state/models/feeds/custom-feed.ts
@@ -38,7 +38,7 @@ export class CustomFeedModel {
   }
 
   get isSaved() {
-    return this.data.viewer?.saved
+    return this.rootStore.preferences.savedFeeds.includes(this.uri)
   }
 
   get isLiked() {
@@ -49,23 +49,11 @@ export class CustomFeedModel {
   // =
 
   async save() {
-    await this.rootStore.agent.app.bsky.feed.saveFeed({
-      feed: this.uri,
-    })
-    runInAction(() => {
-      this.data.viewer = this.data.viewer || {}
-      this.data.viewer.saved = true
-    })
+    await this.rootStore.preferences.addSavedFeed(this.uri)
   }
 
   async unsave() {
-    await this.rootStore.agent.app.bsky.feed.unsaveFeed({
-      feed: this.uri,
-    })
-    runInAction(() => {
-      this.data.viewer = this.data.viewer || {}
-      this.data.viewer.saved = false
-    })
+    await this.rootStore.preferences.removeSavedFeed(this.uri)
   }
 
   async like() {
@@ -82,7 +70,7 @@ export class CustomFeedModel {
   }
 
   async unlike() {
-    if (!this.data.viewer.like) {
+    if (!this.data.viewer?.like) {
       return
     }
     try {
diff --git a/src/state/models/media/image.ts b/src/state/models/media/image.ts
index ec93bf5b6..6edf88d9d 100644
--- a/src/state/models/media/image.ts
+++ b/src/state/models/media/image.ts
@@ -135,7 +135,7 @@ export class ImageModel implements RNImage {
   // Only for mobile
   async crop() {
     try {
-      const cropped = await openCropper({
+      const cropped = await openCropper(this.rootStore, {
         mediaType: 'photo',
         path: this.path,
         freeStyleCropEnabled: true,
diff --git a/src/state/models/ui/preferences.ts b/src/state/models/ui/preferences.ts
index 05a1eb128..120b4adcc 100644
--- a/src/state/models/ui/preferences.ts
+++ b/src/state/models/ui/preferences.ts
@@ -46,6 +46,7 @@ export class PreferencesModel {
   contentLanguages: string[] =
     deviceLocales?.map?.(locale => locale.languageCode) || []
   contentLabels = new LabelPreferencesModel()
+  savedFeeds: string[] = []
   pinnedFeeds: string[] = []
 
   constructor(public rootStore: RootStoreModel) {
@@ -56,6 +57,7 @@ export class PreferencesModel {
     return {
       contentLanguages: this.contentLanguages,
       contentLabels: this.contentLabels,
+      savedFeeds: this.savedFeeds,
       pinnedFeeds: this.pinnedFeeds,
     }
   }
@@ -75,6 +77,13 @@ export class PreferencesModel {
         // default to the device languages
         this.contentLanguages = deviceLocales.map(locale => locale.languageCode)
       }
+      if (
+        hasProp(v, 'savedFeeds') &&
+        Array.isArray(v.savedFeeds) &&
+        typeof v.savedFeeds.every(item => typeof item === 'string')
+      ) {
+        this.savedFeeds = v.savedFeeds
+      }
       if (
         hasProp(v, 'pinnedFeeds') &&
         Array.isArray(v.pinnedFeeds) &&
@@ -106,10 +115,11 @@ export class PreferencesModel {
               pref.visibility as LabelPreference
           }
         } else if (
-          AppBskyActorDefs.isPinnedFeedsPref(pref) &&
-          AppBskyActorDefs.validatePinnedFeedsPref(pref).success
+          AppBskyActorDefs.isSavedFeedsPref(pref) &&
+          AppBskyActorDefs.validateSavedFeedsPref(pref).success
         ) {
-          this.pinnedFeeds = pref.feeds
+          this.savedFeeds = pref.saved
+          this.pinnedFeeds = pref.pinned
         }
       }
     })
@@ -220,38 +230,57 @@ export class PreferencesModel {
     return res
   }
 
-  async setPinnedFeeds(v: string[]) {
-    const old = this.pinnedFeeds
-    this.pinnedFeeds = v
+  async setSavedFeeds(saved: string[], pinned: string[]) {
+    const oldSaved = this.savedFeeds
+    const oldPinned = this.pinnedFeeds
+    this.savedFeeds = saved
+    this.pinnedFeeds = pinned
     try {
       await this.update((prefs: AppBskyActorDefs.Preferences) => {
         const existing = prefs.find(
           pref =>
-            AppBskyActorDefs.isPinnedFeedsPref(pref) &&
-            AppBskyActorDefs.validatePinnedFeedsPref(pref).success,
+            AppBskyActorDefs.isSavedFeedsPref(pref) &&
+            AppBskyActorDefs.validateSavedFeedsPref(pref).success,
         )
         if (existing) {
-          existing.feeds = v
+          existing.saved = saved
+          existing.pinned = pinned
         } else {
           prefs.push({
-            $type: 'app.bsky.actor.defs#pinnedFeedsPref',
-            feeds: v,
+            $type: 'app.bsky.actor.defs#savedFeedsPref',
+            saved,
+            pinned,
           })
         }
       })
     } catch (e) {
       runInAction(() => {
-        this.pinnedFeeds = old
+        this.savedFeeds = oldSaved
+        this.pinnedFeeds = oldPinned
       })
       throw e
     }
   }
 
+  async addSavedFeed(v: string) {
+    return this.setSavedFeeds([...this.savedFeeds, v], this.pinnedFeeds)
+  }
+
+  async removeSavedFeed(v: string) {
+    return this.setSavedFeeds(
+      this.savedFeeds.filter(uri => uri !== v),
+      this.pinnedFeeds.filter(uri => uri !== v),
+    )
+  }
+
   async addPinnedFeed(v: string) {
-    return this.setPinnedFeeds([...this.pinnedFeeds, v])
+    return this.setSavedFeeds(this.savedFeeds, [...this.pinnedFeeds, v])
   }
 
   async removePinnedFeed(v: string) {
-    return this.setPinnedFeeds(this.pinnedFeeds.filter(uri => uri !== v))
+    return this.setSavedFeeds(
+      this.savedFeeds,
+      this.pinnedFeeds.filter(uri => uri !== v),
+    )
   }
 }
diff --git a/src/state/models/ui/saved-feeds.ts b/src/state/models/ui/saved-feeds.ts
index f500aef2e..9de28e028 100644
--- a/src/state/models/ui/saved-feeds.ts
+++ b/src/state/models/ui/saved-feeds.ts
@@ -5,8 +5,6 @@ import {bundleAsync} from 'lib/async/bundle'
 import {cleanError} from 'lib/strings/errors'
 import {CustomFeedModel} from '../feeds/custom-feed'
 
-const PAGE_SIZE = 100
-
 export class SavedFeedsModel {
   // state
   isLoading = false
@@ -69,16 +67,15 @@ export class SavedFeedsModel {
     try {
       let feeds: AppBskyFeedDefs.GeneratorView[] = []
       let cursor
-      for (let i = 0; i < 100; i++) {
-        const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
-          limit: PAGE_SIZE,
-          cursor,
+      for (
+        let i = 0;
+        i < this.rootStore.preferences.savedFeeds.length;
+        i += 25
+      ) {
+        const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerators({
+          feeds: this.rootStore.preferences.savedFeeds.slice(i, 25),
         })
         feeds = feeds.concat(res.data.feeds)
-        cursor = res.data.cursor
-        if (!cursor) {
-          break
-        }
       }
       runInAction(() => {
         this.feeds = feeds.map(f => new CustomFeedModel(this.rootStore, f))
@@ -127,7 +124,8 @@ export class SavedFeedsModel {
   }
 
   async reorderPinnedFeeds(feeds: CustomFeedModel[]) {
-    return this.rootStore.preferences.setPinnedFeeds(
+    return this.rootStore.preferences.setSavedFeeds(
+      this.rootStore.preferences.savedFeeds,
       feeds.filter(feed => this.isPinned(feed)).map(feed => feed.uri),
     )
   }
@@ -151,7 +149,10 @@ export class SavedFeedsModel {
       pinned[index] = pinned[index + 1]
       pinned[index + 1] = temp
     }
-    await this.rootStore.preferences.setPinnedFeeds(pinned)
+    await this.rootStore.preferences.setSavedFeeds(
+      this.rootStore.preferences.savedFeeds,
+      pinned,
+    )
   }
 
   // state transitions
diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts
index 9b9a176be..95b666243 100644
--- a/src/state/models/ui/shell.ts
+++ b/src/state/models/ui/shell.ts
@@ -119,7 +119,7 @@ export type Modal =
   // Moderation
   | ReportAccountModal
   | ReportPostModal
-  | CreateMuteListModal
+  | CreateOrEditMuteListModal
   | ListAddRemoveUserModal
 
   // Posts
diff --git a/src/view/com/feeds/CustomFeed.tsx b/src/view/com/feeds/CustomFeed.tsx
index d4e843b67..9a71eb846 100644
--- a/src/view/com/feeds/CustomFeed.tsx
+++ b/src/view/com/feeds/CustomFeed.tsx
@@ -40,7 +40,7 @@ export const CustomFeed = observer(
     const navigation = useNavigation<NavigationProp>()
 
     const onToggleSaved = React.useCallback(async () => {
-      if (item.data.viewer?.saved) {
+      if (item.isSaved) {
         store.shell.openModal({
           name: 'confirm',
           title: 'Remove from my feeds',
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 7f13f1838..c17a65b14 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -121,7 +121,7 @@ const Container = observer(
   }: {
     children: React.ReactNode
     hideOnScroll: boolean
-    showBorder: boolean
+    showBorder?: boolean
   }) => {
     const store = useStores()
     const pal = usePalette('default')
diff --git a/src/view/com/util/moderation/ImageHider.tsx b/src/view/com/util/moderation/ImageHider.tsx
index b42c6397d..40add5b67 100644
--- a/src/view/com/util/moderation/ImageHider.tsx
+++ b/src/view/com/util/moderation/ImageHider.tsx
@@ -27,6 +27,10 @@ export function ImageHider({
     setOverride(false)
   }, [setOverride])
 
+  if (moderation.behavior === ModerationBehaviorCode.Hide) {
+    return null
+  }
+
   if (moderation.behavior !== ModerationBehaviorCode.WarnImages) {
     return (
       <View testID={testID} style={style}>
@@ -35,10 +39,6 @@ export function ImageHider({
     )
   }
 
-  if (moderation.behavior === ModerationBehaviorCode.Hide) {
-    return null
-  }
-
   return (
     <View style={[styles.container, containerStyle]}>
       <View testID={testID} style={style}>
-- 
cgit 1.4.1


From 0f30f743cb8ab436d8328d6c324c62366693bb33 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Thu, 25 May 2023 15:56:29 -0500
Subject: Use the bars icon on the viewheader

---
 src/view/com/util/ViewHeader.tsx | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

(limited to 'src/view/com/util/ViewHeader.tsx')

diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index c17a65b14..bb60ba68f 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -4,7 +4,6 @@ import {Animated, StyleSheet, TouchableOpacity, View} from 'react-native'
 import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
 import {useNavigation} from '@react-navigation/native'
 import {CenteredView} from './Views'
-import {UserAvatar} from './UserAvatar'
 import {Text} from './text/Text'
 import {useStores} from 'state/index'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -75,7 +74,11 @@ export const ViewHeader = observer(function ({
               style={[styles.backIcon, pal.text]}
             />
           ) : (
-            <UserAvatar size={30} avatar={store.me.avatar} />
+            <FontAwesomeIcon
+              size={18}
+              icon="bars"
+              style={[styles.backIcon, pal.icon]}
+            />
           )}
         </TouchableOpacity>
         <View style={styles.titleContainer} pointerEvents="none">
-- 
cgit 1.4.1


From bf056cf6228155abac25e91b88a8bed43bae2b70 Mon Sep 17 00:00:00 2001
From: Paul Frazee <pfrazee@gmail.com>
Date: Thu, 25 May 2023 17:09:23 -0500
Subject: Visually unify the home header with other screens

---
 src/lib/styles.ts                        | 3 +++
 src/view/com/pager/FeedsTabBarMobile.tsx | 3 ++-
 src/view/com/pager/TabBar.tsx            | 6 +++---
 src/view/com/util/ViewHeader.tsx         | 2 +-
 src/view/screens/Home.tsx                | 2 +-
 5 files changed, 10 insertions(+), 6 deletions(-)

(limited to 'src/view/com/util/ViewHeader.tsx')

diff --git a/src/lib/styles.ts b/src/lib/styles.ts
index 07315c9f2..fb631c0bf 100644
--- a/src/lib/styles.ts
+++ b/src/lib/styles.ts
@@ -52,6 +52,7 @@ export const colors = {
   green5: '#082b03',
 
   unreadNotifBg: '#ebf6ff',
+  brandBlue: '#0066FF',
 }
 
 export const gradients = {
@@ -218,6 +219,8 @@ export const s = StyleSheet.create({
   green3: {color: colors.green3},
   green4: {color: colors.green4},
   green5: {color: colors.green5},
+
+  brandBlue: {color: colors.brandBlue},
 })
 
 export function lh(
diff --git a/src/view/com/pager/FeedsTabBarMobile.tsx b/src/view/com/pager/FeedsTabBarMobile.tsx
index 333feb313..574265eb7 100644
--- a/src/view/com/pager/FeedsTabBarMobile.tsx
+++ b/src/view/com/pager/FeedsTabBarMobile.tsx
@@ -59,7 +59,7 @@ export const FeedsTabBar = observer(
               />
             </TouchableOpacity>
           </View>
-          <Text type="title" style={[pal.link, s.bold]}>
+          <Text type="title-lg" style={[s.brandBlue, s.bold]}>
             Bluesky
           </Text>
           <View style={[pal.view]}>
@@ -104,6 +104,7 @@ const styles = StyleSheet.create({
     justifyContent: 'space-between',
     alignItems: 'center',
     paddingHorizontal: 18,
+    paddingTop: 8,
     paddingBottom: 2,
     width: '100%',
   },
diff --git a/src/view/com/pager/TabBar.tsx b/src/view/com/pager/TabBar.tsx
index ec636d053..501941476 100644
--- a/src/view/com/pager/TabBar.tsx
+++ b/src/view/com/pager/TabBar.tsx
@@ -131,14 +131,14 @@ const styles = isDesktopWeb
         backgroundColor: 'transparent',
       },
       contentContainer: {
-        columnGap: 14,
+        columnGap: 16,
         marginLeft: 14,
         paddingRight: 28,
         backgroundColor: 'transparent',
       },
       item: {
-        paddingTop: 8,
-        paddingBottom: 8,
+        paddingTop: 10,
+        paddingBottom: 10,
         borderBottomWidth: 3,
         borderBottomColor: 'transparent',
       },
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index bb60ba68f..cdbd555b8 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -77,7 +77,7 @@ export const ViewHeader = observer(function ({
             <FontAwesomeIcon
               size={18}
               icon="bars"
-              style={[styles.backIcon, pal.icon]}
+              style={[styles.backIcon, pal.textLight]}
             />
           )}
         </TouchableOpacity>
diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx
index 3d4430415..9057b3fc3 100644
--- a/src/view/screens/Home.tsx
+++ b/src/view/screens/Home.tsx
@@ -23,7 +23,7 @@ import {useAnalytics} from 'lib/analytics'
 import {ComposeIcon2} from 'lib/icons'
 import {isDesktopWeb} from 'platform/detection'
 
-const HEADER_OFFSET = isDesktopWeb ? 50 : 64
+const HEADER_OFFSET = isDesktopWeb ? 50 : 78
 const POLL_FREQ = 30e3 // 30sec
 
 type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
-- 
cgit 1.4.1

