about summary refs log tree commit diff
path: root/src/view/com/lists/ListItems.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/view/com/lists/ListItems.tsx')
-rw-r--r--src/view/com/lists/ListItems.tsx577
1 files changed, 286 insertions, 291 deletions
diff --git a/src/view/com/lists/ListItems.tsx b/src/view/com/lists/ListItems.tsx
index d611bc504..b78cf83cf 100644
--- a/src/view/com/lists/ListItems.tsx
+++ b/src/view/com/lists/ListItems.tsx
@@ -35,319 +35,314 @@ const EMPTY_ITEM = {_reactKey: '__empty__'}
 const ERROR_ITEM = {_reactKey: '__error__'}
 const LOAD_MORE_ERROR_ITEM = {_reactKey: '__load_more_error__'}
 
-export const ListItems = observer(
-  ({
-    list,
-    style,
-    scrollElRef,
-    onPressTryAgain,
-    onToggleSubscribed,
-    onPressEditList,
-    onPressDeleteList,
-    onPressShareList,
-    onPressReportList,
-    renderEmptyState,
-    testID,
-    headerOffset = 0,
-  }: {
-    list: ListModel
-    style?: StyleProp<ViewStyle>
-    scrollElRef?: MutableRefObject<FlatList<any> | null>
-    onPressTryAgain?: () => void
-    onToggleSubscribed: () => void
-    onPressEditList: () => void
-    onPressDeleteList: () => void
-    onPressShareList: () => void
-    onPressReportList: () => void
-    renderEmptyState?: () => JSX.Element
-    testID?: string
-    headerOffset?: number
-  }) => {
-    const pal = usePalette('default')
-    const store = useStores()
-    const {track} = useAnalytics()
-    const [isRefreshing, setIsRefreshing] = React.useState(false)
+export const ListItems = observer(function ListItemsImpl({
+  list,
+  style,
+  scrollElRef,
+  onPressTryAgain,
+  onToggleSubscribed,
+  onPressEditList,
+  onPressDeleteList,
+  onPressShareList,
+  onPressReportList,
+  renderEmptyState,
+  testID,
+  headerOffset = 0,
+}: {
+  list: ListModel
+  style?: StyleProp<ViewStyle>
+  scrollElRef?: MutableRefObject<FlatList<any> | null>
+  onPressTryAgain?: () => void
+  onToggleSubscribed: () => void
+  onPressEditList: () => void
+  onPressDeleteList: () => void
+  onPressShareList: () => void
+  onPressReportList: () => void
+  renderEmptyState?: () => JSX.Element
+  testID?: string
+  headerOffset?: number
+}) {
+  const pal = usePalette('default')
+  const store = useStores()
+  const {track} = useAnalytics()
+  const [isRefreshing, setIsRefreshing] = React.useState(false)
 
-    const data = React.useMemo(() => {
-      let items: any[] = [HEADER_ITEM]
-      if (list.hasLoaded) {
-        if (list.hasError) {
-          items = items.concat([ERROR_ITEM])
-        }
-        if (list.isEmpty) {
-          items = items.concat([EMPTY_ITEM])
-        } else {
-          items = items.concat(list.items)
-        }
-        if (list.loadMoreError) {
-          items = items.concat([LOAD_MORE_ERROR_ITEM])
-        }
-      } else if (list.isLoading) {
-        items = items.concat([LOADING_ITEM])
+  const data = React.useMemo(() => {
+    let items: any[] = [HEADER_ITEM]
+    if (list.hasLoaded) {
+      if (list.hasError) {
+        items = items.concat([ERROR_ITEM])
       }
-      return items
-    }, [
-      list.hasError,
-      list.hasLoaded,
-      list.isLoading,
-      list.isEmpty,
-      list.items,
-      list.loadMoreError,
-    ])
+      if (list.isEmpty) {
+        items = items.concat([EMPTY_ITEM])
+      } else {
+        items = items.concat(list.items)
+      }
+      if (list.loadMoreError) {
+        items = items.concat([LOAD_MORE_ERROR_ITEM])
+      }
+    } else if (list.isLoading) {
+      items = items.concat([LOADING_ITEM])
+    }
+    return items
+  }, [
+    list.hasError,
+    list.hasLoaded,
+    list.isLoading,
+    list.isEmpty,
+    list.items,
+    list.loadMoreError,
+  ])
 
-    // events
-    // =
+  // events
+  // =
 
-    const onRefresh = React.useCallback(async () => {
-      track('Lists:onRefresh')
-      setIsRefreshing(true)
-      try {
-        await list.refresh()
-      } catch (err) {
-        list.rootStore.log.error('Failed to refresh lists', err)
-      }
-      setIsRefreshing(false)
-    }, [list, track, setIsRefreshing])
+  const onRefresh = React.useCallback(async () => {
+    track('Lists:onRefresh')
+    setIsRefreshing(true)
+    try {
+      await list.refresh()
+    } catch (err) {
+      list.rootStore.log.error('Failed to refresh lists', err)
+    }
+    setIsRefreshing(false)
+  }, [list, track, setIsRefreshing])
 
-    const onEndReached = React.useCallback(async () => {
-      track('Lists:onEndReached')
-      try {
-        await list.loadMore()
-      } catch (err) {
-        list.rootStore.log.error('Failed to load more lists', err)
-      }
-    }, [list, track])
+  const onEndReached = React.useCallback(async () => {
+    track('Lists:onEndReached')
+    try {
+      await list.loadMore()
+    } catch (err) {
+      list.rootStore.log.error('Failed to load more lists', err)
+    }
+  }, [list, track])
+
+  const onPressRetryLoadMore = React.useCallback(() => {
+    list.retryLoadMore()
+  }, [list])
 
-    const onPressRetryLoadMore = React.useCallback(() => {
-      list.retryLoadMore()
-    }, [list])
+  const onPressEditMembership = React.useCallback(
+    (profile: AppBskyActorDefs.ProfileViewBasic) => {
+      store.shell.openModal({
+        name: 'list-add-remove-user',
+        subject: profile.did,
+        displayName: profile.displayName || profile.handle,
+        onUpdate() {
+          list.refresh()
+        },
+      })
+    },
+    [store, list],
+  )
 
-    const onPressEditMembership = React.useCallback(
-      (profile: AppBskyActorDefs.ProfileViewBasic) => {
-        store.shell.openModal({
-          name: 'list-add-remove-user',
-          subject: profile.did,
-          displayName: profile.displayName || profile.handle,
-          onUpdate() {
-            list.refresh()
-          },
-        })
-      },
-      [store, list],
-    )
+  // rendering
+  // =
 
-    // rendering
-    // =
+  const renderMemberButton = React.useCallback(
+    (profile: AppBskyActorDefs.ProfileViewBasic) => {
+      if (!list.isOwner) {
+        return null
+      }
+      return (
+        <Button
+          type="default"
+          label="Edit"
+          onPress={() => onPressEditMembership(profile)}
+        />
+      )
+    },
+    [list, onPressEditMembership],
+  )
 
-    const renderMemberButton = React.useCallback(
-      (profile: AppBskyActorDefs.ProfileViewBasic) => {
-        if (!list.isOwner) {
-          return null
+  const renderItem = React.useCallback(
+    ({item}: {item: any}) => {
+      if (item === EMPTY_ITEM) {
+        if (renderEmptyState) {
+          return renderEmptyState()
         }
+        return <View />
+      } else if (item === HEADER_ITEM) {
+        return list.list ? (
+          <ListHeader
+            list={list.list}
+            isOwner={list.isOwner}
+            onToggleSubscribed={onToggleSubscribed}
+            onPressEditList={onPressEditList}
+            onPressDeleteList={onPressDeleteList}
+            onPressShareList={onPressShareList}
+            onPressReportList={onPressReportList}
+          />
+        ) : null
+      } else if (item === ERROR_ITEM) {
         return (
-          <Button
-            type="default"
-            label="Edit"
-            onPress={() => onPressEditMembership(profile)}
+          <ErrorMessage
+            message={list.error}
+            onPressTryAgain={onPressTryAgain}
           />
         )
-      },
-      [list, onPressEditMembership],
-    )
-
-    const renderItem = React.useCallback(
-      ({item}: {item: any}) => {
-        if (item === EMPTY_ITEM) {
-          if (renderEmptyState) {
-            return renderEmptyState()
-          }
-          return <View />
-        } else if (item === HEADER_ITEM) {
-          return list.list ? (
-            <ListHeader
-              list={list.list}
-              isOwner={list.isOwner}
-              onToggleSubscribed={onToggleSubscribed}
-              onPressEditList={onPressEditList}
-              onPressDeleteList={onPressDeleteList}
-              onPressShareList={onPressShareList}
-              onPressReportList={onPressReportList}
-            />
-          ) : null
-        } else if (item === ERROR_ITEM) {
-          return (
-            <ErrorMessage
-              message={list.error}
-              onPressTryAgain={onPressTryAgain}
-            />
-          )
-        } else if (item === LOAD_MORE_ERROR_ITEM) {
-          return (
-            <LoadMoreRetryBtn
-              label="There was an issue fetching the list. Tap here to try again."
-              onPress={onPressRetryLoadMore}
-            />
-          )
-        } else if (item === LOADING_ITEM) {
-          return <ProfileCardFeedLoadingPlaceholder />
-        }
+      } else if (item === LOAD_MORE_ERROR_ITEM) {
         return (
-          <ProfileCard
-            testID={`user-${
-              (item as AppBskyGraphDefs.ListItemView).subject.handle
-            }`}
-            profile={(item as AppBskyGraphDefs.ListItemView).subject}
-            renderButton={renderMemberButton}
+          <LoadMoreRetryBtn
+            label="There was an issue fetching the list. Tap here to try again."
+            onPress={onPressRetryLoadMore}
           />
         )
-      },
-      [
-        renderMemberButton,
-        renderEmptyState,
-        list.list,
-        list.isOwner,
-        list.error,
-        onToggleSubscribed,
-        onPressEditList,
-        onPressDeleteList,
-        onPressShareList,
-        onPressReportList,
-        onPressTryAgain,
-        onPressRetryLoadMore,
-      ],
-    )
+      } else if (item === LOADING_ITEM) {
+        return <ProfileCardFeedLoadingPlaceholder />
+      }
+      return (
+        <ProfileCard
+          testID={`user-${
+            (item as AppBskyGraphDefs.ListItemView).subject.handle
+          }`}
+          profile={(item as AppBskyGraphDefs.ListItemView).subject}
+          renderButton={renderMemberButton}
+        />
+      )
+    },
+    [
+      renderMemberButton,
+      renderEmptyState,
+      list.list,
+      list.isOwner,
+      list.error,
+      onToggleSubscribed,
+      onPressEditList,
+      onPressDeleteList,
+      onPressShareList,
+      onPressReportList,
+      onPressTryAgain,
+      onPressRetryLoadMore,
+    ],
+  )
 
-    const Footer = React.useCallback(
-      () =>
-        list.isLoading ? (
-          <View style={styles.feedFooter}>
-            <ActivityIndicator />
-          </View>
-        ) : (
-          <View />
-        ),
-      [list],
-    )
+  const Footer = React.useCallback(
+    () =>
+      list.isLoading ? (
+        <View style={styles.feedFooter}>
+          <ActivityIndicator />
+        </View>
+      ) : (
+        <View />
+      ),
+    [list],
+  )
 
-    return (
-      <View testID={testID} style={style}>
-        {data.length > 0 && (
-          <FlatList
-            testID={testID ? `${testID}-flatlist` : undefined}
-            ref={scrollElRef}
-            data={data}
-            keyExtractor={item => item._reactKey}
-            renderItem={renderItem}
-            ListFooterComponent={Footer}
-            refreshControl={
-              <RefreshControl
-                refreshing={isRefreshing}
-                onRefresh={onRefresh}
-                tintColor={pal.colors.text}
-                titleColor={pal.colors.text}
-                progressViewOffset={headerOffset}
-              />
-            }
-            contentContainerStyle={s.contentContainer}
-            style={{paddingTop: headerOffset}}
-            onEndReached={onEndReached}
-            onEndReachedThreshold={0.6}
-            removeClippedSubviews={true}
-            contentOffset={{x: 0, y: headerOffset * -1}}
-            // @ts-ignore our .web version only -prf
-            desktopFixedHeight
-          />
-        )}
-      </View>
-    )
-  },
-)
+  return (
+    <View testID={testID} style={style}>
+      {data.length > 0 && (
+        <FlatList
+          testID={testID ? `${testID}-flatlist` : undefined}
+          ref={scrollElRef}
+          data={data}
+          keyExtractor={item => item._reactKey}
+          renderItem={renderItem}
+          ListFooterComponent={Footer}
+          refreshControl={
+            <RefreshControl
+              refreshing={isRefreshing}
+              onRefresh={onRefresh}
+              tintColor={pal.colors.text}
+              titleColor={pal.colors.text}
+              progressViewOffset={headerOffset}
+            />
+          }
+          contentContainerStyle={s.contentContainer}
+          style={{paddingTop: headerOffset}}
+          onEndReached={onEndReached}
+          onEndReachedThreshold={0.6}
+          removeClippedSubviews={true}
+          contentOffset={{x: 0, y: headerOffset * -1}}
+          // @ts-ignore our .web version only -prf
+          desktopFixedHeight
+        />
+      )}
+    </View>
+  )
+})
 
-const ListHeader = observer(
-  ({
-    list,
-    isOwner,
-    onToggleSubscribed,
-    onPressEditList,
-    onPressDeleteList,
-    onPressShareList,
-    onPressReportList,
-  }: {
-    list: AppBskyGraphDefs.ListView
-    isOwner: boolean
-    onToggleSubscribed: () => void
-    onPressEditList: () => void
-    onPressDeleteList: () => void
-    onPressShareList: () => void
-    onPressReportList: () => void
-  }) => {
-    const pal = usePalette('default')
-    const store = useStores()
-    const {isDesktop} = useWebMediaQueries()
-    const descriptionRT = React.useMemo(
-      () =>
-        list?.description &&
-        new RichText({text: list.description, facets: list.descriptionFacets}),
-      [list],
-    )
-    return (
-      <>
-        <View style={[styles.header, pal.border]}>
-          <View style={s.flex1}>
-            <Text testID="listName" type="title-xl" style={[pal.text, s.bold]}>
-              {list.name}
+const ListHeader = observer(function ListHeaderImpl({
+  list,
+  isOwner,
+  onToggleSubscribed,
+  onPressEditList,
+  onPressDeleteList,
+  onPressShareList,
+  onPressReportList,
+}: {
+  list: AppBskyGraphDefs.ListView
+  isOwner: boolean
+  onToggleSubscribed: () => void
+  onPressEditList: () => void
+  onPressDeleteList: () => void
+  onPressShareList: () => void
+  onPressReportList: () => void
+}) {
+  const pal = usePalette('default')
+  const store = useStores()
+  const {isDesktop} = useWebMediaQueries()
+  const descriptionRT = React.useMemo(
+    () =>
+      list?.description &&
+      new RichText({text: list.description, facets: list.descriptionFacets}),
+    [list],
+  )
+  return (
+    <>
+      <View style={[styles.header, pal.border]}>
+        <View style={s.flex1}>
+          <Text testID="listName" type="title-xl" style={[pal.text, s.bold]}>
+            {list.name}
+          </Text>
+          {list && (
+            <Text type="md" style={[pal.textLight]} numberOfLines={1}>
+              {list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list '}
+              by{' '}
+              {list.creator.did === store.me.did ? (
+                'you'
+              ) : (
+                <TextLink
+                  text={sanitizeHandle(list.creator.handle, '@')}
+                  href={makeProfileLink(list.creator)}
+                  style={pal.textLight}
+                />
+              )}
             </Text>
-            {list && (
-              <Text type="md" style={[pal.textLight]} numberOfLines={1}>
-                {list.purpose === 'app.bsky.graph.defs#modlist' && 'Mute list '}
-                by{' '}
-                {list.creator.did === store.me.did ? (
-                  'you'
-                ) : (
-                  <TextLink
-                    text={sanitizeHandle(list.creator.handle, '@')}
-                    href={makeProfileLink(list.creator)}
-                    style={pal.textLight}
-                  />
-                )}
-              </Text>
-            )}
-            {descriptionRT && (
-              <RichTextCom
-                testID="listDescription"
-                style={[pal.text, styles.headerDescription]}
-                richText={descriptionRT}
-              />
-            )}
-            {isDesktop && (
-              <ListActions
-                isOwner={isOwner}
-                muted={list.viewer?.muted}
-                onPressDeleteList={onPressDeleteList}
-                onPressEditList={onPressEditList}
-                onToggleSubscribed={onToggleSubscribed}
-                onPressShareList={onPressShareList}
-                onPressReportList={onPressReportList}
-              />
-            )}
-          </View>
-          <View>
-            <UserAvatar type="list" avatar={list.avatar} size={64} />
-          </View>
+          )}
+          {descriptionRT && (
+            <RichTextCom
+              testID="listDescription"
+              style={[pal.text, styles.headerDescription]}
+              richText={descriptionRT}
+            />
+          )}
+          {isDesktop && (
+            <ListActions
+              isOwner={isOwner}
+              muted={list.viewer?.muted}
+              onPressDeleteList={onPressDeleteList}
+              onPressEditList={onPressEditList}
+              onToggleSubscribed={onToggleSubscribed}
+              onPressShareList={onPressShareList}
+              onPressReportList={onPressReportList}
+            />
+          )}
         </View>
-        <View
-          style={{flexDirection: 'row', paddingHorizontal: isDesktop ? 16 : 6}}>
-          <View
-            style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}>
-            <Text type="md-medium" style={[pal.text]}>
-              Muted users
-            </Text>
-          </View>
+        <View>
+          <UserAvatar type="list" avatar={list.avatar} size={64} />
         </View>
-      </>
-    )
-  },
-)
+      </View>
+      <View
+        style={{flexDirection: 'row', paddingHorizontal: isDesktop ? 16 : 6}}>
+        <View style={[styles.fakeSelectorItem, {borderColor: pal.colors.link}]}>
+          <Text type="md-medium" style={[pal.text]}>
+            Muted users
+          </Text>
+        </View>
+      </View>
+    </>
+  )
+})
 
 const styles = StyleSheet.create({
   header: {