diff options
author | Paul Frazee <pfrazee@gmail.com> | 2023-03-06 15:34:22 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-06 15:34:22 -0600 |
commit | 36791e68b3214cab9714a29d40fd7ecae2794c5e (patch) | |
tree | 47d5703595de08c5e5eff874285ae47b7a2a0427 /src | |
parent | 74c30c60b8b5e68176b1447524db7e725f75a372 (diff) | |
download | voidsky-36791e68b3214cab9714a29d40fd7ecae2794c5e.tar.zst |
Onboarding tweaks (#272)
* Small fix to side menu rendering * Change onboarding to use an explicit 'is onboarding' mode to more clearly control the flow * Add a progress bar to the welcome banner * Dont show the 'unfollow button' on posts in weird times (close #271) * Improve the empty state of the feed * Only suggest recent posts
Diffstat (limited to 'src')
-rw-r--r-- | src/lib/api/build-suggested-posts.ts | 21 | ||||
-rw-r--r-- | src/state/models/feed-view.ts | 57 | ||||
-rw-r--r-- | src/state/models/my-follows.ts | 4 | ||||
-rw-r--r-- | src/state/models/session.ts | 1 | ||||
-rw-r--r-- | src/state/models/shell-ui.ts | 10 | ||||
-rw-r--r-- | src/view/com/posts/Feed.tsx | 95 | ||||
-rw-r--r-- | src/view/com/profile/FollowButton.tsx | 40 | ||||
-rw-r--r-- | src/view/com/util/PostMeta.tsx | 22 | ||||
-rw-r--r-- | src/view/com/util/WelcomeBanner.tsx | 81 | ||||
-rw-r--r-- | src/view/com/util/forms/Button.tsx | 14 | ||||
-rw-r--r-- | src/view/screens/Debug.tsx | 3 | ||||
-rw-r--r-- | src/view/screens/Home.tsx | 24 | ||||
-rw-r--r-- | src/view/shell/mobile/Menu.tsx | 10 |
13 files changed, 259 insertions, 123 deletions
diff --git a/src/lib/api/build-suggested-posts.ts b/src/lib/api/build-suggested-posts.ts index 6250f4a9c..defa45311 100644 --- a/src/lib/api/build-suggested-posts.ts +++ b/src/lib/api/build-suggested-posts.ts @@ -37,13 +37,20 @@ function mergePosts( // 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) { + if ( + !acc?.[0] && + !v.reason && + !v.reply && + isRecentEnough(v.post.indexedAt) + ) { return [v] } if ( acc && !v.reason && - v.post.upvoteCount > acc[0].post.upvoteCount + !v.reply && + v.post.upvoteCount > acc[0]?.post.upvoteCount && + isRecentEnough(v.post.indexedAt) ) { return [v] } @@ -112,6 +119,16 @@ function isCombinedCursor(cursor: string) { return cursor.includes(',') } +const TWO_DAYS_AGO = Date.now() - 1e3 * 60 * 60 * 48 +function isRecentEnough(date: string) { + try { + const d = Number(new Date(date)) + return d > TWO_DAYS_AGO + } catch { + return false + } +} + export { getMultipleAuthorsPosts, mergePosts, diff --git a/src/state/models/feed-view.ts b/src/state/models/feed-view.ts index 535221e63..e27712d11 100644 --- a/src/state/models/feed-view.ts +++ b/src/state/models/feed-view.ts @@ -212,7 +212,7 @@ export class FeedModel { constructor( public rootStore: RootStoreModel, - public feedType: 'home' | 'author', + public feedType: 'home' | 'author' | 'suggested', params: GetTimeline.QueryParams | GetAuthorFeed.QueryParams, ) { makeAutoObservable( @@ -256,7 +256,7 @@ export class FeedModel { item.reply?.root.author.did === item.post.author.did) ) }) - } else { + } else if (this.feedType === 'home') { return this.feed.filter(item => { const isRepost = Boolean(item?.reasonRepost) return ( @@ -267,6 +267,8 @@ export class FeedModel { item.post.upvoteCount >= 2 ) }) + } else { + return this.feed } } @@ -293,6 +295,14 @@ export class FeedModel { this.feed = [] } + switchFeedType(feedType: 'home' | 'suggested') { + if (this.feedType === feedType) { + return + } + this.feedType = feedType + return this.setup() + } + /** * Load for first render */ @@ -427,7 +437,7 @@ export class FeedModel { * Check if new posts are available */ async checkForLatest() { - if (this.hasNewLatest || this.rootStore.me.follows.isEmpty) { + if (this.hasNewLatest || this.feedType === 'suggested') { return } const res = await this._getFeed({limit: 1}) @@ -562,30 +572,25 @@ export class FeedModel { 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, - sampleSize( - SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), - 20, - ), - 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, - } + if (this.feedType === 'suggested') { + const responses = await getMultipleAuthorsPosts( + this.rootStore, + sampleSize(SUGGESTED_FOLLOWS(String(this.rootStore.agent.service)), 20), + 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, } + } else if (this.feedType === 'home') { return this.rootStore.api.app.bsky.feed.getTimeline( params as GetTimeline.QueryParams, ) diff --git a/src/state/models/my-follows.ts b/src/state/models/my-follows.ts index c1fba1352..732c2fe73 100644 --- a/src/state/models/my-follows.ts +++ b/src/state/models/my-follows.ts @@ -72,6 +72,10 @@ export class MyFollowsModel { return !!this.followDidToRecordMap[did] } + get numFollows() { + return Object.keys(this.followDidToRecordMap).length + } + get isEmpty() { return Object.keys(this.followDidToRecordMap).length === 0 } diff --git a/src/state/models/session.ts b/src/state/models/session.ts index 75a60f353..b15c866f4 100644 --- a/src/state/models/session.ts +++ b/src/state/models/session.ts @@ -345,6 +345,7 @@ export class SessionModel { ) this.setActiveSession(agent, did) + this.rootStore.shell.setOnboarding(true) this.rootStore.log.debug('SessionModel:createAccount succeeded') } diff --git a/src/state/models/shell-ui.ts b/src/state/models/shell-ui.ts index 1b0e350a2..0dad2bd9e 100644 --- a/src/state/models/shell-ui.ts +++ b/src/state/models/shell-ui.ts @@ -118,6 +118,7 @@ export class ShellUiModel { activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined isComposerActive = false composerOpts: ComposerOpts | undefined + isOnboarding = false constructor(public rootStore: RootStoreModel) { makeAutoObservable(this, { @@ -185,4 +186,13 @@ export class ShellUiModel { this.isComposerActive = false this.composerOpts = undefined } + + setOnboarding(v: boolean) { + this.isOnboarding = v + if (this.isOnboarding) { + this.rootStore.me.mainFeed.switchFeedType('suggested') + } else { + this.rootStore.me.mainFeed.switchFeedType('home') + } + } } diff --git a/src/view/com/posts/Feed.tsx b/src/view/com/posts/Feed.tsx index 7ed6bc711..f919c6208 100644 --- a/src/view/com/posts/Feed.tsx +++ b/src/view/com/posts/Feed.tsx @@ -7,26 +7,28 @@ import { StyleSheet, ViewStyle, } from 'react-native' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' +import {FontAwesomeIconStyle} from '@fortawesome/react-native-fontawesome' import {CenteredView, FlatList} from '../util/Views' import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder' -import {EmptyState} from '../util/EmptyState' +import {Text} from '../util/text/Text' import {ErrorMessage} from '../util/error/ErrorMessage' +import {Button} from '../util/forms/Button' 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' +import {useAnalytics} from 'lib/analytics' +import {usePalette} from 'lib/hooks/usePalette' +import {MagnifyingGlassIcon} from 'lib/icons' 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, @@ -36,7 +38,6 @@ export const Feed = observer(function Feed({ }: { feed: FeedModel style?: StyleProp<ViewStyle> - showWelcomeBanner?: boolean showPostFollowBtn?: boolean scrollElRef?: MutableRefObject<FlatList<any> | null> onPressTryAgain?: () => void @@ -44,10 +45,11 @@ export const Feed = observer(function Feed({ testID?: string headerOffset?: number }) { - const {track} = useAnalytics() + const pal = usePalette('default') + const palInverted = usePalette('inverted') const store = useStores() + const {track} = useAnalytics() const [isRefreshing, setIsRefreshing] = React.useState(false) - const [isNewUser, setIsNewUser] = React.useState<boolean>(false) const data = React.useMemo(() => { let feedItems: any[] = [] @@ -55,9 +57,6 @@ 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 { @@ -65,39 +64,21 @@ export const Feed = observer(function Feed({ } } return feedItems - }, [ - feed.hasError, - feed.hasLoaded, - feed.isEmpty, - feed.nonReplyFeed, - showWelcomeBanner, - isNewUser, - ]) + }, [feed.hasError, feed.hasLoaded, feed.isEmpty, feed.nonReplyFeed]) // events // = - const checkWelcome = React.useCallback(async () => { - if (showWelcomeBanner && store.me.did) { - await store.me.follows.fetchIfNeeded() - setIsNewUser(store.me.follows.isEmpty) - } - }, [showWelcomeBanner, store.me.follows, store.me.did]) - 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, checkWelcome]) + }, [feed, track, setIsRefreshing]) const onEndReached = React.useCallback(async () => { track('Feed:onEndReached') try { @@ -118,11 +99,30 @@ export const Feed = observer(function Feed({ ({item}: {item: any}) => { if (item === EMPTY_FEED_ITEM) { return ( - <EmptyState - icon="bars" - message="This feed is empty!" - style={styles.emptyState} - /> + <View style={styles.emptyContainer}> + <View style={styles.emptyIconContainer}> + <MagnifyingGlassIcon + style={[styles.emptyIcon, pal.text]} + size={62} + /> + </View> + <Text type="xl-medium" style={[s.textCenter, pal.text]}> + Your feed is empty! You should follow some accounts to fix this. + </Text> + <Button + type="inverted" + style={styles.emptyBtn} + onPress={() => store.nav.navigate('/search')}> + <Text type="lg-medium" style={palInverted.text}> + Find accounts + </Text> + <FontAwesomeIcon + icon="angle-right" + style={palInverted.text as FontAwesomeIconStyle} + size={14} + /> + </Button> + </View> ) } else if (item === ERROR_FEED_ITEM) { return ( @@ -131,12 +131,10 @@ export const Feed = observer(function Feed({ onPressTryAgain={onPressTryAgain} /> ) - } else if (item === WELCOME_FEED_ITEM) { - return <WelcomeBanner /> } return <FeedItem item={item} showFollowBtn={showPostFollowBtn} /> }, - [feed, onPressTryAgain, showPostFollowBtn], + [feed, onPressTryAgain, showPostFollowBtn, pal, palInverted, store.nav], ) const FeedFooter = React.useCallback( @@ -155,7 +153,6 @@ 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> )} @@ -184,5 +181,21 @@ export const Feed = observer(function Feed({ const styles = StyleSheet.create({ feedFooter: {paddingTop: 20}, - emptyState: {paddingVertical: 40}, + emptyContainer: { + paddingVertical: 40, + paddingHorizontal: 30, + }, + emptyIconContainer: { + marginBottom: 16, + }, + emptyIcon: { + marginLeft: 'auto', + marginRight: 'auto', + }, + emptyBtn: { + marginTop: 20, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + }, }) diff --git a/src/view/com/profile/FollowButton.tsx b/src/view/com/profile/FollowButton.tsx index 71462bea8..f24c3d0c9 100644 --- a/src/view/com/profile/FollowButton.tsx +++ b/src/view/com/profile/FollowButton.tsx @@ -1,23 +1,29 @@ import React from 'react' -import {StyleSheet, TouchableOpacity, View} from 'react-native' import {observer} from 'mobx-react-lite' -import {Text} from '../util/text/Text' +import {Button} from '../util/forms/Button' 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}) => { + ({ + did, + declarationCid, + onToggleFollow, + }: { + did: string + declarationCid: string + onToggleFollow?: (v: boolean) => void + }) => { const store = useStores() - const pal = usePalette('default') const isFollowing = store.me.follows.isFollowing(did) - const onToggleFollow = async () => { + const onToggleFollowInner = async () => { if (store.me.follows.isFollowing(did)) { try { await apilib.unfollow(store, store.me.follows.getFollowUri(did)) store.me.follows.removeFollow(did) + onToggleFollow?.(false) } catch (e: any) { store.log.error('Failed fo delete follow', e) Toast.show('An issue occurred, please try again.') @@ -26,6 +32,7 @@ const FollowButton = observer( try { const res = await apilib.follow(store, did, declarationCid) store.me.follows.addFollow(did, res.uri) + onToggleFollow?.(true) } catch (e: any) { store.log.error('Failed fo create follow', e) Toast.show('An issue occurred, please try again.') @@ -34,24 +41,13 @@ const FollowButton = observer( } return ( - <TouchableOpacity onPress={onToggleFollow}> - <View style={[styles.btn, pal.btn]}> - <Text type="button" style={[pal.text]}> - {isFollowing ? 'Unfollow' : 'Follow'} - </Text> - </View> - </TouchableOpacity> + <Button + type={isFollowing ? 'default' : 'primary'} + onPress={onToggleFollowInner} + label={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/util/PostMeta.tsx b/src/view/com/util/PostMeta.tsx index af08708b4..cde5a3e92 100644 --- a/src/view/com/util/PostMeta.tsx +++ b/src/view/com/util/PostMeta.tsx @@ -24,20 +24,18 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { let handle = opts.authorHandle const store = useStores() const isMe = opts.did === store.me.did + const isFollowing = + typeof opts.did === 'string' && store.me.follows.isFollowing(opts.did) - // 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( - () => - typeof opts.did === 'string' && store.me.follows.isFollowing(opts.did), - [opts.did, store.me.follows], - ) + const [didFollow, setDidFollow] = React.useState(false) + const onToggleFollow = React.useCallback(() => { + setDidFollow(true) + }, [setDidFollow]) if ( opts.showFollowBtn && !isMe && - !isFollowing && + (!isFollowing || didFollow) && opts.did && opts.declarationCid ) { @@ -71,7 +69,11 @@ export const PostMeta = observer(function (opts: PostMetaOpts) { </View> <View> - <FollowButton did={opts.did} declarationCid={opts.declarationCid} /> + <FollowButton + did={opts.did} + declarationCid={opts.declarationCid} + onToggleFollow={onToggleFollow} + /> </View> </View> ) diff --git a/src/view/com/util/WelcomeBanner.tsx b/src/view/com/util/WelcomeBanner.tsx index d52288502..e236bfb48 100644 --- a/src/view/com/util/WelcomeBanner.tsx +++ b/src/view/com/util/WelcomeBanner.tsx @@ -1,11 +1,43 @@ import React from 'react' import {StyleSheet, View} from 'react-native' +import {observer} from 'mobx-react-lite' +import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {usePalette} from 'lib/hooks/usePalette' import {Text} from './text/Text' +import {Button} from './forms/Button' import {s} from 'lib/styles' +import {useStores} from 'state/index' +import {SUGGESTED_FOLLOWS} from 'lib/constants' +// @ts-ignore no type definition -prf +import ProgressBar from 'react-native-progress/Bar' -export function WelcomeBanner() { +export const WelcomeBanner = observer(() => { const pal = usePalette('default') + const store = useStores() + const [isReady, setIsReady] = React.useState(false) + + const numFollows = Math.min( + SUGGESTED_FOLLOWS(String(store.agent.service)).length, + 5, + ) + const remaining = numFollows - store.me.follows.numFollows + + React.useEffect(() => { + if (remaining <= 0) { + // wait 500ms for the progress bar anim to finish + const ti = setTimeout(() => { + setIsReady(true) + }, 500) + return () => clearTimeout(ti) + } else { + setIsReady(false) + } + }, [remaining]) + + const onPressDone = React.useCallback(() => { + store.shell.setOnboarding(false) + }, [store]) + return ( <View testID="welcomeBanner" @@ -16,18 +48,53 @@ export function WelcomeBanner() { 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> + {isReady ? ( + <View style={styles.controls}> + <Button + type="primary" + style={[s.flexRow, s.alignCenter]} + onPress={onPressDone}> + <Text type="md-bold" style={s.white}> + See my feed! + </Text> + <FontAwesomeIcon icon="angle-right" size={14} style={s.white} /> + </Button> + </View> + ) : ( + <> + <Text type="lg" style={[pal.text, s.textCenter]}> + Follow at least {remaining} {remaining === 1 ? 'person' : 'people'}{' '} + to build your feed. + </Text> + <View style={[styles.controls, styles.progress]}> + <ProgressBar + progress={Math.max( + store.me.follows.numFollows / numFollows, + 0.05, + )} + /> + </View> + </> + )} </View> ) -} +}) const styles = StyleSheet.create({ container: { - paddingTop: 30, - paddingBottom: 26, + paddingTop: 16, + paddingBottom: 16, paddingHorizontal: 20, borderTopWidth: 1, + borderBottomWidth: 1, + }, + controls: { + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + marginTop: 10, + }, + progress: { + marginTop: 12, }, }) diff --git a/src/view/com/util/forms/Button.tsx b/src/view/com/util/forms/Button.tsx index a070d2f0f..f3f4d1c79 100644 --- a/src/view/com/util/forms/Button.tsx +++ b/src/view/com/util/forms/Button.tsx @@ -13,6 +13,7 @@ import {choose} from 'lib/functions' export type ButtonType = | 'primary' | 'secondary' + | 'default' | 'inverted' | 'primary-outline' | 'secondary-outline' @@ -40,6 +41,9 @@ export function Button({ secondary: { backgroundColor: theme.palette.secondary.background, }, + default: { + backgroundColor: theme.palette.default.backgroundLight, + }, inverted: { backgroundColor: theme.palette.inverted.background, }, @@ -66,15 +70,18 @@ export function Button({ const labelStyle = choose<TextStyle, Record<ButtonType, TextStyle>>(type, { primary: { color: theme.palette.primary.text, - fontWeight: theme.palette.primary.isLowContrast ? '500' : undefined, + fontWeight: '600', }, secondary: { color: theme.palette.secondary.text, fontWeight: theme.palette.secondary.isLowContrast ? '500' : undefined, }, + default: { + color: theme.palette.default.text, + }, inverted: { color: theme.palette.inverted.text, - fontWeight: theme.palette.inverted.isLowContrast ? '500' : undefined, + fontWeight: '600', }, 'primary-outline': { color: theme.palette.primary.textInverted, @@ -114,7 +121,8 @@ export function Button({ const styles = StyleSheet.create({ outer: { - paddingHorizontal: 10, + paddingHorizontal: 14, paddingVertical: 8, + borderRadius: 24, }, }) diff --git a/src/view/screens/Debug.tsx b/src/view/screens/Debug.tsx index 657f38d57..f2349195e 100644 --- a/src/view/screens/Debug.tsx +++ b/src/view/screens/Debug.tsx @@ -341,6 +341,9 @@ function ButtonsView() { <View style={[s.flexRow, s.mb5]}> <Button type="primary" label="Primary solid" style={buttonStyles} /> <Button type="secondary" label="Secondary solid" style={buttonStyles} /> + </View> + <View style={[s.flexRow, s.mb5]}> + <Button type="default" label="Default solid" style={buttonStyles} /> <Button type="inverted" label="Inverted solid" style={buttonStyles} /> </View> <View style={s.flexRow}> diff --git a/src/view/screens/Home.tsx b/src/view/screens/Home.tsx index b9611757c..09006a27f 100644 --- a/src/view/screens/Home.tsx +++ b/src/view/screens/Home.tsx @@ -1,10 +1,11 @@ -import React, {useEffect} from 'react' +import React from 'react' import {FlatList, View} from 'react-native' import {observer} from 'mobx-react-lite' import useAppState from 'react-native-appstate-hook' import {ViewHeader} from '../com/util/ViewHeader' import {Feed} from '../com/posts/Feed' import {LoadLatestBtn} from '../com/util/LoadLatestBtn' +import {WelcomeBanner} from '../com/util/WelcomeBanner' import {useStores} from 'state/index' import {ScreenParams} from '../routes' import {s} from 'lib/styles' @@ -43,7 +44,7 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { scrollElRef.current?.scrollToOffset({offset: -HEADER_HEIGHT}) }, [scrollElRef]) - useEffect(() => { + React.useEffect(() => { const softResetSub = store.onScreenSoftReset(scrollToTop) const feedCleanup = store.me.mainFeed.registerListeners() const pollInterval = setInterval(doPoll, 15e3) @@ -72,7 +73,16 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { store.me.mainFeed.update() } return cleanup - }, [visible, store, store.me.mainFeed, navIdx, doPoll, wasVisible, scrollToTop, screen]) + }, [ + visible, + store, + store.me.mainFeed, + navIdx, + doPoll, + wasVisible, + scrollToTop, + screen, + ]) const onPressTryAgain = () => { store.me.mainFeed.refresh() @@ -84,19 +94,21 @@ export const Home = observer(function Home({navIdx, visible}: ScreenParams) { return ( <View style={s.hContentRegion}> + {store.shell.isOnboarding && <WelcomeBanner />} <Feed testID="homeFeed" key="default" feed={store.me.mainFeed} scrollElRef={scrollElRef} style={s.hContentRegion} - showWelcomeBanner showPostFollowBtn onPressTryAgain={onPressTryAgain} onScroll={onMainScroll} - headerOffset={HEADER_HEIGHT} + headerOffset={store.shell.isOnboarding ? 0 : HEADER_HEIGHT} /> - <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll /> + {!store.shell.isOnboarding && ( + <ViewHeader title="Bluesky" canGoBack={false} hideOnScroll /> + )} {store.me.mainFeed.hasNewLatest && !store.me.mainFeed.isRefreshing && ( <LoadLatestBtn onPress={onPressLoadLatest} /> )} diff --git a/src/view/shell/mobile/Menu.tsx b/src/view/shell/mobile/Menu.tsx index 6c5aa1adb..734e02b08 100644 --- a/src/view/shell/mobile/Menu.tsx +++ b/src/view/shell/mobile/Menu.tsx @@ -131,14 +131,10 @@ export const Menu = observer(({onClose}: {onClose: () => void}) => { /> <Text type="title-lg" - style={[pal.text, s.bold, styles.profileCardDisplayName]} - numberOfLines={1}> + style={[pal.text, s.bold, styles.profileCardDisplayName]}> {store.me.displayName || store.me.handle} </Text> - <Text - type="2xl" - style={[pal.textLight, styles.profileCardHandle]} - numberOfLines={1}> + <Text type="2xl" style={[pal.textLight, styles.profileCardHandle]}> @{store.me.handle} </Text> </TouchableOpacity> @@ -280,9 +276,11 @@ const styles = StyleSheet.create({ profileCardDisplayName: { marginTop: 20, + paddingRight: 20, }, profileCardHandle: { marginTop: 4, + paddingRight: 20, }, menuItem: { |