diff --git a/__tests__/state/models/onboard.test.ts b/__tests__/state/models/onboard.test.ts deleted file mode 100644 index 02ee0feb..00000000 --- a/__tests__/state/models/onboard.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -import { - OnboardModel, - OnboardStageOrder, -} from '../../../src/state/models/onboard' - -describe('OnboardModel', () => { - let onboardModel: OnboardModel - - beforeEach(() => { - onboardModel = new OnboardModel() - }) - - afterAll(() => { - jest.clearAllMocks() - }) - - it('should start/stop correctly', () => { - onboardModel.start() - expect(onboardModel.isOnboarding).toBe(true) - onboardModel.stop() - expect(onboardModel.isOnboarding).toBe(false) - }) - - it('should call the next method until it has no more stages', () => { - onboardModel.start() - onboardModel.next() - expect(onboardModel.stage).toBe(OnboardStageOrder[1]) - - onboardModel.next() - expect(onboardModel.isOnboarding).toBe(false) - expect(onboardModel.stage).toBe(OnboardStageOrder[0]) - }) - - it('serialize and hydrate', () => { - const serialized = onboardModel.serialize() - const newModel = new OnboardModel() - newModel.hydrate(serialized) - expect(newModel).toEqual(onboardModel) - - onboardModel.start() - onboardModel.next() - const serialized2 = onboardModel.serialize() - newModel.hydrate(serialized2) - expect(newModel).toEqual(onboardModel) - }) -}) diff --git a/e2e/tests/happyPath.test.js b/e2e/tests/happyPath.test.js index 15d0b3e3..4176cecb 100644 --- a/e2e/tests/happyPath.test.js +++ b/e2e/tests/happyPath.test.js @@ -1,6 +1,6 @@ /* eslint-env detox/detox */ -describe('Example', () => { +describe('Happy paths', () => { async function grantAccessToUserWithValidCredentials( username, {takeScreenshots} = {takeScreenshots: false}, @@ -65,13 +65,6 @@ describe('Example', () => { await element(by.id('registerIs13Input')).tap() await device.takeScreenshot('4- entered account details') await element(by.id('createAccountButton')).tap() - await expect(element(by.id('onboardFeatureExplainerSkipBtn'))).toBeVisible() - await expect(element(by.id('onboardFeatureExplainerNextBtn'))).toBeVisible() - await device.takeScreenshot('5- onboard feature explainer') - await element(by.id('onboardFeatureExplainerSkipBtn')).tap() - await expect(element(by.id('onboardFollowsSkipBtn'))).toBeVisible() - await expect(element(by.id('onboardFollowsNextBtn'))).toBeVisible() - await device.takeScreenshot('6- onboard follows recommender') - await element(by.id('onboardFollowsSkipBtn')).tap() + await expect(element(by.id('welcomeBanner'))).toBeVisible() }) }) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fe16f141..c9d0aee0 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -26,9 +26,9 @@ PODS: - libwebp/mux (1.2.4): - libwebp/demux - libwebp/webp (1.2.4) - - Permission-Camera (3.6.1): + - Permission-Camera (3.7.2): - RNPermissions - - Permission-PhotoLibrary (3.6.1): + - Permission-PhotoLibrary (3.7.2): - RNPermissions - RCT-Folly (2021.07.22.00): - boost @@ -359,7 +359,7 @@ PODS: - React-Core - RNCAsyncStorage (1.17.11): - React-Core - - RNCClipboard (1.11.1): + - RNCClipboard (1.11.2): - React-Core - RNFastImage (8.6.3): - React-Core @@ -385,7 +385,7 @@ PODS: - RNNotifee/NotifeeCore (= 7.5.0) - RNNotifee/NotifeeCore (7.5.0): - React-Core - - RNPermissions (3.6.1): + - RNPermissions (3.7.2): - React-Core - RNReactNativeHapticFeedback (1.14.0): - React-Core @@ -652,8 +652,8 @@ SPEC CHECKSUMS: hermes-engine: 922ccd744f50d9bfde09e9677bf0f3b562ea5fb9 libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 libwebp: f62cb61d0a484ba548448a4bd52aabf150ff6eef - Permission-Camera: bf6791b17c7f614b6826019fcfdcc286d3a107f6 - Permission-PhotoLibrary: 5b34ca67279f7201ae109cef36f9806a6596002d + Permission-Camera: db22e80aa0858a8b6d65979a97f2f481dd8a0ebd + Permission-PhotoLibrary: 7d80161682e08042fd8b0bf934ea97a8495e0e6a RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1 RCTRequired: fd4d923b964658aa0c4091a32c8b2004c6d9e3a6 RCTTypeSafety: c276d85975bde3d8448907235c70bf0da257adfd @@ -693,14 +693,14 @@ SPEC CHECKSUMS: rn-fetch-blob: f065bb7ab7fb48dd002629f8bdcb0336602d3cba RNBackgroundFetch: 8e16176ff415daac743a6eb57afc8e9e14dbe623 RNCAsyncStorage: 8616bd5a58af409453ea4e1b246521bb76578d60 - RNCClipboard: 2834e1c4af68697089cdd455ee4a4cdd198fa7dd + RNCClipboard: 3f0451a8100393908bea5c5c5b16f96d45f30bfc RNFastImage: 5c9c9fed9c076e521b3f509fe79e790418a544e8 RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 RNGestureHandler: 071d7a9ad81e8b83fe7663b303d132406a7d8f39 RNImageCropPicker: 648356d68fbf9911a1016b3e3723885d28373eda RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 RNNotifee: 053c0ace9c73634709a0214fd9c436a5777a562f - RNPermissions: dcdb7b99796bbeda6975a6e79ad519c41b251b1c + RNPermissions: 2fbbcb7244357507f958d626d58eb15fb0013d85 RNReactNativeHapticFeedback: 1e3efeca9628ff9876ee7cdd9edec1b336913f8c RNReanimated: cc5e3aa479cb9170bcccf8204291a6950a3be128 RNScreens: 218801c16a2782546d30bd2026bb625c0302d70f diff --git a/src/lib/api/build-suggested-posts.ts b/src/lib/api/build-suggested-posts.ts new file mode 100644 index 00000000..6250f4a9 --- /dev/null +++ b/src/lib/api/build-suggested-posts.ts @@ -0,0 +1,120 @@ +import {RootStoreModel} from 'state/index' +import { + AppBskyFeedFeedViewPost, + AppBskyFeedGetAuthorFeed as GetAuthorFeed, +} from '@atproto/api' +type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost + +async function getMultipleAuthorsPosts( + rootStore: RootStoreModel, + authors: string[], + cursor: string | undefined = undefined, + limit: number = 10, +) { + const responses = await Promise.all( + authors.map((author, index) => + rootStore.api.app.bsky.feed + .getAuthorFeed({ + author, + limit, + before: cursor ? cursor.split(',')[index] : undefined, + }) + .catch(_err => ({success: false, headers: {}, data: {feed: []}})), + ), + ) + return responses +} + +function mergePosts( + responses: GetAuthorFeed.Response[], + {repostsOnly, bestOfOnly}: {repostsOnly?: boolean; bestOfOnly?: boolean}, +) { + let posts: AppBskyFeedFeedViewPost.Main[] = [] + + if (bestOfOnly) { + for (const res of responses) { + if (res.success) { + // filter the feed down to the post with the most upvotes + res.data.feed = res.data.feed.reduce( + (acc: AppBskyFeedFeedViewPost.Main[], v) => { + if (!acc?.[0] && !v.reason) { + return [v] + } + if ( + acc && + !v.reason && + v.post.upvoteCount > acc[0].post.upvoteCount + ) { + return [v] + } + return acc + }, + [], + ) + } + } + } + + // merge into one array + for (const res of responses) { + if (res.success) { + posts = posts.concat(res.data.feed) + } + } + + // filter down to reposts of other users + const uris = new Set() + posts = posts.filter(p => { + if (repostsOnly && !isARepostOfSomeoneElse(p)) { + return false + } + if (uris.has(p.post.uri)) { + return false + } + uris.add(p.post.uri) + return true + }) + + // sort by index time + posts.sort((a, b) => { + return ( + Number(new Date(b.post.indexedAt)) - Number(new Date(a.post.indexedAt)) + ) + }) + + return posts +} + +function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean { + return ( + post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' && + post.post.author.did !== (post.reason as ReasonRepost).by.did + ) +} + +function getCombinedCursors(responses: GetAuthorFeed.Response[]) { + let hasCursor = false + const cursors = responses.map(r => { + if (r.data.cursor) { + hasCursor = true + return r.data.cursor + } + return '' + }) + if (!hasCursor) { + return undefined + } + const combinedCursors = cursors.join(',') + return combinedCursors +} + +function isCombinedCursor(cursor: string) { + return cursor.includes(',') +} + +export { + getMultipleAuthorsPosts, + mergePosts, + getCombinedCursors, + isCombinedCursor, +} diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 062fc1aa..a93301b3 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -4,6 +4,31 @@ export const FEEDBACK_FORM_URL = export const MAX_DISPLAY_NAME = 64 export const MAX_DESCRIPTION = 256 +export const PROD_TEAM_HANDLES = [ + 'jay.bsky.social', + 'paul.bsky.social', + 'dan.bsky.social', + 'divy.bsky.social', + 'why.bsky.social', + 'iamrosewang.bsky.social', +] +export const STAGING_TEAM_HANDLES = [ + 'arcalinea.staging.bsky.dev', + 'paul.staging.bsky.dev', + 'paul2.staging.bsky.dev', +] +export const DEV_TEAM_HANDLES = ['alice.test', 'bob.test', 'carla.test'] + +export function TEAM_HANDLES(serviceUrl: string) { + if (serviceUrl.includes('localhost')) { + return DEV_TEAM_HANDLES + } else if (serviceUrl.includes('staging')) { + return STAGING_TEAM_HANDLES + } else { + return PROD_TEAM_HANDLES + } +} + export const PROD_SUGGESTED_FOLLOWS = [ 'john', 'visakanv', @@ -55,14 +80,21 @@ export const PROD_SUGGESTED_FOLLOWS = [ 'jay', 'paul', ].map(handle => `${handle}.bsky.social`) - export const STAGING_SUGGESTED_FOLLOWS = ['arcalinea', 'paul', 'paul2'].map( handle => `${handle}.staging.bsky.dev`, ) - export const DEV_SUGGESTED_FOLLOWS = ['alice', 'bob', 'carla'].map( handle => `${handle}.test`, ) +export function SUGGESTED_FOLLOWS(serviceUrl: string) { + if (serviceUrl.includes('localhost')) { + return DEV_SUGGESTED_FOLLOWS + } else if (serviceUrl.includes('staging')) { + return STAGING_SUGGESTED_FOLLOWS + } else { + return PROD_SUGGESTED_FOLLOWS + } +} export const POST_IMG_MAX_WIDTH = 2000 export const POST_IMG_MAX_HEIGHT = 2000 diff --git a/src/lib/styles.ts b/src/lib/styles.ts index dd3c8691..f6e26d53 100644 --- a/src/lib/styles.ts +++ b/src/lib/styles.ts @@ -62,6 +62,10 @@ export const s = StyleSheet.create({ footerSpacer: {height: 100}, contentContainer: {paddingBottom: 200}, border1: {borderWidth: 1}, + borderTop1: {borderTopWidth: 1}, + borderRight1: {borderRightWidth: 1}, + borderBottom1: {borderBottomWidth: 1}, + borderLeft1: {borderLeftWidth: 1}, // font weights fw600: {fontWeight: '600'}, diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index f80c5f2c..645b1f2e 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -15,6 +15,12 @@ import {RootStoreModel} from './root-store' import * as apilib from 'lib/api/index' import {cleanError} from 'lib/strings/errors' import {RichText} from 'lib/strings/rich-text' +import {SUGGESTED_FOLLOWS} from 'lib/constants' +import { + getCombinedCursors, + getMultipleAuthorsPosts, + mergePosts, +} from 'lib/api/build-suggested-posts' const PAGE_SIZE = 30 @@ -535,11 +541,31 @@ export class FeedModel { } } - protected _getFeed( + protected async _getFeed( params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams = {}, ): Promise { params = Object.assign({}, this.params, params) if (this.feedType === 'home') { + await this.rootStore.me.follows.fetchIfNeeded() + if (this.rootStore.me.follows.isEmpty) { + const responses = await getMultipleAuthorsPosts( + this.rootStore, + SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), + params.before, + 20, + ) + const combinedCursor = getCombinedCursors(responses) + const finalData = mergePosts(responses, {bestOfOnly: true}) + const lastHeaders = responses[responses.length - 1].headers + return { + success: true, + data: { + feed: finalData, + cursor: combinedCursor, + }, + headers: lastHeaders, + } + } return this.rootStore.api.app.bsky.feed.getTimeline( params as GetTimeline.QueryParams, ) diff --git a/src/state/models/me.ts b/src/state/models/me.ts index 0cb84c9f..451d562a 100644 --- a/src/state/models/me.ts +++ b/src/state/models/me.ts @@ -96,6 +96,7 @@ export class MeModel { this.avatar = '' } }) + this.mainFeed.clear() await Promise.all([ this.mainFeed.setup().catch(e => { this.rootStore.log.error('Failed to setup main feed model', e) diff --git a/src/state/models/my-follows.ts b/src/state/models/my-follows.ts index 252e8a3d..c1fba135 100644 --- a/src/state/models/my-follows.ts +++ b/src/state/models/my-follows.ts @@ -20,6 +20,7 @@ export class MyFollowsModel { // data followDidToRecordMap: Record = {} lastSync = 0 + myDid?: string constructor(public rootStore: RootStoreModel) { makeAutoObservable( @@ -36,6 +37,7 @@ export class MyFollowsModel { fetchIfNeeded = bundleAsync(async () => { if ( + this.myDid !== this.rootStore.me.did || Object.keys(this.followDidToRecordMap).length === 0 || Date.now() - this.lastSync > CACHE_TTL ) { @@ -62,6 +64,7 @@ export class MyFollowsModel { this.followDidToRecordMap[record.value.subject.did] = record.uri } this.lastSync = Date.now() + this.myDid = this.rootStore.me.did }) }) @@ -69,6 +72,10 @@ export class MyFollowsModel { return !!this.followDidToRecordMap[did] } + get isEmpty() { + return Object.keys(this.followDidToRecordMap).length === 0 + } + getFollowUri(did: string): string { const v = this.followDidToRecordMap[did] if (!v) { diff --git a/src/state/models/onboard.ts b/src/state/models/onboard.ts deleted file mode 100644 index aa275c6b..00000000 --- a/src/state/models/onboard.ts +++ /dev/null @@ -1,65 +0,0 @@ -import {makeAutoObservable} from 'mobx' -import {isObj, hasProp} from 'lib/type-guards' - -export const OnboardStage = { - Explainers: 'explainers', - Follows: 'follows', -} - -export const OnboardStageOrder = [OnboardStage.Explainers, OnboardStage.Follows] - -export class OnboardModel { - isOnboarding: boolean = false - stage: string = OnboardStageOrder[0] - - constructor() { - makeAutoObservable(this, { - serialize: false, - hydrate: false, - }) - } - - serialize(): unknown { - return { - isOnboarding: this.isOnboarding, - stage: this.stage, - } - } - - hydrate(v: unknown) { - if (isObj(v)) { - if (hasProp(v, 'isOnboarding') && typeof v.isOnboarding === 'boolean') { - this.isOnboarding = v.isOnboarding - } - if ( - hasProp(v, 'stage') && - typeof v.stage === 'string' && - OnboardStageOrder.includes(v.stage) - ) { - this.stage = v.stage - } - } - } - - start() { - this.isOnboarding = true - } - - stop() { - this.isOnboarding = false - } - - next() { - if (!this.isOnboarding) { - return - } - let i = OnboardStageOrder.indexOf(this.stage) - i++ - if (i >= OnboardStageOrder.length) { - this.isOnboarding = false - this.stage = OnboardStageOrder[0] // in case they make a new account - } else { - this.stage = OnboardStageOrder[i] - } - } -} diff --git a/src/state/models/root-store.ts b/src/state/models/root-store.ts index 43523b75..4b62f501 100644 --- a/src/state/models/root-store.ts +++ b/src/state/models/root-store.ts @@ -17,7 +17,6 @@ import {ProfilesViewModel} from './profiles-view' import {LinkMetasViewModel} from './link-metas-view' import {NotificationsViewItemModel} from './notifications-view' import {MeModel} from './me' -import {OnboardModel} from './onboard' export const appInfo = z.object({ build: z.string(), @@ -35,7 +34,6 @@ export class RootStoreModel { nav = new NavigationModel(this) shell = new ShellUiModel(this) me = new MeModel(this) - onboard = new OnboardModel() profiles = new ProfilesViewModel(this) linkMetas = new LinkMetasViewModel(this) @@ -85,7 +83,6 @@ export class RootStoreModel { session: this.session.serialize(), me: this.me.serialize(), nav: this.nav.serialize(), - onboard: this.onboard.serialize(), shell: this.shell.serialize(), } } @@ -107,9 +104,6 @@ export class RootStoreModel { if (hasProp(v, 'nav')) { this.nav.hydrate(v.nav) } - if (hasProp(v, 'onboard')) { - this.onboard.hydrate(v.onboard) - } if (hasProp(v, 'session')) { this.session.hydrate(v.session) } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index 6e816120..75a60f35 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -345,7 +345,6 @@ export class SessionModel { ) this.setActiveSession(agent, did) - this.rootStore.onboard.start() this.rootStore.log.debug('SessionModel:createAccount succeeded') } diff --git a/src/state/models/suggested-actors-view.ts b/src/state/models/suggested-actors-view.ts index 4764f581..33c73b4e 100644 --- a/src/state/models/suggested-actors-view.ts +++ b/src/state/models/suggested-actors-view.ts @@ -4,26 +4,12 @@ import shuffle from 'lodash.shuffle' import {RootStoreModel} from './root-store' import {cleanError} from 'lib/strings/errors' import {bundleAsync} from 'lib/async/bundle' -import { - DEV_SUGGESTED_FOLLOWS, - PROD_SUGGESTED_FOLLOWS, - STAGING_SUGGESTED_FOLLOWS, -} from 'lib/constants' +import {SUGGESTED_FOLLOWS} from 'lib/constants' const PAGE_SIZE = 30 export type SuggestedActor = Profile.ViewBasic | Profile.View -const getSuggestionList = ({serviceUrl}: {serviceUrl: string}) => { - if (serviceUrl.includes('localhost')) { - return DEV_SUGGESTED_FOLLOWS - } else if (serviceUrl.includes('staging')) { - return STAGING_SUGGESTED_FOLLOWS - } else { - return PROD_SUGGESTED_FOLLOWS - } -} - export class SuggestedActorsViewModel { // state pageSize = PAGE_SIZE @@ -126,9 +112,9 @@ export class SuggestedActorsViewModel { try { // clone the array so we can mutate it const actors = [ - ...getSuggestionList({ - serviceUrl: this.rootStore.session.currentSession?.service || '', - }), + ...SUGGESTED_FOLLOWS( + this.rootStore.session.currentSession?.service || '', + ), ] // fetch the profiles in chunks of 25 (the limit allowed by `getProfiles`) diff --git a/src/state/models/suggested-posts-view.ts b/src/state/models/suggested-posts-view.ts index 7b44370d..c6710c44 100644 --- a/src/state/models/suggested-posts-view.ts +++ b/src/state/models/suggested-posts-view.ts @@ -1,21 +1,12 @@ import {makeAutoObservable, runInAction} from 'mobx' -import { - AppBskyFeedFeedViewPost, - AppBskyFeedGetAuthorFeed as GetAuthorFeed, -} from '@atproto/api' -type ReasonRepost = AppBskyFeedFeedViewPost.ReasonRepost import {RootStoreModel} from './root-store' import {FeedItemModel} from './feed-view' import {cleanError} from 'lib/strings/errors' - -const TEAM_HANDLES = [ - 'jay.bsky.social', - 'paul.bsky.social', - 'dan.bsky.social', - 'divy.bsky.social', - 'why.bsky.social', - 'iamrosewang.bsky.social', -] +import {TEAM_HANDLES} from 'lib/constants' +import { + getMultipleAuthorsPosts, + mergePosts, +} from 'lib/api/build-suggested-posts' export class SuggestedPostsView { // state @@ -54,15 +45,18 @@ export class SuggestedPostsView { async setup() { this._xLoading() try { - const responses = await Promise.all( - TEAM_HANDLES.map(handle => - this.rootStore.api.app.bsky.feed - .getAuthorFeed({author: handle, limit: 10}) - .catch(_err => ({success: false, headers: {}, data: {feed: []}})), - ), + const responses = await getMultipleAuthorsPosts( + this.rootStore, + TEAM_HANDLES(String(this.rootStore.agent.service)), ) runInAction(() => { - this.posts = mergeAndFilterResponses(this.rootStore, responses) + const finalPosts = mergePosts(responses, {repostsOnly: true}) + // hydrate into models + this.posts = finalPosts.map((post, i) => { + // strip the reasons to hide that these are reposts + delete post.reason + return new FeedItemModel(this.rootStore, `post-${i}`, post) + }) }) this._xIdle() } catch (e: any) { @@ -90,59 +84,3 @@ export class SuggestedPostsView { } } } - -function mergeAndFilterResponses( - store: RootStoreModel, - responses: GetAuthorFeed.Response[], -): FeedItemModel[] { - let posts: AppBskyFeedFeedViewPost.Main[] = [] - - // merge into one array - for (const res of responses) { - if (res.success) { - posts = posts.concat(res.data.feed) - } - } - - // filter down to reposts of other users - const now = Date.now() - const uris = new Set() - posts = posts.filter(p => { - if (isARepostOfSomeoneElse(p) && isRecentEnough(now, p)) { - if (uris.has(p.post.uri)) { - return false - } - uris.add(p.post.uri) - return true - } - return false - }) - - // sort by index time - posts.sort((a, b) => { - return ( - Number(new Date(b.post.indexedAt)) - Number(new Date(a.post.indexedAt)) - ) - }) - - // hydrate into models and strip the reasons to hide that these are reposts - return posts.map((post, i) => { - delete post.reason - return new FeedItemModel(store, `post-${i}`, post) - }) -} - -function isARepostOfSomeoneElse(post: AppBskyFeedFeedViewPost.Main): boolean { - return ( - post.reason?.$type === 'app.bsky.feed.feedViewPost#reasonRepost' && - post.post.author.did !== (post.reason as ReasonRepost).by.did - ) -} - -const THREE_DAYS = 3 * 24 * 60 * 60 * 1000 -function isRecentEnough( - now: number, - post: AppBskyFeedFeedViewPost.Main, -): boolean { - return now - Number(new Date(post.post.indexedAt)) < THREE_DAYS -} diff --git a/src/view/com/discover/SuggestedPosts.tsx b/src/view/com/discover/SuggestedPosts.tsx index 86a6bd39..9c7745df 100644 --- a/src/view/com/discover/SuggestedPosts.tsx +++ b/src/view/com/discover/SuggestedPosts.tsx @@ -33,7 +33,7 @@ export const SuggestedPosts = observer(() => { <> {suggestedPostsView.posts.map(item => ( - + ))} diff --git a/src/view/com/onboard/FeatureExplainer.tsx b/src/view/com/onboard/FeatureExplainer.tsx deleted file mode 100644 index 323b1ba1..00000000 --- a/src/view/com/onboard/FeatureExplainer.tsx +++ /dev/null @@ -1,196 +0,0 @@ -import React, {useState} from 'react' -import { - Animated, - Image, - SafeAreaView, - StyleSheet, - TouchableOpacity, - useWindowDimensions, - View, -} from 'react-native' -import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {Text} from '../util/text/Text' -import {useStores} from 'state/index' -import {s} from 'lib/styles' -import {TABS_EXPLAINER} from 'lib/assets' -import {TABS_ENABLED} from 'lib/build-flags' - -const ROUTES = TABS_ENABLED - ? [ - {key: 'intro', title: 'Intro'}, - {key: 'tabs', title: 'Tabs'}, - ] - : [{key: 'intro', title: 'Intro'}] - -const Intro = () => ( - - - Welcome to{' '} - - Bluesky - - - - This is an early beta. Your feedback is appreciated! - - -) - -const Tabs = () => ( - - - - - - - Tabs - - Never lose your place! Long-press to open posts and profiles in a new tab. - - - - - -) - -const SCENE_MAP = { - intro: Intro, - tabs: Tabs, -} -const renderScene = SceneMap(SCENE_MAP) - -export const FeatureExplainer = () => { - const layout = useWindowDimensions() - const store = useStores() - const [index, setIndex] = useState(0) - - const onPressSkip = () => store.onboard.next() - const onPressNext = () => { - if (index >= ROUTES.length - 1) { - store.onboard.next() - } else { - setIndex(index + 1) - } - } - - const renderTabBar = (props: TabBarProps) => { - const inputRange = props.navigationState.routes.map((x, i) => i) - return ( - - - {props.navigationState.routes.map((route, i) => { - const opacity = props.position.interpolate({ - inputRange, - outputRange: inputRange.map(inputIndex => - inputIndex === i ? 1 : 0.5, - ), - }) - - return ( - setIndex(i)}> - ° - - ) - })} - - - ) - } - - const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP] - return ( - - {ROUTES.length > 1 ? ( - - ) : FirstExplainer ? ( - - ) : ( - - )} - - - Skip - - - - Next - - - - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - - tabBar: { - flexDirection: 'row', - }, - tabItem: { - alignItems: 'center', - padding: 16, - }, - - explainer: { - flex: 1, - paddingHorizontal: 16, - paddingTop: 80, - }, - explainerIcon: { - flexDirection: 'row', - }, - explainerHeading: { - fontSize: 42, - fontWeight: 'bold', - textAlign: 'center', - marginBottom: 16, - }, - explainerHeadingIntro: { - lineHeight: 60, - paddingTop: 50, - paddingBottom: 50, - }, - explainerHeadingBrand: {fontSize: 56}, - explainerDesc: { - fontSize: 18, - textAlign: 'center', - marginBottom: 16, - }, - explainerDescIntro: {fontSize: 24}, - explainerImg: { - resizeMode: 'contain', - maxWidth: '100%', - maxHeight: 330, - }, - - footer: { - flexDirection: 'row', - paddingHorizontal: 32, - paddingBottom: 24, - }, -}) diff --git a/src/view/com/onboard/FeatureExplainer.web.tsx b/src/view/com/onboard/FeatureExplainer.web.tsx deleted file mode 100644 index 177ac58d..00000000 --- a/src/view/com/onboard/FeatureExplainer.web.tsx +++ /dev/null @@ -1,202 +0,0 @@ -import React, {useState} from 'react' -import { - Animated, - Image, - StyleSheet, - TouchableOpacity, - useWindowDimensions, - View, -} from 'react-native' -import {TabView, SceneMap, Route, TabBarProps} from 'react-native-tab-view' -import { - FontAwesomeIcon, - FontAwesomeIconStyle, -} from '@fortawesome/react-native-fontawesome' -import {CenteredView} from '../util/Views.web' -import {Text} from '../util/text/Text' -import {useStores} from 'state/index' -import {s, colors} from 'lib/styles' -import {TABS_EXPLAINER} from 'lib/assets' -import {TABS_ENABLED} from 'lib/build-flags' - -const ROUTES = TABS_ENABLED - ? [ - {key: 'intro', title: 'Intro'}, - {key: 'tabs', title: 'Tabs'}, - ] - : [{key: 'intro', title: 'Intro'}] - -const Intro = () => ( - - - Welcome to{' '} - - Bluesky - - - - This is an early beta. Your feedback is appreciated! - - -) - -const Tabs = () => ( - - - - - - - Tabs - - Never lose your place! Long-press to open posts and profiles in a new tab. - - - - - -) - -const SCENE_MAP = { - intro: Intro, - tabs: Tabs, -} -const renderScene = SceneMap(SCENE_MAP) - -export const FeatureExplainer = () => { - const layout = useWindowDimensions() - const store = useStores() - const [index, setIndex] = useState(0) - - const onPressSkip = () => store.onboard.next() - const onPressNext = () => { - if (index >= ROUTES.length - 1) { - store.onboard.next() - } else { - setIndex(index + 1) - } - } - - const renderTabBar = (props: TabBarProps) => { - const inputRange = props.navigationState.routes.map((x, i) => i) - return ( - - - {props.navigationState.routes.map((route, i) => { - const opacity = props.position.interpolate({ - inputRange, - outputRange: inputRange.map(inputIndex => - inputIndex === i ? 1 : 0.5, - ), - }) - - return ( - setIndex(i)}> - ° - - ) - })} - - - ) - } - - const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP] - return ( - - {ROUTES.length > 1 ? ( - - ) : FirstExplainer ? ( - - ) : ( - - )} - - - Skip - - - Next - - - - ) -} - -const styles = StyleSheet.create({ - container: { - height: '100%', - justifyContent: 'center', - paddingBottom: '10%', - }, - - tabBar: { - flexDirection: 'row', - }, - tabItem: { - alignItems: 'center', - padding: 16, - }, - - explainer: { - paddingHorizontal: 16, - }, - explainerIcon: { - flexDirection: 'row', - }, - explainerHeading: { - fontSize: 42, - fontWeight: 'bold', - textAlign: 'center', - marginBottom: 16, - }, - explainerHeadingIntro: { - lineHeight: 40, - }, - explainerHeadingBrand: {fontSize: 56}, - explainerDesc: { - fontSize: 18, - textAlign: 'center', - marginBottom: 16, - color: colors.gray5, - }, - explainerDescIntro: {fontSize: 24}, - explainerImg: { - resizeMode: 'contain', - maxWidth: '100%', - maxHeight: 330, - }, - - footer: { - flexDirection: 'row', - justifyContent: 'center', - paddingTop: 24, - }, - footerBtn: { - color: colors.blue3, - fontSize: 19, - paddingHorizontal: 36, - paddingVertical: 8, - }, - footerBtnNext: { - marginLeft: 10, - borderWidth: 1, - borderColor: colors.blue3, - borderRadius: 6, - }, -}) diff --git a/src/view/com/onboard/Follows.tsx b/src/view/com/onboard/Follows.tsx deleted file mode 100644 index e7de82b3..00000000 --- a/src/view/com/onboard/Follows.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import React from 'react' -import {SafeAreaView, StyleSheet, TouchableOpacity, View} from 'react-native' -import {observer} from 'mobx-react-lite' -import {SuggestedFollows} from '../discover/SuggestedFollows' -import {Text} from '../util/text/Text' -import {useStores} from 'state/index' -import {s} from 'lib/styles' - -export const Follows = observer(() => { - const store = useStores() - - const onNoSuggestions = () => { - // no suggestions, bounce from this view - store.onboard.next() - } - const onPressNext = () => store.onboard.next() - - return ( - - Suggested follows - - - - - - Skip - - - - Next - - - - ) -}) - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - - title: { - fontSize: 24, - fontWeight: 'bold', - paddingHorizontal: 16, - paddingBottom: 12, - }, - - footer: { - flexDirection: 'row', - paddingHorizontal: 32, - paddingBottom: 24, - paddingTop: 16, - }, -}) diff --git a/src/view/com/onboard/Follows.web.tsx b/src/view/com/onboard/Follows.web.tsx deleted file mode 100644 index 6b015bb0..00000000 --- a/src/view/com/onboard/Follows.web.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react' -import {SafeAreaView, StyleSheet, TouchableOpacity} from 'react-native' -import {observer} from 'mobx-react-lite' -import {SuggestedFollows} from '../discover/SuggestedFollows' -import {CenteredView} from '../util/Views.web' -import {Text} from '../util/text/Text' -import {useStores} from 'state/index' -import {s} from 'lib/styles' - -export const Follows = observer(() => { - const store = useStores() - - const onNoSuggestions = () => { - // no suggestions, bounce from this view - store.onboard.next() - } - const onPressNext = () => store.onboard.next() - - return ( - - - - Follow these people to see their posts in your feed - - - Next » - - - - - ) -}) - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - title: { - fontSize: 24, - fontWeight: 'bold', - }, - - header: { - paddingTop: 30, - paddingBottom: 40, - }, -}) diff --git a/src/view/com/post-thread/PostThreadItem.tsx b/src/view/com/post-thread/PostThreadItem.tsx index 98d44267..65bae019 100644 --- a/src/view/com/post-thread/PostThreadItem.tsx +++ b/src/view/com/post-thread/PostThreadItem.tsx @@ -305,6 +305,8 @@ export const PostThreadItem = observer(function PostThreadItem({ authorHandle={item.post.author.handle} authorDisplayName={item.post.author.displayName} timestamp={item.post.indexedAt} + did={item.post.author.did} + declarationCid={item.post.author.declaration.cid} /> {item.post.author.viewer?.muted ? ( diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 1550f862..c0ff9541 100644 --- a/src/view/com/post/Post.tsx +++ b/src/view/com/post/Post.tsx @@ -156,6 +156,8 @@ export const Post = observer(function Post({ authorHandle={item.post.author.handle} authorDisplayName={item.post.author.displayName} timestamp={item.post.indexedAt} + did={item.post.author.did} + declarationCid={item.post.author.declaration.cid} /> {replyAuthorDid !== '' && ( diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 7e5d166d..03a719f1 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -13,16 +13,21 @@ import {EmptyState} from '../util/EmptyState' import {ErrorMessage} from '../util/error/ErrorMessage' import {FeedModel} from 'state/models/feed-view' import {FeedItem} from './FeedItem' +import {WelcomeBanner} from '../util/WelcomeBanner' import {OnScrollCb} from 'lib/hooks/useOnMainScroll' import {s} from 'lib/styles' import {useAnalytics} from 'lib/analytics' +import {useStores} from 'state/index' const EMPTY_FEED_ITEM = {_reactKey: '__empty__'} const ERROR_FEED_ITEM = {_reactKey: '__error__'} +const WELCOME_FEED_ITEM = {_reactKey: '__welcome__'} export const Feed = observer(function Feed({ feed, style, + showWelcomeBanner, + showPostFollowBtn, scrollElRef, onPressTryAgain, onScroll, @@ -31,6 +36,8 @@ export const Feed = observer(function Feed({ }: { feed: FeedModel style?: StyleProp + showWelcomeBanner?: boolean + showPostFollowBtn?: boolean scrollElRef?: MutableRefObject | null> onPressTryAgain?: () => void onScroll?: OnScrollCb @@ -38,7 +45,9 @@ export const Feed = observer(function Feed({ headerOffset?: number }) { const {track} = useAnalytics() + const store = useStores() const [isRefreshing, setIsRefreshing] = React.useState(false) + const [isNewUser, setIsNewUser] = React.useState(false) const data = React.useMemo(() => { let feedItems: any[] = [] @@ -46,6 +55,9 @@ export const Feed = observer(function Feed({ if (feed.hasError) { feedItems = feedItems.concat([ERROR_FEED_ITEM]) } + if (showWelcomeBanner && isNewUser) { + feedItems = feedItems.concat([WELCOME_FEED_ITEM]) + } if (feed.isEmpty) { feedItems = feedItems.concat([EMPTY_FEED_ITEM]) } else { @@ -53,21 +65,39 @@ export const Feed = observer(function Feed({ } } return feedItems - }, [feed.hasError, feed.hasLoaded, feed.isEmpty, feed.feed]) + }, [ + feed.hasError, + feed.hasLoaded, + feed.isEmpty, + feed.feed, + showWelcomeBanner, + isNewUser, + ]) // events // = + const checkWelcome = React.useCallback(async () => { + if (showWelcomeBanner) { + await store.me.follows.fetchIfNeeded() + setIsNewUser(store.me.follows.isEmpty) + } + }, [showWelcomeBanner, store.me.follows]) + React.useEffect(() => { + checkWelcome() + }, [checkWelcome]) + const onRefresh = React.useCallback(async () => { track('Feed:onRefresh') setIsRefreshing(true) + checkWelcome() try { await feed.refresh() } catch (err) { feed.rootStore.log.error('Failed to refresh posts feed', err) } setIsRefreshing(false) - }, [feed, track, setIsRefreshing]) + }, [feed, track, setIsRefreshing, checkWelcome]) const onEndReached = React.useCallback(async () => { track('Feed:onEndReached') try { @@ -101,10 +131,12 @@ export const Feed = observer(function Feed({ onPressTryAgain={onPressTryAgain} /> ) + } else if (item === WELCOME_FEED_ITEM) { + return } - return + return }, - [feed, onPressTryAgain], + [feed, onPressTryAgain, showPostFollowBtn], ) const FeedFooter = React.useCallback( @@ -123,6 +155,7 @@ export const Feed = observer(function Feed({ {feed.isLoading && data.length === 0 && ( + {showWelcomeBanner && isNewUser && } )} diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 474afb55..c3e9f61f 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -26,10 +26,12 @@ import {useAnalytics} from 'lib/analytics' export const FeedItem = observer(function ({ item, showReplyLine, + showFollowBtn, ignoreMuteFor, }: { item: FeedItemModel showReplyLine?: boolean + showFollowBtn?: boolean ignoreMuteFor?: string }) { const store = useStores() @@ -175,6 +177,9 @@ export const FeedItem = observer(function ({ authorHandle={item.post.author.handle} authorDisplayName={item.post.author.displayName} timestamp={item.post.indexedAt} + did={item.post.author.did} + declarationCid={item.post.author.declaration.cid} + showFollowBtn={showFollowBtn} /> {!isChild && replyAuthorDid !== '' && ( diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx new file mode 100644 index 00000000..71462bea --- /dev/null +++ b/src/view/com/profile/FollowButton.tsx @@ -0,0 +1,57 @@ +import React from 'react' +import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {Text} from '../util/text/Text' +import {useStores} from 'state/index' +import * as apilib from 'lib/api/index' +import * as Toast from '../util/Toast' +import {usePalette} from 'lib/hooks/usePalette' + +const FollowButton = observer( + ({did, declarationCid}: {did: string; declarationCid: string}) => { + const store = useStores() + const pal = usePalette('default') + const isFollowing = store.me.follows.isFollowing(did) + + const onToggleFollow = async () => { + if (store.me.follows.isFollowing(did)) { + try { + await apilib.unfollow(store, store.me.follows.getFollowUri(did)) + store.me.follows.removeFollow(did) + } catch (e: any) { + store.log.error('Failed fo delete follow', e) + Toast.show('An issue occurred, please try again.') + } + } else { + try { + const res = await apilib.follow(store, did, declarationCid) + store.me.follows.addFollow(did, res.uri) + } catch (e: any) { + store.log.error('Failed fo create follow', e) + Toast.show('An issue occurred, please try again.') + } + } + } + + return ( + + + + {isFollowing ? 'Unfollow' : 'Follow'} + + + + ) + }, +) + +export default FollowButton + +const styles = StyleSheet.create({ + btn: { + paddingVertical: 7, + borderRadius: 50, + marginLeft: 6, + paddingHorizontal: 14, + }, +}) diff --git a/src/view/com/profile/ProfileCard.tsx b/src/view/com/profile/ProfileCard.tsx index 6a136a02..3c487b70 100644 --- a/src/view/com/profile/ProfileCard.tsx +++ b/src/view/com/profile/ProfileCard.tsx @@ -1,14 +1,13 @@ import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {observer} from 'mobx-react-lite' import {Link} from '../util/Link' import {Text} from '../util/text/Text' import {UserAvatar} from '../util/UserAvatar' -import * as Toast from '../util/Toast' import {s} from 'lib/styles' import {usePalette} from 'lib/hooks/usePalette' import {useStores} from 'state/index' -import * as apilib from 'lib/api/index' +import FollowButton from './FollowButton' export function ProfileCard({ handle, @@ -102,26 +101,7 @@ export const ProfileCardWithFollowBtn = observer( }) => { const store = useStores() const isMe = store.me.handle === handle - const isFollowing = store.me.follows.isFollowing(did) - const onToggleFollow = async () => { - if (store.me.follows.isFollowing(did)) { - try { - await apilib.unfollow(store, store.me.follows.getFollowUri(did)) - store.me.follows.removeFollow(did) - } catch (e: any) { - store.log.error('Failed fo delete follow', e) - Toast.show('An issue occurred, please try again.') - } - } else { - try { - const res = await apilib.follow(store, did, declarationCid) - store.me.follows.addFollow(did, res.uri) - } catch (e: any) { - store.log.error('Failed fo create follow', e) - Toast.show('An issue occurred, please try again.') - } - } - } + return ( ( - - ) + : () => } /> ) }, ) -function FollowBtn({ - isFollowing, - onPress, -}: { - isFollowing: boolean - onPress: () => void -}) { - const pal = usePalette('default') - return ( - - - - {isFollowing ? 'Unfollow' : 'Follow'} - - - - ) -} - const styles = StyleSheet.create({ outer: { borderTopWidth: 1, diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 6ba6fac1..a07d9189 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -1,37 +1,74 @@ import React from 'react' -import {Platform, StyleSheet, View} from 'react-native' +import {StyleSheet, View} from 'react-native' import {Text} from './text/Text' import {ago} from 'lib/strings/time' import {usePalette} from 'lib/hooks/usePalette' +import {useStores} from 'state/index' +import {observer} from 'mobx-react-lite' +import FollowButton from '../profile/FollowButton' interface PostMetaOpts { authorHandle: string authorDisplayName: string | undefined timestamp: string + did: string + declarationCid: string + showFollowBtn?: boolean } -export function PostMeta(opts: PostMetaOpts) { +export const PostMeta = observer(function (opts: PostMetaOpts) { const pal = usePalette('default') let displayName = opts.authorDisplayName || opts.authorHandle let handle = opts.authorHandle + const store = useStores() + const isMe = opts.did === store.me.did - // HACK - // Android simply cannot handle the truncation case we need - // so we have to do it manually here - // -prf - if (Platform.OS === 'android') { - if (displayName.length + handle.length > 26) { - if (displayName.length > 26) { - displayName = displayName.slice(0, 23) + '...' - } else { - handle = handle.slice(0, 23 - displayName.length) + '...' - if (handle.endsWith('....')) { - handle = handle.slice(0, -4) + '...' - } - } - } + // NOTE we capture `isFollowing` via a memo so that follows + // don't change this UI immediately, but rather upon future + // renders + const isFollowing = React.useMemo( + () => store.me.follows.isFollowing(opts.did), + [opts.did, store.me.follows], + ) + + if (opts.showFollowBtn && !isMe && !isFollowing) { + // two-liner with follow button + return ( + + + + {displayName}{' '} + + · {ago(opts.timestamp)} + + + + {handle ? ( + + @{handle} + + ) : undefined} + + + + + + + + ) } + // one-liner return ( @@ -53,13 +90,18 @@ export function PostMeta(opts: PostMetaOpts) { ) -} +}) const styles = StyleSheet.create({ meta: { flexDirection: 'row', alignItems: 'baseline', - paddingTop: 0, + paddingBottom: 2, + }, + metaTwoLine: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', paddingBottom: 2, }, metaItem: { diff --git a/src/view/com/util/WelcomeBanner.tsx b/src/view/com/util/WelcomeBanner.tsx new file mode 100644 index 00000000..d5228850 --- /dev/null +++ b/src/view/com/util/WelcomeBanner.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import {StyleSheet, View} from 'react-native' +import {usePalette} from 'lib/hooks/usePalette' +import {Text} from './text/Text' +import {s} from 'lib/styles' + +export function WelcomeBanner() { + const pal = usePalette('default') + return ( + + + Welcome to the private beta! + + + Here are some recent posts. Follow their creators to build your feed. + + + ) +} + +const styles = StyleSheet.create({ + container: { + paddingTop: 30, + paddingBottom: 26, + paddingHorizontal: 20, + borderTopWidth: 1, + }, +}) diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index d11a9fb7..5b5699bc 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -71,8 +71,6 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { store.log.debug('HomeScreen: Updating feed') if (store.me.mainFeed.hasContent) { store.me.mainFeed.update() - } else { - store.me.mainFeed.setup() } return cleanup }, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible, scrollToTop, screen]) @@ -97,6 +95,8 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { feed={store.me.mainFeed} scrollElRef={scrollElRef} style={s.hContentRegion} + showWelcomeBanner + showPostFollowBtn onPressTryAgain={onPressTryAgain} onScroll={onMainScroll} headerOffset={HEADER_HEIGHT} diff --git a/src/view/screens/Onboard.tsx b/src/view/screens/Onboard.tsx deleted file mode 100644 index 1485670e..00000000 --- a/src/view/screens/Onboard.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React, {useEffect} from 'react' -import {StyleSheet, View} from 'react-native' -import {observer} from 'mobx-react-lite' -import {FeatureExplainer} from '../com/onboard/FeatureExplainer' -import {Follows} from '../com/onboard/Follows' -import {OnboardStage, OnboardStageOrder} from 'state/models/onboard' -import {useStores} from 'state/index' - -export const Onboard = observer(() => { - const store = useStores() - - useEffect(() => { - // sanity check - bounce out of onboarding if the stage is wrong somehow - if (!OnboardStageOrder.includes(store.onboard.stage)) { - store.onboard.stop() - } - }, [store.onboard]) - - let Com - if (store.onboard.stage === OnboardStage.Explainers) { - Com = FeatureExplainer - } else if (store.onboard.stage === OnboardStage.Follows) { - Com = Follows - } else { - Com = View - } - - return ( - - - - ) -}) - -const styles = StyleSheet.create({ - container: { - height: '100%', - backgroundColor: '#fff', - }, -}) diff --git a/src/view/shell/mobile/index.tsx b/src/view/shell/mobile/index.tsx index dbfcad0e..80403a6d 100644 --- a/src/view/shell/mobile/index.tsx +++ b/src/view/shell/mobile/index.tsx @@ -26,7 +26,6 @@ import { import {match, MatchResult} from '../../routes' import {Login} from '../../screens/Login' import {Menu} from './Menu' -import {Onboard} from '../../screens/Onboard' import {HorzSwipe} from '../../com/util/gestures/HorzSwipe' import {ModalsContainer} from '../../com/modals/Modal' import {Lightbox} from '../../com/lightbox/Lightbox' @@ -408,17 +407,6 @@ export const MobileShell: React.FC = observer(() => { ) } - if (store.onboard.isOnboarding) { - return ( - - - - - - - - ) - } const isAtHome = store.nav.tab.current.url === TabPurposeMainPath[TabPurpose.Default] diff --git a/src/view/shell/web/index.tsx b/src/view/shell/web/index.tsx index 76b5ed09..a76ae806 100644 --- a/src/view/shell/web/index.tsx +++ b/src/view/shell/web/index.tsx @@ -6,7 +6,6 @@ import {useStores} from 'state/index' import {NavigationModel} from 'state/models/navigation' import {match, MatchResult} from '../../routes' import {DesktopHeader} from './DesktopHeader' -import {Onboard} from '../../screens/Onboard' import {Login} from '../../screens/Login' import {ErrorBoundary} from '../../com/util/ErrorBoundary' import {Lightbox} from '../../com/lightbox/Lightbox' @@ -35,15 +34,6 @@ export const WebShell: React.FC = observer(() => { ) } - if (store.onboard.isOnboarding) { - return ( - - - - - - ) - } return (