diff --git a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx index a411f3d91e..1aaa7d076d 100644 --- a/src/components/StarterPack/Wizard/WizardEditListDialog.tsx +++ b/src/components/StarterPack/Wizard/WizardEditListDialog.tsx @@ -1,7 +1,7 @@ import React, {useRef} from 'react' import type {ListRenderItemInfo} from 'react-native' import {View} from 'react-native' -import {AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {BottomSheetFlatListMethods} from '@discord/bottom-sheet' import {msg, Trans} from '@lingui/macro' @@ -13,8 +13,10 @@ import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, native, useTheme, web} from '#/alf' import {Button, ButtonText} from '#/components/Button' import * as Dialog from '#/components/Dialog' -import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardFeedCard' -import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardProfileCard' +import { + WizardFeedCard, + WizardProfileCard, +} from '#/components/StarterPack/Wizard/WizardListCard' import {Text} from '#/components/Typography' function keyExtractor( @@ -28,10 +30,12 @@ export function WizardEditListDialog({ control, state, dispatch, + moderationOpts, }: { control: Dialog.DialogControlProps state: WizardState dispatch: (action: WizardAction) => void + moderationOpts: ModerationOpts }) { const {_} = useLingui() const t = useTheme() @@ -53,9 +57,19 @@ export function WizardEditListDialog({ const renderItem = ({item}: ListRenderItemInfo) => state.currentStep === 'Profiles' ? ( - + ) : ( - + ) return ( diff --git a/src/components/StarterPack/Wizard/WizardFeedCard.tsx b/src/components/StarterPack/Wizard/WizardFeedCard.tsx deleted file mode 100644 index c7198943c5..0000000000 --- a/src/components/StarterPack/Wizard/WizardFeedCard.tsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react' -import {Keyboard, Pressable, View} from 'react-native' -import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {DISCOVER_FEED_URI} from 'lib/constants' -import {isWeb} from 'platform/detection' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' -import {atoms as a, useTheme} from '#/alf' -import * as Toggle from '#/components/forms/Toggle' -import {Checkbox} from '#/components/forms/Toggle' -import {Text} from '#/components/Typography' - -export function WizardFeedCard({ - generator, - state, - dispatch, -}: { - generator: GeneratorView - state: WizardState - dispatch: (action: WizardAction) => void -}) { - const {_} = useLingui() - const t = useTheme() - - const isDiscover = generator.uri === DISCOVER_FEED_URI - const includesFeed = - isDiscover || state.feeds.some(f => f.uri === generator.uri) - const isDisabled = isDiscover || (!includesFeed && state.feeds.length >= 3) - - const onPressAddRemove = () => { - if (isDisabled) return - - Keyboard.dismiss() - if (includesFeed) { - dispatch({type: 'RemoveFeed', feedUri: generator.uri}) - } else { - dispatch({type: 'AddFeed', feed: generator}) - } - } - - return ( - - - - - {generator.displayName} - - - {_(msg`Feed by @${generator.creator.handle}`)} - - - - - - - ) -} diff --git a/src/components/StarterPack/Wizard/WizardListCard.tsx b/src/components/StarterPack/Wizard/WizardListCard.tsx new file mode 100644 index 0000000000..ec6c3549c2 --- /dev/null +++ b/src/components/StarterPack/Wizard/WizardListCard.tsx @@ -0,0 +1,189 @@ +import React from 'react' +import {Keyboard, Pressable, View} from 'react-native' +import { + AppBskyActorDefs, + AppBskyFeedDefs, + moderateFeedGenerator, + moderateProfile, + ModerationOpts, + ModerationUI, +} from '@atproto/api' +import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import {DISCOVER_FEED_URI} from 'lib/constants' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' +import {isWeb} from 'platform/detection' +import {useSession} from 'state/session' +import {UserAvatar} from 'view/com/util/UserAvatar' +import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' +import {atoms as a, useTheme} from '#/alf' +import * as Toggle from '#/components/forms/Toggle' +import {Checkbox} from '#/components/forms/Toggle' +import {Text} from '#/components/Typography' + +function WizardListCard({ + type, + displayName, + subtitle, + onPress, + avatar, + included, + disabled, + moderationUi, +}: { + type: 'user' | 'algo' + profile?: AppBskyActorDefs.ProfileViewBasic + feed?: AppBskyFeedDefs.GeneratorView + displayName: string + subtitle: string + onPress: () => void + avatar?: string + included?: boolean + disabled?: boolean + moderationUi: ModerationUI +}) { + const t = useTheme() + const {_} = useLingui() + + return ( + + + + + {displayName} + + + {subtitle} + + + + + + + ) +} + +export function WizardProfileCard({ + state, + dispatch, + profile, + moderationOpts, +}: { + state: WizardState + dispatch: (action: WizardAction) => void + profile: AppBskyActorDefs.ProfileViewBasic + moderationOpts: ModerationOpts +}) { + const {currentAccount} = useSession() + + const included = state.profiles.some(p => p.did === profile.did) + const isMe = profile.did === currentAccount?.did + const disabled = isMe || state.profiles.length >= 50 + const moderationUi = moderateProfile(profile, moderationOpts).ui('avatar') + const displayName = profile.displayName + ? sanitizeDisplayName(profile.displayName) + : `@${sanitizeHandle(profile.handle)}` + + const onPress = () => { + if (disabled) return + + Keyboard.dismiss() + if (profile.did === currentAccount?.did) return + + if (!included) { + dispatch({type: 'AddProfile', profile}) + } else { + dispatch({type: 'RemoveProfile', profileDid: profile.did}) + } + } + + return ( + + ) +} + +export function WizardFeedCard({ + generator, + state, + dispatch, + moderationOpts, +}: { + generator: GeneratorView + state: WizardState + dispatch: (action: WizardAction) => void + moderationOpts: ModerationOpts +}) { + const isDiscover = generator.uri === DISCOVER_FEED_URI + const included = isDiscover || state.feeds.some(f => f.uri === generator.uri) + const disabled = isDiscover || (!included && state.feeds.length >= 3) + const moderationUi = moderateFeedGenerator(generator, moderationOpts).ui( + 'avatar', + ) + + const onPress = () => { + if (disabled) return + + Keyboard.dismiss() + if (included) { + dispatch({type: 'RemoveFeed', feedUri: generator.uri}) + } else { + dispatch({type: 'AddFeed', feed: generator}) + } + } + + return ( + + ) +} diff --git a/src/components/StarterPack/Wizard/WizardProfileCard.tsx b/src/components/StarterPack/Wizard/WizardProfileCard.tsx deleted file mode 100644 index c66c885985..0000000000 --- a/src/components/StarterPack/Wizard/WizardProfileCard.tsx +++ /dev/null @@ -1,96 +0,0 @@ -import React from 'react' -import {Keyboard, Pressable, View} from 'react-native' -import {AppBskyActorDefs} from '@atproto/api' -import {msg} from '@lingui/macro' -import {useLingui} from '@lingui/react' - -import {isWeb} from 'platform/detection' -import {useSession} from 'state/session' -import {UserAvatar} from 'view/com/util/UserAvatar' -import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State' -import {atoms as a, useTheme} from '#/alf' -import * as Toggle from '#/components/forms/Toggle' -import {Checkbox} from '#/components/forms/Toggle' -import {Text} from '#/components/Typography' - -export function WizardProfileCard({ - state, - dispatch, - profile, -}: { - state: WizardState - dispatch: (action: WizardAction) => void - profile: AppBskyActorDefs.ProfileViewBasic -}) { - const {_} = useLingui() - const t = useTheme() - const {currentAccount} = useSession() - - const includesProfile = state.profiles.some(p => p.did === profile.did) - const isMe = profile.did === currentAccount?.did - const isDisabled = isMe || state.profiles.length >= 50 - - const onPressAddRemove = () => { - if (isDisabled) return - - Keyboard.dismiss() - if (profile.did === currentAccount?.did) return - - if (!includesProfile) { - dispatch({type: 'AddProfile', profile}) - } else { - dispatch({type: 'RemoveProfile', profileDid: profile.did}) - } - } - - return ( - - - - - {profile?.displayName || profile?.handle} - - - @{profile?.handle} - - - - - - - ) -} diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx index 7b487724eb..da8a0d22e7 100644 --- a/src/screens/StarterPack/Wizard/StepFeeds.tsx +++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx @@ -1,6 +1,7 @@ import React, {useState} from 'react' import {ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' +import {ModerationOpts} from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import debounce from 'lodash.debounce' @@ -14,13 +15,13 @@ import {useWizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, useTheme} from '#/alf' import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' -import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardFeedCard' +import {WizardFeedCard} from '#/components/StarterPack/Wizard/WizardListCard' function keyExtractor(item: GeneratorView) { return item.uri } -export function StepFeeds() { +export function StepFeeds({moderationOpts}: {moderationOpts: ModerationOpts}) { const t = useTheme() const [state, dispatch] = useWizardState() const [query, setQuery] = useState('') @@ -52,7 +53,14 @@ export function StepFeeds() { } const renderItem = ({item}: ListRenderItemInfo) => { - return + return ( + + ) } return ( diff --git a/src/screens/StarterPack/Wizard/StepProfiles.tsx b/src/screens/StarterPack/Wizard/StepProfiles.tsx index ac5996f43d..5882aa828b 100644 --- a/src/screens/StarterPack/Wizard/StepProfiles.tsx +++ b/src/screens/StarterPack/Wizard/StepProfiles.tsx @@ -1,7 +1,7 @@ import React, {useState} from 'react' import {ListRenderItemInfo, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' -import {AppBskyActorDefs} from '@atproto/api' +import {AppBskyActorDefs, ModerationOpts} from '@atproto/api' import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete' import {useActorSearch} from 'state/queries/actor-search' @@ -11,13 +11,17 @@ import {useWizardState} from '#/screens/StarterPack/Wizard/State' import {atoms as a, useTheme} from '#/alf' import {Loader} from '#/components/Loader' import {ScreenTransition} from '#/components/StarterPack/Wizard/ScreenTransition' -import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardProfileCard' +import {WizardProfileCard} from '#/components/StarterPack/Wizard/WizardListCard' function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) { return item.did } -export function StepProfiles() { +export function StepProfiles({ + moderationOpts, +}: { + moderationOpts: ModerationOpts +}) { const t = useTheme() const [state, dispatch] = useWizardState() const [query, setQuery] = useState('') @@ -32,7 +36,12 @@ export function StepProfiles() { item, }: ListRenderItemInfo) => { return ( - + ) } diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx index c555e37c31..84280f2b24 100644 --- a/src/screens/StarterPack/Wizard/index.tsx +++ b/src/screens/StarterPack/Wizard/index.tsx @@ -10,6 +10,7 @@ import { AppBskyGraphDefs, AppBskyGraphStarterpack, AtUri, + ModerationOpts, } from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' @@ -24,8 +25,11 @@ import {HITSLOP_10} from 'lib/constants' import {createStarterPackList} from 'lib/generate-starterpack' import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types' import {logEvent} from 'lib/statsig/statsig' +import {sanitizeDisplayName} from 'lib/strings/display-names' +import {sanitizeHandle} from 'lib/strings/handles' import {enforceLen} from 'lib/strings/helpers' import {isAndroid, isNative, isWeb} from 'platform/detection' +import {useModerationOpts} from 'state/preferences/moderation-opts' import {invalidateActorStarterPacksQuery} from 'state/queries/actor-starter-packs' import { invalidateListMembersQuery, @@ -61,6 +65,7 @@ export function Wizard({ }: NativeStackScreenProps) { const {name, rkey} = route.params ?? {} const {currentAccount} = useSession() + const moderationOpts = useModerationOpts() const {_} = useLingui() @@ -91,7 +96,10 @@ export function Wizard({ } = useProfileQuery({did: currentAccount?.did}) const isEdit = Boolean(name && rkey) - const isReady = (!isEdit || (isEdit && starterPack && listItems)) && profile + const isReady = + (!isEdit || (isEdit && starterPack && listItems)) && + profile && + moderationOpts if (!isReady) { return ( @@ -130,6 +138,7 @@ export function Wizard({ } listItems={listItems} listUri={listUri} + moderationOpts={moderationOpts} /> ) @@ -141,12 +150,14 @@ function WizardInner({ createdAt: initialCreatedAt, listUri: initialListUri, listItems: initialListItems, + moderationOpts, }: { did?: string rkey?: string createdAt?: string listUri?: string listItems?: AppBskyGraphDefs.ListItemView[] + moderationOpts: ModerationOpts }) { const navigation = useNavigation() const {_} = useLingui() @@ -445,11 +456,21 @@ function WizardInner({ - + {state.currentStep === 'Details' ? ( + + ) : state.currentStep === 'Profiles' ? ( + + ) : state.currentStep === 'Feeds' ? ( + + ) : null} {state.currentStep !== 'Details' && ( -