about summary refs log tree commit diff
path: root/src/view/com/lists
diff options
context:
space:
mode:
authorPaul Frazee <pfrazee@gmail.com>2023-11-13 13:29:33 -0800
committerGitHub <noreply@github.com>2023-11-13 13:29:33 -0800
commita01463788d5e38ffca81fd0d50886838b7a3baba (patch)
treee7e27ca844515e4d9ce404e93dbcca926dea2c5c /src/view/com/lists
parent82177613635c0f10f0af5be63db4b15131ccc89f (diff)
downloadvoidsky-a01463788d5e38ffca81fd0d50886838b7a3baba.tar.zst
More profile refactor updates (#1886)
* Update the profile avatar lightbox

* Update profile editor

* Add dynamic likes tab

* Add dynamic feeds and lists tabs

* Implement lists listing on profiles
Diffstat (limited to 'src/view/com/lists')
-rw-r--r--src/view/com/lists/MyLists.tsx (renamed from src/view/com/lists/ListsList.tsx)21
-rw-r--r--src/view/com/lists/ProfileLists.tsx197
2 files changed, 203 insertions, 15 deletions
diff --git a/src/view/com/lists/ListsList.tsx b/src/view/com/lists/MyLists.tsx
index 100e0d609..2c080582e 100644
--- a/src/view/com/lists/ListsList.tsx
+++ b/src/view/com/lists/MyLists.tsx
@@ -12,7 +12,6 @@ import {AppBskyGraphDefs as GraphDefs} from '@atproto/api'
 import {ListCard} from './ListCard'
 import {MyListsFilter, useMyListsQuery} from '#/state/queries/my-lists'
 import {ErrorMessage} from '../util/error/ErrorMessage'
-import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
 import {Text} from '../util/text/Text'
 import {useAnalytics} from 'lib/analytics/analytics'
 import {usePalette} from 'lib/hooks/usePalette'
@@ -25,9 +24,8 @@ import {cleanError} from '#/lib/strings/errors'
 const LOADING = {_reactKey: '__loading__'}
 const EMPTY = {_reactKey: '__empty__'}
 const ERROR_ITEM = {_reactKey: '__error__'}
-const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
 
-export function ListsList({
+export function MyLists({
   filter,
   inline,
   style,
@@ -42,7 +40,7 @@ export function ListsList({
 }) {
   const pal = usePalette('default')
   const {track} = useAnalytics()
-  const [isRefreshing, setIsRefreshing] = React.useState(false)
+  const [isPTRing, setIsPTRing] = React.useState(false)
   const {data, isFetching, isFetched, isError, error, refetch} =
     useMyListsQuery(filter)
   const isEmpty = !isFetching && !data?.length
@@ -67,14 +65,14 @@ export function ListsList({
 
   const onRefresh = React.useCallback(async () => {
     track('Lists:onRefresh')
-    setIsRefreshing(true)
+    setIsPTRing(true)
     try {
       await refetch()
     } catch (err) {
       logger.error('Failed to refresh lists', {error: err})
     }
-    setIsRefreshing(false)
-  }, [refetch, track, setIsRefreshing])
+    setIsPTRing(false)
+  }, [refetch, track, setIsPTRing])
 
   // rendering
   // =
@@ -98,13 +96,6 @@ export function ListsList({
             onPressTryAgain={onRefresh}
           />
         )
-      } else if (item === LOAD_MORE_ERROR_ITEM) {
-        return (
-          <LoadMoreRetryBtn
-            label="There was an issue fetching your lists. Tap here to try again."
-            onPress={onRefresh}
-          />
-        )
       } else if (item === LOADING) {
         return (
           <View style={{padding: 20}}>
@@ -136,7 +127,7 @@ export function ListsList({
           renderItem={renderItemInner}
           refreshControl={
             <RefreshControl
-              refreshing={isRefreshing}
+              refreshing={isPTRing}
               onRefresh={onRefresh}
               tintColor={pal.colors.text}
               titleColor={pal.colors.text}
diff --git a/src/view/com/lists/ProfileLists.tsx b/src/view/com/lists/ProfileLists.tsx
new file mode 100644
index 000000000..a92af9f3c
--- /dev/null
+++ b/src/view/com/lists/ProfileLists.tsx
@@ -0,0 +1,197 @@
+import React, {MutableRefObject} from 'react'
+import {
+  ActivityIndicator,
+  Dimensions,
+  RefreshControl,
+  StyleProp,
+  StyleSheet,
+  View,
+  ViewStyle,
+} from 'react-native'
+import {FlatList} from '../util/Views'
+import {ListCard} from './ListCard'
+import {ErrorMessage} from '../util/error/ErrorMessage'
+import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
+import {Text} from '../util/text/Text'
+import {useAnalytics} from 'lib/analytics/analytics'
+import {usePalette} from 'lib/hooks/usePalette'
+import {useProfileListsQuery} from '#/state/queries/profile-lists'
+import {OnScrollHandler} from '#/lib/hooks/useOnMainScroll'
+import {logger} from '#/logger'
+import {Trans} from '@lingui/macro'
+import {cleanError} from '#/lib/strings/errors'
+import {useAnimatedScrollHandler} from 'react-native-reanimated'
+import {useTheme} from '#/lib/ThemeContext'
+
+const LOADING = {_reactKey: '__loading__'}
+const EMPTY = {_reactKey: '__empty__'}
+const ERROR_ITEM = {_reactKey: '__error__'}
+const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
+
+export function ProfileLists({
+  did,
+  scrollElRef,
+  onScroll,
+  scrollEventThrottle,
+  headerOffset,
+  style,
+  testID,
+}: {
+  did: string
+  scrollElRef?: MutableRefObject<FlatList<any> | null>
+  onScroll?: OnScrollHandler
+  scrollEventThrottle?: number
+  headerOffset: number
+  style?: StyleProp<ViewStyle>
+  testID?: string
+}) {
+  const pal = usePalette('default')
+  const theme = useTheme()
+  const {track} = useAnalytics()
+  const [isPTRing, setIsPTRing] = React.useState(false)
+  const {
+    data,
+    isFetching,
+    isFetched,
+    hasNextPage,
+    fetchNextPage,
+    isError,
+    error,
+    refetch,
+  } = useProfileListsQuery(did)
+  const isEmpty = !isFetching && !data?.pages[0]?.lists.length
+
+  const items = React.useMemo(() => {
+    let items: any[] = []
+    if (isError && isEmpty) {
+      items = items.concat([ERROR_ITEM])
+    }
+    if (!isFetched && isFetching) {
+      items = items.concat([LOADING])
+    } else if (isEmpty) {
+      items = items.concat([EMPTY])
+    } else if (data?.pages) {
+      for (const page of data?.pages) {
+        items = items.concat(page.lists)
+      }
+    }
+    if (isError && !isEmpty) {
+      items = items.concat([LOAD_MORE_ERROR_ITEM])
+    }
+    return items
+  }, [isError, isEmpty, isFetched, isFetching, data])
+
+  // events
+  // =
+
+  const onRefresh = React.useCallback(async () => {
+    track('Lists:onRefresh')
+    setIsPTRing(true)
+    try {
+      await refetch()
+    } catch (err) {
+      logger.error('Failed to refresh lists', {error: err})
+    }
+    setIsPTRing(false)
+  }, [refetch, track, setIsPTRing])
+
+  const onEndReached = React.useCallback(async () => {
+    if (isFetching || !hasNextPage || isError) return
+
+    track('Lists:onEndReached')
+    try {
+      await fetchNextPage()
+    } catch (err) {
+      logger.error('Failed to load more lists', {error: err})
+    }
+  }, [isFetching, hasNextPage, isError, fetchNextPage, track])
+
+  const onPressRetryLoadMore = React.useCallback(() => {
+    fetchNextPage()
+  }, [fetchNextPage])
+
+  // rendering
+  // =
+
+  const renderItemInner = React.useCallback(
+    ({item}: {item: any}) => {
+      if (item === EMPTY) {
+        return (
+          <View
+            testID="listsEmpty"
+            style={[{padding: 18, borderTopWidth: 1}, pal.border]}>
+            <Text style={pal.textLight}>
+              <Trans>You have no lists.</Trans>
+            </Text>
+          </View>
+        )
+      } else if (item === ERROR_ITEM) {
+        return (
+          <ErrorMessage message={cleanError(error)} onPressTryAgain={refetch} />
+        )
+      } else if (item === LOAD_MORE_ERROR_ITEM) {
+        return (
+          <LoadMoreRetryBtn
+            label="There was an issue fetching your lists. Tap here to try again."
+            onPress={onPressRetryLoadMore}
+          />
+        )
+      } else if (item === LOADING) {
+        return (
+          <View style={{padding: 20}}>
+            <ActivityIndicator />
+          </View>
+        )
+      }
+      return (
+        <ListCard
+          list={item}
+          testID={`list-${item.name}`}
+          style={styles.item}
+        />
+      )
+    },
+    [error, refetch, onPressRetryLoadMore, pal],
+  )
+
+  const scrollHandler = useAnimatedScrollHandler(onScroll || {})
+  return (
+    <View testID={testID} style={style}>
+      <FlatList
+        testID={testID ? `${testID}-flatlist` : undefined}
+        ref={scrollElRef}
+        data={items}
+        keyExtractor={(item: any) => item._reactKey}
+        renderItem={renderItemInner}
+        refreshControl={
+          <RefreshControl
+            refreshing={isPTRing}
+            onRefresh={onRefresh}
+            tintColor={pal.colors.text}
+            titleColor={pal.colors.text}
+            progressViewOffset={headerOffset}
+          />
+        }
+        contentContainerStyle={{
+          minHeight: Dimensions.get('window').height * 1.5,
+        }}
+        style={{paddingTop: headerOffset}}
+        onScroll={onScroll != null ? scrollHandler : undefined}
+        scrollEventThrottle={scrollEventThrottle}
+        indicatorStyle={theme.colorScheme === 'dark' ? 'white' : 'black'}
+        removeClippedSubviews={true}
+        contentOffset={{x: 0, y: headerOffset * -1}}
+        // @ts-ignore our .web version only -prf
+        desktopFixedHeight
+        onEndReached={onEndReached}
+      />
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  item: {
+    paddingHorizontal: 18,
+    paddingVertical: 4,
+  },
+})