diff --git a/assets/icons/starter_pack_icon.svg b/assets/icons/starter_pack_icon.svg
new file mode 100644
index 0000000000..47a2f49b64
--- /dev/null
+++ b/assets/icons/starter_pack_icon.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/Navigation.tsx b/src/Navigation.tsx
index dc9ac081cd..f3a3d264f0 100644
--- a/src/Navigation.tsx
+++ b/src/Navigation.tsx
@@ -44,6 +44,7 @@ import HashtagScreen from '#/screens/Hashtag'
import {ModerationScreen} from '#/screens/Moderation'
import {ProfileLabelerLikedByScreen} from '#/screens/Profile/ProfileLabelerLikedBy'
import {Landing} from '#/screens/StarterPack/Landing'
+import {Wizard} from '#/screens/StarterPack/Wizard'
import {init as initAnalytics} from './lib/analytics/analytics'
import {useWebScrollRestoration} from './lib/hooks/useWebScrollRestoration'
import {attachRouteToLogEvents, logEvent} from './lib/statsig/statsig'
@@ -312,6 +313,11 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
getComponent={() => Landing}
options={{title: title(msg`Join Bluesky Today!`)}}
/>
+ Wizard}
+ options={{title: title(msg`Create a starter pack`), requireAuth: true}}
+ />
>
)
}
diff --git a/src/components/forms/TextField.tsx b/src/components/forms/TextField.tsx
index 73a660ea6c..f543c821e0 100644
--- a/src/components/forms/TextField.tsx
+++ b/src/components/forms/TextField.tsx
@@ -140,6 +140,7 @@ export function createInput(Component: typeof TextInput) {
onChangeText,
isInvalid,
inputRef,
+ style,
...rest
}: InputProps) {
const t = useTheme()
@@ -199,6 +200,7 @@ export function createInput(Component: typeof TextInput) {
android({
paddingBottom: 16,
}),
+ style,
]}
/>
diff --git a/src/components/icons/StarterPackIcon.tsx b/src/components/icons/StarterPackIcon.tsx
new file mode 100644
index 0000000000..dea27ea85d
--- /dev/null
+++ b/src/components/icons/StarterPackIcon.tsx
@@ -0,0 +1,26 @@
+import * as React from 'react'
+import Svg, {Defs, LinearGradient, Path, Stop} from 'react-native-svg'
+export function StarterPackIcon({...props}: React.ComponentProps) {
+ return (
+
+ )
+}
diff --git a/src/lib/hooks/useBottomBarOffset.ts b/src/lib/hooks/useBottomBarOffset.ts
new file mode 100644
index 0000000000..205378b61b
--- /dev/null
+++ b/src/lib/hooks/useBottomBarOffset.ts
@@ -0,0 +1,11 @@
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {clamp} from 'lib/numbers'
+import {isWeb} from 'platform/detection'
+
+export function useBottomBarOffset() {
+ const {isTabletOrDesktop} = useWebMediaQueries()
+ const {bottom: bottomInset} = useSafeAreaInsets()
+ return isWeb && isTabletOrDesktop ? 0 : clamp(60 + bottomInset, 60, 75)
+}
diff --git a/src/lib/routes/types.ts b/src/lib/routes/types.ts
index b19c7bdb64..8c82f44b16 100644
--- a/src/lib/routes/types.ts
+++ b/src/lib/routes/types.ts
@@ -41,6 +41,11 @@ export type CommonNavigatorParams = {
MessagesConversation: {conversation: string; embed?: string}
MessagesSettings: undefined
StarterPackLanding: {id: string}
+ StarterPackWizard: {
+ mode: 'Create' | 'Edit'
+ id?: string
+ initialStep?: 'Details' | 'Profiles' | 'Feeds'
+ }
}
export type BottomTabNavigatorParams = CommonNavigatorParams & {
@@ -99,6 +104,11 @@ export type AllNavigatorParams = CommonNavigatorParams & {
MessagesTab: undefined
Messages: {animation?: 'push' | 'pop'}
StarterPackLanding: {id: string}
+ StarterPackWizard: {
+ mode: 'Create' | 'Edit'
+ id?: string
+ initialStep?: 'Details' | 'Profiles' | 'Feeds'
+ }
}
// NOTE
diff --git a/src/routes.ts b/src/routes.ts
index f4b0234b01..98b0b6cbfc 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -41,4 +41,5 @@ export const router = new Router({
MessagesSettings: '/messages/settings',
MessagesConversation: '/messages/:conversation',
StarterPackLanding: '/start/:id',
+ StarterPackWizard: '/starter-pack/create',
})
diff --git a/src/screens/StarterPack/Wizard/State.tsx b/src/screens/StarterPack/Wizard/State.tsx
new file mode 100644
index 0000000000..d82254e43f
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/State.tsx
@@ -0,0 +1,137 @@
+import React from 'react'
+import {AppBskyActorDefs} from '@atproto/api'
+
+const steps = ['Landing', 'Details', 'Profiles', 'Feeds'] as const
+type Step = (typeof steps)[number]
+
+type Action =
+ | {type: 'Next'}
+ | {type: 'Back'}
+ | {type: 'SetCanNext'; canNext: boolean}
+ | {type: 'SetName'; name: string}
+ | {type: 'SetDescription'; description: string}
+ | {type: 'AddProfile'; profile: AppBskyActorDefs.ProfileViewBasic}
+ | {type: 'RemoveProfile'; profileDid: string}
+ | {type: 'AddFeed'; uri: string}
+ | {type: 'RemoveFeed'; uri: string}
+ | {type: 'SetProcessing'; processing: boolean}
+
+interface State {
+ canNext: boolean
+ currentStep: Step
+ name?: string
+ description?: string
+ avatar?: string
+ profiles: AppBskyActorDefs.ProfileViewBasic[]
+ feedUris: string[]
+ processing: boolean
+}
+
+type TStateContext = [State, (action: Action) => void]
+
+const StateContext = React.createContext([
+ {} as State,
+ (_: Action) => {},
+])
+export const useWizardState = () => React.useContext(StateContext)
+
+function reducer(state: State, action: Action): State {
+ let updatedState = state
+
+ // -- Navigation
+ if (action.type === 'Next' && state.currentStep !== 'Feeds') {
+ const currentIndex = steps.indexOf(state.currentStep)
+ updatedState = {...state, currentStep: steps[currentIndex + 1]}
+ } else if (action.type === 'Back' && state.currentStep !== 'Landing') {
+ const currentIndex = steps.indexOf(state.currentStep)
+ updatedState = {...state, currentStep: steps[currentIndex - 1]}
+ }
+
+ switch (action.type) {
+ case 'SetName':
+ updatedState = {...state, name: action.name}
+ break
+ case 'SetDescription':
+ updatedState = {...state, description: action.description}
+ break
+ case 'AddProfile':
+ updatedState = {...state, profiles: [...state.profiles, action.profile]}
+ break
+ case 'RemoveProfile':
+ updatedState = {
+ ...state,
+ profiles: state.profiles.filter(
+ profile => profile.did !== action.profileDid,
+ ),
+ }
+ break
+ case 'AddFeed':
+ updatedState = {...state, feedUris: [...state.feedUris, action.uri]}
+ break
+ case 'RemoveFeed':
+ updatedState = {
+ ...state,
+ feedUris: state.feedUris.filter(uri => uri !== action.uri),
+ }
+ break
+ case 'SetProcessing':
+ updatedState = {...state, processing: action.processing}
+ break
+ }
+
+ switch (updatedState.currentStep) {
+ case 'Landing':
+ updatedState = {
+ ...updatedState,
+ canNext: true,
+ }
+ break
+ case 'Details':
+ updatedState = {
+ ...updatedState,
+ canNext: Boolean(updatedState.description),
+ }
+ break
+ }
+
+ return updatedState
+}
+
+// TODO supply the initial state to this component
+export function Provider({
+ initialState,
+ initialStep = 'Landing',
+ children,
+}: {
+ initialState?: any // TODO update this type
+ initialStep?: Step
+ children: React.ReactNode
+}) {
+ const stateAndReducer = React.useReducer(
+ reducer,
+ initialState
+ ? {
+ ...initialState,
+ step: initialStep,
+ }
+ : {
+ canNext: true,
+ currentStep: initialStep,
+ profiles: [],
+ feedUris: [],
+ processing: false,
+ },
+ )
+
+ return (
+
+ {children}
+
+ )
+}
+
+export {
+ type Action as WizardAction,
+ type State as WizardState,
+ type Step as WizardStep,
+}
diff --git a/src/screens/StarterPack/Wizard/StepDetails.tsx b/src/screens/StarterPack/Wizard/StepDetails.tsx
new file mode 100644
index 0000000000..f6388aed60
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepDetails.tsx
@@ -0,0 +1,58 @@
+import React from 'react'
+import {View} from 'react-native'
+import {msg} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useProfileQuery} from 'state/queries/profile'
+import {useSession} from 'state/session'
+import {useWizardState} from '#/screens/StarterPack/Wizard/State'
+import {atoms as a} from '#/alf'
+import * as TextField from '#/components/forms/TextField'
+import {StarterPackIcon} from '#/components/icons/StarterPackIcon'
+
+export function StepDetails() {
+ const {_} = useLingui()
+ const [state, dispatch] = useWizardState()
+
+ const {currentAccount} = useSession()
+ const {data: currentProfile} = useProfileQuery({
+ did: currentAccount?.did,
+ staleTime: 300,
+ })
+
+ return (
+
+
+
+
+
+ {_(msg`Starter pack name`)}
+ dispatch({type: 'SetName', name: text})}
+ />
+
+
+ {_(msg`Description`)}
+
+
+ dispatch({type: 'SetDescription', description: text})
+ }
+ multiline
+ style={{minHeight: 150}}
+ />
+
+
+
+ )
+}
diff --git a/src/screens/StarterPack/Wizard/StepFeeds.tsx b/src/screens/StarterPack/Wizard/StepFeeds.tsx
new file mode 100644
index 0000000000..2b889ae767
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepFeeds.tsx
@@ -0,0 +1,88 @@
+import React from 'react'
+import {ListRenderItemInfo, View} from 'react-native'
+import {GeneratorView} from '@atproto/api/dist/client/types/app/bsky/feed/defs'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {useProfileFeedgensQuery} from 'state/queries/profile-feedgens'
+import {useSession} from 'state/session'
+import {List} from 'view/com/util/List'
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {useWizardState} from '#/screens/StarterPack/Wizard/State'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import {Text} from '#/components/Typography'
+
+function renderItem({item}: ListRenderItemInfo) {
+ return
+}
+
+function keyExtractor(item: GeneratorView) {
+ return item.uri
+}
+
+export function StepFeeds() {
+ const {currentAccount} = useSession()
+ const {data} = useProfileFeedgensQuery(currentAccount!.did)
+ const feeds = data?.pages.flatMap(page => page.feeds) || []
+
+ return (
+
+ )
+}
+
+function FeedCard({generator}: {generator: GeneratorView}) {
+ const {_} = useLingui()
+ const t = useTheme()
+ const [state, dispatch] = useWizardState()
+
+ const includesFeed = state.feedUris.includes(generator.uri)
+ const onAdd = () => {
+ if (includesFeed) {
+ dispatch({type: 'RemoveFeed', uri: generator.uri})
+ } else {
+ dispatch({type: 'AddFeed', uri: generator.uri})
+ }
+ }
+
+ return (
+
+
+
+
+ {generator.displayName}
+
+
+ {_(msg`Feed by @${generator.creator.handle}`)}
+
+
+
+
+ )
+}
diff --git a/src/screens/StarterPack/Wizard/StepFinished.tsx b/src/screens/StarterPack/Wizard/StepFinished.tsx
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/screens/StarterPack/Wizard/StepLanding.tsx b/src/screens/StarterPack/Wizard/StepLanding.tsx
new file mode 100644
index 0000000000..a6df20eaa5
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepLanding.tsx
@@ -0,0 +1,33 @@
+import React from 'react'
+import {View} from 'react-native'
+import {Trans} from '@lingui/macro'
+
+import {atoms as a, useTheme} from '#/alf'
+import {StarterPackIcon} from '#/components/icons/StarterPackIcon'
+import {Text} from '#/components/Typography'
+
+export function StepLanding() {
+ const t = useTheme()
+
+ return (
+
+
+
+
+
+
+ Starter packs
+
+
+ Invites, but personal
+
+
+
+ Create your own Bluesky starter packs and invite people directly to
+ your favorite feeds, profiles, and more.
+
+
+
+
+ )
+}
diff --git a/src/screens/StarterPack/Wizard/StepProfiles/WizardAddProfilesDialog.tsx b/src/screens/StarterPack/Wizard/StepProfiles/WizardAddProfilesDialog.tsx
new file mode 100644
index 0000000000..d74f24b05d
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepProfiles/WizardAddProfilesDialog.tsx
@@ -0,0 +1,189 @@
+import React, {useLayoutEffect, useRef, useState} from 'react'
+import type {ListRenderItemInfo, TextInput as RNTextInput} from 'react-native'
+import {View} from 'react-native'
+import {AppBskyActorDefs} from '@atproto/api'
+import {BottomSheetFlatListMethods} from '@discord/bottom-sheet'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {isWeb} from '#/platform/detection'
+import {useActorAutocompleteQuery} from 'state/queries/actor-autocomplete'
+import {useProfileFollowsQuery} from 'state/queries/profile-follows'
+import {useSession} from 'state/session'
+import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State'
+import {WizardProfileCard} from '#/screens/StarterPack/Wizard/StepProfiles/WizardProfileCard'
+import {atoms as a, native, useTheme, web} from '#/alf'
+import * as Dialog from '#/components/Dialog'
+import {TextInput} from '#/components/dms/dialogs/TextInput'
+import {useInteractionState} from '#/components/hooks/useInteractionState'
+import {MagnifyingGlass2_Stroke2_Corner0_Rounded as Search} from '#/components/icons/MagnifyingGlass2'
+import {Text} from '#/components/Typography'
+
+function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
+ return item.did
+}
+
+export function WizardAddProfilesDialog({
+ control,
+ state,
+ dispatch,
+}: {
+ control: Dialog.DialogControlProps
+ state: WizardState
+ dispatch: (action: WizardAction) => void
+}) {
+ const [searchText, setSearchText] = useState('')
+
+ const {currentAccount} = useSession()
+ const {data: results} = useActorAutocompleteQuery(searchText, true, 12)
+ const {data: followsPages, fetchNextPage} = useProfileFollowsQuery(
+ currentAccount?.did,
+ )
+ const follows = followsPages?.pages.flatMap(page => page.follows) || []
+
+ const listRef = useRef(null)
+ const inputRef = useRef(null)
+
+ useLayoutEffect(() => {
+ if (isWeb) {
+ setImmediate(() => {
+ inputRef?.current?.focus()
+ })
+ }
+ }, [])
+
+ const renderItem = ({
+ item,
+ }: ListRenderItemInfo) => {
+ return (
+
+ )
+ }
+
+ return (
+
+ 0 ? results : follows}
+ renderItem={renderItem}
+ keyExtractor={keyExtractor}
+ ListHeaderComponent={
+
+ }
+ stickyHeaderIndices={[0]}
+ style={[
+ web([a.py_0, {height: '100vh', maxHeight: 600}, a.px_0]),
+ native({
+ height: '100%',
+ paddingHorizontal: 0,
+ marginTop: 0,
+ paddingTop: 0,
+ borderTopLeftRadius: 40,
+ borderTopRightRadius: 40,
+ }),
+ ]}
+ webInnerStyle={[a.py_0, {maxWidth: 500, minWidth: 200}]}
+ keyboardDismissMode="on-drag"
+ onEndReached={() => fetchNextPage()}
+ onEndReachedThreshold={2}
+ removeClippedSubviews={true}
+ />
+
+ )
+}
+
+function ListHeader({
+ searchText,
+ setSearchText,
+ inputRef,
+}: {
+ searchText: string
+ setSearchText: (text: string) => void
+ inputRef: React.Ref
+}) {
+ const t = useTheme()
+ const {_} = useLingui()
+ const {
+ state: hovered,
+ onIn: onMouseEnter,
+ onOut: onMouseLeave,
+ } = useInteractionState()
+ const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
+ const interacted = hovered || focused
+
+ return (
+
+
+
+ Select profiles to add
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/screens/StarterPack/Wizard/StepProfiles/WizardProfileCard.tsx b/src/screens/StarterPack/Wizard/StepProfiles/WizardProfileCard.tsx
new file mode 100644
index 0000000000..659de00cb3
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepProfiles/WizardProfileCard.tsx
@@ -0,0 +1,72 @@
+import React from 'react'
+import {View} from 'react-native'
+import {AppBskyActorDefs} from '@atproto/api'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+
+import {UserAvatar} from 'view/com/util/UserAvatar'
+import {WizardAction, WizardState} from '#/screens/StarterPack/Wizard/State'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+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 includesProfile = state.profiles.some(p => p.did === profile.did)
+
+ const onPressAddRemove = () => {
+ if (!profile?.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/StepProfiles/index.tsx b/src/screens/StarterPack/Wizard/StepProfiles/index.tsx
new file mode 100644
index 0000000000..84a138ee89
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/StepProfiles/index.tsx
@@ -0,0 +1,52 @@
+import React from 'react'
+import {ListRenderItemInfo, View} from 'react-native'
+import {AppBskyActorDefs} from '@atproto/api'
+import {Trans} from '@lingui/macro'
+
+import {List} from 'view/com/util/List'
+import {useWizardState} from '#/screens/StarterPack/Wizard/State'
+import {WizardProfileCard} from '#/screens/StarterPack/Wizard/StepProfiles/WizardProfileCard'
+import {atoms as a} from '#/alf'
+import {Text} from '#/components/Typography'
+
+function keyExtractor(item: AppBskyActorDefs.ProfileViewBasic) {
+ return item.did
+}
+
+export function StepProfiles() {
+ const [state, dispatch] = useWizardState()
+
+ const renderItem = ({
+ item,
+ }: ListRenderItemInfo) => {
+ return (
+
+ )
+ }
+
+ return (
+ <>
+
+ {state.profiles.length > 0 ? (
+
+ ) : (
+
+ )}
+
+ >
+ )
+}
+
+function ListEmpty() {
+ return (
+
+
+ Add the people you recommend to your starter pack!
+
+
+ )
+}
diff --git a/src/screens/StarterPack/Wizard/index.tsx b/src/screens/StarterPack/Wizard/index.tsx
new file mode 100644
index 0000000000..080a0cefd7
--- /dev/null
+++ b/src/screens/StarterPack/Wizard/index.tsx
@@ -0,0 +1,228 @@
+import React from 'react'
+import {Keyboard, View} from 'react-native'
+import {
+ KeyboardAwareScrollView,
+ KeyboardStickyView,
+} from 'react-native-keyboard-controller'
+import {msg, Trans} from '@lingui/macro'
+import {useLingui} from '@lingui/react'
+import {useNavigation} from '@react-navigation/native'
+import {NativeStackScreenProps} from '@react-navigation/native-stack'
+
+import {useBottomBarOffset} from 'lib/hooks/useBottomBarOffset'
+import {CommonNavigatorParams, NavigationProp} from 'lib/routes/types'
+import {useProfileQuery} from 'state/queries/profile'
+import {useSession} from 'state/session'
+import {ViewHeader} from 'view/com/util/ViewHeader'
+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 {StepLanding} from '#/screens/StarterPack/Wizard/StepLanding'
+import {StepProfiles} from '#/screens/StarterPack/Wizard/StepProfiles'
+import {WizardAddProfilesDialog} from '#/screens/StarterPack/Wizard/StepProfiles/WizardAddProfilesDialog'
+import {atoms as a, useTheme} from '#/alf'
+import {Button, ButtonText} from '#/components/Button'
+import {useDialogControl} from '#/components/Dialog'
+import {Loader} from '#/components/Loader'
+import {Provider} from './State'
+
+export function Wizard({
+ route,
+}: NativeStackScreenProps) {
+ const params = route.params
+ const {mode, initialStep, id} = params ?? {}
+
+ // TODO load query here
+ const starterPack = {}
+
+ // TODO use this to wait for loading the starterpack for editing
+ if (mode === 'Edit' && false) {
+ // Await here
+ return
+ }
+
+ return (
+
+ )
+}
+
+function WizardReady({
+ mode,
+ initialStep,
+ starterPack,
+}: {
+ mode: 'Create' | 'Edit'
+ id?: string
+ initialStep?: 'Details' | 'Profiles' | 'Feeds'
+ starterPack?: any
+}) {
+ return (
+
+
+
+ )
+}
+
+function WizardInner() {
+ const navigation = useNavigation()
+ const {_} = useLingui()
+ const t = useTheme()
+ const bottomOffset = useBottomBarOffset()
+ const [state, dispatch] = useWizardState()
+ const {currentAccount} = useSession()
+ const {data: currentProfile} = useProfileQuery({
+ did: currentAccount?.did,
+ staleTime: 0,
+ })
+ const bottomBarOffset = useBottomBarOffset()
+ const addProfilesControl = useDialogControl()
+
+ React.useEffect(() => {
+ navigation.setOptions({
+ gestureEnabled: false,
+ })
+ }, [navigation])
+
+ const wizardUiStrings: Record =
+ {
+ Landing: {
+ header: _(msg`Create a starter pack`),
+ button: _(msg`Create`),
+ },
+ Details: {
+ header: _(msg`Details`),
+ button: _(msg`Add profiles`),
+ },
+ Profiles: {
+ header: _(msg`Add profiles`),
+ button: _(msg`Add feeds`),
+ },
+ Feeds: {
+ header: _(msg`Add feeds`),
+ button: _(msg`Finish`),
+ },
+ }
+
+ const uiStrings = wizardUiStrings[state.currentStep]
+
+ const onNext = () => {
+ if (state.currentStep === 'Details' && !state.name) {
+ dispatch({
+ type: 'SetName',
+ name: _(
+ msg`${currentProfile?.displayName || currentProfile?.handle}'s`,
+ ),
+ })
+ } else if (state.currentStep === 'Feeds') {
+ dispatch({type: 'SetProcessing', processing: true})
+ return
+ }
+
+ const keyboardVisible = Keyboard.isVisible()
+ Keyboard.dismiss()
+ setTimeout(
+ () => {
+ dispatch({type: 'Next'})
+ },
+ keyboardVisible ? 16 : 0,
+ )
+ }
+
+ return (
+
+ dispatch({type: 'Back'})
+ : undefined
+ }
+ showBorder={true}
+ showOnDesktop={true}
+ renderButton={
+ state.currentStep === 'Profiles'
+ ? () => (
+
+ )
+ : undefined
+ }
+ />
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
+
+function Container({children}: {children: React.ReactNode}) {
+ const [state] = useWizardState()
+
+ if (state.currentStep === 'Profiles' || state.currentStep === 'Feeds') {
+ return {children}
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+function StepView() {
+ const [state] = useWizardState()
+
+ if (state.currentStep === 'Landing') {
+ return
+ }
+ if (state.currentStep === 'Details') {
+ return
+ }
+ if (state.currentStep === 'Profiles') {
+ return
+ }
+ if (state.currentStep === 'Feeds') {
+ return
+ }
+}
diff --git a/src/view/com/util/ViewHeader.tsx b/src/view/com/util/ViewHeader.tsx
index 4c0f0e3e5c..1147339e54 100644
--- a/src/view/com/util/ViewHeader.tsx
+++ b/src/view/com/util/ViewHeader.tsx
@@ -28,6 +28,7 @@ export function ViewHeader({
showOnDesktop,
showBorder,
renderButton,
+ onBackPress: onPressBackOverride,
}: {
title: string
subtitle?: string
@@ -37,6 +38,7 @@ export function ViewHeader({
showOnDesktop?: boolean
showBorder?: boolean
renderButton?: () => JSX.Element
+ onBackPress?: () => void
}) {
const pal = usePalette('default')
const {_} = useLingui()
@@ -48,11 +50,15 @@ export function ViewHeader({
const onPressBack = React.useCallback(() => {
if (navigation.canGoBack()) {
- navigation.goBack()
+ if (typeof onPressBackOverride === 'function') {
+ onPressBackOverride()
+ } else {
+ navigation.goBack()
+ }
} else {
navigation.navigate('Home')
}
- }, [navigation])
+ }, [navigation, onPressBackOverride])
const onPressMenu = React.useCallback(() => {
track('ViewHeader:MenuButtonClicked')
diff --git a/src/view/screens/Settings/index.tsx b/src/view/screens/Settings/index.tsx
index f3ecc40d1c..48727a98b4 100644
--- a/src/view/screens/Settings/index.tsx
+++ b/src/view/screens/Settings/index.tsx
@@ -876,6 +876,18 @@ export function SettingsScreen({}: Props) {
Delete chat declaration record
+
+ navigation.navigate('StarterPackWizard', {mode: 'Create'})
+ }
+ accessibilityRole="button"
+ accessibilityLabel={_(msg`Navigate to Starter Pack Wizard`)}
+ accessibilityHint={_(msg`Navigates to Starter Pack Wizard`)}>
+
+ Navigate to Starter Pack Wizard
+
+