diff options
Diffstat (limited to 'src')
28 files changed, 416 insertions, 803 deletions
diff --git a/src/lib/api/build-suggested-posts.ts b/src/lib/api/build-suggested-posts.ts new file mode 100644 index 000000000..6250f4a9c --- /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 062fc1aa8..a93301b34 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 dd3c86910..f6e26d53f 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 f80c5f2c0..645b1f2eb 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<GetTimeline.Response | GetAuthorFeed.Response> { 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 0cb84c9fc..451d562a4 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 252e8a3d3..c1fba1352 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<string, string> = {} 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 aa275c6b7..000000000 --- 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 43523b759..4b62f501e 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 6e816120d..75a60f353 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 4764f581e..33c73b4e1 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 7b44370de..c6710c44e 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 86a6bd394..9c7745dfa 100644 --- a/src/view/com/discover/SuggestedPosts.tsx +++ b/src/view/com/discover/SuggestedPosts.tsx @@ -33,7 +33,7 @@ export const SuggestedPosts = observer(() => { <> <View style={[pal.border, styles.bottomBorder]}> {suggestedPostsView.posts.map(item => ( - <Post item={item} key={item._reactKey} /> + <Post item={item} key={item._reactKey} showFollowBtn /> ))} </View> </> diff --git a/src/view/com/onboard/FeatureExplainer.tsx b/src/view/com/onboard/FeatureExplainer.tsx deleted file mode 100644 index 323b1ba14..000000000 --- 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 = () => ( - <View style={styles.explainer}> - <Text - style={[styles.explainerHeading, s.normal, styles.explainerHeadingIntro]}> - Welcome to{' '} - <Text style={[s.bold, s.blue3, styles.explainerHeadingBrand]}> - Bluesky - </Text> - </Text> - <Text style={[styles.explainerDesc, styles.explainerDescIntro]}> - This is an early beta. Your feedback is appreciated! - </Text> - </View> -) - -const Tabs = () => ( - <View style={styles.explainer}> - <View style={styles.explainerIcon}> - <View style={s.flex1} /> - <FontAwesomeIcon - icon={['far', 'clone']} - style={[s.black as FontAwesomeIconStyle, s.mb5]} - size={36} - /> - <View style={s.flex1} /> - </View> - <Text style={styles.explainerHeading}>Tabs</Text> - <Text style={styles.explainerDesc}> - Never lose your place! Long-press to open posts and profiles in a new tab. - </Text> - <Text style={styles.explainerDesc}> - <Image source={TABS_EXPLAINER} style={styles.explainerImg} /> - </Text> - </View> -) - -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<Route>) => { - const inputRange = props.navigationState.routes.map((x, i) => i) - return ( - <View style={styles.tabBar}> - <View style={s.flex1} /> - {props.navigationState.routes.map((route, i) => { - const opacity = props.position.interpolate({ - inputRange, - outputRange: inputRange.map(inputIndex => - inputIndex === i ? 1 : 0.5, - ), - }) - - return ( - <TouchableOpacity - key={i} - style={styles.tabItem} - onPress={() => setIndex(i)}> - <Animated.Text style={{opacity}}>°</Animated.Text> - </TouchableOpacity> - ) - })} - <View style={s.flex1} /> - </View> - ) - } - - const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP] - return ( - <SafeAreaView style={styles.container}> - {ROUTES.length > 1 ? ( - <TabView - navigationState={{index, routes: ROUTES}} - renderScene={renderScene} - renderTabBar={renderTabBar} - onIndexChange={setIndex} - initialLayout={{width: layout.width}} - tabBarPosition="bottom" - /> - ) : FirstExplainer ? ( - <FirstExplainer /> - ) : ( - <View /> - )} - <View style={styles.footer}> - <TouchableOpacity - onPress={onPressSkip} - testID="onboardFeatureExplainerSkipBtn"> - <Text style={[s.blue3, s.f18]}>Skip</Text> - </TouchableOpacity> - <View style={s.flex1} /> - <TouchableOpacity - onPress={onPressNext} - testID="onboardFeatureExplainerNextBtn"> - <Text style={[s.blue3, s.f18]}>Next</Text> - </TouchableOpacity> - </View> - </SafeAreaView> - ) -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - }, - - tabBar: { - flexDirection: 'row', - }, - tabItem: { - alignItems: 'center', - padding: 16, - }, - - explainer: { - flex: 1, - paddingHorizontal: 16, - 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 177ac58dd..000000000 --- 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 = () => ( - <View style={styles.explainer}> - <Text - style={[styles.explainerHeading, s.normal, styles.explainerHeadingIntro]}> - Welcome to{' '} - <Text style={[s.bold, s.blue3, styles.explainerHeadingBrand]}> - Bluesky - </Text> - </Text> - <Text style={[styles.explainerDesc, styles.explainerDescIntro]}> - This is an early beta. Your feedback is appreciated! - </Text> - </View> -) - -const Tabs = () => ( - <View style={styles.explainer}> - <View style={styles.explainerIcon}> - <View style={s.flex1} /> - <FontAwesomeIcon - icon={['far', 'clone']} - style={[s.black as FontAwesomeIconStyle, s.mb5]} - size={36} - /> - <View style={s.flex1} /> - </View> - <Text style={styles.explainerHeading}>Tabs</Text> - <Text style={styles.explainerDesc}> - Never lose your place! Long-press to open posts and profiles in a new tab. - </Text> - <Text style={styles.explainerDesc}> - <Image source={TABS_EXPLAINER} style={styles.explainerImg} /> - </Text> - </View> -) - -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<Route>) => { - const inputRange = props.navigationState.routes.map((x, i) => i) - return ( - <View style={styles.tabBar}> - <View style={s.flex1} /> - {props.navigationState.routes.map((route, i) => { - const opacity = props.position.interpolate({ - inputRange, - outputRange: inputRange.map(inputIndex => - inputIndex === i ? 1 : 0.5, - ), - }) - - return ( - <TouchableOpacity - key={i} - style={styles.tabItem} - onPress={() => setIndex(i)}> - <Animated.Text style={{opacity}}>°</Animated.Text> - </TouchableOpacity> - ) - })} - <View style={s.flex1} /> - </View> - ) - } - - const FirstExplainer = SCENE_MAP[ROUTES[0]?.key as keyof typeof SCENE_MAP] - return ( - <CenteredView style={styles.container}> - {ROUTES.length > 1 ? ( - <TabView - navigationState={{index, routes: ROUTES}} - renderScene={renderScene} - renderTabBar={renderTabBar} - onIndexChange={setIndex} - initialLayout={{width: layout.width}} - tabBarPosition="bottom" - /> - ) : FirstExplainer ? ( - <FirstExplainer /> - ) : ( - <View /> - )} - <View style={styles.footer}> - <TouchableOpacity onPress={onPressSkip}> - <Text style={styles.footerBtn}>Skip</Text> - </TouchableOpacity> - <TouchableOpacity onPress={onPressNext}> - <Text style={[styles.footerBtn, styles.footerBtnNext]}>Next</Text> - </TouchableOpacity> - </View> - </CenteredView> - ) -} - -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 e7de82b39..000000000 --- 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 ( - <SafeAreaView style={styles.container}> - <Text style={styles.title}>Suggested follows</Text> - <View style={s.flex1}> - <SuggestedFollows onNoSuggestions={onNoSuggestions} /> - </View> - <View style={styles.footer}> - <TouchableOpacity onPress={onPressNext} testID="onboardFollowsSkipBtn"> - <Text style={[s.blue3, s.f18]}>Skip</Text> - </TouchableOpacity> - <View style={s.flex1} /> - <TouchableOpacity onPress={onPressNext} testID="onboardFollowsNextBtn"> - <Text style={[s.blue3, s.f18]}>Next</Text> - </TouchableOpacity> - </View> - </SafeAreaView> - ) -}) - -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 6b015bb09..000000000 --- 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 ( - <SafeAreaView style={styles.container}> - <CenteredView style={styles.header}> - <Text type="title-lg"> - Follow these people to see their posts in your feed - </Text> - <TouchableOpacity onPress={onPressNext}> - <Text style={[styles.title, s.blue3, s.pr10]}>Next »</Text> - </TouchableOpacity> - </CenteredView> - <SuggestedFollows onNoSuggestions={onNoSuggestions} /> - </SafeAreaView> - ) -}) - -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 98d44267d..65bae0192 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 ? ( <View style={[styles.mutedWarning, pal.btn]}> diff --git a/src/view/com/post/Post.tsx b/src/view/com/post/Post.tsx index 1550f8620..c0ff95416 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 !== '' && ( <View style={[s.flexRow, s.mb2, s.alignCenter]}> diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 7e5d166d2..03a719f16 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<ViewStyle> + showWelcomeBanner?: boolean + showPostFollowBtn?: boolean scrollElRef?: MutableRefObject<FlatList<any> | 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<boolean>(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 <WelcomeBanner /> } - return <FeedItem item={item} /> + return <FeedItem item={item} showFollowBtn={showPostFollowBtn} /> }, - [feed, onPressTryAgain], + [feed, onPressTryAgain, showPostFollowBtn], ) const FeedFooter = React.useCallback( @@ -123,6 +155,7 @@ export const Feed = observer(function Feed({ <View testID={testID} style={style}> {feed.isLoading && data.length === 0 && ( <CenteredView style={{paddingTop: headerOffset}}> + {showWelcomeBanner && isNewUser && <WelcomeBanner />} <PostFeedLoadingPlaceholder /> </CenteredView> )} diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 474afb55b..c3e9f61fa 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 !== '' && ( <View style={[s.flexRow, s.mb2, s.alignCenter]}> diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx new file mode 100644 index 000000000..71462bea8 --- /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 ( + <TouchableOpacity onPress={onToggleFollow}> + <View style={[styles.btn, pal.btn]}> + <Text type="button" style={[pal.text]}> + {isFollowing ? 'Unfollow' : 'Follow'} + </Text> + </View> + </TouchableOpacity> + ) + }, +) + +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 6a136a02d..3c487b70f 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 ( <ProfileCard handle={handle} @@ -132,34 +112,13 @@ export const ProfileCardWithFollowBtn = observer( renderButton={ isMe ? undefined - : () => ( - <FollowBtn isFollowing={isFollowing} onPress={onToggleFollow} /> - ) + : () => <FollowButton did={did} declarationCid={declarationCid} /> } /> ) }, ) -function FollowBtn({ - isFollowing, - onPress, -}: { - isFollowing: boolean - onPress: () => void -}) { - const pal = usePalette('default') - return ( - <TouchableOpacity onPress={onPress}> - <View style={[styles.btn, pal.btn]}> - <Text type="button" style={[pal.text]}> - {isFollowing ? 'Unfollow' : 'Follow'} - </Text> - </View> - </TouchableOpacity> - ) -} - const styles = StyleSheet.create({ outer: { borderTopWidth: 1, diff --git a/src/view/com/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index 6ba6fac1b..a07d91899 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 ( + <View style={[styles.metaTwoLine]}> + <View> + <Text + type="lg-bold" + style={[pal.text]} + numberOfLines={1} + lineHeight={1.2}> + {displayName}{' '} + <Text + type="md" + style={[styles.metaItem, pal.textLight]} + lineHeight={1.2}> + · {ago(opts.timestamp)} + </Text> + </Text> + <Text + type="md" + style={[styles.metaItem, pal.textLight]} + lineHeight={1.2}> + {handle ? ( + <Text type="md" style={[pal.textLight]}> + @{handle} + </Text> + ) : undefined} + </Text> + </View> + + <View> + <FollowButton did={opts.did} declarationCid={opts.declarationCid} /> + </View> + </View> + ) } + // one-liner return ( <View style={styles.meta}> <View style={[styles.metaItem, styles.maxWidth]}> @@ -53,13 +90,18 @@ export function PostMeta(opts: PostMetaOpts) { </Text> </View> ) -} +}) 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 000000000..d52288502 --- /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 ( + <View + testID="welcomeBanner" + style={[pal.view, styles.container, pal.border]}> + <Text + type="title-lg" + style={[pal.text, s.textCenter, s.bold, s.pb5]} + lineHeight={1.1}> + Welcome to the private beta! + </Text> + <Text type="lg" style={[pal.text, s.textCenter]}> + Here are some recent posts. Follow their creators to build your feed. + </Text> + </View> + ) +} + +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 d11a9fb72..5b5699bcc 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 1485670e7..000000000 --- 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 ( - <View style={styles.container}> - <Com /> - </View> - ) -}) - -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 dbfcad0ee..80403a6de 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(() => { </View> ) } - if (store.onboard.isOnboarding) { - return ( - <View testID="onboardOuterView" style={styles.outerContainer}> - <View style={styles.innerContainer}> - <ErrorBoundary> - <Onboard /> - </ErrorBoundary> - </View> - </View> - ) - } 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 76b5ed09f..a76ae8060 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(() => { </View> ) } - if (store.onboard.isOnboarding) { - return ( - <View style={styles.outerContainer}> - <ErrorBoundary> - <Onboard /> - </ErrorBoundary> - </View> - ) - } return ( <View style={[styles.outerContainer, pageBg]}> |