import React from 'react'
import {Keyboard, 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 {
type AppBskyActorDefs,
type AppBskyFeedDefs,
type AppBskyGraphDefs,
AtUri,
type ModerationOpts,
} from '@atproto/api'
import {msg, Plural, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {STARTER_PACK_MAX_SIZE} from '#/lib/constants'
import {useEnableKeyboardControllerScreen} from '#/lib/hooks/useEnableKeyboardController'
import {createSanitizedDisplayName} from '#/lib/moderation/create-sanitized-display-name'
import {
type CommonNavigatorParams,
type 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 {isNative} 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 {
useWizardState,
type 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, ButtonIcon, 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 type * as bsky from '#/types/bsky'
import {Provider} from './State'
export function Wizard({
route,
}: NativeStackScreenProps<
CommonNavigatorParams,
'StarterPackEdit' | 'StarterPackWizard'
>) {
const params = route.params ?? {}
const rkey = 'rkey' in params ? params.rkey : undefined
const fromDialog = 'fromDialog' in params ? params.fromDialog : false
const targetDid = 'targetDid' in params ? params.targetDid : undefined
const onSuccess = 'onSuccess' in params ? params.onSuccess : undefined
const {currentAccount} = useSession()
const moderationOpts = useModerationOpts()
const {_} = useLingui()
// Use targetDid if provided (from dialog), otherwise use current account
const profileDid = targetDid || currentAccount!.did
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: profileDid})
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,
fromDialog,
onSuccess,
}: {
currentStarterPack?: AppBskyGraphDefs.StarterPackView
currentListItems?: AppBskyGraphDefs.ListItemView[]
profile: AppBskyActorDefs.ProfileViewDetailed
moderationOpts: ModerationOpts
fromDialog?: boolean
onSuccess?: () => void
}) {
const navigation = useNavigation()
const {_} = useLingui()
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})
if (fromDialog) {
navigation.goBack()
onSuccess?.()
} else {
navigation.replace('StarterPack', {
name: profile!.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,
)
}
const items = state.currentStep === 'Profiles' ? state.profiles : state.feeds
const isEditEnabled =
(state.currentStep === 'Profiles' && items.length > 1) ||
(state.currentStep === 'Feeds' && items.length > 0)
const editDialogControl = useDialogControl()
return (
{
if (state.currentStep !== 'Details') {
evt.preventDefault()
dispatch({type: 'Back'})
}
}}
/>
{currUiStrings.header}
{isEditEnabled ? (
) : (
)}
{state.currentStep === 'Details' ? (
) : state.currentStep === 'Profiles' ? (
) : state.currentStep === 'Feeds' ? (
) : null}
{state.currentStep !== 'Details' && (
)}
)
}
function Container({children}: {children: React.ReactNode}) {
const {_} = useLingui()
const [state, dispatch] = useWizardState()
if (state.currentStep === 'Profiles' || state.currentStep === 'Feeds') {
return {children}
}
return (
{children}
{state.currentStep === 'Details' && (
<>
>
)}
)
}
function Footer({
onNext,
nextBtnText,
}: {
onNext: () => void
nextBtnText: string
}) {
const t = useTheme()
const [state] = useWizardState()
const {bottom: bottomInset} = useSafeAreaInsets()
const {currentAccount} = useSession()
const items = state.currentStep === 'Profiles' ? state.profiles : state.feeds
const minimumItems = state.currentStep === 'Profiles' ? 8 : 0
const textStyles = [a.text_md]
return (
{items.length > minimumItems && (
{items.length}/
{state.currentStep === 'Profiles' ? STARTER_PACK_MAX_SIZE : 3}
)}
{items.slice(0, 6).map((p, index) => (
0 ? -8 : 0}
: {marginRight: 4},
]}>
))}
{
state.currentStep === 'Profiles' ? (
{
items.length < 2 ? (
currentAccount?.did === items[0].did ? (
It's just you right now! Add more people to your starter
pack by searching above.
) : (
It's just{' '}
{getName(items[0])}{' '}
right now! Add more people to your starter pack by searching
above.
)
) : items.length === 2 ? (
currentAccount?.did === items[0].did ? (
You and
{getName(items[1] /* [0] is self, skip it */)}{' '}
are included in your starter pack
) : (
{getName(items[0])}
{' '}
and
{getName(items[1] /* [0] is self, skip it */)}{' '}
are included in your starter pack
)
) : items.length > 2 ? (
{getName(items[1] /* [0] is self, skip it */)},{' '}
{getName(items[2])},{' '}
and{' '}
{' '}
are included in your starter pack
) : null /* Should not happen. */
}
) : state.currentStep === 'Feeds' ? (
items.length === 0 ? (
Add some feeds to your starter pack!
Search for feeds that you want to suggest to others.
) : (
{
items.length === 1 ? (
{getName(items[0])}
{' '}
is included in your starter pack
) : items.length === 2 ? (
{getName(items[0])}
{' '}
and
{getName(items[1])}{' '}
are included in your starter pack
) : items.length > 2 ? (
{getName(items[0])},{' '}
{getName(items[1])},{' '}
and{' '}
{' '}
are included in your starter pack
) : null /* Should not happen. */
}
)
) : null /* Should not happen. */
}
{state.currentStep === 'Profiles' && items.length < 8 && (
Add {8 - items.length} more to continue
)}
)
}
function getName(
item: bsky.profile.AnyProfileView | AppBskyFeedDefs.GeneratorView,
) {
if (typeof item.displayName === 'string') {
return enforceLen(sanitizeDisplayName(item.displayName), 28, true)
} else if ('handle' in item && typeof item.handle === 'string') {
return enforceLen(sanitizeHandle(item.handle), 28, true)
}
return ''
}