about summary refs log tree commit diff
path: root/src/view/com/lists/MyLists.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/lists/MyLists.tsx')
-rw-r--r--src/view/com/lists/MyLists.tsx151
1 files changed, 151 insertions, 0 deletions
diff --git a/src/view/com/lists/MyLists.tsx b/src/view/com/lists/MyLists.tsx
new file mode 100644
index 000000000..2c080582e
--- /dev/null
+++ b/src/view/com/lists/MyLists.tsx
@@ -0,0 +1,151 @@
+import React from 'react'
+import {
+  ActivityIndicator,
+  FlatList as RNFlatList,
+  RefreshControl,
+  StyleProp,
+  StyleSheet,
+  View,
+  ViewStyle,
+} from 'react-native'
+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 {Text} from '../util/text/Text'
+import {useAnalytics} from 'lib/analytics/analytics'
+import {usePalette} from 'lib/hooks/usePalette'
+import {FlatList} from '../util/Views'
+import {s} from 'lib/styles'
+import {logger} from '#/logger'
+import {Trans} from '@lingui/macro'
+import {cleanError} from '#/lib/strings/errors'
+
+const LOADING = {_reactKey: '__loading__'}
+const EMPTY = {_reactKey: '__empty__'}
+const ERROR_ITEM = {_reactKey: '__error__'}
+
+export function MyLists({
+  filter,
+  inline,
+  style,
+  renderItem,
+  testID,
+}: {
+  filter: MyListsFilter
+  inline?: boolean
+  style?: StyleProp<ViewStyle>
+  renderItem?: (list: GraphDefs.ListView, index: number) => JSX.Element
+  testID?: string
+}) {
+  const pal = usePalette('default')
+  const {track} = useAnalytics()
+  const [isPTRing, setIsPTRing] = React.useState(false)
+  const {data, isFetching, isFetched, isError, error, refetch} =
+    useMyListsQuery(filter)
+  const isEmpty = !isFetching && !data?.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 {
+      items = items.concat(data)
+    }
+    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])
+
+  // rendering
+  // =
+
+  const renderItemInner = React.useCallback(
+    ({item, index}: {item: any; index: number}) => {
+      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={onRefresh}
+          />
+        )
+      } else if (item === LOADING) {
+        return (
+          <View style={{padding: 20}}>
+            <ActivityIndicator />
+          </View>
+        )
+      }
+      return renderItem ? (
+        renderItem(item, index)
+      ) : (
+        <ListCard
+          list={item}
+          testID={`list-${item.name}`}
+          style={styles.item}
+        />
+      )
+    },
+    [error, onRefresh, renderItem, pal],
+  )
+
+  const FlatListCom = inline ? RNFlatList : FlatList
+  return (
+    <View testID={testID} style={style}>
+      {items.length > 0 && (
+        <FlatListCom
+          testID={testID ? `${testID}-flatlist` : undefined}
+          data={items}
+          keyExtractor={(item: any) => item._reactKey}
+          renderItem={renderItemInner}
+          refreshControl={
+            <RefreshControl
+              refreshing={isPTRing}
+              onRefresh={onRefresh}
+              tintColor={pal.colors.text}
+              titleColor={pal.colors.text}
+            />
+          }
+          contentContainerStyle={[s.contentContainer]}
+          removeClippedSubviews={true}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+        />
+      )}
+    </View>
+  )
+}
+
+const styles = StyleSheet.create({
+  item: {
+    paddingHorizontal: 18,
+    paddingVertical: 4,
+  },
+})