add custom feed discovery to onboarding

zio/stable
Ansh Nanda 2023-08-28 15:41:02 -07:00
parent c4cf288296
commit 3f1b313fa4
6 changed files with 225 additions and 39 deletions

View File

@ -67,7 +67,7 @@ import {getRoutingInstrumentation} from 'lib/sentry'
import {bskyTitle} from 'lib/strings/headings'
import {JSX} from 'react/jsx-runtime'
import {timeout} from 'lib/async/timeout'
import {Welcome} from 'view/com/auth/onboarding/Welcome'
import {Welcome, WelcomeHeaderRight} from 'view/com/auth/onboarding/Welcome'
import {RecommendedFeeds} from 'view/com/auth/onboarding/RecommendedFeeds'
const navigationRef = createNavigationContainerRef<AllNavigatorParams>()
@ -221,18 +221,26 @@ function commonScreens(Stack: typeof HomeTab, unreadCountLabel?: string) {
component={SavedFeeds}
options={{title: title('Edit My Feeds')}}
/>
<Stack.Group
screenOptions={{
animation: 'slide_from_bottom',
presentation: 'modal',
}}>
<Stack.Screen
name="Welcome"
component={Welcome}
options={{title: title('Welcome')}}
/>
<Stack.Screen name="RecommendedFeeds" component={RecommendedFeeds} />
</Stack.Group>
<Stack.Screen
name="Welcome"
component={Welcome}
options={{
title: title('Welcome'),
presentation: 'card',
headerShown: true,
headerTransparent: true,
headerTitle: '',
headerBackVisible: false,
headerRight: props => <WelcomeHeaderRight {...props} />,
}}
/>
<Stack.Screen
name="RecommendedFeeds"
component={RecommendedFeeds}
options={{
title: title('Recommended Feeds'),
}}
/>
</>
)
}

View File

@ -122,6 +122,7 @@ interface TrackPropertiesMap {
// ONBOARDING events
'Onboarding:Begin': {}
'Onboarding:Complete': {}
'Onboarding:Skipped': {}
}
interface ScreenPropertiesMap {

View File

@ -1,12 +1,12 @@
import {makeAutoObservable} from 'mobx'
import {RootStoreModel} from '../root-store'
import {NavigationProp} from 'lib/routes/types'
import {hasProp} from 'lib/type-guards'
import {track} from 'lib/analytics/analytics'
export const OnboardingScreenSteps = {
Welcome: 'Welcome',
RecommendedFeeds: 'RecommendedFeeds',
Complete: 'Complete',
Home: 'Home',
} as const
type OnboardingStep =
@ -34,6 +34,7 @@ export class OnboardingModel {
typeof v.step === 'string' &&
OnboardingStepsArray.includes(v.step as OnboardingStep)
) {
console.log('hydrating onboarding', v.step)
this.step = v.step as OnboardingStep
}
}
@ -41,31 +42,33 @@ export class OnboardingModel {
this.reset()
}
nextScreenName() {
if (this.step === 'Welcome') {
nextScreenName(currentScreenName?: OnboardingStep) {
if (currentScreenName === 'Welcome' || this.step === 'Welcome') {
this.step = 'RecommendedFeeds'
return this.step
} else if (this.step === 'RecommendedFeeds') {
this.step = 'Complete'
} else if (
this.step === 'RecommendedFeeds' ||
currentScreenName === 'RecommendedFeeds'
) {
this.step = 'Home'
return this.step
} else if (this.step === 'Complete') {
return 'Home'
} else {
// if we get here, we're in an invalid state, let's just go Home
return 'Home'
}
}
complete(navigation: NavigationProp) {
navigation.navigate('Home')
}
reset() {
this.step = 'Welcome'
}
skip() {
track('Onboarding:Skipped')
this.step = 'Home'
}
get isComplete() {
return this.step === 'Complete'
return this.step === 'Home'
}
get isRemaining() {

View File

@ -1,5 +1,5 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {FlatList, StyleSheet, View} from 'react-native'
import {Text} from 'view/com/util/text/Text'
import {usePalette} from 'lib/hooks/usePalette'
import {Button} from 'view/com/util/forms/Button'
@ -7,6 +7,117 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {HomeTabNavigatorParams} from 'lib/routes/types'
import {useStores} from 'state/index'
import {observer} from 'mobx-react-lite'
import {CustomFeed} from 'view/com/feeds/CustomFeed'
import {useCustomFeed} from 'lib/hooks/useCustomFeed'
import {makeRecordUri} from 'lib/strings/url-helpers'
import {ViewHeader} from 'view/com/util/ViewHeader'
const TEMPORARY_RECOMMENDED_FEEDS = [
{
did: 'did:plc:hsqwcidfez66lwm3gxhfv5in',
rkey: 'aaaf2pqeodmpy',
},
{
did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn',
rkey: 'aaap4tbjcfe5y',
},
{
did: 'did:plc:5rw2on4i56btlcajojaxwcat',
rkey: 'aaao6g552b33o',
},
{
did: 'did:plc:jfhpnnst6flqway4eaeqzj2a',
rkey: 'for-science',
},
{
did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy',
rkey: 'bsky-news',
},
{
did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25',
rkey: 'astro',
},
{
did: 'did:plc:tenurhgjptubkk5zf5qhi3og',
rkey: 'h-nba',
},
{
did: 'did:plc:vpkhqolt662uhesyj6nxm7ys',
rkey: 'devfeed',
},
{
did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5',
rkey: 'flipboard-tech',
},
{
did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw',
rkey: 'blacksky',
},
{
did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe',
rkey: 'aaaotfjzjplna',
},
{
did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z',
rkey: 'formula-one',
},
{
did: 'did:plc:q6gjnaw2blty4crticxkmujt',
rkey: 'positivifeed',
},
{
did: 'did:plc:l72uci4styb4jucsgcrrj5ap',
rkey: 'aaao5dzfm36u4',
},
{
did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n',
rkey: 'aaaavlyvqzst2',
},
{
did: 'did:plc:nkahctfdi6bxk72umytfwghw',
rkey: 'aaado2uvfsc6w',
},
{
did: 'did:plc:epihigio3d7un7u3gpqiy5gv',
rkey: 'aaaekwsc7zsvs',
},
{
did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7',
rkey: 'aaaejxlobe474',
},
{
did: 'did:plc:mlq4aycufcuolr7ax6sezpc4',
rkey: 'aaaoudweck6uy',
},
{
did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg',
rkey: 'aaadzjxbcddzi',
},
{
did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr',
rkey: 'aaab2vesjtszc',
},
{
did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if',
rkey: 'aaacynbxwimok',
},
{
did: 'did:plc:abv47bjgzjgoh3yrygwoi36x',
rkey: 'aaagt6amuur5e',
},
{
did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt',
rkey: 'aaacjerk7gwek',
},
{
did: 'did:plc:geoqe3qls5mwezckxxsewys2',
rkey: 'aaai43yetqshu',
},
{
did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34',
rkey: 'authors',
},
]
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'RecommendedFeeds'>
export const RecommendedFeeds = observer(({navigation}: Props) => {
@ -14,41 +125,68 @@ export const RecommendedFeeds = observer(({navigation}: Props) => {
const store = useStores()
const next = () => {
const nextScreenName = store.onboarding.nextScreenName()
const nextScreenName = store.onboarding.nextScreenName('RecommendedFeeds')
if (nextScreenName) {
navigation.navigate(nextScreenName)
}
}
return (
<View style={[styles.container]}>
<View testID="recommendedFeedsScreen">
<Text type="lg-bold" style={[pal.text]}>
Check out some recommended feeds. Click + to add them to your list of
pinned feeds.
</Text>
</View>
<View style={[styles.container]} testID="recommendedFeedsScreen">
<ViewHeader title="Recommended Feeds" canGoBack />
<Text type="lg-medium" style={[pal.text, styles.header]}>
Check out some recommended feeds. Click + to add them to your list of
pinned feeds.
</Text>
<FlatList
data={TEMPORARY_RECOMMENDED_FEEDS}
renderItem={({item}) => <Item item={item} />}
keyExtractor={item => item.did + item.rkey}
style={{flex: 1}}
/>
<Button
onPress={next}
label="Continue"
testID="continueBtn"
style={styles.button}
labelStyle={styles.buttonText}
/>
</View>
)
})
type ItemProps = {
did: string
rkey: string
}
const Item = ({item}: {item: ItemProps}) => {
const uri = makeRecordUri(item.did, 'app.bsky.feed.generator', item.rkey)
const data = useCustomFeed(uri)
if (!data) return null
return (
<CustomFeed item={data} key={uri} showDescription showLikes showSaveBtn />
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
marginVertical: 60,
marginHorizontal: 16,
justifyContent: 'space-between',
},
header: {
marginBottom: 16,
},
button: {
marginBottom: 48,
marginTop: 16,
},
buttonText: {
textAlign: 'center',
fontSize: 18,
marginVertical: 4,
paddingVertical: 4,
},
})

View File

@ -1,5 +1,5 @@
import React from 'react'
import {StyleSheet, View} from 'react-native'
import {Pressable, StyleSheet, View} from 'react-native'
import {Text} from 'view/com/util/text/Text'
import {s} from 'lib/styles'
import {usePalette} from 'lib/hooks/usePalette'
@ -9,14 +9,23 @@ import {NativeStackScreenProps} from '@react-navigation/native-stack'
import {HomeTabNavigatorParams} from 'lib/routes/types'
import {useStores} from 'state/index'
import {observer} from 'mobx-react-lite'
import {HeaderButtonProps} from '@react-navigation/native-stack/lib/typescript/src/types'
import {NavigationProp, useNavigation} from '@react-navigation/native'
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Welcome'>
export const Welcome = observer(({navigation}: Props) => {
const pal = usePalette('default')
const store = useStores()
// make sure bottom nav is hidden
React.useEffect(() => {
if (!store.shell.minimalShellMode) {
store.shell.setMinimalShellMode(true)
}
}, [store.shell.minimalShellMode, store])
const next = () => {
const nextScreenName = store.onboarding.nextScreenName()
const nextScreenName = store.onboarding.nextScreenName('Welcome')
if (nextScreenName) {
navigation.navigate(nextScreenName)
}
@ -76,6 +85,31 @@ export const Welcome = observer(({navigation}: Props) => {
)
})
export const WelcomeHeaderRight = (props: HeaderButtonProps) => {
const {canGoBack} = props
const pal = usePalette('default')
const navigation = useNavigation<NavigationProp<HomeTabNavigatorParams>>()
const store = useStores()
return (
<Pressable
accessibilityRole="button"
style={[s.flexRow, s.alignCenter]}
onPress={() => {
if (canGoBack) {
store.onboarding.skip()
navigation.goBack()
}
}}>
<Text style={[pal.link]}>Skip</Text>
<FontAwesomeIcon
icon={'chevron-right'}
size={14}
color={pal.colors.link}
/>
</Pressable>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,

View File

@ -92,6 +92,7 @@ import {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay'
import {faPause} from '@fortawesome/free-solid-svg-icons/faPause'
import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack'
import {faList} from '@fortawesome/free-solid-svg-icons/faList'
import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight'
export function setup() {
library.add(
@ -187,5 +188,6 @@ export function setup() {
faPlay,
faPause,
faList,
faChevronRight,
)
}