about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/lib/icons.tsx4
-rw-r--r--src/lib/routes/types.ts2
-rw-r--r--src/state/models/feeds/algo/algo-item.ts77
-rw-r--r--src/view/com/algos/AlgoItem.tsx18
-rw-r--r--src/view/com/algos/useCustomFeed.ts27
-rw-r--r--src/view/com/util/PostCtrls.tsx2
-rw-r--r--src/view/screens/CustomFeed.tsx103
7 files changed, 199 insertions, 34 deletions
diff --git a/src/lib/icons.tsx b/src/lib/icons.tsx
index 4cb491e46..960090ad7 100644
--- a/src/lib/icons.tsx
+++ b/src/lib/icons.tsx
@@ -443,7 +443,7 @@ export function HeartIcon({
   size = 24,
   strokeWidth = 1.5,
 }: {
-  style?: StyleProp<ViewStyle>
+  style?: StyleProp<TextStyle>
   size?: string | number
   strokeWidth: number
 }) {
@@ -464,7 +464,7 @@ export function HeartIconSolid({
   style,
   size = 24,
 }: {
-  style?: StyleProp<ViewStyle>
+  style?: StyleProp<TextStyle>
   size?: string | number
 }) {
   return (
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index 29fadd709..77ed58cc3 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -21,7 +21,7 @@ export type CommonNavigatorParams = {
   CopyrightPolicy: undefined
   AppPasswords: undefined
   SavedFeeds: undefined
-  CustomFeed: {name: string; rkey: string}
+  CustomFeed: {name?: string; rkey: string}
   MutedAccounts: undefined
   BlockedAccounts: undefined
 }
diff --git a/src/state/models/feeds/algo/algo-item.ts b/src/state/models/feeds/algo/algo-item.ts
index 3dee5e2bf..41fe6a976 100644
--- a/src/state/models/feeds/algo/algo-item.ts
+++ b/src/state/models/feeds/algo/algo-item.ts
@@ -33,6 +33,20 @@ export class AlgoItemModel {
     return this.data.uri
   }
 
+  get isSaved() {
+    return this.data.viewer?.saved
+  }
+
+  get isLiked() {
+    return this.data.viewer?.liked
+  }
+
+  set toggleLiked(value: boolean) {
+    if (this.data.viewer) {
+      this.data.viewer.liked = value
+    }
+  }
+
   // public apis
   // =
   async save() {
@@ -57,19 +71,52 @@ export class AlgoItemModel {
     }
   }
 
-  // async getFeedSkeleton() {
-  //   const res = await this.rootStore.agent.app.bsky.feed.getFeedSkeleton({
-  //     feed: this.data.uri,
-  //   })
-  //   const skeleton = res.data.feed
-  //   console.log('skeleton', skeleton)
-  //   return skeleton
-  // }
-  // async getFeed() {
-  //   const feed = await this.rootStore.agent.app.bsky.feed.getFeed({
-  //     feed: this.data.uri,
-  //   })
-  //   console.log('feed', feed)
-  //   return feed
-  // }
+  async like() {
+    try {
+      this.toggleLiked = true
+      await this.rootStore.agent.app.bsky.feed.like.create(
+        {
+          repo: this.rootStore.me.did,
+        },
+        {
+          subject: {
+            uri: this.data.uri,
+            cid: this.data.cid,
+          },
+          createdAt: new Date().toString(),
+        },
+      )
+    } catch (e: any) {
+      this.rootStore.log.error('Failed to like feed', e)
+    }
+  }
+
+  static async getView(store: RootStoreModel, uri: string) {
+    const res = await store.agent.app.bsky.feed.getFeedGenerator({
+      feed: uri,
+    })
+    const view = res.data.view
+    return view
+  }
+
+  async checkIsValid() {
+    const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
+      feed: this.data.uri,
+    })
+    return res.data.isValid
+  }
+
+  async checkIsOnline() {
+    const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
+      feed: this.data.uri,
+    })
+    return res.data.isOnline
+  }
+
+  async reload() {
+    const res = await this.rootStore.agent.app.bsky.feed.getFeedGenerator({
+      feed: this.data.uri,
+    })
+    this.data = res.data.view
+  }
 }
diff --git a/src/view/com/algos/AlgoItem.tsx b/src/view/com/algos/AlgoItem.tsx
index 04117e589..f2fb075da 100644
--- a/src/view/com/algos/AlgoItem.tsx
+++ b/src/view/com/algos/AlgoItem.tsx
@@ -29,7 +29,7 @@ const AlgoItem = observer(
         style={[styles.container, style]}
         onPress={() => {
           navigation.navigate('CustomFeed', {
-            name: item.data.creator.did,
+            name: item.data.displayName,
             rkey: item.data.uri,
           })
         }}
@@ -40,25 +40,27 @@ const AlgoItem = observer(
           </View>
           <View style={[styles.headerTextContainer]}>
             <Text style={[pal.text, s.bold]}>
-              {item.data.displayName ? item.data.displayName : 'Feed name'}
+              {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.'}
+              {item.data.description ?? 'Feed description'}
             </Text>
           </View>
         </View>
 
-        {/* TODO: this feed is like by *3* people UserAvatars and others */}
         <View style={styles.bottomContainer}>
           <View style={styles.likedByContainer}>
-            <View style={styles.likedByAvatars}>
+            {/* <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>
+            </View> */}
 
-            <Text style={[pal.text, pal.textLight]}>Liked by 3 others</Text>
+            <Text style={[pal.text, pal.textLight]}>
+              {item.data.likeCount && item.data.likeCount > 1
+                ? `Liked by ${item.data.likeCount} others`
+                : 'Be the first to like this'}
+            </Text>
           </View>
           <View>
             <Button
diff --git a/src/view/com/algos/useCustomFeed.ts b/src/view/com/algos/useCustomFeed.ts
new file mode 100644
index 000000000..cea9c1cea
--- /dev/null
+++ b/src/view/com/algos/useCustomFeed.ts
@@ -0,0 +1,27 @@
+import {useEffect, useState} from 'react'
+import {useStores} from 'state/index'
+import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
+
+export function useCustomFeed(uri: string) {
+  const store = useStores()
+  const [item, setItem] = useState<AlgoItemModel>()
+  useEffect(() => {
+    async function fetchView() {
+      const res = await store.agent.app.bsky.feed.getFeedGenerator({
+        feed: uri,
+      })
+      const view = res.data.view
+      return view
+    }
+    async function buildFeedItem() {
+      const view = await fetchView()
+      if (view) {
+        const temp = new AlgoItemModel(store, view)
+        setItem(temp)
+      }
+    }
+    buildFeedItem()
+  }, [store, uri])
+
+  return item
+}
diff --git a/src/view/com/util/PostCtrls.tsx b/src/view/com/util/PostCtrls.tsx
index 11b73cea0..3be6b59f1 100644
--- a/src/view/com/util/PostCtrls.tsx
+++ b/src/view/com/util/PostCtrls.tsx
@@ -240,7 +240,7 @@ export function PostCtrls(opts: PostCtrlsOpts) {
         }>
         {opts.isLiked ? (
           <HeartIconSolid
-            style={styles.ctrlIconLiked as StyleProp<ViewStyle>}
+            style={styles.ctrlIconLiked}
             size={opts.big ? 22 : 16}
           />
         ) : (
diff --git a/src/view/screens/CustomFeed.tsx b/src/view/screens/CustomFeed.tsx
index 1d4343b29..070db8c07 100644
--- a/src/view/screens/CustomFeed.tsx
+++ b/src/view/screens/CustomFeed.tsx
@@ -1,23 +1,30 @@
 import {NativeStackScreenProps} from '@react-navigation/native-stack'
+import {usePalette} from 'lib/hooks/usePalette'
+import {HeartIcon} from 'lib/icons'
 import {CommonNavigatorParams} from 'lib/routes/types'
+import {colors, s} from 'lib/styles'
 import {observer} from 'mobx-react-lite'
-import React, {useEffect, useMemo, useRef} from 'react'
-import {FlatList, StyleSheet, View} from 'react-native'
+import React, {useMemo, useRef} from 'react'
+import {FlatList, StyleSheet, TouchableOpacity, View} from 'react-native'
 import {useStores} from 'state/index'
-import {AlgoItemModel} from 'state/models/feeds/algo/algo-item'
 import {PostsFeedModel} from 'state/models/feeds/posts'
+import {useCustomFeed} from 'view/com/algos/useCustomFeed'
 import {withAuthRequired} from 'view/com/auth/withAuthRequired'
 import {Feed} from 'view/com/posts/Feed'
+import {UserAvatar} from 'view/com/util/UserAvatar'
 import {ViewHeader} from 'view/com/util/ViewHeader'
+import {Button} from 'view/com/util/forms/Button'
 import {Text} from 'view/com/util/text/Text'
 
 type Props = NativeStackScreenProps<CommonNavigatorParams, 'CustomFeed'>
 export const CustomFeed = withAuthRequired(
-  observer(({route}: Props) => {
+  observer(({route, navigation}: Props) => {
     const rootStore = useStores()
-    const scrollElRef = useRef<FlatList>(null)
-
     const {rkey, name} = route.params
+    const currentFeed = useCustomFeed(rkey)
+    const pal = usePalette('default')
+
+    const scrollElRef = useRef<FlatList>(null)
 
     const algoFeed: PostsFeedModel = useMemo(() => {
       const feed = new PostsFeedModel(rootStore, 'custom', {
@@ -29,13 +36,62 @@ export const CustomFeed = withAuthRequired(
 
     return (
       <View style={[styles.container]}>
-        <ViewHeader title={'Custom Feed'} showOnDesktop />
+        <View>
+          <ViewHeader
+            title={name ?? `${currentFeed?.data.creator.displayName}'s feed`}
+            showOnDesktop
+          />
+          <View style={[styles.center]}>
+            <View style={[styles.header]}>
+              <View style={styles.avatarContainer}>
+                <UserAvatar
+                  size={30}
+                  avatar={currentFeed?.data.creator.avatar}
+                />
+                <Text style={[pal.textLight]}>
+                  @{currentFeed?.data.creator.handle}
+                </Text>
+              </View>
+              <Text style={[pal.text]}>{currentFeed?.data.description}</Text>
+            </View>
+
+            <View style={[styles.buttonsContainer]}>
+              <Button
+                type={currentFeed?.isSaved ? 'default' : 'inverted'}
+                style={[styles.saveButton]}
+                onPress={() => {
+                  if (currentFeed?.data.viewer?.saved) {
+                    currentFeed?.unsave()
+                    rootStore.me.savedFeeds.removeFeed(currentFeed!.data.uri)
+                  } else {
+                    currentFeed!.save()
+                    rootStore.me.savedFeeds.addFeed(currentFeed!)
+                  }
+                }}
+                label={currentFeed?.data.viewer?.saved ? 'Unsave' : 'Save'}
+              />
+
+              <TouchableOpacity
+                accessibilityRole="button"
+                onPress={() => {
+                  currentFeed?.like()
+                }}
+                style={[styles.likeButton]}>
+                <Text style={[pal.text, s.semiBold]}>
+                  {currentFeed?.data.likeCount}
+                </Text>
+                <HeartIcon strokeWidth={3} size={18} style={styles.liked} />
+              </TouchableOpacity>
+            </View>
+          </View>
+        </View>
 
         <Feed
           scrollElRef={scrollElRef}
           testID={'test-feed'}
           key="default"
           feed={algoFeed}
+          headerOffset={12}
         />
       </View>
     )
@@ -47,4 +103,37 @@ const styles = StyleSheet.create({
     flex: 1,
     height: '100%',
   },
+  center: {alignItems: 'center', justifyContent: 'center', gap: 8},
+  header: {
+    alignItems: 'center',
+    gap: 8,
+  },
+  avatarContainer: {
+    flexDirection: 'row',
+    gap: 8,
+  },
+  buttonsContainer: {
+    flexDirection: 'row',
+    gap: 8,
+  },
+  saveButton: {
+    minWidth: 100,
+    alignItems: 'center',
+  },
+  liked: {
+    color: colors.red3,
+  },
+  notLiked: {
+    color: colors.gray3,
+  },
+  likeButton: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    justifyContent: 'center',
+    paddingVertical: 4,
+    paddingHorizontal: 8,
+    borderRadius: 24,
+    backgroundColor: colors.gray1,
+    gap: 4,
+  },
 })