From 9513b574dfc938b2ed89929b7fb34698fe7e2eed Mon Sep 17 00:00:00 2001 From: Paul Frazee Date: Mon, 1 Jul 2024 10:18:05 -0700 Subject: [PATCH] Implement progress-guide controller --- src/App.native.tsx | 12 +- src/App.web.tsx | 5 +- src/components/ProgressGuide/List.tsx | 91 ++++++------ src/components/ProgressGuide/Toast.tsx | 99 ++++++++------ src/state/shell/progress-guide.tsx | 137 +++++++++++++++++++ src/view/com/home/HomeHeaderLayoutMobile.tsx | 12 ++ src/view/shell/desktop/RightNav.tsx | 10 +- 7 files changed, 274 insertions(+), 92 deletions(-) create mode 100644 src/state/shell/progress-guide.tsx diff --git a/src/App.native.tsx b/src/App.native.tsx index 18af744097..f0dde6ee1a 100644 --- a/src/App.native.tsx +++ b/src/App.native.tsx @@ -45,6 +45,7 @@ import { import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' +import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import {TestCtrls} from '#/view/com/testing/TestCtrls' @@ -119,10 +120,13 @@ function InnerApp() { - - - - + + + + + + diff --git a/src/App.web.tsx b/src/App.web.tsx index f45806e4d2..eb4a925d07 100644 --- a/src/App.web.tsx +++ b/src/App.web.tsx @@ -34,6 +34,7 @@ import { import {readLastActiveAccount} from '#/state/session/util' import {Provider as ShellStateProvider} from '#/state/shell' import {Provider as LoggedOutViewProvider} from '#/state/shell/logged-out' +import {Provider as ProgressGuideProvider} from '#/state/shell/progress-guide' import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed' import {Provider as StarterPackProvider} from '#/state/shell/starter-pack' import * as Toast from '#/view/com/util/Toast' @@ -104,7 +105,9 @@ function InnerApp() { - + + + diff --git a/src/components/ProgressGuide/List.tsx b/src/components/ProgressGuide/List.tsx index 489e083dd8..05f8cb25b2 100644 --- a/src/components/ProgressGuide/List.tsx +++ b/src/components/ProgressGuide/List.tsx @@ -1,58 +1,61 @@ import React from 'react' -import {View} from 'react-native' +import {StyleProp, View, ViewStyle} from 'react-native' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' +import { + ProgressGuideAction, + ProgressGuideName, + useProgressGuide, + useProgressGuideControls, +} from '#/state/shell/progress-guide' import {atoms as a, useTheme} from '#/alf' -import {Button, ButtonIcon} from '../Button' -import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '../icons/Times' -import {Text} from '../Typography' +import {Button, ButtonIcon} from '#/components/Button' +import {TimesLarge_Stroke2_Corner0_Rounded as Times} from '#/components/icons/Times' +import {Text} from '#/components/Typography' import {ProgressGuideTask} from './Task' -export function ProgressGuideList() { +export function ProgressGuideList({style}: {style: StyleProp}) { const t = useTheme() const {_} = useLingui() + const guide = useProgressGuide(ProgressGuideName.Like10Posts) + const {captureAction, endProgressGuide} = useProgressGuideControls() - const [numLikes, setNumLikes] = React.useState(0) - const [numFollows, setNumFollows] = React.useState(0) - - // DEBUG React.useEffect(() => { - const i = setInterval(() => { - setNumLikes(v => (v >= 10 ? 0 : v + 1)) - setNumFollows(v => (v >= 7 ? 0 : v + 1)) - }, 1000) - return () => { - clearInterval(i) - } - }, [setNumLikes]) + const i = setInterval(() => captureAction(ProgressGuideAction.Like), 2e3) + return () => clearInterval(i) + }, [captureAction]) - return ( - - - - GET STARTED - - + if (guide?.guide === ProgressGuideName.Like10Posts) { + return ( + + + + Get started + + + + - - - - ) + ) + } + return null } diff --git a/src/components/ProgressGuide/Toast.tsx b/src/components/ProgressGuide/Toast.tsx index 3d040df5f5..346312af51 100644 --- a/src/components/ProgressGuide/Toast.tsx +++ b/src/components/ProgressGuide/Toast.tsx @@ -1,5 +1,5 @@ import React, {useImperativeHandle} from 'react' -import {Keyboard, Pressable, View} from 'react-native' +import {Pressable, useWindowDimensions, View} from 'react-native' import Animated, { Easing, runOnJS, @@ -11,6 +11,7 @@ import {useSafeAreaInsets} from 'react-native-safe-area-context' import {msg} from '@lingui/macro' import {useLingui} from '@lingui/react' +import {isWeb} from '#/platform/detection' import {atoms as a, useTheme} from '#/alf' import {Portal} from '#/components/Portal' import {AnimatedCheck, AnimatedCheckRef} from '../anim/AnimatedCheck' @@ -39,6 +40,7 @@ export const ProgressGuideToast = React.forwardRef< const opacity = useSharedValue(0) const animatedCheckRef = React.useRef(null) const timeoutRef = React.useRef() + const winDim = useWindowDimensions() /** * Methods @@ -97,6 +99,20 @@ export const ProgressGuideToast = React.forwardRef< [open, close], ) + const containerStyle = React.useMemo(() => { + let left = 10 + let right = 10 + if (isWeb && winDim.width > 400) { + left = right = (winDim.width - 380) / 2 + } + return { + position: isWeb ? 'fixed' : 'absolute', + top: 0, + left, + right, + } + }, [winDim.width]) + const animatedStyle = useAnimatedStyle(() => ({ transform: [{translateY: translateY.value}], opacity: opacity.value, @@ -105,45 +121,48 @@ export const ProgressGuideToast = React.forwardRef< return ( isOpen && ( - Keyboard.dismiss()}> - - - - - {title} - {subtitle && ( - - {subtitle} - - )} - - - - + + + + + {title} + {subtitle && ( + + {subtitle} + + )} + + + ) ) diff --git a/src/state/shell/progress-guide.tsx b/src/state/shell/progress-guide.tsx new file mode 100644 index 0000000000..b759a0a884 --- /dev/null +++ b/src/state/shell/progress-guide.tsx @@ -0,0 +1,137 @@ +import React from 'react' +import {msg} from '@lingui/macro' +import {useLingui} from '@lingui/react' + +import { + ProgressGuideToast, + ProgressGuideToastRef, +} from '#/components/ProgressGuide/Toast' + +export enum ProgressGuideAction { + Like = 'like', +} + +export enum ProgressGuideName { + Like10Posts = 'like-10-posts', +} + +interface BaseProgressGuide { + guide: ProgressGuideName + isComplete: boolean +} + +interface Like10PostsProgressGuide extends BaseProgressGuide { + guide: ProgressGuideName.Like10Posts + numLikes: number +} + +type ProgressGuide = Like10PostsProgressGuide | undefined + +const ProgressGuideContext = React.createContext(undefined) + +const ProgressGuideControlContext = React.createContext<{ + startProgressGuide(guide: ProgressGuideName): void + endProgressGuide(): void + captureAction(action: ProgressGuideAction): void +}>({ + startProgressGuide: (_guide: ProgressGuideName) => {}, + endProgressGuide: () => {}, + captureAction: (_action: ProgressGuideAction) => {}, +}) + +export function useProgressGuide(guide: ProgressGuideName) { + const ctx = React.useContext(ProgressGuideContext) + if (ctx?.guide === guide) { + return ctx + } + return undefined +} + +export function useProgressGuideControls() { + return React.useContext(ProgressGuideControlContext) +} + +export function Provider({children}: React.PropsWithChildren<{}>) { + const {_} = useLingui() + + const [activeProgressGuide, setActiveProgressGuide] = + React.useState({ + guide: ProgressGuideName.Like10Posts, + numLikes: 0, + isComplete: false, + }) + + const firstLikeToastRef = React.useRef(null) + const fifthLikeToastRef = React.useRef(null) + const tenthLikeToastRef = React.useRef(null) + + const controls = React.useMemo(() => { + return { + startProgressGuide(guide: ProgressGuideName) { + if (guide === ProgressGuideName.Like10Posts) { + setActiveProgressGuide({ + guide: ProgressGuideName.Like10Posts, + numLikes: 0, + isComplete: false, + }) + } + }, + + endProgressGuide() { + setActiveProgressGuide(undefined) + }, + + captureAction(action: ProgressGuideAction) { + let guide = activeProgressGuide + if (guide?.isComplete) { + return + } + if (guide?.guide === ProgressGuideName.Like10Posts) { + if (action === ProgressGuideAction.Like) { + guide = { + ...guide, + numLikes: (guide.numLikes || 0) + 1, + } + if (guide.numLikes === 1) { + firstLikeToastRef.current?.open() + } + if (guide.numLikes === 5) { + fifthLikeToastRef.current?.open() + } + if (guide.numLikes === 10) { + tenthLikeToastRef.current?.open() + guide = { + ...guide, + isComplete: true, + } + } + } + } + setActiveProgressGuide(guide) + }, + } + }, [activeProgressGuide, setActiveProgressGuide]) + + return ( + + + {children} + + + + + + ) +} diff --git a/src/view/com/home/HomeHeaderLayoutMobile.tsx b/src/view/com/home/HomeHeaderLayoutMobile.tsx index ed353cf168..168340d7af 100644 --- a/src/view/com/home/HomeHeaderLayoutMobile.tsx +++ b/src/view/com/home/HomeHeaderLayoutMobile.tsx @@ -6,6 +6,10 @@ import {useLingui} from '@lingui/react' import {useSession} from '#/state/session' import {useSetDrawerOpen} from '#/state/shell/drawer-open' +import { + ProgressGuideAction, + useProgressGuideControls, +} from '#/state/shell/progress-guide' import {useShellLayout} from '#/state/shell/shell-layout' import {HITSLOP_10} from 'lib/constants' import {useMinimalShellHeaderTransform} from 'lib/hooks/useMinimalShellTransform' @@ -34,11 +38,19 @@ export function HomeHeaderLayoutMobile({ const {headerHeight} = useShellLayout() const headerMinimalShellTransform = useMinimalShellHeaderTransform() const {hasSession} = useSession() + const {captureAction} = useProgressGuideControls() const onPressAvi = React.useCallback(() => { setDrawerOpen(true) }, [setDrawerOpen]) + React.useEffect(() => { + const i = setInterval(() => captureAction(ProgressGuideAction.Like), 1000) + return () => { + clearInterval(i) + } + }, [captureAction]) + return ( {hasSession && ( - - - + <> + + + + + )} )}