Add onboarding (WIP)
parent
b4097e25d6
commit
d228a5f4f5
|
@ -220,6 +220,8 @@ PODS:
|
|||
- React-jsinspector (0.68.2)
|
||||
- React-logger (0.68.2):
|
||||
- glog
|
||||
- react-native-pager-view (6.0.2):
|
||||
- React-Core
|
||||
- react-native-safe-area-context (4.3.4):
|
||||
- RCT-Folly
|
||||
- RCTRequired
|
||||
|
@ -357,6 +359,7 @@ DEPENDENCIES:
|
|||
- React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`)
|
||||
- React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`)
|
||||
- React-logger (from `../node_modules/react-native/ReactCommon/logger`)
|
||||
- react-native-pager-view (from `../node_modules/react-native-pager-view`)
|
||||
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
|
||||
- react-native-splash-screen (from `../node_modules/react-native-splash-screen`)
|
||||
- React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`)
|
||||
|
@ -423,6 +426,8 @@ EXTERNAL SOURCES:
|
|||
:path: "../node_modules/react-native/ReactCommon/jsinspector"
|
||||
React-logger:
|
||||
:path: "../node_modules/react-native/ReactCommon/logger"
|
||||
react-native-pager-view:
|
||||
:path: "../node_modules/react-native-pager-view"
|
||||
react-native-safe-area-context:
|
||||
:path: "../node_modules/react-native-safe-area-context"
|
||||
react-native-splash-screen:
|
||||
|
@ -489,6 +494,7 @@ SPEC CHECKSUMS:
|
|||
React-jsiexecutor: b7b553412f2ec768fe6c8f27cd6bafdb9d8719e6
|
||||
React-jsinspector: c5989c77cb89ae6a69561095a61cce56a44ae8e8
|
||||
React-logger: a0833912d93b36b791b7a521672d8ee89107aff1
|
||||
react-native-pager-view: 592421df0259bf7a7a4fe85b74c24f3f39905605
|
||||
react-native-safe-area-context: dfe5aa13bee37a0c7e8059d14f72ffc076d120e9
|
||||
react-native-splash-screen: 4312f786b13a81b5169ef346d76d33bc0c6dc457
|
||||
React-perflogger: a18b4f0bd933b8b24ecf9f3c54f9bf65180f3fe6
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"react-native-gesture-handler": "^2.5.0",
|
||||
"react-native-inappbrowser-reborn": "^3.6.3",
|
||||
"react-native-linear-gradient": "^2.6.2",
|
||||
"react-native-pager-view": "^6.0.2",
|
||||
"react-native-progress": "^5.0.0",
|
||||
"react-native-reanimated": "^2.9.1",
|
||||
"react-native-root-siblings": "^4.1.1",
|
||||
|
@ -40,6 +41,7 @@
|
|||
"react-native-screens": "^3.13.1",
|
||||
"react-native-splash-screen": "^3.3.0",
|
||||
"react-native-svg": "^12.4.0",
|
||||
"react-native-tab-view": "^3.3.0",
|
||||
"react-native-url-polyfill": "^1.3.0",
|
||||
"react-native-web": "^0.17.7"
|
||||
},
|
||||
|
|
|
@ -23,13 +23,14 @@ export async function setupState() {
|
|||
console.error('Failed to load state from storage', e)
|
||||
}
|
||||
|
||||
await rootStore.session.setup()
|
||||
|
||||
// track changes & save to storage
|
||||
autorun(() => {
|
||||
const snapshot = rootStore.serialize()
|
||||
storage.save(ROOT_STATE_STORAGE_KEY, snapshot)
|
||||
})
|
||||
|
||||
await rootStore.session.setup()
|
||||
await rootStore.fetchStateUpdate()
|
||||
console.log(rootStore.me)
|
||||
|
||||
|
|
|
@ -8,3 +8,7 @@ export function hasProp<K extends PropertyKey>(
|
|||
): data is Record<K, unknown> {
|
||||
return prop in data
|
||||
}
|
||||
|
||||
export function isStrArray(v: unknown): v is string[] {
|
||||
return Array.isArray(v) && v.every(item => typeof item === 'string')
|
||||
}
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {isObj, hasProp} from '../lib/type-guards'
|
||||
|
||||
export const OnboardStage = {
|
||||
Explainers: 'explainers',
|
||||
Follows: 'follows',
|
||||
}
|
||||
|
||||
export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows]
|
||||
|
||||
export class OnboardModel {
|
||||
isOnboarding: boolean = true
|
||||
stage: string = OnboardStageOrder[0]
|
||||
|
||||
constructor() {
|
||||
makeAutoObservable(this, {
|
||||
serialize: false,
|
||||
hydrate: false,
|
||||
})
|
||||
}
|
||||
|
||||
serialize(): unknown {
|
||||
return {
|
||||
isOnboarding: this.isOnboarding,
|
||||
stage: this.stage,
|
||||
}
|
||||
}
|
||||
|
||||
hydrate(v: unknown) {
|
||||
if (isObj(v)) {
|
||||
if (hasProp(v, 'isOnboarding') && typeof v.isOnboarding === 'boolean') {
|
||||
this.isOnboarding = v.isOnboarding
|
||||
}
|
||||
if (
|
||||
hasProp(v, 'stage') &&
|
||||
typeof v.stage === 'string' &&
|
||||
OnboardStageOrder.includes(v.stage)
|
||||
) {
|
||||
this.stage = v.stage
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
this.isOnboarding = true
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.isOnboarding = false
|
||||
}
|
||||
|
||||
next() {
|
||||
if (!this.isOnboarding) return
|
||||
let i = OnboardStageOrder.indexOf(this.stage)
|
||||
i++
|
||||
if (i >= OnboardStageOrder.length) {
|
||||
this.isOnboarding = false
|
||||
} else {
|
||||
this.stage = OnboardStageOrder[i]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,12 +11,14 @@ import {SessionModel} from './session'
|
|||
import {NavigationModel} from './navigation'
|
||||
import {ShellModel} from './shell'
|
||||
import {MeModel} from './me'
|
||||
import {OnboardModel} from './onboard'
|
||||
|
||||
export class RootStoreModel {
|
||||
session = new SessionModel(this)
|
||||
nav = new NavigationModel()
|
||||
shell = new ShellModel()
|
||||
me = new MeModel(this)
|
||||
onboard = new OnboardModel()
|
||||
|
||||
constructor(public api: SessionServiceClient) {
|
||||
makeAutoObservable(this, {
|
||||
|
@ -53,6 +55,7 @@ export class RootStoreModel {
|
|||
return {
|
||||
session: this.session.serialize(),
|
||||
nav: this.nav.serialize(),
|
||||
onboard: this.onboard.serialize(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -64,6 +67,9 @@ export class RootStoreModel {
|
|||
if (hasProp(v, 'nav')) {
|
||||
this.nav.hydrate(v.nav)
|
||||
}
|
||||
if (hasProp(v, 'onboard')) {
|
||||
this.onboard.hydrate(v.onboard)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,17 +15,8 @@ interface SessionData {
|
|||
did: string
|
||||
}
|
||||
|
||||
export enum OnboardingStage {
|
||||
Init = 'init',
|
||||
}
|
||||
|
||||
interface OnboardingState {
|
||||
stage: OnboardingStage
|
||||
}
|
||||
|
||||
export class SessionModel {
|
||||
data: SessionData | null = null
|
||||
onboardingState: OnboardingState | null = null
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(this, {
|
||||
|
@ -42,7 +33,6 @@ export class SessionModel {
|
|||
serialize(): unknown {
|
||||
return {
|
||||
data: this.data,
|
||||
onboardingState: this.onboardingState,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,18 +77,6 @@ export class SessionModel {
|
|||
this.data = data
|
||||
}
|
||||
}
|
||||
if (
|
||||
this.data &&
|
||||
hasProp(v, 'onboardingState') &&
|
||||
isObj(v.onboardingState)
|
||||
) {
|
||||
if (
|
||||
hasProp(v.onboardingState, 'stage') &&
|
||||
typeof v.onboardingState === 'string'
|
||||
) {
|
||||
this.onboardingState = v.onboardingState
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,7 +190,7 @@ export class SessionModel {
|
|||
handle: res.data.handle,
|
||||
did: res.data.did,
|
||||
})
|
||||
this.setOnboardingStage(OnboardingStage.Init)
|
||||
this.rootStore.onboard.start()
|
||||
this.configureApi()
|
||||
this.rootStore.me.load().catch(e => {
|
||||
console.error('Failed to fetch local user information', e)
|
||||
|
@ -228,12 +206,4 @@ export class SessionModel {
|
|||
}
|
||||
this.rootStore.clearAll()
|
||||
}
|
||||
|
||||
setOnboardingStage(stage: OnboardingStage | null) {
|
||||
if (stage === null) {
|
||||
this.onboardingState = null
|
||||
} else {
|
||||
this.onboardingState = {stage}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,121 @@
|
|||
import {makeAutoObservable} from 'mobx'
|
||||
import {RootStoreModel} from './root-store'
|
||||
|
||||
interface Response {
|
||||
data: {
|
||||
suggestions: ResponseSuggestedActor[]
|
||||
}
|
||||
}
|
||||
export type ResponseSuggestedActor = {
|
||||
did: string
|
||||
handle: string
|
||||
displayName?: string
|
||||
description?: string
|
||||
createdAt?: string
|
||||
indexedAt: string
|
||||
}
|
||||
|
||||
export type SuggestedActor = ResponseSuggestedActor & {
|
||||
_reactKey: string
|
||||
}
|
||||
|
||||
export class SuggestedActorsViewModel {
|
||||
// state
|
||||
isLoading = false
|
||||
isRefreshing = false
|
||||
hasLoaded = false
|
||||
error = ''
|
||||
|
||||
// data
|
||||
suggestions: SuggestedActor[] = []
|
||||
|
||||
constructor(public rootStore: RootStoreModel) {
|
||||
makeAutoObservable(
|
||||
this,
|
||||
{
|
||||
rootStore: false,
|
||||
},
|
||||
{autoBind: true},
|
||||
)
|
||||
}
|
||||
|
||||
get hasContent() {
|
||||
return this.suggestions.length > 0
|
||||
}
|
||||
|
||||
get hasError() {
|
||||
return this.error !== ''
|
||||
}
|
||||
|
||||
get isEmpty() {
|
||||
return this.hasLoaded && !this.hasContent
|
||||
}
|
||||
|
||||
// public api
|
||||
// =
|
||||
|
||||
async setup() {
|
||||
await this._fetch()
|
||||
}
|
||||
|
||||
async refresh() {
|
||||
await this._fetch(true)
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
// TODO
|
||||
}
|
||||
|
||||
// state transitions
|
||||
// =
|
||||
|
||||
private _xLoading(isRefreshing = false) {
|
||||
this.isLoading = true
|
||||
this.isRefreshing = isRefreshing
|
||||
this.error = ''
|
||||
}
|
||||
|
||||
private _xIdle(err: string = '') {
|
||||
this.isLoading = false
|
||||
this.isRefreshing = false
|
||||
this.hasLoaded = true
|
||||
this.error = err
|
||||
}
|
||||
|
||||
// loader functions
|
||||
// =
|
||||
|
||||
private async _fetch(isRefreshing = false) {
|
||||
this._xLoading(isRefreshing)
|
||||
try {
|
||||
const debugRes = await this.rootStore.api.app.bsky.graph.getFollowers({
|
||||
user: 'alice.test',
|
||||
})
|
||||
const res = {
|
||||
data: {
|
||||
suggestions: debugRes.data.followers,
|
||||
},
|
||||
}
|
||||
this._replaceAll(res)
|
||||
this._xIdle()
|
||||
} catch (e: any) {
|
||||
this._xIdle(e.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private _replaceAll(res: Response) {
|
||||
this.suggestions.length = 0
|
||||
let counter = 0
|
||||
for (const item of res.data.suggestions) {
|
||||
this._append({
|
||||
_reactKey: `item-${counter++}`,
|
||||
description: 'Just another cool person using Bluesky',
|
||||
...item,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private _append(item: SuggestedActor) {
|
||||
this.suggestions.push(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,147 @@
|
|||
import React, {useState} from 'react'
|
||||
import {
|
||||
Animated,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
useWindowDimensions,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view'
|
||||
import {UserGroupIcon} from '../../lib/icons'
|
||||
import {useStores} from '../../../state'
|
||||
import {s} from '../../lib/styles'
|
||||
|
||||
const Scenes = () => (
|
||||
<View style={styles.explainer}>
|
||||
<View style={styles.explainerIcon}>
|
||||
<View style={s.flex1} />
|
||||
<UserGroupIcon style={s.black} size="48" />
|
||||
<View style={s.flex1} />
|
||||
</View>
|
||||
<Text style={styles.explainerHeading}>Scenes</Text>
|
||||
<Text style={styles.explainerDesc}>
|
||||
Scenes are invite-only groups of users. Follow them to see what's trending
|
||||
with the scene's members.
|
||||
</Text>
|
||||
<Text style={styles.explainerDesc}>[ TODO screenshot ]</Text>
|
||||
</View>
|
||||
)
|
||||
|
||||
const SCENE_MAP = {
|
||||
scenes: Scenes,
|
||||
}
|
||||
const renderScene = SceneMap(SCENE_MAP)
|
||||
|
||||
export const FeatureExplainer = () => {
|
||||
const layout = useWindowDimensions()
|
||||
const store = useStores()
|
||||
const [index, setIndex] = useState(0)
|
||||
const routes = [{key: 'scenes', title: 'Scenes'}]
|
||||
|
||||
const onPressSkip = () => store.onboard.next()
|
||||
const onPressNext = () => {
|
||||
if (index >= routes.length - 1) {
|
||||
store.onboard.next()
|
||||
} else {
|
||||
setIndex(index + 1)
|
||||
}
|
||||
}
|
||||
|
||||
const renderTabBar = (props: TabBarProps<Route>) => {
|
||||
const inputRange = props.navigationState.routes.map((x, i) => i)
|
||||
return (
|
||||
<View style={styles.tabBar}>
|
||||
<View style={s.flex1} />
|
||||
{props.navigationState.routes.map((route, i) => {
|
||||
const opacity = props.position.interpolate({
|
||||
inputRange,
|
||||
outputRange: inputRange.map(inputIndex =>
|
||||
inputIndex === i ? 1 : 0.5,
|
||||
),
|
||||
})
|
||||
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={i}
|
||||
style={styles.tabItem}
|
||||
onPress={() => setIndex(i)}>
|
||||
<Animated.Text style={{opacity}}>°</Animated.Text>
|
||||
</TouchableOpacity>
|
||||
)
|
||||
})}
|
||||
<View style={s.flex1} />
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const FirstExplainer = SCENE_MAP[routes[0]?.key as keyof typeof SCENE_MAP]
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
{routes.length > 1 ? (
|
||||
<TabView
|
||||
navigationState={{index, routes}}
|
||||
renderScene={renderScene}
|
||||
renderTabBar={renderTabBar}
|
||||
onIndexChange={setIndex}
|
||||
initialLayout={{width: layout.width}}
|
||||
tabBarPosition="bottom"
|
||||
/>
|
||||
) : FirstExplainer ? (
|
||||
<FirstExplainer />
|
||||
) : (
|
||||
<View />
|
||||
)}
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity onPress={onPressSkip}>
|
||||
<Text style={[s.blue3, s.f18]}>Skip</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<Text style={[s.blue3, s.f18]}>Next</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
tabBar: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
tabItem: {
|
||||
alignItems: 'center',
|
||||
padding: 16,
|
||||
},
|
||||
|
||||
explainer: {
|
||||
flex: 1,
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
explainerIcon: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
explainerHeading: {
|
||||
fontSize: 42,
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
explainerDesc: {
|
||||
fontSize: 18,
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 32,
|
||||
paddingBottom: 24,
|
||||
},
|
||||
})
|
|
@ -0,0 +1,202 @@
|
|||
import React, {useMemo, useEffect} from 'react'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
FlatList,
|
||||
SafeAreaView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native'
|
||||
import LinearGradient from 'react-native-linear-gradient'
|
||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {ErrorScreen} from '../util/ErrorScreen'
|
||||
import {UserAvatar} from '../util/UserAvatar'
|
||||
import {useStores} from '../../../state'
|
||||
import {
|
||||
SuggestedActorsViewModel,
|
||||
SuggestedActor,
|
||||
} from '../../../state/models/suggested-actors-view'
|
||||
import {s, colors, gradients} from '../../lib/styles'
|
||||
|
||||
export const Follows = observer(() => {
|
||||
const store = useStores()
|
||||
|
||||
const view = useMemo<SuggestedActorsViewModel>(
|
||||
() => new SuggestedActorsViewModel(store),
|
||||
[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Fetching suggested actors')
|
||||
view
|
||||
.setup()
|
||||
.catch((err: any) => console.error('Failed to fetch suggestions', err))
|
||||
}, [view])
|
||||
|
||||
useEffect(() => {
|
||||
if (!view.isLoading && !view.hasError && !view.hasContent) {
|
||||
// no suggestions, bounce from this view
|
||||
store.onboard.next()
|
||||
}
|
||||
}, [view, view.isLoading, view.hasError, view.hasContent])
|
||||
|
||||
const onPressTryAgain = () =>
|
||||
view
|
||||
.setup()
|
||||
.catch((err: any) => console.error('Failed to fetch suggestions', err))
|
||||
const onPressNext = () => store.onboard.next()
|
||||
|
||||
const renderItem = ({item}: {item: SuggestedActor}) => <User item={item} />
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<Text style={styles.title}>Suggested follows</Text>
|
||||
{view.isLoading ? (
|
||||
<View>
|
||||
<ActivityIndicator />
|
||||
</View>
|
||||
) : view.hasError ? (
|
||||
<ErrorScreen
|
||||
title="Failed to load suggestions"
|
||||
message="There was an error while trying to load suggested follows."
|
||||
details={view.error}
|
||||
onPressTryAgain={onPressTryAgain}
|
||||
/>
|
||||
) : (
|
||||
<View style={styles.suggestionsContainer}>
|
||||
<FlatList
|
||||
data={view.suggestions}
|
||||
keyExtractor={item => item._reactKey}
|
||||
renderItem={renderItem}
|
||||
style={s.flex1}
|
||||
/>
|
||||
</View>
|
||||
)}
|
||||
<View style={styles.footer}>
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<Text style={[s.blue3, s.f18]}>Skip</Text>
|
||||
</TouchableOpacity>
|
||||
<View style={s.flex1} />
|
||||
<TouchableOpacity onPress={onPressNext}>
|
||||
<Text style={[s.blue3, s.f18]}>Next</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</SafeAreaView>
|
||||
)
|
||||
})
|
||||
|
||||
const User = ({item}: {item: SuggestedActor}) => {
|
||||
return (
|
||||
<View style={styles.actor}>
|
||||
<View style={styles.actorMeta}>
|
||||
<View style={styles.actorAvi}>
|
||||
<UserAvatar
|
||||
size={40}
|
||||
displayName={item.displayName}
|
||||
handle={item.handle}
|
||||
/>
|
||||
</View>
|
||||
<View style={styles.actorContent}>
|
||||
<Text style={[s.f17, s.bold]} numberOfLines={1}>
|
||||
{item.displayName}
|
||||
</Text>
|
||||
<Text style={[s.f14, s.gray5]} numberOfLines={1}>
|
||||
@{item.handle}
|
||||
</Text>
|
||||
</View>
|
||||
<View style={styles.actorBtn}>
|
||||
<TouchableOpacity>
|
||||
<LinearGradient
|
||||
colors={[gradients.primary.start, gradients.primary.end]}
|
||||
start={{x: 0, y: 0}}
|
||||
end={{x: 1, y: 1}}
|
||||
style={[styles.btn, styles.gradientBtn]}>
|
||||
<FontAwesomeIcon icon="plus" style={[s.white, s.mr5]} size={15} />
|
||||
<Text style={[s.white, s.fw600, s.f15]}>Follow</Text>
|
||||
</LinearGradient>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
{item.description ? (
|
||||
<View style={styles.actorDetails}>
|
||||
<Text style={[s.f15]} numberOfLines={4}>
|
||||
{item.description}
|
||||
</Text>
|
||||
</View>
|
||||
) : undefined}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
},
|
||||
|
||||
title: {
|
||||
fontSize: 24,
|
||||
fontWeight: 'bold',
|
||||
paddingHorizontal: 16,
|
||||
paddingBottom: 12,
|
||||
},
|
||||
|
||||
suggestionsContainer: {
|
||||
flex: 1,
|
||||
backgroundColor: colors.gray1,
|
||||
},
|
||||
|
||||
actor: {
|
||||
backgroundColor: colors.white,
|
||||
borderRadius: 6,
|
||||
margin: 2,
|
||||
marginBottom: 0,
|
||||
},
|
||||
actorMeta: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
actorAvi: {
|
||||
width: 60,
|
||||
paddingLeft: 10,
|
||||
paddingTop: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
actorContent: {
|
||||
flex: 1,
|
||||
paddingRight: 10,
|
||||
paddingTop: 10,
|
||||
},
|
||||
actorBtn: {
|
||||
paddingRight: 10,
|
||||
paddingTop: 10,
|
||||
},
|
||||
actorDetails: {
|
||||
paddingLeft: 60,
|
||||
paddingRight: 10,
|
||||
paddingBottom: 10,
|
||||
},
|
||||
|
||||
gradientBtn: {
|
||||
paddingHorizontal: 24,
|
||||
paddingVertical: 6,
|
||||
},
|
||||
secondaryBtn: {
|
||||
paddingHorizontal: 14,
|
||||
},
|
||||
btn: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 7,
|
||||
borderRadius: 50,
|
||||
backgroundColor: colors.gray1,
|
||||
marginLeft: 6,
|
||||
},
|
||||
|
||||
footer: {
|
||||
flexDirection: 'row',
|
||||
paddingHorizontal: 32,
|
||||
paddingBottom: 24,
|
||||
paddingTop: 16,
|
||||
},
|
||||
})
|
|
@ -2,7 +2,6 @@ import React from 'react'
|
|||
import {observer} from 'mobx-react-lite'
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Image,
|
||||
StyleSheet,
|
||||
Text,
|
||||
TouchableOpacity,
|
||||
|
|
|
@ -441,7 +441,6 @@ function cleanUsername(v: string): string {
|
|||
|
||||
export const Login = observer(
|
||||
(/*{navigation}: RootTabsScreenProps<'Login'>*/) => {
|
||||
// const store = useStores()
|
||||
const [screenState, setScreenState] = useState<ScreenState>(
|
||||
ScreenState.SigninOrCreateAccount,
|
||||
)
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
import React, {useEffect} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {observer} from 'mobx-react-lite'
|
||||
import {FeatureExplainer} from '../com/onboard/FeatureExplainer'
|
||||
import {Follows} from '../com/onboard/Follows'
|
||||
import {OnboardStage, OnboardStageOrder} from '../../state/models/onboard'
|
||||
import {useStores} from '../../state'
|
||||
|
||||
export const Onboard = observer(() => {
|
||||
const store = useStores()
|
||||
|
||||
useEffect(() => {
|
||||
// sanity check - bounce out of onboarding if the stage is wrong somehow
|
||||
if (!OnboardStageOrder.includes(store.onboard.stage)) {
|
||||
store.onboard.stop()
|
||||
}
|
||||
}, [store.onboard.stage])
|
||||
|
||||
let Com
|
||||
if (store.onboard.stage === OnboardStage.Explainers) {
|
||||
Com = FeatureExplainer
|
||||
} else if (store.onboard.stage === OnboardStage.Follows) {
|
||||
Com = Follows
|
||||
} else {
|
||||
Com = View
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={{flex: 1}}>
|
||||
<Com />
|
||||
</View>
|
||||
)
|
||||
})
|
|
@ -27,6 +27,7 @@ import {useStores} from '../../../state'
|
|||
import {NavigationModel} from '../../../state/models/navigation'
|
||||
import {match, MatchResult} from '../../routes'
|
||||
import {Login} from '../../screens/Login'
|
||||
import {Onboard} from '../../screens/Onboard'
|
||||
import {Modal} from '../../com/modals/Modal'
|
||||
import {MainMenu} from './MainMenu'
|
||||
import {TabsSelector} from './TabsSelector'
|
||||
|
@ -161,6 +162,15 @@ export const MobileShell: React.FC = observer(() => {
|
|||
</LinearGradient>
|
||||
)
|
||||
}
|
||||
if (store.onboard.isOnboarding) {
|
||||
return (
|
||||
<View style={styles.outerContainer}>
|
||||
<View style={styles.innerContainer}>
|
||||
<Onboard />
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={styles.outerContainer}>
|
||||
|
|
17
yarn.lock
17
yarn.lock
|
@ -10177,6 +10177,11 @@ react-native-linear-gradient@^2.6.2:
|
|||
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.6.2.tgz#56598a76832724b2afa7889747635b5c80948f38"
|
||||
integrity sha512-Z8Xxvupsex+9BBFoSYS87bilNPWcRfRsGC0cpJk72Nxb5p2nEkGSBv73xZbEHnW2mUFvP+huYxrVvjZkr/gRjQ==
|
||||
|
||||
react-native-pager-view@^6.0.2:
|
||||
version "6.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.0.2.tgz#447b85fcb9f35225c4d6885c18689a7d30c181d9"
|
||||
integrity sha512-XL3Qc9k7o0BykclGHtuRUz97FpF6rcKbP8LqszLeS2hKnINYcbUPYqg46EhbwVhFOUJE+XhT3idrSO1e/D6jtQ==
|
||||
|
||||
react-native-progress@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-progress/-/react-native-progress-5.0.0.tgz#f5ac6ceaeee27f184c660b00f29419e82a9d0ab0"
|
||||
|
@ -10237,6 +10242,13 @@ react-native-svg@^12.4.0:
|
|||
css-select "^5.1.0"
|
||||
css-tree "^1.1.3"
|
||||
|
||||
react-native-tab-view@^3.3.0:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-tab-view/-/react-native-tab-view-3.3.0.tgz#3d24ae4f4c55cfd54cd1d2d1f8915b0b2c33a5da"
|
||||
integrity sha512-xjAQe657Gp/de2QHb7ptksTg8Jcb+j3fLAdcYryzfavt/pe+HtKLpkCtQsxyIJpRrAO7YPxFsymi2N4MnNfePA==
|
||||
dependencies:
|
||||
use-latest-callback "^0.1.5"
|
||||
|
||||
react-native-url-polyfill@^1.3.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-native-url-polyfill/-/react-native-url-polyfill-1.3.0.tgz#c1763de0f2a8c22cc3e959b654c8790622b6ef6a"
|
||||
|
@ -11980,6 +11992,11 @@ url-parse@^1.5.3:
|
|||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
use-latest-callback@^0.1.5:
|
||||
version "0.1.5"
|
||||
resolved "https://registry.yarnpkg.com/use-latest-callback/-/use-latest-callback-0.1.5.tgz#a4a836c08fa72f6608730b5b8f4bbd9c57c04f51"
|
||||
integrity sha512-HtHatS2U4/h32NlkhupDsPlrbiD27gSH5swBdtXbCAlc6pfOFzaj0FehW/FO12rx8j2Vy4/lJScCiJyM01E+bQ==
|
||||
|
||||
"use-subscription@>=1.0.0 <1.6.0":
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/use-subscription/-/use-subscription-1.5.1.tgz#73501107f02fad84c6dd57965beb0b75c68c42d1"
|
||||
|
|
Loading…
Reference in New Issue