Merge branch 'ansh/app-812-add-custom-feed-discovery-to-onboarding' into main
This commit is contained in:
commit
f9cab178b9
29 changed files with 1033 additions and 217 deletions
|
@ -260,6 +260,7 @@ function TabsNavigator() {
|
||||||
|
|
||||||
function HomeTabNavigator() {
|
function HomeTabNavigator() {
|
||||||
const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
|
const contentStyle = useColorSchemeStyle(styles.bgLight, styles.bgDark)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HomeTab.Navigator
|
<HomeTab.Navigator
|
||||||
screenOptions={{
|
screenOptions={{
|
||||||
|
|
|
@ -122,6 +122,8 @@ interface TrackPropertiesMap {
|
||||||
// ONBOARDING events
|
// ONBOARDING events
|
||||||
'Onboarding:Begin': {}
|
'Onboarding:Begin': {}
|
||||||
'Onboarding:Complete': {}
|
'Onboarding:Complete': {}
|
||||||
|
'Onboarding:Skipped': {}
|
||||||
|
'Onboarding:Reset': {}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ScreenPropertiesMap {
|
interface ScreenPropertiesMap {
|
||||||
|
|
|
@ -148,3 +148,110 @@ export const HITSLOP_10 = createHitslop(10)
|
||||||
export const HITSLOP_20 = createHitslop(20)
|
export const HITSLOP_20 = createHitslop(20)
|
||||||
export const HITSLOP_30 = createHitslop(30)
|
export const HITSLOP_30 = createHitslop(30)
|
||||||
export const BACK_HITSLOP = HITSLOP_30
|
export const BACK_HITSLOP = HITSLOP_30
|
||||||
|
|
||||||
|
export const 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',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
import {useMediaQuery} from 'react-responsive'
|
import {useMediaQuery} from 'react-responsive'
|
||||||
|
import {isNative} from 'platform/detection'
|
||||||
|
|
||||||
export function useWebMediaQueries() {
|
export function useWebMediaQueries() {
|
||||||
const isDesktop = useMediaQuery({
|
const isDesktop = useMediaQuery({
|
||||||
query: '(min-width: 1230px)',
|
query: '(min-width: 1224px)',
|
||||||
})
|
})
|
||||||
return {isDesktop}
|
const isTabletOrMobile = useMediaQuery({query: '(max-width: 1224px)'})
|
||||||
|
const isMobile = useMediaQuery({query: '(max-width: 800px)'})
|
||||||
|
if (isNative) {
|
||||||
|
return {isMobile: true, isTabletOrMobile: true, isDesktop: false}
|
||||||
|
}
|
||||||
|
return {isMobile, isTabletOrMobile, isDesktop}
|
||||||
}
|
}
|
||||||
|
|
94
src/state/models/discovery/onboarding.ts
Normal file
94
src/state/models/discovery/onboarding.ts
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
import {makeAutoObservable} from 'mobx'
|
||||||
|
import {RootStoreModel} from '../root-store'
|
||||||
|
import {hasProp} from 'lib/type-guards'
|
||||||
|
import {track} from 'lib/analytics/analytics'
|
||||||
|
|
||||||
|
export const OnboardingScreenSteps = {
|
||||||
|
Welcome: 'Welcome',
|
||||||
|
RecommendedFeeds: 'RecommendedFeeds',
|
||||||
|
Home: 'Home',
|
||||||
|
} as const
|
||||||
|
|
||||||
|
type OnboardingStep =
|
||||||
|
(typeof OnboardingScreenSteps)[keyof typeof OnboardingScreenSteps]
|
||||||
|
const OnboardingStepsArray = Object.values(OnboardingScreenSteps)
|
||||||
|
export class OnboardingModel {
|
||||||
|
// state
|
||||||
|
step: OnboardingStep = 'Home' // default state to skip onboarding, only enabled for new users by calling start()
|
||||||
|
|
||||||
|
constructor(public rootStore: RootStoreModel) {
|
||||||
|
makeAutoObservable(this, {
|
||||||
|
rootStore: false,
|
||||||
|
hydrate: false,
|
||||||
|
serialize: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
serialize(): unknown {
|
||||||
|
return {
|
||||||
|
step: this.step,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hydrate(v: unknown) {
|
||||||
|
if (typeof v === 'object' && v !== null) {
|
||||||
|
if (
|
||||||
|
hasProp(v, 'step') &&
|
||||||
|
typeof v.step === 'string' &&
|
||||||
|
OnboardingStepsArray.includes(v.step as OnboardingStep)
|
||||||
|
) {
|
||||||
|
this.step = v.step as OnboardingStep
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if there is no valid state, we'll just reset
|
||||||
|
this.reset()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the name of the next screen in the onboarding process based on the current step or screen name provided.
|
||||||
|
* @param {OnboardingStep} [currentScreenName]
|
||||||
|
* @returns name of next screen in the onboarding process
|
||||||
|
*/
|
||||||
|
next(currentScreenName?: OnboardingStep) {
|
||||||
|
currentScreenName = currentScreenName || this.step
|
||||||
|
if (currentScreenName === 'Welcome') {
|
||||||
|
this.step = 'RecommendedFeeds'
|
||||||
|
return this.step
|
||||||
|
} else if (this.step === 'RecommendedFeeds') {
|
||||||
|
this.finish()
|
||||||
|
return this.step
|
||||||
|
} else {
|
||||||
|
// if we get here, we're in an invalid state, let's just go Home
|
||||||
|
return 'Home'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
start() {
|
||||||
|
this.step = 'Welcome'
|
||||||
|
track('Onboarding:Begin')
|
||||||
|
}
|
||||||
|
|
||||||
|
finish() {
|
||||||
|
this.step = 'Home'
|
||||||
|
track('Onboarding:Complete')
|
||||||
|
}
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.step = 'Welcome'
|
||||||
|
track('Onboarding:Reset')
|
||||||
|
}
|
||||||
|
|
||||||
|
skip() {
|
||||||
|
this.step = 'Home'
|
||||||
|
track('Onboarding:Skipped')
|
||||||
|
}
|
||||||
|
|
||||||
|
get isComplete() {
|
||||||
|
return this.step === 'Home'
|
||||||
|
}
|
||||||
|
|
||||||
|
get isActive() {
|
||||||
|
return !this.isComplete
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,6 +67,19 @@ export class CustomFeedModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async pin() {
|
||||||
|
try {
|
||||||
|
await this.rootStore.preferences.addPinnedFeed(this.uri)
|
||||||
|
} catch (error) {
|
||||||
|
this.rootStore.log.error('Failed to pin feed', error)
|
||||||
|
} finally {
|
||||||
|
track('CustomFeed:Pin', {
|
||||||
|
name: this.data.displayName,
|
||||||
|
uri: this.uri,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async unsave() {
|
async unsave() {
|
||||||
try {
|
try {
|
||||||
await this.rootStore.preferences.removeSavedFeed(this.uri)
|
await this.rootStore.preferences.removeSavedFeed(this.uri)
|
||||||
|
|
|
@ -27,6 +27,7 @@ import {reset as resetNavigation} from '../../Navigation'
|
||||||
// remove after backend testing finishes
|
// remove after backend testing finishes
|
||||||
// -prf
|
// -prf
|
||||||
import {applyDebugHeader} from 'lib/api/debug-appview-proxy-header'
|
import {applyDebugHeader} from 'lib/api/debug-appview-proxy-header'
|
||||||
|
import {OnboardingModel} from './discovery/onboarding'
|
||||||
|
|
||||||
export const appInfo = z.object({
|
export const appInfo = z.object({
|
||||||
build: z.string(),
|
build: z.string(),
|
||||||
|
@ -44,6 +45,7 @@ export class RootStoreModel {
|
||||||
shell = new ShellUiModel(this)
|
shell = new ShellUiModel(this)
|
||||||
preferences = new PreferencesModel(this)
|
preferences = new PreferencesModel(this)
|
||||||
me = new MeModel(this)
|
me = new MeModel(this)
|
||||||
|
onboarding = new OnboardingModel(this)
|
||||||
invitedUsers = new InvitedUsers(this)
|
invitedUsers = new InvitedUsers(this)
|
||||||
handleResolutions = new HandleResolutionsCache()
|
handleResolutions = new HandleResolutionsCache()
|
||||||
profiles = new ProfilesCache(this)
|
profiles = new ProfilesCache(this)
|
||||||
|
@ -70,6 +72,7 @@ export class RootStoreModel {
|
||||||
appInfo: this.appInfo,
|
appInfo: this.appInfo,
|
||||||
session: this.session.serialize(),
|
session: this.session.serialize(),
|
||||||
me: this.me.serialize(),
|
me: this.me.serialize(),
|
||||||
|
onboarding: this.onboarding.serialize(),
|
||||||
shell: this.shell.serialize(),
|
shell: this.shell.serialize(),
|
||||||
preferences: this.preferences.serialize(),
|
preferences: this.preferences.serialize(),
|
||||||
invitedUsers: this.invitedUsers.serialize(),
|
invitedUsers: this.invitedUsers.serialize(),
|
||||||
|
@ -88,6 +91,9 @@ export class RootStoreModel {
|
||||||
if (hasProp(v, 'me')) {
|
if (hasProp(v, 'me')) {
|
||||||
this.me.hydrate(v.me)
|
this.me.hydrate(v.me)
|
||||||
}
|
}
|
||||||
|
if (hasProp(v, 'onboarding')) {
|
||||||
|
this.onboarding.hydrate(v.onboarding)
|
||||||
|
}
|
||||||
if (hasProp(v, 'session')) {
|
if (hasProp(v, 'session')) {
|
||||||
this.session.hydrate(v.session)
|
this.session.hydrate(v.session)
|
||||||
}
|
}
|
||||||
|
|
|
@ -109,10 +109,10 @@ export class CreateAccountModel {
|
||||||
this.setError('')
|
this.setError('')
|
||||||
this.setIsProcessing(true)
|
this.setIsProcessing(true)
|
||||||
|
|
||||||
// open the onboarding modal after the session is created
|
// open the onboarding screens after the session is created
|
||||||
const sessionReadySub = this.rootStore.onSessionReady(() => {
|
const sessionReadySub = this.rootStore.onSessionReady(() => {
|
||||||
sessionReadySub.remove()
|
sessionReadySub.remove()
|
||||||
this.rootStore.shell.openModal({name: 'onboarding'})
|
this.rootStore.onboarding.start()
|
||||||
})
|
})
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -136,10 +136,6 @@ export interface PostLanguagesSettingsModal {
|
||||||
name: 'post-languages-settings'
|
name: 'post-languages-settings'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OnboardingModal {
|
|
||||||
name: 'onboarding'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Modal =
|
export type Modal =
|
||||||
// Account
|
// Account
|
||||||
| AddAppPasswordModal
|
| AddAppPasswordModal
|
||||||
|
@ -171,9 +167,6 @@ export type Modal =
|
||||||
| WaitlistModal
|
| WaitlistModal
|
||||||
| InviteCodesModal
|
| InviteCodesModal
|
||||||
|
|
||||||
// Onboarding
|
|
||||||
| OnboardingModal
|
|
||||||
|
|
||||||
// Generic
|
// Generic
|
||||||
| ConfirmModal
|
| ConfirmModal
|
||||||
|
|
||||||
|
|
34
src/view/com/auth/Onboarding.tsx
Normal file
34
src/view/com/auth/Onboarding.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {SafeAreaView} from 'react-native'
|
||||||
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {ErrorBoundary} from 'view/com/util/ErrorBoundary'
|
||||||
|
import {s} from 'lib/styles'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {useStores} from 'state/index'
|
||||||
|
import {Welcome} from './onboarding/Welcome'
|
||||||
|
import {RecommendedFeeds} from './onboarding/RecommendedFeeds'
|
||||||
|
|
||||||
|
export const Onboarding = observer(() => {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const store = useStores()
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
store.shell.setMinimalShellMode(true)
|
||||||
|
}, [store])
|
||||||
|
|
||||||
|
const next = () => store.onboarding.next()
|
||||||
|
const skip = () => store.onboarding.skip()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SafeAreaView testID="onboardingView" style={[s.hContentRegion, pal.view]}>
|
||||||
|
<ErrorBoundary>
|
||||||
|
{store.onboarding.step === 'Welcome' && (
|
||||||
|
<Welcome skip={skip} next={next} />
|
||||||
|
)}
|
||||||
|
{store.onboarding.step === 'RecommendedFeeds' && (
|
||||||
|
<RecommendedFeeds next={next} />
|
||||||
|
)}
|
||||||
|
</ErrorBoundary>
|
||||||
|
</SafeAreaView>
|
||||||
|
)
|
||||||
|
})
|
|
@ -1,66 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {StyleSheet, View} from 'react-native'
|
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {Welcome} from './Welcome'
|
|
||||||
import {useStores} from 'state/index'
|
|
||||||
import {track} from 'lib/analytics/analytics'
|
|
||||||
|
|
||||||
enum OnboardingStep {
|
|
||||||
WELCOME = 'WELCOME',
|
|
||||||
// SELECT_INTERESTS = 'SELECT_INTERESTS',
|
|
||||||
COMPLETE = 'COMPLETE',
|
|
||||||
}
|
|
||||||
type OnboardingState = {
|
|
||||||
currentStep: OnboardingStep
|
|
||||||
}
|
|
||||||
type Action = {type: 'NEXT_STEP'}
|
|
||||||
const initialState: OnboardingState = {
|
|
||||||
currentStep: OnboardingStep.WELCOME,
|
|
||||||
}
|
|
||||||
const reducer = (state: OnboardingState, action: Action): OnboardingState => {
|
|
||||||
switch (action.type) {
|
|
||||||
case 'NEXT_STEP':
|
|
||||||
switch (state.currentStep) {
|
|
||||||
case OnboardingStep.WELCOME:
|
|
||||||
track('Onboarding:Begin')
|
|
||||||
return {...state, currentStep: OnboardingStep.COMPLETE}
|
|
||||||
case OnboardingStep.COMPLETE:
|
|
||||||
track('Onboarding:Complete')
|
|
||||||
return state
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Onboarding = () => {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const rootStore = useStores()
|
|
||||||
const [state, dispatch] = React.useReducer(reducer, initialState)
|
|
||||||
const next = React.useCallback(
|
|
||||||
() => dispatch({type: 'NEXT_STEP'}),
|
|
||||||
[dispatch],
|
|
||||||
)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (state.currentStep === OnboardingStep.COMPLETE) {
|
|
||||||
// navigate to home
|
|
||||||
rootStore.shell.closeModal()
|
|
||||||
}
|
|
||||||
}, [state.currentStep, rootStore.shell])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<View style={[pal.view, styles.container]}>
|
|
||||||
{state.currentStep === OnboardingStep.WELCOME && <Welcome next={next} />}
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
paddingHorizontal: 20,
|
|
||||||
},
|
|
||||||
})
|
|
176
src/view/com/auth/onboarding/RecommendedFeeds.tsx
Normal file
176
src/view/com/auth/onboarding/RecommendedFeeds.tsx
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {FlatList, StyleSheet, View} from 'react-native'
|
||||||
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints'
|
||||||
|
import {Text} from 'view/com/util/text/Text'
|
||||||
|
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||||
|
import {TitleColumnLayout} from 'view/com/util/layouts/TitleColumnLayout'
|
||||||
|
import {Button} from 'view/com/util/forms/Button'
|
||||||
|
import {RecommendedFeedsItem} from './RecommendedFeedsItem'
|
||||||
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {RECOMMENDED_FEEDS} from 'lib/constants'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
next: () => void
|
||||||
|
}
|
||||||
|
export const RecommendedFeeds = observer(({next}: Props) => {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
|
|
||||||
|
const title = (
|
||||||
|
<>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
pal.textLight,
|
||||||
|
tdStyles.title1,
|
||||||
|
isTabletOrMobile && tdStyles.title1Small,
|
||||||
|
]}>
|
||||||
|
Choose your
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
pal.link,
|
||||||
|
tdStyles.title2,
|
||||||
|
isTabletOrMobile && tdStyles.title2Small,
|
||||||
|
]}>
|
||||||
|
Recomended
|
||||||
|
</Text>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
pal.link,
|
||||||
|
tdStyles.title2,
|
||||||
|
isTabletOrMobile && tdStyles.title2Small,
|
||||||
|
]}>
|
||||||
|
Feeds
|
||||||
|
</Text>
|
||||||
|
<Text type="2xl-medium" style={[pal.textLight, tdStyles.description]}>
|
||||||
|
Feeds are created by users to curate content. Choose some feeds that you
|
||||||
|
find interesting.
|
||||||
|
</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}}>
|
||||||
|
Done
|
||||||
|
</Text>
|
||||||
|
<FontAwesomeIcon icon="angle-right" color="#fff" size={14} />
|
||||||
|
</View>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<TabletOrDesktop>
|
||||||
|
<TitleColumnLayout
|
||||||
|
testID="recommendedFeedsScreen"
|
||||||
|
title={title}
|
||||||
|
horizontal
|
||||||
|
titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}}
|
||||||
|
contentStyle={{paddingHorizontal: 0}}>
|
||||||
|
<FlatList
|
||||||
|
data={RECOMMENDED_FEEDS}
|
||||||
|
renderItem={({item}) => <RecommendedFeedsItem {...item} />}
|
||||||
|
keyExtractor={item => item.did + item.rkey}
|
||||||
|
style={{flex: 1}}
|
||||||
|
/>
|
||||||
|
</TitleColumnLayout>
|
||||||
|
</TabletOrDesktop>
|
||||||
|
<Mobile>
|
||||||
|
<View style={[mStyles.container]} testID="recommendedFeedsScreen">
|
||||||
|
<ViewHeader
|
||||||
|
title="Recommended Feeds"
|
||||||
|
showBackButton={false}
|
||||||
|
showOnDesktop
|
||||||
|
/>
|
||||||
|
<Text type="lg-medium" style={[pal.text, mStyles.header]}>
|
||||||
|
Check out some recommended feeds. Tap + to add them to your list of
|
||||||
|
pinned feeds.
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
<FlatList
|
||||||
|
data={RECOMMENDED_FEEDS}
|
||||||
|
renderItem={({item}) => <RecommendedFeedsItem {...item} />}
|
||||||
|
keyExtractor={item => item.did + item.rkey}
|
||||||
|
style={{flex: 1}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onPress={next}
|
||||||
|
label="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,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 18,
|
||||||
|
paddingVertical: 4,
|
||||||
|
},
|
||||||
|
})
|
142
src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
Normal file
142
src/view/com/auth/onboarding/RecommendedFeedsItem.tsx
Normal file
|
@ -0,0 +1,142 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {View} from 'react-native'
|
||||||
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
|
import {Text} from 'view/com/util/text/Text'
|
||||||
|
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 {useCustomFeed} from 'lib/hooks/useCustomFeed'
|
||||||
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
import {makeRecordUri} from 'lib/strings/url-helpers'
|
||||||
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
|
|
||||||
|
export const RecommendedFeedsItem = observer(
|
||||||
|
({did, rkey}: {did: string; rkey: string}) => {
|
||||||
|
const {isMobile} = useWebMediaQueries()
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const uri = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
|
||||||
|
const item = useCustomFeed(uri)
|
||||||
|
if (!item) return null
|
||||||
|
const onToggle = async () => {
|
||||||
|
if (item.isSaved) {
|
||||||
|
try {
|
||||||
|
await item.unsave()
|
||||||
|
} catch (e) {
|
||||||
|
Toast.show('There was an issue contacting your server')
|
||||||
|
console.error('Failed to unsave feed', {e})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await item.save()
|
||||||
|
await item.pin()
|
||||||
|
} catch (e) {
|
||||||
|
Toast.show('There was an issue contacting your server')
|
||||||
|
console.error('Failed to pin feed', {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.data.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}>
|
||||||
|
by {sanitizeHandle(item.data.creator.handle, '@')}
|
||||||
|
</Text>
|
||||||
|
|
||||||
|
{item.data.description ? (
|
||||||
|
<Text
|
||||||
|
type="xl"
|
||||||
|
style={[
|
||||||
|
pal.text,
|
||||||
|
{
|
||||||
|
flex: isMobile ? 1 : undefined,
|
||||||
|
maxWidth: 550,
|
||||||
|
marginBottom: 18,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
numberOfLines={6}>
|
||||||
|
{item.data.description}
|
||||||
|
</Text>
|
||||||
|
) : 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,
|
||||||
|
}}>
|
||||||
|
{item.isSaved ? (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="check"
|
||||||
|
size={16}
|
||||||
|
color={pal.colors.textInverted}
|
||||||
|
/>
|
||||||
|
<Text type="lg-medium" style={pal.textInverted}>
|
||||||
|
Added
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon="plus"
|
||||||
|
size={16}
|
||||||
|
color={pal.colors.textInverted}
|
||||||
|
/>
|
||||||
|
<Text type="lg-medium" style={pal.textInverted}>
|
||||||
|
Add
|
||||||
|
</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.data.likeCount || 0}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
|
@ -1,92 +1,10 @@
|
||||||
import React from 'react'
|
import 'react'
|
||||||
import {StyleSheet, View} from 'react-native'
|
import {withBreakpoints} from 'view/com/util/layouts/withBreakpoints'
|
||||||
import {Text} from 'view/com/util/text/Text'
|
import {WelcomeDesktop} from './WelcomeDesktop'
|
||||||
import {s} from 'lib/styles'
|
import {WelcomeMobile} from './WelcomeMobile'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {Button} from 'view/com/util/forms/Button'
|
|
||||||
|
|
||||||
export const Welcome = ({next}: {next: () => void}) => {
|
export const Welcome = withBreakpoints(
|
||||||
const pal = usePalette('default')
|
WelcomeMobile,
|
||||||
return (
|
WelcomeDesktop,
|
||||||
<View style={[styles.container]}>
|
WelcomeDesktop,
|
||||||
<View testID="welcomeScreen">
|
)
|
||||||
<Text style={[pal.text, styles.title]}>Welcome to </Text>
|
|
||||||
<Text style={[pal.text, pal.link, styles.title]}>Bluesky</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]}>
|
|
||||||
Bluesky is public.
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
Your posts, likes, and blocks are public. Mutes are private.
|
|
||||||
</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]}>
|
|
||||||
Bluesky is open.
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
Never lose access to your followers and data.
|
|
||||||
</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]}>
|
|
||||||
Bluesky is flexible.
|
|
||||||
</Text>
|
|
||||||
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
|
||||||
Choose the algorithms that power your experience with custom
|
|
||||||
feeds.
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
onPress={next}
|
|
||||||
label="Continue"
|
|
||||||
testID="continueBtn"
|
|
||||||
labelStyle={styles.buttonText}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
container: {
|
|
||||||
flex: 1,
|
|
||||||
marginVertical: 60,
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
},
|
|
||||||
title: {
|
|
||||||
fontSize: 48,
|
|
||||||
fontWeight: '800',
|
|
||||||
},
|
|
||||||
row: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
columnGap: 20,
|
|
||||||
alignItems: 'center',
|
|
||||||
marginVertical: 20,
|
|
||||||
},
|
|
||||||
rowText: {
|
|
||||||
flex: 1,
|
|
||||||
},
|
|
||||||
spacer: {
|
|
||||||
height: 20,
|
|
||||||
},
|
|
||||||
buttonText: {
|
|
||||||
textAlign: 'center',
|
|
||||||
fontSize: 18,
|
|
||||||
marginVertical: 4,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
123
src/view/com/auth/onboarding/WelcomeDesktop.tsx
Normal file
123
src/view/com/auth/onboarding/WelcomeDesktop.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
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 {observer} from 'mobx-react-lite'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
next: () => void
|
||||||
|
skip: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WelcomeDesktop = observer(({next}: Props) => {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const horizontal = useMediaQuery({
|
||||||
|
query: '(min-width: 1230px)',
|
||||||
|
})
|
||||||
|
const title = (
|
||||||
|
<>
|
||||||
|
<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>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
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]}>
|
||||||
|
Bluesky is public.
|
||||||
|
</Text>
|
||||||
|
<Text type="xl" style={[pal.text, s.pt2]}>
|
||||||
|
Your posts, likes, and blocks are public. Mutes are private.
|
||||||
|
</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]}>
|
||||||
|
Bluesky is open.
|
||||||
|
</Text>
|
||||||
|
<Text type="xl" style={[pal.text, s.pt2]}>
|
||||||
|
Never lose access to your followers and data.
|
||||||
|
</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]}>
|
||||||
|
Bluesky is flexible.
|
||||||
|
</Text>
|
||||||
|
<Text type="xl" style={[pal.text, s.pt2]}>
|
||||||
|
Choose the algorithms that power your experience with custom feeds.
|
||||||
|
</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}}>
|
||||||
|
Next
|
||||||
|
</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,
|
||||||
|
},
|
||||||
|
})
|
123
src/view/com/auth/onboarding/WelcomeMobile.tsx
Normal file
123
src/view/com/auth/onboarding/WelcomeMobile.tsx
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
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 {observer} from 'mobx-react-lite'
|
||||||
|
import {ViewHeader} from 'view/com/util/ViewHeader'
|
||||||
|
import {isDesktopWeb} from 'platform/detection'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
next: () => void
|
||||||
|
skip: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const WelcomeMobile = observer(({next, skip}: Props) => {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
|
||||||
|
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]}>Skip</Text>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={'chevron-right'}
|
||||||
|
size={14}
|
||||||
|
color={pal.colors.link}
|
||||||
|
/>
|
||||||
|
</Pressable>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<View>
|
||||||
|
<Text style={[pal.text, styles.title]}>
|
||||||
|
Welcome to{' '}
|
||||||
|
<Text style={[pal.text, pal.link, styles.title]}>Bluesky</Text>
|
||||||
|
</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]}>
|
||||||
|
Bluesky is public.
|
||||||
|
</Text>
|
||||||
|
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
||||||
|
Your posts, likes, and blocks are public. Mutes are private.
|
||||||
|
</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]}>
|
||||||
|
Bluesky is open.
|
||||||
|
</Text>
|
||||||
|
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
||||||
|
Never lose access to your followers and data.
|
||||||
|
</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]}>
|
||||||
|
Bluesky is flexible.
|
||||||
|
</Text>
|
||||||
|
<Text type="lg-thin" style={[pal.text, s.pt2]}>
|
||||||
|
Choose the algorithms that power your experience with custom
|
||||||
|
feeds.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onPress={next}
|
||||||
|
label="Continue"
|
||||||
|
testID="continueBtn"
|
||||||
|
labelStyle={styles.buttonText}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flex: 1,
|
||||||
|
marginBottom: isDesktopWeb ? 30 : 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,
|
||||||
|
},
|
||||||
|
buttonText: {
|
||||||
|
textAlign: 'center',
|
||||||
|
fontSize: 18,
|
||||||
|
marginVertical: 4,
|
||||||
|
},
|
||||||
|
})
|
|
@ -9,6 +9,7 @@ import {observer} from 'mobx-react-lite'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import {CenteredView} from '../util/Views'
|
import {CenteredView} from '../util/Views'
|
||||||
import {LoggedOut} from './LoggedOut'
|
import {LoggedOut} from './LoggedOut'
|
||||||
|
import {Onboarding} from './Onboarding'
|
||||||
import {Text} from '../util/text/Text'
|
import {Text} from '../util/text/Text'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {STATUS_PAGE_URL} from 'lib/constants'
|
import {STATUS_PAGE_URL} from 'lib/constants'
|
||||||
|
@ -24,6 +25,9 @@ export const withAuthRequired = <P extends object>(
|
||||||
if (!store.session.hasSession) {
|
if (!store.session.hasSession) {
|
||||||
return <LoggedOut />
|
return <LoggedOut />
|
||||||
}
|
}
|
||||||
|
if (store.onboarding.isActive) {
|
||||||
|
return <Onboarding />
|
||||||
|
}
|
||||||
return <Component {...props} />
|
return <Component {...props} />
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,6 @@ import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||||
import * as OnboardingModal from './OnboardingModal'
|
|
||||||
import * as ModerationDetailsModal from './ModerationDetails'
|
import * as ModerationDetailsModal from './ModerationDetails'
|
||||||
|
|
||||||
const DEFAULT_SNAPPOINTS = ['90%']
|
const DEFAULT_SNAPPOINTS = ['90%']
|
||||||
|
@ -130,9 +129,6 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
} else if (activeModal?.name === 'post-languages-settings') {
|
} else if (activeModal?.name === 'post-languages-settings') {
|
||||||
snapPoints = PostLanguagesSettingsModal.snapPoints
|
snapPoints = PostLanguagesSettingsModal.snapPoints
|
||||||
element = <PostLanguagesSettingsModal.Component />
|
element = <PostLanguagesSettingsModal.Component />
|
||||||
} else if (activeModal?.name === 'onboarding') {
|
|
||||||
snapPoints = OnboardingModal.snapPoints
|
|
||||||
element = <OnboardingModal.Component />
|
|
||||||
} else if (activeModal?.name === 'moderation-details') {
|
} else if (activeModal?.name === 'moderation-details') {
|
||||||
snapPoints = ModerationDetailsModal.snapPoints
|
snapPoints = ModerationDetailsModal.snapPoints
|
||||||
element = <ModerationDetailsModal.Component {...activeModal} />
|
element = <ModerationDetailsModal.Component {...activeModal} />
|
||||||
|
|
|
@ -26,7 +26,6 @@ import * as AddAppPassword from './AddAppPasswords'
|
||||||
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
import * as ContentFilteringSettingsModal from './ContentFilteringSettings'
|
||||||
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
import * as ContentLanguagesSettingsModal from './lang-settings/ContentLanguagesSettings'
|
||||||
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
import * as PostLanguagesSettingsModal from './lang-settings/PostLanguagesSettings'
|
||||||
import * as OnboardingModal from './OnboardingModal'
|
|
||||||
import * as ModerationDetailsModal from './ModerationDetails'
|
import * as ModerationDetailsModal from './ModerationDetails'
|
||||||
|
|
||||||
export const ModalsContainer = observer(function ModalsContainer() {
|
export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
|
@ -105,8 +104,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||||
element = <AltTextImageModal.Component {...modal} />
|
element = <AltTextImageModal.Component {...modal} />
|
||||||
} else if (modal.name === 'edit-image') {
|
} else if (modal.name === 'edit-image') {
|
||||||
element = <EditImageModal.Component {...modal} />
|
element = <EditImageModal.Component {...modal} />
|
||||||
} else if (modal.name === 'onboarding') {
|
|
||||||
element = <OnboardingModal.Component />
|
|
||||||
} else if (modal.name === 'moderation-details') {
|
} else if (modal.name === 'moderation-details') {
|
||||||
element = <ModerationDetailsModal.Component {...modal} />
|
element = <ModerationDetailsModal.Component {...modal} />
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,8 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import {Onboarding} from '../auth/onboarding/Onboarding'
|
|
||||||
|
|
||||||
export const snapPoints = ['90%']
|
|
||||||
|
|
||||||
export function Component() {
|
|
||||||
return <Onboarding />
|
|
||||||
}
|
|
|
@ -17,6 +17,7 @@ const BACK_HITSLOP = {left: 20, top: 20, right: 50, bottom: 20}
|
||||||
export const ViewHeader = observer(function ({
|
export const ViewHeader = observer(function ({
|
||||||
title,
|
title,
|
||||||
canGoBack,
|
canGoBack,
|
||||||
|
showBackButton = true,
|
||||||
hideOnScroll,
|
hideOnScroll,
|
||||||
showOnDesktop,
|
showOnDesktop,
|
||||||
showBorder,
|
showBorder,
|
||||||
|
@ -24,6 +25,7 @@ export const ViewHeader = observer(function ({
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
canGoBack?: boolean
|
canGoBack?: boolean
|
||||||
|
showBackButton?: boolean
|
||||||
hideOnScroll?: boolean
|
hideOnScroll?: boolean
|
||||||
showOnDesktop?: boolean
|
showOnDesktop?: boolean
|
||||||
showBorder?: boolean
|
showBorder?: boolean
|
||||||
|
@ -49,7 +51,13 @@ export const ViewHeader = observer(function ({
|
||||||
|
|
||||||
if (isDesktopWeb) {
|
if (isDesktopWeb) {
|
||||||
if (showOnDesktop) {
|
if (showOnDesktop) {
|
||||||
return <DesktopWebHeader title={title} renderButton={renderButton} />
|
return (
|
||||||
|
<DesktopWebHeader
|
||||||
|
title={title}
|
||||||
|
renderButton={renderButton}
|
||||||
|
showBorder={showBorder}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
|
@ -59,30 +67,32 @@ export const ViewHeader = observer(function ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
|
<Container hideOnScroll={hideOnScroll || false} showBorder={showBorder}>
|
||||||
<TouchableOpacity
|
{showBackButton ? (
|
||||||
testID="viewHeaderDrawerBtn"
|
<TouchableOpacity
|
||||||
onPress={canGoBack ? onPressBack : onPressMenu}
|
testID="viewHeaderDrawerBtn"
|
||||||
hitSlop={BACK_HITSLOP}
|
onPress={canGoBack ? onPressBack : onPressMenu}
|
||||||
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
hitSlop={BACK_HITSLOP}
|
||||||
accessibilityRole="button"
|
style={canGoBack ? styles.backBtn : styles.backBtnWide}
|
||||||
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
|
accessibilityRole="button"
|
||||||
accessibilityHint={
|
accessibilityLabel={canGoBack ? 'Back' : 'Menu'}
|
||||||
canGoBack ? '' : 'Access navigation links and settings'
|
accessibilityHint={
|
||||||
}>
|
canGoBack ? '' : 'Access navigation links and settings'
|
||||||
{canGoBack ? (
|
}>
|
||||||
<FontAwesomeIcon
|
{canGoBack ? (
|
||||||
size={18}
|
<FontAwesomeIcon
|
||||||
icon="angle-left"
|
size={18}
|
||||||
style={[styles.backIcon, pal.text]}
|
icon="angle-left"
|
||||||
/>
|
style={[styles.backIcon, pal.text]}
|
||||||
) : (
|
/>
|
||||||
<FontAwesomeIcon
|
) : (
|
||||||
size={18}
|
<FontAwesomeIcon
|
||||||
icon="bars"
|
size={18}
|
||||||
style={[styles.backIcon, pal.textLight]}
|
icon="bars"
|
||||||
/>
|
style={[styles.backIcon, pal.textLight]}
|
||||||
)}
|
/>
|
||||||
</TouchableOpacity>
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : null}
|
||||||
<View style={styles.titleContainer} pointerEvents="none">
|
<View style={styles.titleContainer} pointerEvents="none">
|
||||||
<Text type="title" style={[pal.text, styles.title]}>
|
<Text type="title" style={[pal.text, styles.title]}>
|
||||||
{title}
|
{title}
|
||||||
|
@ -90,9 +100,9 @@ export const ViewHeader = observer(function ({
|
||||||
</View>
|
</View>
|
||||||
{renderButton ? (
|
{renderButton ? (
|
||||||
renderButton()
|
renderButton()
|
||||||
) : (
|
) : showBackButton ? (
|
||||||
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
|
<View style={canGoBack ? styles.backBtn : styles.backBtnWide} />
|
||||||
)}
|
) : null}
|
||||||
</Container>
|
</Container>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -101,13 +111,23 @@ export const ViewHeader = observer(function ({
|
||||||
function DesktopWebHeader({
|
function DesktopWebHeader({
|
||||||
title,
|
title,
|
||||||
renderButton,
|
renderButton,
|
||||||
|
showBorder = true,
|
||||||
}: {
|
}: {
|
||||||
title: string
|
title: string
|
||||||
renderButton?: () => JSX.Element
|
renderButton?: () => JSX.Element
|
||||||
|
showBorder?: boolean
|
||||||
}) {
|
}) {
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
return (
|
return (
|
||||||
<CenteredView style={[styles.header, styles.desktopHeader, pal.border]}>
|
<CenteredView
|
||||||
|
style={[
|
||||||
|
styles.header,
|
||||||
|
styles.desktopHeader,
|
||||||
|
pal.border,
|
||||||
|
{
|
||||||
|
borderBottomWidth: showBorder ? 1 : 0,
|
||||||
|
},
|
||||||
|
]}>
|
||||||
<View style={styles.titleContainer} pointerEvents="none">
|
<View style={styles.titleContainer} pointerEvents="none">
|
||||||
<Text type="title-lg" style={[pal.text, styles.title]}>
|
<Text type="title-lg" style={[pal.text, styles.title]}>
|
||||||
{title}
|
{title}
|
||||||
|
@ -195,13 +215,11 @@ const styles = StyleSheet.create({
|
||||||
width: '100%',
|
width: '100%',
|
||||||
},
|
},
|
||||||
desktopHeader: {
|
desktopHeader: {
|
||||||
borderBottomWidth: 1,
|
|
||||||
paddingVertical: 12,
|
paddingVertical: 12,
|
||||||
},
|
},
|
||||||
border: {
|
border: {
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
},
|
},
|
||||||
|
|
||||||
titleContainer: {
|
titleContainer: {
|
||||||
marginLeft: 'auto',
|
marginLeft: 'auto',
|
||||||
marginRight: 'auto',
|
marginRight: 'auto',
|
||||||
|
|
8
src/view/com/util/layouts/Breakpoints.tsx
Normal file
8
src/view/com/util/layouts/Breakpoints.tsx
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
export const Desktop = ({}: React.PropsWithChildren<{}>) => null
|
||||||
|
export const TabletOrDesktop = ({}: React.PropsWithChildren<{}>) => null
|
||||||
|
export const Tablet = ({}: React.PropsWithChildren<{}>) => null
|
||||||
|
export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) =>
|
||||||
|
children
|
||||||
|
export const Mobile = ({children}: React.PropsWithChildren<{}>) => children
|
20
src/view/com/util/layouts/Breakpoints.web.tsx
Normal file
20
src/view/com/util/layouts/Breakpoints.web.tsx
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import React from 'react'
|
||||||
|
import MediaQuery from 'react-responsive'
|
||||||
|
|
||||||
|
export const Desktop = ({children}: React.PropsWithChildren<{}>) => (
|
||||||
|
<MediaQuery minWidth={1224}>{children}</MediaQuery>
|
||||||
|
)
|
||||||
|
export const TabletOrDesktop = ({children}: React.PropsWithChildren<{}>) => (
|
||||||
|
<MediaQuery minWidth={800}>{children}</MediaQuery>
|
||||||
|
)
|
||||||
|
export const Tablet = ({children}: React.PropsWithChildren<{}>) => (
|
||||||
|
<MediaQuery minWidth={800} maxWidth={1224}>
|
||||||
|
{children}
|
||||||
|
</MediaQuery>
|
||||||
|
)
|
||||||
|
export const TabletOrMobile = ({children}: React.PropsWithChildren<{}>) => (
|
||||||
|
<MediaQuery maxWidth={1224}>{children}</MediaQuery>
|
||||||
|
)
|
||||||
|
export const Mobile = ({children}: React.PropsWithChildren<{}>) => (
|
||||||
|
<MediaQuery maxWidth={800}>{children}</MediaQuery>
|
||||||
|
)
|
69
src/view/com/util/layouts/TitleColumnLayout.tsx
Normal file
69
src/view/com/util/layouts/TitleColumnLayout.tsx
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {StyleProp, StyleSheet, View, ViewStyle} from 'react-native'
|
||||||
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
|
import {useColorSchemeStyle} from 'lib/hooks/useColorSchemeStyle'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
testID?: string
|
||||||
|
title: JSX.Element
|
||||||
|
horizontal: boolean
|
||||||
|
titleStyle?: StyleProp<ViewStyle>
|
||||||
|
contentStyle?: StyleProp<ViewStyle>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TitleColumnLayout({
|
||||||
|
testID,
|
||||||
|
title,
|
||||||
|
horizontal,
|
||||||
|
children,
|
||||||
|
titleStyle,
|
||||||
|
contentStyle,
|
||||||
|
}: React.PropsWithChildren<Props>) {
|
||||||
|
const pal = usePalette('default')
|
||||||
|
const titleBg = useColorSchemeStyle(pal.viewLight, pal.view)
|
||||||
|
const contentBg = useColorSchemeStyle(pal.view, {
|
||||||
|
backgroundColor: pal.colors.background,
|
||||||
|
borderColor: pal.colors.border,
|
||||||
|
borderLeftWidth: 1,
|
||||||
|
})
|
||||||
|
|
||||||
|
const layoutStyles = horizontal ? styles2Column : styles1Column
|
||||||
|
return (
|
||||||
|
<View testID={testID} style={layoutStyles.container}>
|
||||||
|
<View style={[layoutStyles.title, titleBg, titleStyle]}>{title}</View>
|
||||||
|
<View style={[layoutStyles.content, contentBg, contentStyle]}>
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles2Column = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
height: '100%',
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
flex: 1,
|
||||||
|
paddingHorizontal: 40,
|
||||||
|
paddingBottom: 80,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
flex: 2,
|
||||||
|
paddingHorizontal: 40,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const styles1Column = StyleSheet.create({
|
||||||
|
container: {},
|
||||||
|
title: {
|
||||||
|
paddingHorizontal: 40,
|
||||||
|
paddingVertical: 40,
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
paddingHorizontal: 40,
|
||||||
|
paddingVertical: 40,
|
||||||
|
},
|
||||||
|
})
|
21
src/view/com/util/layouts/withBreakpoints.tsx
Normal file
21
src/view/com/util/layouts/withBreakpoints.tsx
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
import React from 'react'
|
||||||
|
import {isNative} from 'platform/detection'
|
||||||
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
|
|
||||||
|
export const withBreakpoints =
|
||||||
|
<P extends object>(
|
||||||
|
Mobile: React.ComponentType<P>,
|
||||||
|
Tablet: React.ComponentType<P>,
|
||||||
|
Desktop: React.ComponentType<P>,
|
||||||
|
): React.FC<P> =>
|
||||||
|
(props: P) => {
|
||||||
|
const {isMobile, isTabletOrMobile} = useWebMediaQueries()
|
||||||
|
|
||||||
|
if (isMobile || isNative) {
|
||||||
|
return <Mobile {...props} />
|
||||||
|
}
|
||||||
|
if (isTabletOrMobile) {
|
||||||
|
return <Tablet {...props} />
|
||||||
|
}
|
||||||
|
return <Desktop {...props} />
|
||||||
|
}
|
|
@ -92,6 +92,7 @@ import {faPlay} from '@fortawesome/free-solid-svg-icons/faPlay'
|
||||||
import {faPause} from '@fortawesome/free-solid-svg-icons/faPause'
|
import {faPause} from '@fortawesome/free-solid-svg-icons/faPause'
|
||||||
import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack'
|
import {faThumbtack} from '@fortawesome/free-solid-svg-icons/faThumbtack'
|
||||||
import {faList} from '@fortawesome/free-solid-svg-icons/faList'
|
import {faList} from '@fortawesome/free-solid-svg-icons/faList'
|
||||||
|
import {faChevronRight} from '@fortawesome/free-solid-svg-icons/faChevronRight'
|
||||||
|
|
||||||
export function setup() {
|
export function setup() {
|
||||||
library.add(
|
library.add(
|
||||||
|
@ -187,5 +188,6 @@ export function setup() {
|
||||||
faPlay,
|
faPlay,
|
||||||
faPause,
|
faPause,
|
||||||
faList,
|
faList,
|
||||||
|
faChevronRight,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ const POLL_FREQ = 30e3 // 30sec
|
||||||
|
|
||||||
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
type Props = NativeStackScreenProps<HomeTabNavigatorParams, 'Home'>
|
||||||
export const HomeScreen = withAuthRequired(
|
export const HomeScreen = withAuthRequired(
|
||||||
observer((_opts: Props) => {
|
observer(({}: Props) => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const pagerRef = React.useRef<PagerRef>(null)
|
const pagerRef = React.useRef<PagerRef>(null)
|
||||||
const [selectedPage, setSelectedPage] = React.useState(0)
|
const [selectedPage, setSelectedPage] = React.useState(0)
|
||||||
|
|
|
@ -162,6 +162,11 @@ export const SettingsScreen = withAuthRequired(
|
||||||
Toast.show('Preferences reset')
|
Toast.show('Preferences reset')
|
||||||
}, [store])
|
}, [store])
|
||||||
|
|
||||||
|
const onPressResetOnboarding = React.useCallback(async () => {
|
||||||
|
store.onboarding.reset()
|
||||||
|
Toast.show('Onboarding reset')
|
||||||
|
}, [store])
|
||||||
|
|
||||||
const onPressBuildInfo = React.useCallback(() => {
|
const onPressBuildInfo = React.useCallback(() => {
|
||||||
Clipboard.setString(
|
Clipboard.setString(
|
||||||
`Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`,
|
`Build version: ${AppInfo.appVersion}; Platform: ${Platform.OS}`,
|
||||||
|
@ -533,6 +538,16 @@ export const SettingsScreen = withAuthRequired(
|
||||||
Reset preferences state
|
Reset preferences state
|
||||||
</Text>
|
</Text>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[pal.view, styles.linkCardNoIcon]}
|
||||||
|
onPress={onPressResetOnboarding}
|
||||||
|
accessibilityRole="button"
|
||||||
|
accessibilityHint="Reset onboarding"
|
||||||
|
accessibilityLabel="Resets the onboarding state">
|
||||||
|
<Text type="lg" style={pal.text}>
|
||||||
|
Reset onboarding state
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
<View style={[styles.footer]}>
|
<View style={[styles.footer]}>
|
||||||
|
|
|
@ -20,7 +20,6 @@ import {NavigationProp} from 'lib/routes/types'
|
||||||
const ShellInner = observer(() => {
|
const ShellInner = observer(() => {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
const {isDesktop} = useWebMediaQueries()
|
const {isDesktop} = useWebMediaQueries()
|
||||||
|
|
||||||
const navigator = useNavigation<NavigationProp>()
|
const navigator = useNavigation<NavigationProp>()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -29,6 +28,9 @@ const ShellInner = observer(() => {
|
||||||
})
|
})
|
||||||
}, [navigator, store.shell])
|
}, [navigator, store.shell])
|
||||||
|
|
||||||
|
const showBottomBar = !isDesktop && !store.onboarding.isActive
|
||||||
|
const showSideNavs =
|
||||||
|
isDesktop && store.session.hasSession && !store.onboarding.isActive
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<View style={s.hContentRegion}>
|
<View style={s.hContentRegion}>
|
||||||
|
@ -36,7 +38,7 @@ const ShellInner = observer(() => {
|
||||||
<FlatNavigator />
|
<FlatNavigator />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</View>
|
</View>
|
||||||
{isDesktop && store.session.hasSession && (
|
{showSideNavs && (
|
||||||
<>
|
<>
|
||||||
<DesktopLeftNav />
|
<DesktopLeftNav />
|
||||||
<DesktopRightNav />
|
<DesktopRightNav />
|
||||||
|
@ -51,7 +53,7 @@ const ShellInner = observer(() => {
|
||||||
onPost={store.shell.composerOpts?.onPost}
|
onPost={store.shell.composerOpts?.onPost}
|
||||||
mention={store.shell.composerOpts?.mention}
|
mention={store.shell.composerOpts?.mention}
|
||||||
/>
|
/>
|
||||||
{!isDesktop && <BottomBarWeb />}
|
{showBottomBar && <BottomBarWeb />}
|
||||||
<ModalsContainer />
|
<ModalsContainer />
|
||||||
<Lightbox />
|
<Lightbox />
|
||||||
{!isDesktop && store.shell.isDrawerOpen && (
|
{!isDesktop && store.shell.isDrawerOpen && (
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue