import React from 'react' import {Keyboard, TouchableOpacity, View} from 'react-native' import {KeyboardAwareScrollView} from 'react-native-keyboard-controller' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Image} from 'expo-image' import { AppBskyActorDefs, AppBskyGraphDefs, AtUri, ModerationOpts, } from '@atproto/api' import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {msg, Plural, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {HITSLOP_10, STARTER_PACK_MAX_SIZE} from '#/lib/constants' import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController' import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name' 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 { getStarterPackOgCard, parseStarterPackUri, } from '#/lib/strings/starter-pack' import {logger} from '#/logger' import {isAndroid, isNative, isWeb} from '#/platform/detection' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useAllListMembersQuery} from '#/state/queries/list-members' import {useProfileQuery} from '#/state/queries/profile' import { useCreateStarterPackMutation, useEditStarterPackMutation, useStarterPackQuery, } from '#/state/queries/starter-packs' import {useSession} from '#/state/session' import {useSetMinimalShellMode} from '#/state/shell' import * as Toast from '#/view/com/util/Toast' import {UserAvatar} from '#/view/com/util/UserAvatar' import {CenteredView} from '#/view/com/util/Views' import {useWizardState, WizardStep} from '#/screens/StarterPack/Wizard/State' import {StepDetails} from '#/screens/StarterPack/Wizard/StepDetails' import {StepFeeds} from '#/screens/StarterPack/Wizard/StepFeeds' import {StepProfiles} from '#/screens/StarterPack/Wizard/StepProfiles' import {atoms as a, useTheme, web} from '#/alf' import {Button, ButtonText} from '#/components/Button' import {useDialogControl} from '#/components/Dialog' import * as Layout from '#/components/Layout' import {ListMaybePlaceholder} from '#/components/Lists' import {Loader} from '#/components/Loader' import {WizardEditListDialog} from '#/components/StarterPack/Wizard/WizardEditListDialog' import {Text} from '#/components/Typography' import {Provider} from './State' export function Wizard({ route, }: NativeStackScreenProps< CommonNavigatorParams, 'StarterPackEdit' | 'StarterPackWizard' >) { const {rkey} = route.params ?? {} const {currentAccount} = useSession() const moderationOpts = useModerationOpts() const {_} = useLingui() const { data: starterPack, isLoading: isLoadingStarterPack, isError: isErrorStarterPack, } = useStarterPackQuery({did: currentAccount!.did, rkey}) const listUri = starterPack?.list?.uri const { data: listItems, isLoading: isLoadingProfiles, isError: isErrorProfiles, } = useAllListMembersQuery(listUri) const { data: profile, isLoading: isLoadingProfile, isError: isErrorProfile, } = useProfileQuery({did: currentAccount?.did}) const isEdit = Boolean(rkey) const isReady = (!isEdit || (isEdit && starterPack && listItems)) && profile && moderationOpts if (!isReady) { return ( ) } else if (isEdit && starterPack?.creator.did !== currentAccount?.did) { return ( ) } return ( ) } function WizardInner({ currentStarterPack, currentListItems, profile, moderationOpts, }: { currentStarterPack?: AppBskyGraphDefs.StarterPackView currentListItems?: AppBskyGraphDefs.ListItemView[] profile: AppBskyActorDefs.ProfileViewBasic moderationOpts: ModerationOpts }) { const navigation = useNavigation() const {_} = useLingui() const t = useTheme() const setMinimalShellMode = useSetMinimalShellMode() const [state, dispatch] = useWizardState() const {currentAccount} = useSession() const {data: currentProfile} = useProfileQuery({ did: currentAccount?.did, staleTime: 0, }) const parsed = parseStarterPackUri(currentStarterPack?.uri) React.useEffect(() => { navigation.setOptions({ gestureEnabled: false, }) }, [navigation]) useEnableKeyboardControllerScreen(true) useFocusEffect( React.useCallback(() => { setMinimalShellMode(true) return () => { setMinimalShellMode(false) } }, [setMinimalShellMode]), ) const getDefaultName = () => { const displayName = createSanitizedDisplayName(currentProfile!, true) return _(msg`${displayName}'s Starter Pack`).slice(0, 50) } const wizardUiStrings: Record< WizardStep, {header: string; nextBtn: string; subtitle?: string} > = { Details: { header: _(msg`Starter Pack`), nextBtn: _(msg`Next`), }, Profiles: { header: _(msg`Choose People`), nextBtn: _(msg`Next`), }, Feeds: { header: _(msg`Choose Feeds`), nextBtn: state.feeds.length === 0 ? _(msg`Skip`) : _(msg`Finish`), }, } const currUiStrings = wizardUiStrings[state.currentStep] const onSuccessCreate = (data: {uri: string; cid: string}) => { const rkey = new AtUri(data.uri).rkey logEvent('starterPack:create', { setName: state.name != null, setDescription: state.description != null, profilesCount: state.profiles.length, feedsCount: state.feeds.length, }) Image.prefetch([getStarterPackOgCard(currentProfile!.did, rkey)]) dispatch({type: 'SetProcessing', processing: false}) navigation.replace('StarterPack', { name: currentAccount!.handle, rkey, new: true, }) } const onSuccessEdit = () => { if (navigation.canGoBack()) { navigation.goBack() } else { navigation.replace('StarterPack', { name: currentAccount!.handle, rkey: parsed!.rkey, }) } } const {mutate: createStarterPack} = useCreateStarterPackMutation({ onSuccess: onSuccessCreate, onError: e => { logger.error('Failed to create starter pack', {safeMessage: e}) dispatch({type: 'SetProcessing', processing: false}) Toast.show(_(msg`Failed to create starter pack`), 'xmark') }, }) const {mutate: editStarterPack} = useEditStarterPackMutation({ onSuccess: onSuccessEdit, onError: e => { logger.error('Failed to edit starter pack', {safeMessage: e}) dispatch({type: 'SetProcessing', processing: false}) Toast.show(_(msg`Failed to create starter pack`), 'xmark') }, }) const submit = async () => { dispatch({type: 'SetProcessing', processing: true}) if (currentStarterPack && currentListItems) { editStarterPack({ name: state.name?.trim() || getDefaultName(), description: state.description?.trim(), profiles: state.profiles, feeds: state.feeds, currentStarterPack: currentStarterPack, currentListItems: currentListItems, }) } else { createStarterPack({ name: state.name?.trim() || getDefaultName(), description: state.description?.trim(), profiles: state.profiles, feeds: state.feeds, }) } } const onNext = () => { if (state.currentStep === 'Feeds') { submit() return } const keyboardVisible = Keyboard.isVisible() Keyboard.dismiss() setTimeout( () => { dispatch({type: 'Next'}) }, keyboardVisible ? 16 : 0, ) } return ( { if (state.currentStep === 'Details') { navigation.pop() } else { dispatch({type: 'Back'}) } }}> {currUiStrings.header} {state.currentStep === 'Details' ? ( ) : state.currentStep === 'Profiles' ? ( ) : state.currentStep === 'Feeds' ? ( ) : null} {state.currentStep !== 'Details' && (