New user progress guides (#4716)

* Add the animated checkmark svg

* Add progress guide list and task components

* Add ProgressGuide Toast component

* Implement progress-guide controller

* Add 7 follows to the progress guide

* Wire up action captures

* Wire up progress-guide persistence

* Trigger progress guide on account creation

* Clear the progress guide from storage on complete

* Add progress guide interstitial, put behind gate

* Fix: read progress guide state from prefs

* Some defensive type checks

* Create separate toast for completion

* List tweaks

* Only show on Discover

* Spacing and progress tweaks

* Completely hide when complete

* Capture the progress guide in local state, and only render toasts while guide is active

* Fix: ensure persisted hydrates into local state

* Gate

---------

Co-authored-by: Eric Bailey <git@esb.lol>
Co-authored-by: Dan Abramov <dan.abramov@gmail.com>
This commit is contained in:
Paul Frazee 2024-07-03 19:05:19 -07:00 committed by GitHub
parent aa7117edb6
commit 0ed99b840d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 721 additions and 22 deletions

View file

@ -34,7 +34,11 @@ import {useSession} from '#/state/session'
import {useAnalytics} from 'lib/analytics/analytics'
import {useInitialNumToRender} from 'lib/hooks/useInitialNumToRender'
import {useTheme} from 'lib/ThemeContext'
import {SuggestedFeeds, SuggestedFollows} from '#/components/FeedInterstitials'
import {
ProgressGuide,
SuggestedFeeds,
SuggestedFollows,
} from '#/components/FeedInterstitials'
import {List, ListRef} from '../util/List'
import {PostFeedLoadingPlaceholder} from '../util/LoadingPlaceholder'
import {LoadMoreRetryBtn} from '../util/LoadMoreRetryBtn'
@ -85,12 +89,26 @@ type FeedItem =
}
slot: number
}
| {
type: 'interstitialProgressGuide'
key: string
params: {
variant: 'default' | string
}
slot: number
}
const feedInterstitialType = 'interstitialFeeds'
const followInterstitialType = 'interstitialFollows'
const progressGuideInterstitialType = 'interstitialProgressGuide'
const interstials: Record<
'following' | 'discover',
(FeedItem & {type: 'interstitialFeeds' | 'interstitialFollows'})[]
(FeedItem & {
type:
| 'interstitialFeeds'
| 'interstitialFollows'
| 'interstitialProgressGuide'
})[]
> = {
following: [
{
@ -111,6 +129,14 @@ const interstials: Record<
},
],
discover: [
{
type: progressGuideInterstitialType,
params: {
variant: 'default',
},
key: progressGuideInterstitialType,
slot: 0,
},
{
type: feedInterstitialType,
params: {
@ -336,14 +362,14 @@ let Feed = ({
if (feedType) {
for (const interstitial of interstials[feedType]) {
const feedInterstitialEnabled =
interstitial.type === feedInterstitialType &&
gate('suggested_feeds_interstitial')
const followInterstitialEnabled =
interstitial.type === followInterstitialType &&
gate('suggested_follows_interstitial')
const shouldShow =
(interstitial.type === feedInterstitialType &&
gate('suggested_feeds_interstitial')) ||
(interstitial.type === followInterstitialType &&
gate('suggested_follows_interstitial')) ||
interstitial.type === progressGuideInterstitialType
if (feedInterstitialEnabled || followInterstitialEnabled) {
if (shouldShow) {
const variant = 'default' // replace with experiment variant
const int = {
...interstitial,
@ -460,6 +486,8 @@ let Feed = ({
return <SuggestedFeeds />
} else if (item.type === followInterstitialType) {
return <SuggestedFollows />
} else if (item.type === progressGuideInterstitialType) {
return <ProgressGuide />
} else if (item.type === 'slice') {
if (item.slice.rootUri === FALLBACK_MARKER_POST.post.uri) {
// HACK

View file

@ -31,6 +31,10 @@ import {
} from '#/state/queries/post'
import {useRequireAuth, useSession} from '#/state/session'
import {useComposerControls} from '#/state/shell/composer'
import {
ProgressGuideAction,
useProgressGuideControls,
} from '#/state/shell/progress-guide'
import {atoms as a, useTheme} from '#/alf'
import {useDialogControl} from '#/components/Dialog'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as ArrowOutOfBox} from '#/components/icons/ArrowOutOfBox'
@ -77,6 +81,7 @@ let PostCtrls = ({
const requireAuth = useRequireAuth()
const loggedOutWarningPromptControl = useDialogControl()
const {sendInteraction} = useFeedFeedbackContext()
const {captureAction} = useProgressGuideControls()
const playHaptic = useHaptics()
const gate = useGate()
@ -103,6 +108,7 @@ let PostCtrls = ({
event: 'app.bsky.feed.defs#interactionLike',
feedContext,
})
captureAction(ProgressGuideAction.Like)
await queueLike()
} else {
await queueUnlike()
@ -119,6 +125,7 @@ let PostCtrls = ({
queueLike,
queueUnlike,
sendInteraction,
captureAction,
feedContext,
])

View file

@ -14,6 +14,7 @@ import {Text} from 'view/com/util/text/Text'
import {DesktopFeeds} from './Feeds'
import {DesktopSearch} from './Search'
import hairlineWidth = StyleSheet.hairlineWidth
import {ProgressGuideList} from '#/components/ProgressGuide/List'
export function DesktopRightNav({routeName}: {routeName: string}) {
const pal = usePalette('default')
@ -39,9 +40,12 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
<DesktopSearch />
{hasSession && (
<View style={[pal.border, styles.desktopFeedsContainer]}>
<DesktopFeeds />
</View>
<>
<ProgressGuideList style={[{marginTop: 22, marginBottom: 8}]} />
<View style={[pal.border, styles.desktopFeedsContainer]}>
<DesktopFeeds />
</View>
</>
)}
</>
)}