diff options
Diffstat (limited to 'src/view/com/lists/ListItems.tsx')
-rw-r--r-- | src/view/com/lists/ListItems.tsx | 577 |
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: { |