about summary refs log tree commit diff
path: root/src/components/StarterPack/Main
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-06-21 21:38:04 -0700
committerGitHub <noreply@github.com>2024-06-21 21:38:04 -0700
commitf089f4578131e83cd177b7809ce0f7b75779dfdc (patch)
tree51978aede2040fb8dc319f0749d3de77c7811fbe /src/components/StarterPack/Main
parent35f64535cb8dfa0fe46e740a6398f3b991ecfbc7 (diff)
downloadvoidsky-f089f4578131e83cd177b7809ce0f7b75779dfdc.tar.zst
Starter Packs (#4332)
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
Co-authored-by: Paul Frazee <pfrazee@gmail.com>
Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
Diffstat (limited to 'src/components/StarterPack/Main')
-rw-r--r--src/components/StarterPack/Main/FeedsList.tsx68
-rw-r--r--src/components/StarterPack/Main/ProfilesList.tsx119
2 files changed, 187 insertions, 0 deletions
diff --git a/src/components/StarterPack/Main/FeedsList.tsx b/src/components/StarterPack/Main/FeedsList.tsx
new file mode 100644
index 000000000..e350a422c
--- /dev/null
+++ b/src/components/StarterPack/Main/FeedsList.tsx
@@ -0,0 +1,68 @@
+import React, {useCallback} from 'react'
+import {ListRenderItemInfo, View} from 'react-native'
+import {AppBskyFeedDefs} from '@atproto/api'
+import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
+
+import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
+import {isNative, isWeb} from 'platform/detection'
+import {List, ListRef} from 'view/com/util/List'
+import {SectionRef} from '#/screens/Profile/Sections/types'
+import {atoms as a, useTheme} from '#/alf'
+import * as FeedCard from '#/components/FeedCard'
+
+function keyExtractor(item: AppBskyFeedDefs.GeneratorView) {
+  return item.uri
+}
+
+interface ProfilesListProps {
+  feeds: AppBskyFeedDefs.GeneratorView[]
+  headerHeight: number
+  scrollElRef: ListRef
+}
+
+export const FeedsList = React.forwardRef<SectionRef, ProfilesListProps>(
+  function FeedsListImpl({feeds, headerHeight, scrollElRef}, ref) {
+    const [initialHeaderHeight] = React.useState(headerHeight)
+    const bottomBarOffset = useBottomBarOffset(20)
+    const t = useTheme()
+
+    const onScrollToTop = useCallback(() => {
+      scrollElRef.current?.scrollToOffset({
+        animated: isNative,
+        offset: -headerHeight,
+      })
+    }, [scrollElRef, headerHeight])
+
+    React.useImperativeHandle(ref, () => ({
+      scrollToTop: onScrollToTop,
+    }))
+
+    const renderItem = ({item, index}: ListRenderItemInfo<GeneratorView>) => {
+      return (
+        <View
+          style={[
+            a.p_lg,
+            (isWeb || index !== 0) && a.border_t,
+            t.atoms.border_contrast_low,
+          ]}>
+          <FeedCard.Default type="feed" view={item} />
+        </View>
+      )
+    }
+
+    return (
+      <List
+        data={feeds}
+        renderItem={renderItem}
+        keyExtractor={keyExtractor}
+        ref={scrollElRef}
+        headerOffset={headerHeight}
+        ListFooterComponent={
+          <View style={[{height: initialHeaderHeight + bottomBarOffset}]} />
+        }
+        showsVerticalScrollIndicator={false}
+        desktopFixedHeight={true}
+      />
+    )
+  },
+)
diff --git a/src/components/StarterPack/Main/ProfilesList.tsx b/src/components/StarterPack/Main/ProfilesList.tsx
new file mode 100644
index 000000000..72d35fe2b
--- /dev/null
+++ b/src/components/StarterPack/Main/ProfilesList.tsx
@@ -0,0 +1,119 @@
+import React, {useCallback} from 'react'
+import {ListRenderItemInfo, View} from 'react-native'
+import {
+  AppBskyActorDefs,
+  AppBskyGraphGetList,
+  AtUri,
+  ModerationOpts,
+} from '@atproto/api'
+import {InfiniteData, UseInfiniteQueryResult} from '@tanstack/react-query'
+
+import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
+import {isNative, isWeb} from 'platform/detection'
+import {useSession} from 'state/session'
+import {List, ListRef} from 'view/com/util/List'
+import {SectionRef} from '#/screens/Profile/Sections/types'
+import {atoms as a, useTheme} from '#/alf'
+import {Default as ProfileCard} from '#/components/ProfileCard'
+
+function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic, index: number) {
+  return `${item.did}-${index}`
+}
+
+interface ProfilesListProps {
+  listUri: string
+  listMembersQuery: UseInfiniteQueryResult<
+    InfiniteData<AppBskyGraphGetList.OutputSchema>
+  >
+  moderationOpts: ModerationOpts
+  headerHeight: number
+  scrollElRef: ListRef
+}
+
+export const ProfilesList = React.forwardRef<SectionRef, ProfilesListProps>(
+  function ProfilesListImpl(
+    {listUri, listMembersQuery, moderationOpts, headerHeight, scrollElRef},
+    ref,
+  ) {
+    const t = useTheme()
+    const [initialHeaderHeight] = React.useState(headerHeight)
+    const bottomBarOffset = useBottomBarOffset(20)
+    const {currentAccount} = useSession()
+
+    const [isPTRing, setIsPTRing] = React.useState(false)
+
+    const {data, refetch} = listMembersQuery
+
+    // The server returns these sorted by descending creation date, so we want to invert
+    const profiles = data?.pages
+      .flatMap(p => p.items.map(i => i.subject))
+      .reverse()
+    const isOwn = new AtUri(listUri).host === currentAccount?.did
+
+    const getSortedProfiles = () => {
+      if (!profiles) return
+      if (!isOwn) return profiles
+
+      const myIndex = profiles.findIndex(p => p.did === currentAccount?.did)
+      return myIndex !== -1
+        ? [
+            profiles[myIndex],
+            ...profiles.slice(0, myIndex),
+            ...profiles.slice(myIndex + 1),
+          ]
+        : profiles
+    }
+    const onScrollToTop = useCallback(() => {
+      scrollElRef.current?.scrollToOffset({
+        animated: isNative,
+        offset: -headerHeight,
+      })
+    }, [scrollElRef, headerHeight])
+
+    React.useImperativeHandle(ref, () => ({
+      scrollToTop: onScrollToTop,
+    }))
+
+    const renderItem = ({
+      item,
+      index,
+    }: ListRenderItemInfo<AppBskyActorDefs.ProfileViewBasic>) => {
+      return (
+        <View
+          style={[
+            a.p_lg,
+            t.atoms.border_contrast_low,
+            (isWeb || index !== 0) && a.border_t,
+          ]}>
+          <ProfileCard
+            profile={item}
+            moderationOpts={moderationOpts}
+            logContext="StarterPackProfilesList"
+          />
+        </View>
+      )
+    }
+
+    if (listMembersQuery)
+      return (
+        <List
+          data={getSortedProfiles()}
+          renderItem={renderItem}
+          keyExtractor={keyExtractor}
+          ref={scrollElRef}
+          headerOffset={headerHeight}
+          ListFooterComponent={
+            <View style={[{height: initialHeaderHeight + bottomBarOffset}]} />
+          }
+          showsVerticalScrollIndicator={false}
+          desktopFixedHeight
+          refreshing={isPTRing}
+          onRefresh={async () => {
+            setIsPTRing(true)
+            await refetch()
+            setIsPTRing(false)
+          }}
+        />
+      )
+  },
+)