about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/state/models/feeds/algo/actor.ts (renamed from src/state/models/feeds/actor.ts)14
-rw-r--r--src/state/models/feeds/algo/algo-item.ts56
-rw-r--r--src/state/models/feeds/algo/saved.ts (renamed from src/state/models/feeds/bookmarked.ts)40
-rw-r--r--src/state/models/ui/profile.ts2
-rw-r--r--src/view/com/algos/AlgoItem.tsx93
-rw-r--r--src/view/com/util/post-embeds/index.tsx3
-rw-r--r--src/view/screens/Profile.tsx8
7 files changed, 144 insertions, 72 deletions
diff --git a/src/state/models/feeds/actor.ts b/src/state/models/feeds/algo/actor.ts
index 08b7c2a74..e42df8495 100644
--- a/src/state/models/feeds/actor.ts
+++ b/src/state/models/feeds/algo/actor.ts
@@ -1,11 +1,9 @@
 import {makeAutoObservable} from 'mobx'
-import {
-  AppBskyFeedDefs as FeedDefs,
-  AppBskyFeedGetActorFeeds as GetActorFeeds,
-} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
+import {AppBskyFeedGetActorFeeds as GetActorFeeds} from '@atproto/api'
+import {RootStoreModel} from '../../root-store'
 import {bundleAsync} from 'lib/async/bundle'
 import {cleanError} from 'lib/strings/errors'
+import {AlgoItemModel} from './algo-item'
 
 const PAGE_SIZE = 30
 
@@ -19,7 +17,7 @@ export class ActorFeedsModel {
   loadMoreCursor?: string
 
   // data
-  feeds: FeedDefs.GeneratorView[] = []
+  feeds: AlgoItemModel[] = []
 
   constructor(
     public rootStore: RootStoreModel,
@@ -116,6 +114,8 @@ export class ActorFeedsModel {
   _appendAll(res: GetActorFeeds.Response) {
     this.loadMoreCursor = res.data.cursor
     this.hasMore = !!this.loadMoreCursor
-    this.feeds = this.feeds.concat(res.data.feeds)
+    for (const f of res.data.feeds) {
+      this.feeds.push(new AlgoItemModel(this.rootStore, f))
+    }
   }
 }
diff --git a/src/state/models/feeds/algo/algo-item.ts b/src/state/models/feeds/algo/algo-item.ts
new file mode 100644
index 000000000..555d1d56d
--- /dev/null
+++ b/src/state/models/feeds/algo/algo-item.ts
@@ -0,0 +1,56 @@
+import {AppBskyFeedDefs} from '@atproto/api'
+import {makeAutoObservable, makeObservable} from 'mobx'
+import {RootStoreModel} from 'state/models/root-store'
+
+// algoitemmodel implemented in mobx
+export class AlgoItemModel {
+  // data
+  data: AppBskyFeedDefs.GeneratorView
+
+  constructor(
+    public rootStore: RootStoreModel,
+    view: AppBskyFeedDefs.GeneratorView,
+  ) {
+    this.data = view
+    makeAutoObservable(
+      this,
+      {
+        rootStore: false,
+      },
+      {autoBind: true},
+    )
+  }
+
+  set toggleSaved(value: boolean) {
+    console.log('toggleSaved', this.data.viewer)
+    if (this.data.viewer) {
+      this.data.viewer.saved = value
+    }
+  }
+
+  async save() {
+    try {
+      // runInAction(() => {
+      this.toggleSaved = true
+      // })
+      const res = await this.rootStore.agent.app.bsky.feed.saveFeed({
+        feed: this.data.uri,
+      })
+    } catch (e: any) {
+      this.rootStore.log.error('Failed to save feed', e)
+    }
+  }
+
+  async unsave() {
+    try {
+      // runInAction(() => {
+      this.toggleSaved = false
+      // })
+      const res = await this.rootStore.agent.app.bsky.feed.unsaveFeed({
+        feed: this.data.uri,
+      })
+    } catch (e: any) {
+      this.rootStore.log.error('Failed to unsanve feed', e)
+    }
+  }
+}
diff --git a/src/state/models/feeds/bookmarked.ts b/src/state/models/feeds/algo/saved.ts
index d472f0480..fabb75ae0 100644
--- a/src/state/models/feeds/bookmarked.ts
+++ b/src/state/models/feeds/algo/saved.ts
@@ -1,17 +1,13 @@
 import {makeAutoObservable} from 'mobx'
-import {
-  AppBskyFeedGetBookmarkedFeeds as GetBookmarkedFeeds,
-  // AppBskyFeedBookmarkFeed as bookmarkedFeed,
-  // AppBskyFeedUnbookmarkFeed as unbookmarkFeed,
-  AppBskyFeedDefs as FeedDefs,
-} from '@atproto/api'
-import {RootStoreModel} from '../root-store'
+import {AppBskyFeedGetSavedFeeds as GetSavedFeeds} from '@atproto/api'
+import {RootStoreModel} from '../../root-store'
 import {bundleAsync} from 'lib/async/bundle'
 import {cleanError} from 'lib/strings/errors'
+import {AlgoItemModel} from './algo-item'
 
 const PAGE_SIZE = 30
 
-export class BookmarkedFeedsModel {
+export class SavedFeedsModel {
   // state
   isLoading = false
   isRefreshing = false
@@ -21,7 +17,7 @@ export class BookmarkedFeedsModel {
   loadMoreCursor?: string
 
   // data
-  feeds: FeedDefs.GeneratorView[] = []
+  feeds: AlgoItemModel[] = []
 
   constructor(public rootStore: RootStoreModel) {
     makeAutoObservable(
@@ -68,7 +64,7 @@ export class BookmarkedFeedsModel {
     }
     this._xLoading(replace)
     try {
-      const res = await this.rootStore.agent.app.bsky.feed.getBookmarkedFeeds({
+      const res = await this.rootStore.agent.app.bsky.feed.getSavedFeeds({
         limit: PAGE_SIZE,
         cursor: replace ? undefined : this.loadMoreCursor,
       })
@@ -83,22 +79,6 @@ export class BookmarkedFeedsModel {
     }
   })
 
-  async bookmark(feed: FeedDefs.GeneratorView) {
-    try {
-      await this.rootStore.agent.app.bsky.feed.bookmarkFeed({feed: feed.uri})
-    } catch (e: any) {
-      this.rootStore.log.error('Failed to bookmark feed', e)
-    }
-  }
-
-  async unbookmark(feed: FeedDefs.GeneratorView) {
-    try {
-      await this.rootStore.agent.app.bsky.feed.unbookmarkFeed({feed: feed.uri})
-    } catch (e: any) {
-      this.rootStore.log.error('Failed to unbookmark feed', e)
-    }
-  }
-
   // state transitions
   // =
 
@@ -121,14 +101,16 @@ export class BookmarkedFeedsModel {
   // helper functions
   // =
 
-  _replaceAll(res: GetBookmarkedFeeds.Response) {
+  _replaceAll(res: GetSavedFeeds.Response) {
     this.feeds = []
     this._appendAll(res)
   }
 
-  _appendAll(res: GetBookmarkedFeeds.Response) {
+  _appendAll(res: GetSavedFeeds.Response) {
     this.loadMoreCursor = res.data.cursor
     this.hasMore = !!this.loadMoreCursor
-    this.feeds = this.feeds.concat(res.data.feeds)
+    for (const f of res.data.feeds) {
+      this.feeds.push(new AlgoItemModel(f))
+    }
   }
 }
diff --git a/src/state/models/ui/profile.ts b/src/state/models/ui/profile.ts
index 86199108e..855955d12 100644
--- a/src/state/models/ui/profile.ts
+++ b/src/state/models/ui/profile.ts
@@ -2,7 +2,7 @@ import {makeAutoObservable} from 'mobx'
 import {RootStoreModel} from '../root-store'
 import {ProfileModel} from '../content/profile'
 import {PostsFeedModel} from '../feeds/posts'
-import {ActorFeedsModel} from '../feeds/actor'
+import {ActorFeedsModel} from '../feeds/algo/actor'
 import {AppBskyFeedDefs} from '@atproto/api'
 
 export enum Sections {
diff --git a/src/view/com/algos/AlgoItem.tsx b/src/view/com/algos/AlgoItem.tsx
index 979518f1d..987bfd68d 100644
--- a/src/view/com/algos/AlgoItem.tsx
+++ b/src/view/com/algos/AlgoItem.tsx
@@ -1,41 +1,62 @@
 import React from 'react'
 import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
 import {Text} from '../util/text/Text'
-import {AppBskyFeedDefs} from '@atproto/api'
 import {usePalette} from 'lib/hooks/usePalette'
 import {s} from 'lib/styles'
 import {UserAvatar} from '../util/UserAvatar'
+import {Button} from '../util/forms/Button'
+import {observer} from 'mobx-react-lite'
+import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
 
-const AlgoItem = ({
-  item,
-  style,
-}: {
-  item: AppBskyFeedDefs.GeneratorView
-  style?: StyleProp<ViewStyle>
-}) => {
-  const pal = usePalette('default')
-  return (
-    <View style={[styles.container, style]} key={item.uri}>
-      <View style={[styles.headerContainer]}>
-        <View style={[s.mr20]}>
-          <UserAvatar size={56} avatar={item.avatar} />
+const AlgoItem = observer(
+  ({item, style}: {item: AlgoItemModel; style?: StyleProp<ViewStyle>}) => {
+    const pal = usePalette('default')
+    return (
+      <View style={[styles.container, style]} key={item.data.uri}>
+        <View style={[styles.headerContainer]}>
+          <View style={[s.mr10]}>
+            <UserAvatar size={36} avatar={item.data.avatar} />
+          </View>
+          <View style={[styles.headerTextContainer]}>
+            <Text style={[pal.text, s.bold]}>
+              {item.data.displayName ?? 'Feed name'}
+            </Text>
+            <Text style={[pal.textLight, styles.description]}>
+              {item.data.description ??
+                'THIS IS A FEED DESCRIPTION, IT WILL TELL YOU WHAT THE FEED IS ABOUT. THIS IS A COOL FEED ABOUT COOL PEOPLE.'}
+            </Text>
+          </View>
         </View>
-        <View style={[styles.headerTextContainer]}>
-          <Text style={[pal.text, s.bold]}>
-            {item.displayName ?? 'Feed name'}
-          </Text>
-          <Text style={[pal.textLight, styles.description]}>
-            {item.description ??
-              'THIS IS A FEED DESCRIPTION, IT WILL TELL YOU WHAT THE FEED IS ABOUT. THIS IS A COOL FEED ABOUT COOL PEOPLE.'}
-          </Text>
-        </View>
-      </View>
 
-      {/* TODO: this feed is like by *3* people UserAvatars and others */}
-    </View>
-  )
-}
+        {/* TODO: this feed is like by *3* people UserAvatars and others */}
+        <View style={styles.bottomContainer}>
+          <View style={styles.likedByContainer}>
+            <View style={styles.likedByAvatars}>
+              <UserAvatar size={24} avatar={item.data.avatar} />
+              <UserAvatar size={24} avatar={item.data.avatar} />
+              <UserAvatar size={24} avatar={item.data.avatar} />
+            </View>
 
+            <Text style={[pal.text, pal.textLight]}>Liked by 3 others</Text>
+          </View>
+          <View>
+            <Button
+              type="inverted"
+              onPress={() => {
+                if (item.data.viewer?.saved) {
+                  item.unsave()
+                } else {
+                  item.save()
+                }
+              }}
+              label={item.data.viewer?.saved ? 'Unsave' : 'Save'}
+            />
+          </View>
+        </View>
+      </View>
+    )
+  },
+)
 export default AlgoItem
 
 const styles = StyleSheet.create({
@@ -43,10 +64,10 @@ const styles = StyleSheet.create({
     paddingHorizontal: 18,
     paddingVertical: 20,
     flexDirection: 'column',
-    columnGap: 36,
     flex: 1,
     borderTopWidth: 1,
     borderTopColor: '#E5E5E5',
+    gap: 18,
   },
   headerContainer: {
     flexDirection: 'row',
@@ -60,4 +81,18 @@ const styles = StyleSheet.create({
     flex: 1,
     flexWrap: 'wrap',
   },
+  bottomContainer: {
+    flexDirection: 'row',
+    justifyContent: 'space-between',
+    alignItems: 'center',
+  },
+  likedByContainer: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 2,
+  },
+  likedByAvatars: {
+    flexDirection: 'row',
+    gap: -12,
+  },
 })
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index 72158af42..3eb2720c0 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -26,6 +26,7 @@ import {getYoutubeVideoId} from 'lib/strings/url-helpers'
 import QuoteEmbed from './QuoteEmbed'
 import {AutoSizedImage} from '../images/AutoSizedImage'
 import AlgoItem from 'view/com/algos/AlgoItem'
+import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
 
 type Embed =
   | AppBskyEmbedRecord.View
@@ -171,7 +172,7 @@ export function PostEmbeds({
   ) {
     return (
       <AlgoItem
-        item={embed.record}
+        item={new AlgoItemModel(store, embed.record)}
         style={[pal.view, pal.border, styles.extOuter]}
       />
     )
diff --git a/src/view/screens/Profile.tsx b/src/view/screens/Profile.tsx
index b88caf1f8..68aa97b66 100644
--- a/src/view/screens/Profile.tsx
+++ b/src/view/screens/Profile.tsx
@@ -21,8 +21,8 @@ import {FAB} from '../com/util/fab/FAB'
 import {s, colors} from 'lib/styles'
 import {useAnalytics} from 'lib/analytics'
 import {ComposeIcon2} from 'lib/icons'
-import {AppBskyFeedDefs} from '@atproto/api'
 import AlgoItem from 'view/com/algos/AlgoItem'
+import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'Profile'>
 export const ProfileScreen = withAuthRequired(
@@ -154,10 +154,8 @@ export const ProfileScreen = withAuthRequired(
           )
         } else if (item instanceof PostsFeedSliceModel) {
           return <FeedSlice slice={item} ignoreMuteFor={uiState.profile.did} />
-        } else if (item.creator) {
-          // TODO: this is a hack to see if it is a custom feed. fix it to something more robust
-          const typedItem = item as AppBskyFeedDefs.GeneratorView
-          return <AlgoItem item={typedItem} />
+        } else if (item instanceof AlgoItemModel) {
+          return <AlgoItem item={item} />
         }
         return <View />
       },