Remove old old onboarding (#3674)
parent
24da3a8f4e
commit
05beb1bbad
|
@ -1,3 +1,2 @@
|
||||||
export const LOGIN_INCLUDE_DEV_SERVERS = true
|
export const LOGIN_INCLUDE_DEV_SERVERS = true
|
||||||
export const PWI_ENABLED = true
|
export const PWI_ENABLED = true
|
||||||
export const NEW_ONBOARDING_ENABLED = true
|
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {SafeAreaView, Platform} from 'react-native'
|
|
||||||
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {Welcome} from './onboarding/Welcome'
|
|
||||||
import {RecommendedFeeds} from './onboarding/RecommendedFeeds'
|
|
||||||
import {RecommendedFollows} from './onboarding/RecommendedFollows'
|
|
||||||
import {useSetMinimalShellMode} from '#/state/shell/minimal-mode'
|
|
||||||
import {useOnboardingState, useOnboardingDispatch} from '#/state/shell'
|
|
||||||
|
|
||||||
export function Onboarding() {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const setMinimalShellMode = useSetMinimalShellMode()
|
|
||||||
const onboardingState = useOnboardingState()
|
|
||||||
const onboardingDispatch = useOnboardingDispatch()
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
setMinimalShellMode(true)
|
|
||||||
}, [setMinimalShellMode])
|
|
||||||
|
|
||||||
const next = () => onboardingDispatch({type: 'next'})
|
|
||||||
const skip = () => onboardingDispatch({type: 'skip'})
|
|
||||||
|
|
||||||
return (
|
|
||||||
<SafeAreaView
|
|
||||||
testID="onboardingView"
|
|
||||||
style={[
|
|
||||||
s.hContentRegion,
|
|
||||||
pal.view,
|
|
||||||
// @ts-ignore web only -esb
|
|
||||||
Platform.select({
|
|
||||||
web: {
|
|
||||||
height: '100vh',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
]}>
|
|
||||||
<ErrorBoundary>
|
|
||||||
{onboardingState.step === 'Welcome' && (
|
|
||||||
<Welcome skip={skip} next={next} />
|
|
||||||
)}
|
|
||||||
{onboardingState.step === 'RecommendedFeeds' && (
|
|
||||||
<RecommendedFeeds next={next} />
|
|
||||||
)}
|
|
||||||
{onboardingState.step === 'RecommendedFollows' && (
|
|
||||||
<RecommendedFollows next={next} />
|
|
||||||
)}
|
|
||||||
</ErrorBoundary>
|
|
||||||
</SafeAreaView>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,211 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {useSuggestedFeedsQuery} from '#/state/queries/suggested-feeds'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
import {Mobile, TabletOrDesktop} from 'view/com/util/layouts/Breakpoints'
|
|
||||||
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
|
||||||
import {RecommendedFeedsItem} from './RecommendedFeedsItem'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
next: () => void
|
|
||||||
}
|
|
||||||
export function RecommendedFeeds({next}: Props) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {isTabletOrMobile} = useWebMediaQueries()
|
|
||||||
const {isLoading, data} = useSuggestedFeedsQuery()
|
|
||||||
|
|
||||||
const hasFeeds = data && data.pages[0].feeds.length
|
|
||||||
|
|
||||||
const title = (
|
|
||||||
<>
|
|
||||||
<Trans>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.textLight,
|
|
||||||
tdStyles.title1,
|
|
||||||
isTabletOrMobile && tdStyles.title1Small,
|
|
||||||
]}>
|
|
||||||
Choose your
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.link,
|
|
||||||
tdStyles.title2,
|
|
||||||
isTabletOrMobile && tdStyles.title2Small,
|
|
||||||
]}>
|
|
||||||
Recommended
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.link,
|
|
||||||
tdStyles.title2,
|
|
||||||
isTabletOrMobile && tdStyles.title2Small,
|
|
||||||
]}>
|
|
||||||
Feeds
|
|
||||||
</Text>
|
|
||||||
</Trans>
|
|
||||||
<Text type="2xl-medium" style={[pal.textLight, tdStyles.description]}>
|
|
||||||
<Trans>
|
|
||||||
Feeds are created by users to curate content. Choose some feeds that
|
|
||||||
you find interesting.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
marginTop: 20,
|
|
||||||
}}>
|
|
||||||
<Button onPress={next} testID="continueBtn">
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 2,
|
|
||||||
gap: 6,
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
type="2xl-medium"
|
|
||||||
style={{color: '#fff', position: 'relative', top: -1}}>
|
|
||||||
<Trans>Next</Trans>
|
|
||||||
</Text>
|
|
||||||
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
|
|
||||||
</View>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TabletOrDesktop>
|
|
||||||
<TitleColumnLayout
|
|
||||||
testID="recommendedFeedsOnboarding"
|
|
||||||
title={title}
|
|
||||||
horizontal
|
|
||||||
titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}}
|
|
||||||
contentStyle={{paddingHorizontal: 0}}>
|
|
||||||
{hasFeeds ? (
|
|
||||||
<FlatList
|
|
||||||
data={data.pages[0].feeds}
|
|
||||||
renderItem={({item}) => <RecommendedFeedsItem item={item} />}
|
|
||||||
keyExtractor={item => item.uri}
|
|
||||||
style={{flex: 1}}
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<View>
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<ErrorMessage message={_(msg`Failed to load recommended feeds`)} />
|
|
||||||
)}
|
|
||||||
</TitleColumnLayout>
|
|
||||||
</TabletOrDesktop>
|
|
||||||
<Mobile>
|
|
||||||
<View style={[mStyles.container]} testID="recommendedFeedsOnboarding">
|
|
||||||
<ViewHeader
|
|
||||||
title={_(msg`Recommended Feeds`)}
|
|
||||||
showBackButton={false}
|
|
||||||
showOnDesktop
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={[pal.text, mStyles.header]}>
|
|
||||||
<Trans>
|
|
||||||
Check out some recommended feeds. Tap + to add them to your list
|
|
||||||
of pinned feeds.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{hasFeeds ? (
|
|
||||||
<FlatList
|
|
||||||
data={data.pages[0].feeds}
|
|
||||||
renderItem={({item}) => <RecommendedFeedsItem item={item} />}
|
|
||||||
keyExtractor={item => item.uri}
|
|
||||||
style={{flex: 1}}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
/>
|
|
||||||
) : isLoading ? (
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
</View>
|
|
||||||
) : (
|
|
||||||
<View style={{flex: 1}}>
|
|
||||||
<ErrorMessage
|
|
||||||
message={_(msg`Failed to load recommended feeds`)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={next}
|
|
||||||
label={_(msg`Continue`)}
|
|
||||||
testID="continueBtn"
|
|
||||||
style={mStyles.button}
|
|
||||||
labelStyle={mStyles.buttonText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Mobile>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tdStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
title1: {
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
title1Small: {
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
title2: {
|
|
||||||
fontSize: 58,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
title2Small: {
|
|
||||||
fontSize: 36,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
maxWidth: 400,
|
|
||||||
marginTop: 10,
|
|
||||||
marginLeft: 'auto',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const mStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
marginBottom: 16,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
marginBottom: 16,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
marginTop: 16,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
paddingVertical: 4,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,172 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {View} from 'react-native'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {AppBskyFeedDefs, RichText as BskRichText} from '@atproto/api'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import {RichText} from 'view/com/util/text/RichText'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
|
||||||
import * as Toast from 'view/com/util/Toast'
|
|
||||||
import {HeartIcon} from 'lib/icons'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
|
||||||
import {
|
|
||||||
usePreferencesQuery,
|
|
||||||
usePinFeedMutation,
|
|
||||||
useRemoveFeedMutation,
|
|
||||||
} from '#/state/queries/preferences'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {useAnalytics} from '#/lib/analytics/analytics'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
export function RecommendedFeedsItem({
|
|
||||||
item,
|
|
||||||
}: {
|
|
||||||
item: AppBskyFeedDefs.GeneratorView
|
|
||||||
}) {
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {data: preferences} = usePreferencesQuery()
|
|
||||||
const {
|
|
||||||
mutateAsync: pinFeed,
|
|
||||||
variables: pinnedFeed,
|
|
||||||
reset: resetPinFeed,
|
|
||||||
} = usePinFeedMutation()
|
|
||||||
const {
|
|
||||||
mutateAsync: removeFeed,
|
|
||||||
variables: removedFeed,
|
|
||||||
reset: resetRemoveFeed,
|
|
||||||
} = useRemoveFeedMutation()
|
|
||||||
const {track} = useAnalytics()
|
|
||||||
|
|
||||||
if (!item || !preferences) return null
|
|
||||||
|
|
||||||
const isPinned =
|
|
||||||
!removedFeed?.uri &&
|
|
||||||
(pinnedFeed?.uri || preferences.feeds.saved.includes(item.uri))
|
|
||||||
|
|
||||||
const onToggle = async () => {
|
|
||||||
if (isPinned) {
|
|
||||||
try {
|
|
||||||
await removeFeed({uri: item.uri})
|
|
||||||
resetRemoveFeed()
|
|
||||||
} catch (e) {
|
|
||||||
Toast.show(_(msg`There was an issue contacting your server`))
|
|
||||||
logger.error('Failed to unsave feed', {message: e})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await pinFeed({uri: item.uri})
|
|
||||||
resetPinFeed()
|
|
||||||
track('Onboarding:CustomFeedAdded')
|
|
||||||
} catch (e) {
|
|
||||||
Toast.show(_(msg`There was an issue contacting your server`))
|
|
||||||
logger.error('Failed to pin feed', {message: e})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View testID={`feed-${item.displayName}`}>
|
|
||||||
<View
|
|
||||||
style={[
|
|
||||||
pal.border,
|
|
||||||
{
|
|
||||||
flex: isMobile ? 1 : undefined,
|
|
||||||
flexDirection: 'row',
|
|
||||||
gap: 18,
|
|
||||||
maxWidth: isMobile ? undefined : 670,
|
|
||||||
borderRightWidth: isMobile ? undefined : 1,
|
|
||||||
paddingHorizontal: 24,
|
|
||||||
paddingVertical: isMobile ? 12 : 24,
|
|
||||||
borderTopWidth: 1,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<View style={{marginTop: 2}}>
|
|
||||||
<UserAvatar type="algo" size={42} avatar={item.avatar} />
|
|
||||||
</View>
|
|
||||||
<View style={{flex: isMobile ? 1 : undefined}}>
|
|
||||||
<Text
|
|
||||||
type="2xl-bold"
|
|
||||||
numberOfLines={1}
|
|
||||||
style={[pal.text, {fontSize: 19}]}>
|
|
||||||
{item.displayName}
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
<Text style={[pal.textLight, {marginBottom: 8}]} numberOfLines={1}>
|
|
||||||
<Trans>by {sanitizeHandle(item.creator.handle, '@')}</Trans>
|
|
||||||
</Text>
|
|
||||||
|
|
||||||
{item.description ? (
|
|
||||||
<RichText
|
|
||||||
type="xl"
|
|
||||||
style={[
|
|
||||||
pal.text,
|
|
||||||
{
|
|
||||||
flex: isMobile ? 1 : undefined,
|
|
||||||
maxWidth: 550,
|
|
||||||
marginBottom: 18,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
richText={new BskRichText({text: item.description || ''})}
|
|
||||||
numberOfLines={6}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<View style={{flexDirection: 'row', alignItems: 'center', gap: 12}}>
|
|
||||||
<Button
|
|
||||||
type="inverted"
|
|
||||||
style={{paddingVertical: 6}}
|
|
||||||
onPress={onToggle}>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingRight: 2,
|
|
||||||
gap: 6,
|
|
||||||
}}>
|
|
||||||
{isPinned ? (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="check"
|
|
||||||
size={16}
|
|
||||||
color={pal.colors.textInverted}
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={pal.textInverted}>
|
|
||||||
<Trans>Added</Trans>
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="plus"
|
|
||||||
size={16}
|
|
||||||
color={pal.colors.textInverted}
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={pal.textInverted}>
|
|
||||||
<Trans>Add</Trans>
|
|
||||||
</Text>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<View style={{flexDirection: 'row', gap: 4}}>
|
|
||||||
<HeartIcon
|
|
||||||
size={16}
|
|
||||||
strokeWidth={2.5}
|
|
||||||
style={[pal.textLight, {position: 'relative', top: 2}]}
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={[pal.text, pal.textLight]}>
|
|
||||||
{item.likeCount || 0}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,272 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
|
|
||||||
import {AppBskyActorDefs, moderateProfile} from '@atproto/api'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {msg, Trans} from '@lingui/macro'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
import {useModerationOpts} from '#/state/queries/preferences'
|
|
||||||
import {useSuggestedFollowsQuery} from '#/state/queries/suggested-follows'
|
|
||||||
import {useGetSuggestedFollowersByActor} from '#/state/queries/suggested-follows'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
import {Mobile, TabletOrDesktop} from 'view/com/util/layouts/Breakpoints'
|
|
||||||
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
|
||||||
import {RecommendedFollowsItem} from './RecommendedFollowsItem'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
next: () => void
|
|
||||||
}
|
|
||||||
export function RecommendedFollows({next}: Props) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const {isTabletOrMobile} = useWebMediaQueries()
|
|
||||||
const {data: suggestedFollows} = useSuggestedFollowsQuery()
|
|
||||||
const getSuggestedFollowsByActor = useGetSuggestedFollowersByActor()
|
|
||||||
const [additionalSuggestions, setAdditionalSuggestions] = React.useState<{
|
|
||||||
[did: string]: AppBskyActorDefs.ProfileView[]
|
|
||||||
}>({})
|
|
||||||
const existingDids = React.useRef<string[]>([])
|
|
||||||
const moderationOpts = useModerationOpts()
|
|
||||||
|
|
||||||
const title = (
|
|
||||||
<>
|
|
||||||
<Trans>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.textLight,
|
|
||||||
tdStyles.title1,
|
|
||||||
isTabletOrMobile && tdStyles.title1Small,
|
|
||||||
]}>
|
|
||||||
Follow some
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.link,
|
|
||||||
tdStyles.title2,
|
|
||||||
isTabletOrMobile && tdStyles.title2Small,
|
|
||||||
]}>
|
|
||||||
Recommended
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.link,
|
|
||||||
tdStyles.title2,
|
|
||||||
isTabletOrMobile && tdStyles.title2Small,
|
|
||||||
]}>
|
|
||||||
Users
|
|
||||||
</Text>
|
|
||||||
</Trans>
|
|
||||||
<Text type="2xl-medium" style={[pal.textLight, tdStyles.description]}>
|
|
||||||
<Trans>
|
|
||||||
Follow some users to get started. We can recommend you more users
|
|
||||||
based on who you find interesting.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'flex-end',
|
|
||||||
marginTop: 20,
|
|
||||||
}}>
|
|
||||||
<Button onPress={next} testID="continueBtn">
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 2,
|
|
||||||
gap: 6,
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
type="2xl-medium"
|
|
||||||
style={{color: '#fff', position: 'relative', top: -1}}>
|
|
||||||
<Trans context="action">Done</Trans>
|
|
||||||
</Text>
|
|
||||||
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
|
|
||||||
</View>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
|
|
||||||
const suggestions = React.useMemo(() => {
|
|
||||||
if (!suggestedFollows) return []
|
|
||||||
|
|
||||||
const additional = Object.entries(additionalSuggestions)
|
|
||||||
const items = suggestedFollows.pages.flatMap(page => page.actors)
|
|
||||||
|
|
||||||
outer: while (additional.length) {
|
|
||||||
const additionalAccount = additional.shift()
|
|
||||||
|
|
||||||
if (!additionalAccount) break
|
|
||||||
|
|
||||||
const [followedUser, relatedAccounts] = additionalAccount
|
|
||||||
|
|
||||||
for (let i = 0; i < items.length; i++) {
|
|
||||||
if (items[i].did === followedUser) {
|
|
||||||
items.splice(i + 1, 0, ...relatedAccounts)
|
|
||||||
continue outer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
existingDids.current = items.map(i => i.did)
|
|
||||||
|
|
||||||
return items
|
|
||||||
}, [suggestedFollows, additionalSuggestions])
|
|
||||||
|
|
||||||
const onFollowStateChange = React.useCallback(
|
|
||||||
async ({following, did}: {following: boolean; did: string}) => {
|
|
||||||
if (following) {
|
|
||||||
try {
|
|
||||||
const {suggestions: results} = await getSuggestedFollowsByActor(did)
|
|
||||||
|
|
||||||
if (results.length) {
|
|
||||||
const deduped = results.filter(
|
|
||||||
r => !existingDids.current.find(did => did === r.did),
|
|
||||||
)
|
|
||||||
setAdditionalSuggestions(s => ({
|
|
||||||
...s,
|
|
||||||
[did]: deduped.slice(0, 3),
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
logger.error('RecommendedFollows: failed to get suggestions', {
|
|
||||||
message: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// not handling the unfollow case
|
|
||||||
},
|
|
||||||
[existingDids, getSuggestedFollowsByActor, setAdditionalSuggestions],
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<TabletOrDesktop>
|
|
||||||
<TitleColumnLayout
|
|
||||||
testID="recommendedFollowsOnboarding"
|
|
||||||
title={title}
|
|
||||||
horizontal
|
|
||||||
titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}}
|
|
||||||
contentStyle={{paddingHorizontal: 0}}>
|
|
||||||
{!suggestedFollows || !moderationOpts ? (
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
) : (
|
|
||||||
<FlatList
|
|
||||||
data={suggestions}
|
|
||||||
renderItem={({item}) => (
|
|
||||||
<RecommendedFollowsItem
|
|
||||||
profile={item}
|
|
||||||
onFollowStateChange={onFollowStateChange}
|
|
||||||
moderation={moderateProfile(item, moderationOpts)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
keyExtractor={item => item.did}
|
|
||||||
style={{flex: 1}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</TitleColumnLayout>
|
|
||||||
</TabletOrDesktop>
|
|
||||||
|
|
||||||
<Mobile>
|
|
||||||
<View style={[mStyles.container]} testID="recommendedFollowsOnboarding">
|
|
||||||
<View>
|
|
||||||
<ViewHeader
|
|
||||||
title={_(msg`Recommended Users`)}
|
|
||||||
showBackButton={false}
|
|
||||||
showOnDesktop
|
|
||||||
/>
|
|
||||||
<Text type="lg-medium" style={[pal.text, mStyles.header]}>
|
|
||||||
<Trans>
|
|
||||||
Check out some recommended users. Follow them to see similar
|
|
||||||
users.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
{!suggestedFollows || !moderationOpts ? (
|
|
||||||
<ActivityIndicator size="large" />
|
|
||||||
) : (
|
|
||||||
<FlatList
|
|
||||||
data={suggestions}
|
|
||||||
renderItem={({item}) => (
|
|
||||||
<RecommendedFollowsItem
|
|
||||||
profile={item}
|
|
||||||
onFollowStateChange={onFollowStateChange}
|
|
||||||
moderation={moderateProfile(item, moderationOpts)}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
keyExtractor={item => item.did}
|
|
||||||
style={{flex: 1}}
|
|
||||||
showsVerticalScrollIndicator={false}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<Button
|
|
||||||
onPress={next}
|
|
||||||
label={_(msg`Continue`)}
|
|
||||||
testID="continueBtn"
|
|
||||||
style={mStyles.button}
|
|
||||||
labelStyle={mStyles.buttonText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</Mobile>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const tdStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
title1: {
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
title1Small: {
|
|
||||||
fontSize: 24,
|
|
||||||
},
|
|
||||||
title2: {
|
|
||||||
fontSize: 58,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
title2Small: {
|
|
||||||
fontSize: 36,
|
|
||||||
},
|
|
||||||
description: {
|
|
||||||
maxWidth: 400,
|
|
||||||
marginTop: 10,
|
|
||||||
marginLeft: 'auto',
|
|
||||||
textAlign: 'right',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
const mStyles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
header: {
|
|
||||||
marginBottom: 16,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
},
|
|
||||||
button: {
|
|
||||||
marginBottom: 16,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
marginTop: 16,
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
paddingVertical: 4,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,202 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {View, StyleSheet, ActivityIndicator} from 'react-native'
|
|
||||||
import {ModerationDecision, AppBskyActorDefs} from '@atproto/api'
|
|
||||||
import {Button} from '#/view/com/util/forms/Button'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {sanitizeDisplayName} from 'lib/strings/display-names'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {UserAvatar} from 'view/com/util/UserAvatar'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import Animated, {FadeInRight} from 'react-native-reanimated'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
|
||||||
import {useAnalytics} from 'lib/analytics/analytics'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
import {Shadow, useProfileShadow} from '#/state/cache/profile-shadow'
|
|
||||||
import {useProfileFollowMutationQueue} from '#/state/queries/profile'
|
|
||||||
import {logger} from '#/logger'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
profile: AppBskyActorDefs.ProfileViewBasic
|
|
||||||
moderation: ModerationDecision
|
|
||||||
onFollowStateChange: (props: {
|
|
||||||
did: string
|
|
||||||
following: boolean
|
|
||||||
}) => Promise<void>
|
|
||||||
}
|
|
||||||
|
|
||||||
export function RecommendedFollowsItem({
|
|
||||||
profile,
|
|
||||||
moderation,
|
|
||||||
onFollowStateChange,
|
|
||||||
}: React.PropsWithChildren<Props>) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {isMobile} = useWebMediaQueries()
|
|
||||||
const shadowedProfile = useProfileShadow(profile)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Animated.View
|
|
||||||
entering={FadeInRight}
|
|
||||||
style={[
|
|
||||||
styles.cardContainer,
|
|
||||||
pal.view,
|
|
||||||
pal.border,
|
|
||||||
{
|
|
||||||
maxWidth: isMobile ? undefined : 670,
|
|
||||||
borderRightWidth: isMobile ? undefined : 1,
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
<ProfileCard
|
|
||||||
key={profile.did}
|
|
||||||
profile={shadowedProfile}
|
|
||||||
onFollowStateChange={onFollowStateChange}
|
|
||||||
moderation={moderation}
|
|
||||||
/>
|
|
||||||
</Animated.View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function ProfileCard({
|
|
||||||
profile,
|
|
||||||
onFollowStateChange,
|
|
||||||
moderation,
|
|
||||||
}: {
|
|
||||||
profile: Shadow<AppBskyActorDefs.ProfileViewBasic>
|
|
||||||
moderation: ModerationDecision
|
|
||||||
onFollowStateChange: (props: {
|
|
||||||
did: string
|
|
||||||
following: boolean
|
|
||||||
}) => Promise<void>
|
|
||||||
}) {
|
|
||||||
const {track} = useAnalytics()
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
const [addingMoreSuggestions, setAddingMoreSuggestions] =
|
|
||||||
React.useState(false)
|
|
||||||
const [queueFollow, queueUnfollow] = useProfileFollowMutationQueue(
|
|
||||||
profile,
|
|
||||||
'RecommendedFollowsItem',
|
|
||||||
)
|
|
||||||
|
|
||||||
const onToggleFollow = React.useCallback(async () => {
|
|
||||||
try {
|
|
||||||
if (profile.viewer?.following) {
|
|
||||||
await queueUnfollow()
|
|
||||||
} else {
|
|
||||||
setAddingMoreSuggestions(true)
|
|
||||||
await queueFollow()
|
|
||||||
await onFollowStateChange({did: profile.did, following: true})
|
|
||||||
setAddingMoreSuggestions(false)
|
|
||||||
track('Onboarding:SuggestedFollowFollowed')
|
|
||||||
}
|
|
||||||
} catch (e: any) {
|
|
||||||
if (e?.name !== 'AbortError') {
|
|
||||||
logger.error('RecommendedFollows: failed to toggle following', {
|
|
||||||
message: e,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
setAddingMoreSuggestions(false)
|
|
||||||
}
|
|
||||||
}, [
|
|
||||||
profile,
|
|
||||||
queueFollow,
|
|
||||||
queueUnfollow,
|
|
||||||
setAddingMoreSuggestions,
|
|
||||||
track,
|
|
||||||
onFollowStateChange,
|
|
||||||
])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={styles.card}>
|
|
||||||
<View style={styles.layout}>
|
|
||||||
<View style={styles.layoutAvi}>
|
|
||||||
<UserAvatar
|
|
||||||
size={40}
|
|
||||||
avatar={profile.avatar}
|
|
||||||
moderation={moderation.ui('avatar')}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.layoutContent}>
|
|
||||||
<Text
|
|
||||||
type="2xl-bold"
|
|
||||||
style={[s.bold, pal.text]}
|
|
||||||
numberOfLines={1}
|
|
||||||
lineHeight={1.2}>
|
|
||||||
{sanitizeDisplayName(
|
|
||||||
profile.displayName || sanitizeHandle(profile.handle),
|
|
||||||
moderation.ui('displayName'),
|
|
||||||
)}
|
|
||||||
</Text>
|
|
||||||
<Text type="xl" style={[pal.textLight]} numberOfLines={1}>
|
|
||||||
{sanitizeHandle(profile.handle, '@')}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type={profile.viewer?.following ? 'default' : 'inverted'}
|
|
||||||
labelStyle={styles.followButton}
|
|
||||||
onPress={onToggleFollow}
|
|
||||||
label={profile.viewer?.following ? _(msg`Unfollow`) : _(msg`Follow`)}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
{profile.description ? (
|
|
||||||
<View style={styles.details}>
|
|
||||||
<Text type="lg" style={pal.text} numberOfLines={4}>
|
|
||||||
{profile.description as string}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : undefined}
|
|
||||||
{addingMoreSuggestions ? (
|
|
||||||
<View style={styles.addingMoreContainer}>
|
|
||||||
<ActivityIndicator size="small" color={pal.colors.text} />
|
|
||||||
<Text style={[pal.text]}>
|
|
||||||
<Trans>Finding similar accounts...</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
cardContainer: {
|
|
||||||
borderTopWidth: 1,
|
|
||||||
},
|
|
||||||
card: {
|
|
||||||
paddingHorizontal: 10,
|
|
||||||
},
|
|
||||||
layout: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
layoutAvi: {
|
|
||||||
width: 54,
|
|
||||||
paddingLeft: 4,
|
|
||||||
paddingTop: 8,
|
|
||||||
paddingBottom: 10,
|
|
||||||
},
|
|
||||||
layoutContent: {
|
|
||||||
flex: 1,
|
|
||||||
paddingRight: 10,
|
|
||||||
paddingTop: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
},
|
|
||||||
details: {
|
|
||||||
paddingLeft: 54,
|
|
||||||
paddingRight: 10,
|
|
||||||
paddingBottom: 10,
|
|
||||||
},
|
|
||||||
addingMoreContainer: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 54,
|
|
||||||
paddingTop: 4,
|
|
||||||
paddingBottom: 12,
|
|
||||||
gap: 4,
|
|
||||||
},
|
|
||||||
followButton: {
|
|
||||||
fontSize: 16,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,10 +0,0 @@
|
||||||
import 'react'
|
|
||||||
import {withBreakpoints} from 'view/com/util/layouts/withBreakpoints'
|
|
||||||
import {WelcomeDesktop} from './WelcomeDesktop'
|
|
||||||
import {WelcomeMobile} from './WelcomeMobile'
|
|
||||||
|
|
||||||
export const Welcome = withBreakpoints(
|
|
||||||
WelcomeMobile,
|
|
||||||
WelcomeDesktop,
|
|
||||||
WelcomeDesktop,
|
|
||||||
)
|
|
|
@ -1,126 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {StyleSheet, View} from 'react-native'
|
|
||||||
import {useMediaQuery} from 'react-responsive'
|
|
||||||
import {Text} from 'view/com/util/text/Text'
|
|
||||||
import {s} from 'lib/styles'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
import {Trans} from '@lingui/macro'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
next: () => void
|
|
||||||
skip: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WelcomeDesktop({next}: Props) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const horizontal = useMediaQuery({minWidth: 1300})
|
|
||||||
const title = (
|
|
||||||
<Trans>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.textLight,
|
|
||||||
{
|
|
||||||
fontSize: 36,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: horizontal ? 'right' : 'left',
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
Welcome to
|
|
||||||
</Text>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
pal.link,
|
|
||||||
{
|
|
||||||
fontSize: 72,
|
|
||||||
fontWeight: '800',
|
|
||||||
textAlign: horizontal ? 'right' : 'left',
|
|
||||||
},
|
|
||||||
]}>
|
|
||||||
Bluesky
|
|
||||||
</Text>
|
|
||||||
</Trans>
|
|
||||||
)
|
|
||||||
return (
|
|
||||||
<TitleColumnLayout
|
|
||||||
testID="welcomeOnboarding"
|
|
||||||
title={title}
|
|
||||||
horizontal={horizontal}
|
|
||||||
titleStyle={horizontal ? {paddingBottom: 160} : undefined}>
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="xl-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is public.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="xl" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>
|
|
||||||
Your posts, likes, and blocks are public. Mutes are private.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'at'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="xl-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is open.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="xl" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>Never lose access to your followers and data.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'gear'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="xl-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is flexible.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="xl" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>
|
|
||||||
Choose the algorithms that power your experience with custom
|
|
||||||
feeds.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={styles.spacer} />
|
|
||||||
<View style={{flexDirection: 'row'}}>
|
|
||||||
<Button onPress={next} testID="continueBtn">
|
|
||||||
<View
|
|
||||||
style={{
|
|
||||||
flexDirection: 'row',
|
|
||||||
alignItems: 'center',
|
|
||||||
paddingLeft: 2,
|
|
||||||
gap: 6,
|
|
||||||
}}>
|
|
||||||
<Text
|
|
||||||
type="2xl-medium"
|
|
||||||
style={{color: '#fff', position: 'relative', top: -1}}>
|
|
||||||
<Trans context="action">Next</Trans>
|
|
||||||
</Text>
|
|
||||||
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
|
|
||||||
</View>
|
|
||||||
</Button>
|
|
||||||
</View>
|
|
||||||
</TitleColumnLayout>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
row: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
columnGap: 20,
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: 20,
|
|
||||||
},
|
|
||||||
rowText: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
spacer: {
|
|
||||||
height: 20,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,136 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
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'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
import {ViewHeader} from 'view/com/util/ViewHeader'
|
|
||||||
import {useLingui} from '@lingui/react'
|
|
||||||
import {Trans, msg} from '@lingui/macro'
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
next: () => void
|
|
||||||
skip: () => void
|
|
||||||
}
|
|
||||||
|
|
||||||
export function WelcomeMobile({next, skip}: Props) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const {_} = useLingui()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[styles.container]} testID="welcomeOnboarding">
|
|
||||||
<ViewHeader
|
|
||||||
showOnDesktop
|
|
||||||
showBorder={false}
|
|
||||||
showBackButton={false}
|
|
||||||
title=""
|
|
||||||
renderButton={() => {
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
accessibilityRole="button"
|
|
||||||
style={[s.flexRow, s.alignCenter]}
|
|
||||||
onPress={skip}>
|
|
||||||
<Text style={[pal.link]}>
|
|
||||||
<Trans>Skip</Trans>
|
|
||||||
</Text>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={'chevron-right'}
|
|
||||||
size={14}
|
|
||||||
color={pal.colors.link}
|
|
||||||
/>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<View>
|
|
||||||
<Text style={[pal.text, styles.title]}>
|
|
||||||
<Trans>
|
|
||||||
Welcome to{' '}
|
|
||||||
<Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text>
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
<View style={styles.spacer} />
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'globe'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="lg-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is public.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>
|
|
||||||
Your posts, likes, and blocks are public. Mutes are private.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'at'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="lg-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is open.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>Never lose access to your followers and data.</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
<View style={[styles.row]}>
|
|
||||||
<FontAwesomeIcon icon={'gear'} size={36} color={pal.colors.link} />
|
|
||||||
<View style={[styles.rowText]}>
|
|
||||||
<Text type="lg-bold" style={[pal.text]}>
|
|
||||||
<Trans>Bluesky is flexible.</Trans>
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
<Trans>
|
|
||||||
Choose the algorithms that power your experience with custom
|
|
||||||
feeds.
|
|
||||||
</Trans>
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={next}
|
|
||||||
label={_(msg`Continue`)}
|
|
||||||
testID="continueBtn"
|
|
||||||
style={[styles.buttonContainer]}
|
|
||||||
labelStyle={styles.buttonText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
marginBottom: 60,
|
|
||||||
marginHorizontal: 16,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 42,
|
|
||||||
fontWeight: '800',
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
columnGap: 20,
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: 20,
|
|
||||||
},
|
|
||||||
rowText: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
spacer: {
|
|
||||||
height: 20,
|
|
||||||
},
|
|
||||||
buttonContainer: {
|
|
||||||
alignItems: 'center',
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
marginVertical: 4,
|
|
||||||
},
|
|
||||||
})
|
|
|
@ -1,11 +1,8 @@
|
||||||
import * as React from 'react'
|
import * as React from 'react'
|
||||||
import {View} from 'react-native'
|
import {View} from 'react-native'
|
||||||
import {PWI_ENABLED, NEW_ONBOARDING_ENABLED} from '#/lib/build-flags'
|
|
||||||
|
|
||||||
// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts
|
// Based on @react-navigation/native-stack/src/createNativeStackNavigator.ts
|
||||||
// MIT License
|
// MIT License
|
||||||
// Copyright (c) 2017 React Navigation Contributors
|
// Copyright (c) 2017 React Navigation Contributors
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createNavigatorFactory,
|
createNavigatorFactory,
|
||||||
EventArg,
|
EventArg,
|
||||||
|
@ -21,24 +18,24 @@ import type {
|
||||||
NativeStackNavigationEventMap,
|
NativeStackNavigationEventMap,
|
||||||
NativeStackNavigationOptions,
|
NativeStackNavigationOptions,
|
||||||
} from '@react-navigation/native-stack'
|
} from '@react-navigation/native-stack'
|
||||||
import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
|
|
||||||
import {NativeStackView} from '@react-navigation/native-stack'
|
import {NativeStackView} from '@react-navigation/native-stack'
|
||||||
|
import type {NativeStackNavigatorProps} from '@react-navigation/native-stack/src/types'
|
||||||
|
|
||||||
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
import {PWI_ENABLED} from '#/lib/build-flags'
|
||||||
import {DesktopLeftNav} from './desktop/LeftNav'
|
|
||||||
import {DesktopRightNav} from './desktop/RightNav'
|
|
||||||
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
import {useOnboardingState} from '#/state/shell'
|
import {useOnboardingState} from '#/state/shell'
|
||||||
import {
|
import {
|
||||||
useLoggedOutView,
|
useLoggedOutView,
|
||||||
useLoggedOutViewControls,
|
useLoggedOutViewControls,
|
||||||
} from '#/state/shell/logged-out'
|
} from '#/state/shell/logged-out'
|
||||||
import {useSession} from '#/state/session'
|
|
||||||
import {isWeb} from 'platform/detection'
|
import {isWeb} from 'platform/detection'
|
||||||
import {Deactivated} from '#/screens/Deactivated'
|
import {Deactivated} from '#/screens/Deactivated'
|
||||||
|
import {Onboarding} from '#/screens/Onboarding'
|
||||||
import {LoggedOut} from '../com/auth/LoggedOut'
|
import {LoggedOut} from '../com/auth/LoggedOut'
|
||||||
import {Onboarding} from '../com/auth/Onboarding'
|
import {BottomBarWeb} from './bottom-bar/BottomBarWeb'
|
||||||
import {Onboarding as NewOnboarding} from '#/screens/Onboarding'
|
import {DesktopLeftNav} from './desktop/LeftNav'
|
||||||
|
import {DesktopRightNav} from './desktop/RightNav'
|
||||||
|
|
||||||
type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
|
type NativeStackNavigationOptionsWithAuth = NativeStackNavigationOptions & {
|
||||||
requireAuth?: boolean
|
requireAuth?: boolean
|
||||||
|
@ -112,12 +109,8 @@ function NativeStackNavigator({
|
||||||
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
|
return <LoggedOut onDismiss={() => setShowLoggedOut(false)} />
|
||||||
}
|
}
|
||||||
if (onboardingState.isActive) {
|
if (onboardingState.isActive) {
|
||||||
if (NEW_ONBOARDING_ENABLED) {
|
|
||||||
return <NewOnboarding />
|
|
||||||
} else {
|
|
||||||
return <Onboarding />
|
return <Onboarding />
|
||||||
}
|
}
|
||||||
}
|
|
||||||
const newDescriptors: typeof descriptors = {}
|
const newDescriptors: typeof descriptors = {}
|
||||||
for (let key in descriptors) {
|
for (let key in descriptors) {
|
||||||
const descriptor = descriptors[key]
|
const descriptor = descriptors[key]
|
||||||
|
|
Loading…
Reference in New Issue