diff options
-rw-r--r-- | src/view/com/lists/ListMembers.tsx | 35 | ||||
-rw-r--r-- | src/view/com/util/EmptyState.tsx | 4 | ||||
-rw-r--r-- | src/view/screens/ProfileList.tsx | 123 |
3 files changed, 98 insertions, 64 deletions
diff --git a/src/view/com/lists/ListMembers.tsx b/src/view/com/lists/ListMembers.tsx index cf7bb6b9e..0caae6701 100644 --- a/src/view/com/lists/ListMembers.tsx +++ b/src/view/com/lists/ListMembers.tsx @@ -1,11 +1,5 @@ -import React from 'react' -import { - ActivityIndicator, - Dimensions, - StyleProp, - View, - ViewStyle, -} from 'react-native' +import React, {useCallback} from 'react' +import {Dimensions, StyleProp, View, ViewStyle} from 'react-native' import {AppBskyActorDefs, AppBskyGraphDefs} from '@atproto/api' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' @@ -16,6 +10,7 @@ import {logger} from '#/logger' import {useModalControls} from '#/state/modals' import {useListMembersQuery} from '#/state/queries/list-members' import {useSession} from '#/state/session' +import {ListFooter} from '#/components/Lists' import {ProfileCard} from '../profile/ProfileCard' import {ErrorMessage} from '../util/error/ErrorMessage' import {Button} from '../util/forms/Button' @@ -66,6 +61,7 @@ export function ListMembers({ refetch, fetchNextPage, hasNextPage, + isFetchingNextPage, } = useListMembersQuery(list) const isEmpty = !isFetching && !data?.pages[0].items.length const isOwner = @@ -197,14 +193,17 @@ export function ListMembers({ ], ) - const Footer = React.useCallback( - () => ( - <View style={{paddingTop: 20, paddingBottom: 400}}> - {isFetching && <ActivityIndicator />} - </View> - ), - [isFetching], - ) + const renderFooter = useCallback(() => { + if (isEmpty) return null + return ( + <ListFooter + hasNextPage={hasNextPage} + error={cleanError(error)} + isFetchingNextPage={isFetchingNextPage} + onRetry={fetchNextPage} + /> + ) + }, [hasNextPage, error, isFetchingNextPage, fetchNextPage, isEmpty]) return ( <View testID={testID} style={style}> @@ -214,8 +213,8 @@ export function ListMembers({ data={items} keyExtractor={(item: any) => item.subject?.did || item._reactKey} renderItem={renderItem} - ListHeaderComponent={renderHeader} - ListFooterComponent={Footer} + ListHeaderComponent={!isEmpty ? renderHeader : undefined} + ListFooterComponent={renderFooter} refreshing={isRefreshing} onRefresh={onRefresh} headerOffset={headerOffset} diff --git a/src/view/com/util/EmptyState.tsx b/src/view/com/util/EmptyState.tsx index a6352c2fe..6b2600a40 100644 --- a/src/view/com/util/EmptyState.tsx +++ b/src/view/com/util/EmptyState.tsx @@ -26,9 +26,7 @@ export function EmptyState({ const {isTabletOrDesktop} = useWebMediaQueries() const iconSize = isTabletOrDesktop ? 64 : 48 return ( - <View - testID={testID} - style={[isTabletOrDesktop && {paddingRight: 20}, style]}> + <View testID={testID} style={style}> <View style={[ styles.iconContainer, diff --git a/src/view/screens/ProfileList.tsx b/src/view/screens/ProfileList.tsx index 2e661ff46..788f41c55 100644 --- a/src/view/screens/ProfileList.tsx +++ b/src/view/screens/ProfileList.tsx @@ -1,5 +1,5 @@ import React, {useCallback, useMemo} from 'react' -import {Pressable, StyleSheet, View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' import { AppBskyGraphDefs, @@ -70,7 +70,9 @@ import {Text} from '#/view/com/util/text/Text' import * as Toast from '#/view/com/util/Toast' import {ListHiddenScreen} from '#/screens/List/ListHiddenScreen' import {atoms as a} from '#/alf' +import {Button as NewButton, ButtonIcon, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' +import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' import * as Layout from '#/components/Layout' import * as Hider from '#/components/moderation/Hider' import * as Prompt from '#/components/Prompt' @@ -220,6 +222,7 @@ function ProfileListScreenLoaded({ scrollElRef={scrollElRef as ListRef} headerHeight={headerHeight} isFocused={isScreenFocused && isFocused} + onPressAddUser={onPressAddUser} /> )} {({headerHeight, scrollElRef}) => ( @@ -771,9 +774,13 @@ interface FeedSectionProps { headerHeight: number scrollElRef: ListRef isFocused: boolean + onPressAddUser: () => void } const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( - function FeedSectionImpl({feed, scrollElRef, headerHeight, isFocused}, ref) { + function FeedSectionImpl( + {feed, scrollElRef, headerHeight, isFocused, onPressAddUser}, + ref, + ) { const queryClient = useQueryClient() const [hasNew, setHasNew] = React.useState(false) const [isScrolledDown, setIsScrolledDown] = React.useState(false) @@ -800,8 +807,23 @@ const FeedSection = React.forwardRef<SectionRef, FeedSectionProps>( }, [onScrollToTop, isScreenFocused]) const renderPostsEmpty = useCallback(() => { - return <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> - }, [_]) + return ( + <View style={[a.gap_xl, a.align_center]}> + <EmptyState icon="hashtag" message={_(msg`This feed is empty.`)} /> + <NewButton + label={_(msg`Start adding people`)} + onPress={onPressAddUser} + color="primary" + size="small" + variant="solid"> + <ButtonIcon icon={PersonPlusIcon} /> + <ButtonText> + <Trans>Start adding people!</Trans> + </ButtonText> + </NewButton> + </View> + ) + }, [_, onPressAddUser]) return ( <View> @@ -840,10 +862,9 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( {list, onPressAddUser, headerHeight, scrollElRef}, ref, ) { - const pal = usePalette('default') const {_} = useLingui() - const {isMobile} = useWebMediaQueries() const {currentAccount} = useSession() + const {isMobile} = useWebMediaQueries() const [isScrolledDown, setIsScrolledDown] = React.useState(false) const isOwner = list.creator.did === currentAccount?.did @@ -862,50 +883,66 @@ const AboutSection = React.forwardRef<SectionRef, AboutSectionProps>( if (!isOwner) { return <View /> } - return ( - <View style={a.pt_lg}> - <View - style={[ - { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: isMobile ? 14 : 20, - paddingBottom: isMobile ? 14 : 18, - }, - ]}> - {isOwner && ( - <Pressable - testID="addUserBtn" - accessibilityRole="button" - accessibilityLabel={_(msg`Add a user to this list`)} - accessibilityHint="" - onPress={onPressAddUser} - style={{flexDirection: 'row', alignItems: 'center', gap: 6}}> - <FontAwesomeIcon - icon="user-plus" - color={pal.colors.link} - size={16} - /> - <Text style={pal.link}> - <Trans>Add</Trans> - </Text> - </Pressable> - )} + if (isMobile) { + return ( + <View style={[a.px_sm, a.py_sm]}> + <NewButton + testID="addUserBtn" + label={_(msg`Add a user to this list`)} + onPress={onPressAddUser} + color="primary" + size="small" + variant="outline" + style={[a.py_md]}> + <ButtonIcon icon={PersonPlusIcon} /> + <ButtonText> + <Trans>Add people</Trans> + </ButtonText> + </NewButton> </View> + ) + } + return ( + <View style={[a.px_lg, a.py_md, a.flex_row_reverse]}> + <NewButton + testID="addUserBtn" + label={_(msg`Add a user to this list`)} + onPress={onPressAddUser} + color="primary" + size="small" + variant="ghost" + style={[a.py_sm]}> + <ButtonIcon icon={PersonPlusIcon} /> + <ButtonText> + <Trans>Add people</Trans> + </ButtonText> + </NewButton> </View> ) - }, [isMobile, pal.colors.link, pal.link, isOwner, _, onPressAddUser]) + }, [isOwner, _, onPressAddUser, isMobile]) const renderEmptyState = useCallback(() => { return ( - <EmptyState - icon="users-slash" - message={_(msg`This list is empty!`)} - style={{paddingTop: 40}} - /> + <View style={[a.gap_xl, a.align_center]}> + <EmptyState + icon="users-slash" + message={_(msg`This list is empty.`)} + /> + <NewButton + testID="emptyStateAddUserBtn" + label={_(msg`Start adding people`)} + onPress={onPressAddUser} + color="primary" + size="small" + variant="solid"> + <ButtonIcon icon={PersonPlusIcon} /> + <ButtonText> + <Trans>Start adding people!</Trans> + </ButtonText> + </NewButton> + </View> ) - }, [_]) + }, [_, onPressAddUser]) return ( <View> |